aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Vuong <akvuong@google.com>2023-02-14 20:41:37 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-02-14 20:41:37 +0000
commit47d7817e55fb09b92eb16823f57ba6f03cea5486 (patch)
tree8e1380b130c548b03135010a952574030e77ada9
parent2a77099c93be7bbf4d56b677d968bfca258b558f (diff)
parenta44dd5043caffbb854707d37bc0c2f0d3b994d83 (diff)
downloadapache-commons-lang-47d7817e55fb09b92eb16823f57ba6f03cea5486.tar.gz
Initial import of apache-commons-lang from upstream master am: ff344be6c0 am: a44dd5043c
Original change: https://android-review.googlesource.com/c/platform/external/apache-commons-lang/+/2434253 Change-Id: If961374cebabfe877f03f6f9fea997a374d685e6 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.asf.yaml29
-rw-r--r--.gitattributes24
-rw-r--r--.github/GH-ROBOTS.txt19
-rw-r--r--.github/dependabot.yml28
-rw-r--r--.github/workflows/codeql-analysis.yml85
-rw-r--r--.github/workflows/coverage.yml52
-rw-r--r--.github/workflows/maven.yml51
-rw-r--r--.github/workflows/scorecards-analysis.yml69
-rw-r--r--.gitignore20
-rw-r--r--Android.bp49
-rw-r--r--CODE_OF_CONDUCT.md17
-rw-r--r--CONTRIBUTING.md115
-rw-r--r--Jenkinsfile115
l---------LICENSE1
-rw-r--r--LICENSE.txt202
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--NOTICE.txt5
-rw-r--r--OWNERS2
-rw-r--r--README.md162
-rw-r--r--RELEASE-NOTES.txt1610
-rw-r--r--SECURITY.md17
-rw-r--r--TEST_MAPPING7
-rw-r--r--pom.xml1002
-rw-r--r--src/assembly/bin.xml46
-rw-r--r--src/assembly/src.xml44
-rw-r--r--src/changes/changes.xml1394
-rw-r--r--src/changes/release-notes.vm144
-rw-r--r--src/conf/exclude-pmd.properties25
-rw-r--r--src/conf/spotbugs-exclude-filter.xml197
-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
-rw-r--r--src/media/logo.xcfbin0 -> 19992 bytes
-rw-r--r--src/site/resources/.htaccess16
-rw-r--r--src/site/resources/checkstyle/checkstyle-suppressions.xml27
-rw-r--r--src/site/resources/checkstyle/checkstyle.xml60
-rwxr-xr-xsrc/site/resources/download_lang.cgi4
-rw-r--r--src/site/resources/images/logo.pngbin0 -> 11862 bytes
-rw-r--r--src/site/resources/lang2-lang3-clirr-report.html265
-rw-r--r--src/site/resources/profile.jacoco0
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-1.0.1.txt63
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-1.0.txt161
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-2.0.txt676
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-2.1.txt150
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-2.2.txt127
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-2.3.txt105
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-2.4.txt139
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-2.5.txt98
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-2.6.txt74
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.0.1.txt57
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.0.txt170
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.1.txt55
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.10.txt121
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.2.1.txt417
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.2.txt405
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.3.1.txt496
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.3.2.txt509
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.3.txt482
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.4.txt642
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.5.txt940
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.6.txt1124
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.7.txt1176
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.8.1.txt1270
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.8.txt1237
-rw-r--r--src/site/resources/release-notes/RELEASE-NOTES-3.9.txt1293
-rw-r--r--src/site/site.xml46
-rw-r--r--src/site/xdoc/article2_4.xml195
-rw-r--r--src/site/xdoc/article2_5.xml135
-rw-r--r--src/site/xdoc/article3_0.xml208
-rw-r--r--src/site/xdoc/building.xml72
-rw-r--r--src/site/xdoc/developerguide.xml150
-rw-r--r--src/site/xdoc/download_lang.xml186
-rw-r--r--src/site/xdoc/index.xml106
-rw-r--r--src/site/xdoc/issue-tracking.xml102
-rw-r--r--src/site/xdoc/mail-lists.xml205
-rw-r--r--src/site/xdoc/proposal.xml97
-rw-r--r--src/site/xdoc/upgradeto2_0.xml688
-rw-r--r--src/site/xdoc/upgradeto2_1.xml163
-rw-r--r--src/site/xdoc/upgradeto2_2.xml110
-rw-r--r--src/site/xdoc/upgradeto2_3.xml118
-rw-r--r--src/site/xdoc/upgradeto2_4.xml153
-rw-r--r--src/site/xdoc/upgradeto2_5.xml105
-rw-r--r--src/site/xdoc/upgradeto2_6.xml87
-rw-r--r--src/site/xdoc/upgradeto3_0.xml172
-rw-r--r--src/site/xdoc/userguide.xml33
-rw-r--r--src/test/java/org/apache/commons/lang3/AbstractLangTest.java41
-rw-r--r--src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java528
-rw-r--r--src/test/java/org/apache/commons/lang3/ArchUtilsTest.java192
-rw-r--r--src/test/java/org/apache/commons/lang3/ArraySorterTest.java100
-rw-r--r--src/test/java/org/apache/commons/lang3/ArrayUtilsAddTest.java675
-rw-r--r--src/test/java/org/apache/commons/lang3/ArrayUtilsInsertTest.java253
-rw-r--r--src/test/java/org/apache/commons/lang3/ArrayUtilsRemoveMultipleTest.java1264
-rw-r--r--src/test/java/org/apache/commons/lang3/ArrayUtilsRemoveTest.java779
-rw-r--r--src/test/java/org/apache/commons/lang3/ArrayUtilsSetTest.java61
-rw-r--r--src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java6584
-rw-r--r--src/test/java/org/apache/commons/lang3/BitFieldTest.java259
-rw-r--r--src/test/java/org/apache/commons/lang3/BooleanUtilsTest.java1173
-rw-r--r--src/test/java/org/apache/commons/lang3/CharEncodingTest.java84
-rw-r--r--src/test/java/org/apache/commons/lang3/CharRangeTest.java381
-rw-r--r--src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java311
-rw-r--r--src/test/java/org/apache/commons/lang3/CharSetTest.java471
-rw-r--r--src/test/java/org/apache/commons/lang3/CharSetUtilsTest.java247
-rw-r--r--src/test/java/org/apache/commons/lang3/CharUtilsPerfRun.java156
-rw-r--r--src/test/java/org/apache/commons/lang3/CharUtilsTest.java350
-rw-r--r--src/test/java/org/apache/commons/lang3/CharsetsTest.java51
-rw-r--r--src/test/java/org/apache/commons/lang3/ClassLoaderUtilsTest.java50
-rw-r--r--src/test/java/org/apache/commons/lang3/ClassPathUtilsTest.java135
-rw-r--r--src/test/java/org/apache/commons/lang3/ClassUtilsTest.java1523
-rw-r--r--src/test/java/org/apache/commons/lang3/ConversionTest.java1766
-rw-r--r--src/test/java/org/apache/commons/lang3/DoubleRangeTest.java412
-rw-r--r--src/test/java/org/apache/commons/lang3/EnumUtilsTest.java615
-rw-r--r--src/test/java/org/apache/commons/lang3/FunctionsTest.java1097
-rw-r--r--src/test/java/org/apache/commons/lang3/HashSetvBitSetTest.java96
-rw-r--r--src/test/java/org/apache/commons/lang3/IntegerRangeTest.java411
-rw-r--r--src/test/java/org/apache/commons/lang3/JavaVersionTest.java96
-rw-r--r--src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java555
-rw-r--r--src/test/java/org/apache/commons/lang3/LongRangeTest.java426
-rw-r--r--src/test/java/org/apache/commons/lang3/NotImplementedExceptionTest.java58
-rw-r--r--src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java860
-rw-r--r--src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java535
-rw-r--r--src/test/java/org/apache/commons/lang3/RandomUtilsTest.java290
-rw-r--r--src/test/java/org/apache/commons/lang3/RangeTest.java450
-rw-r--r--src/test/java/org/apache/commons/lang3/RegExUtilsTest.java249
-rw-r--r--src/test/java/org/apache/commons/lang3/SerializationUtilsTest.java368
-rw-r--r--src/test/java/org/apache/commons/lang3/StreamsTest.java201
-rw-r--r--src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java573
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsContainsTest.java467
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java171
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java800
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsIsTest.java173
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsStartsEndsWithTest.java200
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsSubstringTest.java386
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsTest.java3391
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsTrimStripTest.java248
-rw-r--r--src/test/java/org/apache/commons/lang3/StringUtilsValueOfTest.java43
-rw-r--r--src/test/java/org/apache/commons/lang3/Supplementary.java49
-rw-r--r--src/test/java/org/apache/commons/lang3/SystemPropertiesTest.java254
-rw-r--r--src/test/java/org/apache/commons/lang3/SystemUtilsTest.java885
-rw-r--r--src/test/java/org/apache/commons/lang3/ThreadUtilsTest.java391
-rw-r--r--src/test/java/org/apache/commons/lang3/ValidateTest.java1626
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/CompareToBuilderTest.java1166
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/DefaultToStringStyleTest.java154
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/DiffBuilderTest.java498
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/DiffResultTest.java163
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/DiffTest.java72
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/EqualsBuilderTest.java1422
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderAndEqualsBuilderTest.java138
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderTest.java623
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/JsonToStringStyleTest.java698
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/MultiLineToStringStyleTest.java154
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyleTest.java310
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/NoClassNameToStringStyleTest.java152
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/NoFieldNamesToStringStyleTest.java154
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/RecursiveToStringStyleTest.java163
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java131
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderConcurrencyTest.java118
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeNullValuesTest.java165
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeTest.java138
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeWithAnnotationTest.java58
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderIncludeTest.java239
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderMutateInspectConcurrencyTest.java108
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderSummaryTest.java38
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderTest.java32
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ShortPrefixToStringStyleTest.java154
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/SimpleToStringStyleTest.java152
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/StandardToStringStyleTest.java230
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ToStringBuilderTest.java1291
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ToStringStyleConcurrencyTest.java110
-rw-r--r--src/test/java/org/apache/commons/lang3/builder/ToStringStyleTest.java138
-rw-r--r--src/test/java/org/apache/commons/lang3/compare/ComparableUtilsTest.java474
-rw-r--r--src/test/java/org/apache/commons/lang3/compare/ObjectToStringComparatorTest.java74
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java121
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java37
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java77
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/BackgroundInitializerTest.java309
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/BasicThreadFactoryTest.java297
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializerTest.java107
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/CircuitBreakingExceptionTest.java92
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java496
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/ConstantInitializerTest.java133
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreakerTest.java414
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/FutureTasksTest.java41
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.java56
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/MemoizerComputableTest.java107
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/MemoizerFunctionTest.java109
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializerTest.java399
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreakerTest.java88
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java553
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/UncheckedExecutionExceptionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/UncheckedFutureTest.java124
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/UncheckedTimeoutExceptionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java142
-rw-r--r--src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java224
-rw-r--r--src/test/java/org/apache/commons/lang3/event/EventUtilsTest.java228
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/AbstractExceptionContextTest.java190
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/AbstractExceptionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/CloneFailedExceptionTest.java83
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/ContextedExceptionTest.java118
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/ContextedRuntimeExceptionTest.java117
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/CustomCheckedException.java39
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/CustomUncheckedException.java39
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/DefaultExceptionContextTest.java40
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java853
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/UncheckedExceptionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/UncheckedIllegalAccessExceptionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/UncheckedInterruptedExceptionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/UncheckedReflectiveOperationExceptionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/function/AnnotationTestFixture.java29
-rw-r--r--src/test/java/org/apache/commons/lang3/function/BooleanConsumerTest.java80
-rw-r--r--src/test/java/org/apache/commons/lang3/function/ConsumersTest.java49
-rw-r--r--src/test/java/org/apache/commons/lang3/function/FailableFunctionsTest.java2671
-rw-r--r--src/test/java/org/apache/commons/lang3/function/IntToCharFunctionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodFixtures.java223
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodInvokersBiConsumerTest.java56
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodInvokersBiFunctionTest.java65
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableBiConsumerTest.java62
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableBiFunctionTest.java69
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableFunctionTest.java84
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableSupplierTest.java44
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodInvokersFunctionTest.java101
-rw-r--r--src/test/java/org/apache/commons/lang3/function/MethodInvokersSupplierTest.java48
-rwxr-xr-xsrc/test/java/org/apache/commons/lang3/function/Objects.java164
-rwxr-xr-xsrc/test/java/org/apache/commons/lang3/function/ObjectsTest.java141
-rw-r--r--src/test/java/org/apache/commons/lang3/function/SuppliersTest.java42
-rw-r--r--src/test/java/org/apache/commons/lang3/function/ToBooleanBiFunctionTest.java35
-rw-r--r--src/test/java/org/apache/commons/lang3/function/TriConsumerTest.java72
-rw-r--r--src/test/java/org/apache/commons/lang3/function/TriFunctionTest.java74
-rw-r--r--src/test/java/org/apache/commons/lang3/math/FractionTest.java1124
-rw-r--r--src/test/java/org/apache/commons/lang3/math/IEEE754rUtilsTest.java105
-rw-r--r--src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java1746
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/MutableBooleanTest.java145
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/MutableByteTest.java271
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/MutableDoubleTest.java284
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/MutableFloatTest.java285
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/MutableIntTest.java281
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/MutableLongTest.java275
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/MutableObjectTest.java101
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/MutableShortTest.java257
-rw-r--r--src/test/java/org/apache/commons/lang3/mutable/PrintAtomicVsMutable.java44
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java296
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/FieldUtilsTest.java1071
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/InheritanceUtilsTest.java80
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java1092
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/TypeLiteralTest.java58
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java1039
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/Ambig.java26
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/Annotated.java28
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/AnotherChild.java23
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/AnotherParent.java23
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/Bar.java25
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/Foo.java26
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/GenericConsumer.java21
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/GenericParent.java34
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/GenericTypeHolder.java29
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/Grandchild.java23
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/Parent.java38
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/PrivatelyShadowedChild.java27
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/PublicChild.java39
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/PubliclyShadowedChild.java26
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/StaticContainer.java58
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/StaticContainerChild.java23
-rw-r--r--src/test/java/org/apache/commons/lang3/reflect/testbed/StringParameterizedChild.java43
-rw-r--r--src/test/java/org/apache/commons/lang3/stream/IntStreamsTest.java38
-rw-r--r--src/test/java/org/apache/commons/lang3/stream/LangCollectorsTest.java141
-rw-r--r--src/test/java/org/apache/commons/lang3/stream/StreamsTest.java272
-rw-r--r--src/test/java/org/apache/commons/lang3/test/NotVisibleExceptionFactory.java52
-rw-r--r--src/test/java/org/apache/commons/lang3/text/CompositeFormatTest.java89
-rw-r--r--src/test/java/org/apache/commons/lang3/text/ExtendedMessageFormatTest.java483
-rw-r--r--src/test/java/org/apache/commons/lang3/text/FormattableUtilsTest.java118
-rw-r--r--src/test/java/org/apache/commons/lang3/text/StrBuilderAppendInsertTest.java1449
-rw-r--r--src/test/java/org/apache/commons/lang3/text/StrBuilderTest.java1853
-rw-r--r--src/test/java/org/apache/commons/lang3/text/StrLookupTest.java112
-rw-r--r--src/test/java/org/apache/commons/lang3/text/StrMatcherTest.java203
-rw-r--r--src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java712
-rw-r--r--src/test/java/org/apache/commons/lang3/text/StrTokenizerTest.java848
-rw-r--r--src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java432
-rw-r--r--src/test/java/org/apache/commons/lang3/text/translate/EntityArraysTest.java74
-rw-r--r--src/test/java/org/apache/commons/lang3/text/translate/LookupTranslatorTest.java53
-rw-r--r--src/test/java/org/apache/commons/lang3/text/translate/NumericEntityEscaperTest.java70
-rw-r--r--src/test/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaperTest.java77
-rw-r--r--src/test/java/org/apache/commons/lang3/text/translate/OctalUnescaperTest.java84
-rw-r--r--src/test/java/org/apache/commons/lang3/text/translate/UnicodeEscaperTest.java57
-rw-r--r--src/test/java/org/apache/commons/lang3/text/translate/UnicodeUnescaperTest.java60
-rw-r--r--src/test/java/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemoverTest.java51
-rw-r--r--src/test/java/org/apache/commons/lang3/time/CalendarUtilsTest.java87
-rw-r--r--src/test/java/org/apache/commons/lang3/time/DateFormatUtilsTest.java248
-rw-r--r--src/test/java/org/apache/commons/lang3/time/DateUtilsFragmentTest.java520
-rw-r--r--src/test/java/org/apache/commons/lang3/time/DateUtilsRoundingTest.java759
-rw-r--r--src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java1702
-rw-r--r--src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java635
-rw-r--r--src/test/java/org/apache/commons/lang3/time/DurationUtilsTest.java156
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java361
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.java33
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java218
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java730
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastDateParser_MoreOrLessTest.java116
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java59
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java450
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastDatePrinterTimeZonesTest.java50
-rw-r--r--src/test/java/org/apache/commons/lang3/time/FastTimeZoneTest.java101
-rw-r--r--src/test/java/org/apache/commons/lang3/time/GmtTimeZoneTest.java96
-rw-r--r--src/test/java/org/apache/commons/lang3/time/Java15BugFastDateParserTest.java158
-rw-r--r--src/test/java/org/apache/commons/lang3/time/StopWatchTest.java367
-rw-r--r--src/test/java/org/apache/commons/lang3/time/WeekYearTest.java80
-rw-r--r--src/test/java/org/apache/commons/lang3/tuple/ImmutablePairTest.java260
-rw-r--r--src/test/java/org/apache/commons/lang3/tuple/ImmutableTripleTest.java202
-rw-r--r--src/test/java/org/apache/commons/lang3/tuple/MutablePairTest.java155
-rw-r--r--src/test/java/org/apache/commons/lang3/tuple/MutableTripleTest.java139
-rw-r--r--src/test/java/org/apache/commons/lang3/tuple/PairTest.java157
-rw-r--r--src/test/java/org/apache/commons/lang3/tuple/TripleTest.java153
-rw-r--r--src/test/java/org/apache/commons/lang3/util/FluentBitSetTest.java1828
-rw-r--r--src/test/resources/java.policy375
-rw-r--r--src/test/resources/lang-708-input.txt1
551 files changed, 192385 insertions, 0 deletions
diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 000000000..b76b8087f
--- /dev/null
+++ b/.asf.yaml
@@ -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.
+
+github:
+ description: "Apache Commons Lang"
+ homepage: https://commons.apache.org/lang/
+
+notifications:
+ commits: commits@commons.apache.org
+ issues: issues@commons.apache.org
+ pullrequests: issues@commons.apache.org
+ jira_options: link label
+ jobs: notifications@commons.apache.org
+ issues_bot_dependabot: notifications@commons.apache.org
+ pullrequests_bot_dependabot: notifications@commons.apache.org
+ issues_bot_codecov-commenter: notifications@commons.apache.org
+ pullrequests_bot_codecov-commenter: notifications@commons.apache.org
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..a3546495c
--- /dev/null
+++ b/.gitattributes
@@ -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.
+#
+
+# Auto detect text files and perform LF normalization
+* text=auto
+
+*.java text diff=java
+*.html text diff=html
+*.css text
+*.js text
+*.sql text
diff --git a/.github/GH-ROBOTS.txt b/.github/GH-ROBOTS.txt
new file mode 100644
index 000000000..e3329e55f
--- /dev/null
+++ b/.github/GH-ROBOTS.txt
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Keeps on creating FUD PRs in test code
+# Does not follow Apache disclosure policies
+User-agent: JLLeitschuh/security-research
+Disallow: *
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..430d76dc4
--- /dev/null
+++ b/.github/dependabot.yml
@@ -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.
+
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "friday"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "friday"
+
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 000000000..6355cb7a7
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,85 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '33 9 * * 4'
+
+permissions:
+ contents: read
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'java' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
+ with:
+ persist-credentials: false
+ - uses: actions/cache@v3.2.5
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 000000000..4a9ec6ac3
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -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.
+
+name: Coverage
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ java: [ 8 ]
+
+ steps:
+ - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
+ with:
+ persist-credentials: false
+ - uses: actions/cache@v3.2.5
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@v3.10.0
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.java }}
+ - name: Build with Maven
+ run: mvn -V test jacoco:report --file pom.xml --no-transfer-progress
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v3
+ with:
+ files: ./target/site/jacoco/jacoco.xml
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 000000000..4f9441c7a
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -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.
+
+name: Java CI
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+ continue-on-error: ${{ matrix.experimental }}
+ strategy:
+ matrix:
+ java: [ 8, 11, 17 ]
+ experimental: [false]
+# include:
+# - java: 18-ea
+# experimental: true
+ steps:
+ - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
+ with:
+ persist-credentials: false
+ - uses: actions/cache@v3.2.5
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@v3.10.0
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.java }}
+ - name: Build with Maven
+ run: mvn -V -Ddoclint=all --file pom.xml --no-transfer-progress
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
new file mode 100644
index 000000000..0f7047adc
--- /dev/null
+++ b/.github/workflows/scorecards-analysis.yml
@@ -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.
+
+name: "Scorecards supply-chain security"
+
+on:
+ branch_protection_rule:
+ schedule:
+ - cron: "30 1 * * 6" # Weekly on Saturdays
+ push:
+ branches: [ "master" ]
+
+permissions: read-all
+
+jobs:
+
+ analysis:
+
+ name: "Scorecards analysis"
+ runs-on: ubuntu-latest
+ permissions:
+ # Needed to upload the results to the code-scanning dashboard.
+ security-events: write
+ actions: read
+ id-token: write # This is required for requesting the JWT
+ contents: read # This is required for actions/checkout
+
+ steps:
+
+ - name: "Checkout code"
+ uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # 3.3.0
+ with:
+ persist-credentials: false
+
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # 2.1.2
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # A read-only PAT token, which is sufficient for the action to function.
+ # The relevant discussion: https://github.com/ossf/scorecard-action/issues/188
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ # Publish the results for public repositories to enable scorecard badges.
+ # For more details: https://github.com/ossf/scorecard-action#publishing-results
+ publish_results: true
+
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # 3.1.0
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@b398f525a5587552e573b247ac661067fafa920b # 2.1.22
+ with:
+ sarif_file: results.sarif
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..c30f980e3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+# Maven build files
+target
+*.log
+maven-eclipse.xml
+build.properties
+site-content
+*~
+
+# IntelliJ IDEA files
+.idea
+.iws
+*.iml
+*.ipr
+
+# Eclipse files
+.settings
+.classpath
+.project
+.externalToolBuilders
+.checkstyle
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 000000000..883e3e361
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["external_apache_commons_lang_license"],
+}
+
+license {
+ name: "external_apache_commons_lang_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "LICENSE",
+ "LICENSE.txt",
+ "NOTICE.txt",
+ ],
+}
+
+java_library {
+ name: "apache-commons-lang",
+ host_supported: true,
+ srcs: ["src/main/java/**/*.java"],
+ exclude_srcs: ["src/main/java/**/MethodInvokers.java"],
+ sdk_version: "current",
+ min_sdk_version: "33",
+
+ java_version: "1.8",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.ondevicepersonalization",
+ ],
+ visibility: [
+ "//external/apache-velocity-engine",
+ "//packages/modules/OnDevicePersonalization:__subpackages__",
+ ],
+}
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..3ed501501
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,17 @@
+<!---
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+The Apache code of conduct page is [https://www.apache.org/foundation/policies/conduct.html](https://www.apache.org/foundation/policies/conduct.html).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..3e7b0a917
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,115 @@
+<!---
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!---
+ +======================================================================+
+ |**** ****|
+ |**** THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN ****|
+ |**** DO NOT EDIT DIRECTLY ****|
+ |**** ****|
+ +======================================================================+
+ | TEMPLATE FILE: contributing-md-template.md |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ | |
+ | 1) Re-generate using: mvn commons-build:contributing-md |
+ | |
+ | 2) Set the following properties in the component's pom: |
+ | - commons.jira.id (required, alphabetic, upper case) |
+ | |
+ | 3) Example Properties |
+ | |
+ | <properties> |
+ | <commons.jira.id>MATH</commons.jira.id> |
+ | </properties> |
+ | |
+ +======================================================================+
+--->
+Contributing to Apache Commons Lang
+======================
+
+You have found a bug or you have an idea for a cool new feature? Contributing code is a great way to give something back to
+the open source community. Before you dig right into the code there are a few guidelines that we need contributors to
+follow so that we can have a chance of keeping on top of things.
+
+Getting Started
+---------------
+
++ Make sure you have a [JIRA account](https://issues.apache.org/jira/).
++ Make sure you have a [GitHub account](https://github.com/signup/free).
++ If you're planning to implement a new feature it makes sense to discuss your changes on the [dev list](https://commons.apache.org/mail-lists.html) first. This way you can make sure you're not wasting your time on something that isn't considered to be in Apache Commons Lang's scope.
++ Submit a [Jira Ticket][jira] for your issue, assuming one does not already exist.
+ + Clearly describe the issue including steps to reproduce when it is a bug.
+ + Make sure you fill in the earliest version that you know has the issue.
++ Find the corresponding [repository on GitHub](https://github.com/apache/?query=commons-),
+[fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) and check out your forked repository.
+
+Making Changes
+--------------
+
++ Create a _topic branch_ for your isolated work.
+ * Usually you should base your branch on the `master` branch.
+ * A good topic branch name can be the JIRA bug id plus a keyword, e.g. `LANG-123-InputStream`.
+ * If you have submitted multiple JIRA issues, try to maintain separate branches and pull requests.
++ Make commits of logical units.
+ * Make sure your commit messages are meaningful and in the proper format. Your commit message should contain the key of the JIRA issue.
+ * e.g. `LANG-123: Close input stream earlier`
++ Respect the original code style:
+ + Only use spaces for indentation.
+ + Create minimal diffs - disable _On Save_ actions like _Reformat Source Code_ or _Organize Imports_. If you feel the source code should be reformatted create a separate PR for this change first.
+ + Check for unnecessary whitespace with `git diff` -- check before committing.
++ Make sure you have added the necessary tests for your changes, typically in `src/test/java`.
++ Run all the tests with `mvn clean verify` to assure nothing else was accidentally broken.
+
+Making Trivial Changes
+----------------------
+
+The JIRA tickets are used to generate the changelog for the next release.
+
+For changes of a trivial nature to comments and documentation, it is not always necessary to create a new ticket in JIRA.
+In this case, it is appropriate to start the first line of a commit with '(doc)' instead of a ticket number.
+
+
+Submitting Changes
+------------------
+
++ Sign and submit the Apache [Contributor License Agreement][cla] if you haven't already.
+ * Note that small patches & typical bug fixes do not require a CLA as
+ clause 5 of the [Apache License](https://www.apache.org/licenses/LICENSE-2.0.html#contributions)
+ covers them.
++ Push your changes to a topic branch in your fork of the repository.
++ Submit a _Pull Request_ to the corresponding repository in the `apache` organization.
+ * Verify _Files Changed_ shows only your intended changes and does not
+ include additional files like `target/*.class`
++ Update your JIRA ticket and include a link to the pull request in the ticket.
+
+If you prefer to not use GitHub, then you can instead use
+`git format-patch` (or `svn diff`) and attach the patch file to the JIRA issue.
+
+
+Additional Resources
+--------------------
+
++ [Contributing patches](https://commons.apache.org/patches.html)
++ [Apache Commons Lang JIRA project page][jira]
++ [Contributor License Agreement][cla]
++ [General GitHub documentation](https://docs.github.com/)
++ [GitHub pull request documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)
++ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons)
++ `#apache-commons` IRC channel on `irc.freenode.net`
+
+[cla]:https://www.apache.org/licenses/#clas
+[jira]:https://issues.apache.org/jira/browse/LANG
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 000000000..36799d0f5
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,115 @@
+#!groovy
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+pipeline {
+ agent {
+ node {
+ label 'ubuntu'
+ }
+ }
+
+ tools {
+ maven 'Maven 3 (latest)'
+ jdk 'JDK 1.8 (latest)'
+ }
+
+ stages {
+ stage('Build') {
+ steps {
+ sh 'mvn'
+ }
+ post {
+ always {
+ junit(testResults: '**/surefire-reports/*.xml', allowEmptyResults: true)
+ }
+ }
+ }
+ stage('Deploy') {
+ when {
+ branch 'master'
+ }
+ steps {
+ sh 'mvn deploy'
+ }
+ }
+ }
+
+ // Send out notifications on unsuccessful builds.
+ post {
+ // If this build failed, send an email to the list.
+ failure {
+ script {
+ if(env.BRANCH_NAME == "master") {
+ def state = (currentBuild.previousBuild != null) && (currentBuild.previousBuild.result == 'FAILURE') ? "Still failing" : "Failure"
+ emailext(
+ subject: "[Lang] Change on branch \"${env.BRANCH_NAME}\": ${env.JOB_NAME} - Build # ${env.BUILD_NUMBER} - $state",
+ body: """The Apache Jenkins build system has built ${env.JOB_NAME} (build #${env.BUILD_NUMBER})
+
+Status: ${currentBuild.result}
+
+Check console output at <a href="${env.BUILD_URL}">${env.BUILD_URL}</a> to view the results.
+""",
+ to: "notifications@commons.apache.org",
+ recipientProviders: [[$class: 'DevelopersRecipientProvider']]
+ )
+ }
+ }
+ }
+
+ // If this build didn't fail, but there were failing tests, send an email to the list.
+ unstable {
+ script {
+ if(env.BRANCH_NAME == "master") {
+ def state = (currentBuild.previousBuild != null) && (currentBuild.previousBuild.result == 'UNSTABLE') ? "Still unstable" : "Unstable"
+ emailext(
+ subject: "[Lang] Change on branch \"${env.BRANCH_NAME}\": ${env.JOB_NAME} - Build # ${env.BUILD_NUMBER} - $state",
+ body: """The Apache Jenkins build system has built ${env.JOB_NAME} (build #${env.BUILD_NUMBER})
+
+Status: ${currentBuild.result}
+
+Check console output at <a href="${env.BUILD_URL}">${env.BUILD_URL}</a> to view the results.
+""",
+ to: "notifications@commons.apache.org",
+ recipientProviders: [[$class: 'DevelopersRecipientProvider']]
+ )
+ }
+ }
+ }
+
+ // Send an email, if the last build was not successful and this one is.
+ success {
+ script {
+ if ((env.BRANCH_NAME == "master") && (currentBuild.previousBuild != null) && (currentBuild.previousBuild.result != 'SUCCESS')) {
+ emailext (
+ subject: "[Lang] Change on branch \"${env.BRANCH_NAME}\": ${env.JOB_NAME} - Build # ${env.BUILD_NUMBER} - Back to normal",
+ body: """The Apache Jenkins build system has built ${env.JOB_NAME} (build #${env.BUILD_NUMBER})
+
+Status: ${currentBuild.result}
+
+Check console output at <a href="${env.BUILD_URL}">${env.BUILD_URL}</a> to view the results.
+""",
+ to: "notifications@commons.apache.org",
+ recipientProviders: [[$class: 'DevelopersRecipientProvider']]
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 000000000..85de3d454
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE.txt \ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/METADATA b/METADATA
new file mode 100644
index 000000000..9f2ca16b4
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "apache-commons-lang"
+description:
+ "Apache Commons Lang, a package of Java utility classes for the classes "
+ "that are in java.lang's hierarchy, or are considered to be so standard as "
+ "to justify existence in java.lang."
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://commons.apache.org/proper/commons-lang/"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/apache/commons-lang.git"
+ }
+ version: "eaff7e0a693455081932b53688029f0700447305"
+ last_upgrade_date { year: 2023 month: 02 day: 13 }
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 000000000..307ec45cf
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,5 @@
+Apache Commons Lang
+Copyright 2001-2023 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (https://www.apache.org/).
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 000000000..3f20f5463
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+akvuong@google.com
+karthikmahesh@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..7e966370a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,162 @@
+<!---
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!---
+ +======================================================================+
+ |**** ****|
+ |**** THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN ****|
+ |**** DO NOT EDIT DIRECTLY ****|
+ |**** ****|
+ +======================================================================+
+ | TEMPLATE FILE: readme-md-template.md |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ | |
+ | 1) Re-generate using: mvn commons-build:readme-md |
+ | |
+ | 2) Set the following properties in the component's pom: |
+ | - commons.componentid (required, alphabetic, lower case) |
+ | - commons.release.version (required) |
+ | |
+ | 3) Example Properties |
+ | |
+ | <properties> |
+ | <commons.componentid>math</commons.componentid> |
+ | <commons.release.version>1.2</commons.release.version> |
+ | </properties> |
+ | |
+ +======================================================================+
+--->
+Apache Commons Lang
+===================
+
+[![GitHub Actions Status](https://github.com/apache/commons-lang/workflows/Java%20CI/badge.svg)](https://github.com/apache/commons-lang/actions)
+[![Coverage Status](https://codecov.io/gh/apache/commons-lang/branch/master/graph/badge.svg)](https://app.codecov.io/gh/apache/commons-lang)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.commons/commons-lang3/badge.svg?gav=true)](https://maven-badges.herokuapp.com/maven-central/org.apache.commons/commons-lang3/?gav=true)
+[![Javadocs](https://javadoc.io/badge/org.apache.commons/commons-lang3/3.12.0.svg)](https://javadoc.io/doc/org.apache.commons/commons-lang3/3.12.0)
+[![CodeQL](https://github.com/apache/commons-lang/workflows/CodeQL/badge.svg)](https://github.com/apache/commons-lang/actions/workflows/codeql-analysis.yml?query=workflow%3ACodeQL)
+[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/apache/commons-lang/badge)](https://api.securityscorecards.dev/projects/github.com/apache/commons-lang)
+
+Apache Commons Lang, a package of Java utility classes for the
+ classes that are in java.lang's hierarchy, or are considered to be so
+ standard as to justify existence in java.lang.
+
+Documentation
+-------------
+
+More information can be found on the [Apache Commons Lang homepage](https://commons.apache.org/proper/commons-lang).
+The [Javadoc](https://commons.apache.org/proper/commons-lang/apidocs) can be browsed.
+Questions related to the usage of Apache Commons Lang should be posted to the [user mailing list][ml].
+
+Where can I get the latest release?
+-----------------------------------
+You can download source and binaries from our [download page](https://commons.apache.org/proper/commons-lang/download_lang.cgi).
+
+Alternatively you can pull it from the central Maven repositories:
+
+```xml
+<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.12.0</version>
+</dependency>
+```
+
+Contributing
+------------
+
+We accept Pull Requests via GitHub. The [developer mailing list](https://commons.apache.org/mail-lists.html) is the main channel of communication for contributors.
+There are some guidelines which will make applying PRs easier for us:
++ No tabs! Please use spaces for indentation.
++ Respect the code style.
++ Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change.
++ Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running ```mvn```.
+
+If you plan to contribute on a regular basis, please consider filing a [contributor license agreement](https://www.apache.org/licenses/#clas).
+You can learn more about contributing via GitHub in our [contribution guidelines](CONTRIBUTING.md).
+
+License
+-------
+This code is under the [Apache Licence v2](https://www.apache.org/licenses/LICENSE-2.0).
+
+See the `NOTICE.txt` file for required notices and attributions.
+
+Donations
+---------
+You like Apache Commons Lang? Then [donate back to the ASF](https://www.apache.org/foundation/contributing.html) to support the development.
+
+Additional Resources
+--------------------
+
++ [Apache Commons Homepage](https://commons.apache.org/)
++ [Apache Issue Tracker (JIRA)](https://issues.apache.org/jira/browse/LANG)
++ [Apache Commons Slack Channel](https://the-asf.slack.com/archives/C60NVB8AD)
++ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons)
++ `#apache-commons` IRC channel on `irc.freenode.org`
+
+Apache Commons Components
+-------------------------
+| Component | GitHub Repository | Apache Homepage |
+| --------- | ----------------- | ----------------|
+| Apache Commons BCEL | [commons-bcel](https://github.com/apache/commons-bcel) | [commons-bcel](https://commons.apache.org/proper/commons-bcel) |
+| Apache Commons Beanutils | [commons-beanutils](https://github.com/apache/commons-beanutils) | [commons-beanutils](https://commons.apache.org/proper/commons-beanutils) |
+| Apache Commons BSF | [commons-bsf](https://github.com/apache/commons-bsf) | [commons-bsf](https://commons.apache.org/proper/commons-bsf) |
+| Apache Commons Build-plugin | [commons-build-plugin](https://github.com/apache/commons-build-plugin) | [commons-build-plugin](https://commons.apache.org/proper/commons-build-plugin) |
+| Apache Commons Chain | [commons-chain](https://github.com/apache/commons-chain) | [commons-chain](https://commons.apache.org/proper/commons-chain) |
+| Apache Commons CLI | [commons-cli](https://github.com/apache/commons-cli) | [commons-cli](https://commons.apache.org/proper/commons-cli) |
+| Apache Commons Codec | [commons-codec](https://github.com/apache/commons-codec) | [commons-codec](https://commons.apache.org/proper/commons-codec) |
+| Apache Commons Collections | [commons-collections](https://github.com/apache/commons-collections) | [commons-collections](https://commons.apache.org/proper/commons-collections) |
+| Apache Commons Compress | [commons-compress](https://github.com/apache/commons-compress) | [commons-compress](https://commons.apache.org/proper/commons-compress) |
+| Apache Commons Configuration | [commons-configuration](https://github.com/apache/commons-configuration) | [commons-configuration](https://commons.apache.org/proper/commons-configuration) |
+| Apache Commons Crypto | [commons-crypto](https://github.com/apache/commons-crypto) | [commons-crypto](https://commons.apache.org/proper/commons-crypto) |
+| Apache Commons CSV | [commons-csv](https://github.com/apache/commons-csv) | [commons-csv](https://commons.apache.org/proper/commons-csv) |
+| Apache Commons Daemon | [commons-daemon](https://github.com/apache/commons-daemon) | [commons-daemon](https://commons.apache.org/proper/commons-daemon) |
+| Apache Commons DBCP | [commons-dbcp](https://github.com/apache/commons-dbcp) | [commons-dbcp](https://commons.apache.org/proper/commons-dbcp) |
+| Apache Commons Dbutils | [commons-dbutils](https://github.com/apache/commons-dbutils) | [commons-dbutils](https://commons.apache.org/proper/commons-dbutils) |
+| Apache Commons Digester | [commons-digester](https://github.com/apache/commons-digester) | [commons-digester](https://commons.apache.org/proper/commons-digester) |
+| Apache Commons Email | [commons-email](https://github.com/apache/commons-email) | [commons-email](https://commons.apache.org/proper/commons-email) |
+| Apache Commons Exec | [commons-exec](https://github.com/apache/commons-exec) | [commons-exec](https://commons.apache.org/proper/commons-exec) |
+| Apache Commons Fileupload | [commons-fileupload](https://github.com/apache/commons-fileupload) | [commons-fileupload](https://commons.apache.org/proper/commons-fileupload) |
+| Apache Commons Functor | [commons-functor](https://github.com/apache/commons-functor) | [commons-functor](https://commons.apache.org/proper/commons-functor) |
+| Apache Commons Geometry | [commons-geometry](https://github.com/apache/commons-geometry) | [commons-geometry](https://commons.apache.org/proper/commons-geometry) |
+| Apache Commons Graph | [commons-graph](https://github.com/apache/commons-graph) | [commons-graph](https://commons.apache.org/proper/commons-graph) |
+| Apache Commons Imaging | [commons-imaging](https://github.com/apache/commons-imaging) | [commons-imaging](https://commons.apache.org/proper/commons-imaging) |
+| Apache Commons IO | [commons-io](https://github.com/apache/commons-io) | [commons-io](https://commons.apache.org/proper/commons-io) |
+| Apache Commons JCI | [commons-jci](https://github.com/apache/commons-jci) | [commons-jci](https://commons.apache.org/proper/commons-jci) |
+| Apache Commons JCS | [commons-jcs](https://github.com/apache/commons-jcs) | [commons-jcs](https://commons.apache.org/proper/commons-jcs) |
+| Apache Commons Jelly | [commons-jelly](https://github.com/apache/commons-jelly) | [commons-jelly](https://commons.apache.org/proper/commons-jelly) |
+| Apache Commons Jexl | [commons-jexl](https://github.com/apache/commons-jexl) | [commons-jexl](https://commons.apache.org/proper/commons-jexl) |
+| Apache Commons Jxpath | [commons-jxpath](https://github.com/apache/commons-jxpath) | [commons-jxpath](https://commons.apache.org/proper/commons-jxpath) |
+| Apache Commons Lang | [commons-lang](https://github.com/apache/commons-lang) | [commons-lang](https://commons.apache.org/proper/commons-lang) |
+| Apache Commons Logging | [commons-logging](https://github.com/apache/commons-logging) | [commons-logging](https://commons.apache.org/proper/commons-logging) |
+| Apache Commons Math | [commons-math](https://github.com/apache/commons-math) | [commons-math](https://commons.apache.org/proper/commons-math) |
+| Apache Commons Net | [commons-net](https://github.com/apache/commons-net) | [commons-net](https://commons.apache.org/proper/commons-net) |
+| Apache Commons Numbers | [commons-numbers](https://github.com/apache/commons-numbers) | [commons-numbers](https://commons.apache.org/proper/commons-numbers) |
+| Apache Commons Parent | [commons-parent](https://github.com/apache/commons-parent) | [commons-parent](https://commons.apache.org/proper/commons-parent) |
+| Apache Commons Pool | [commons-pool](https://github.com/apache/commons-pool) | [commons-pool](https://commons.apache.org/proper/commons-pool) |
+| Apache Commons Proxy | [commons-proxy](https://github.com/apache/commons-proxy) | [commons-proxy](https://commons.apache.org/proper/commons-proxy) |
+| Apache Commons RDF | [commons-rdf](https://github.com/apache/commons-rdf) | [commons-rdf](https://commons.apache.org/proper/commons-rdf) |
+| Apache Commons Release-plugin | [commons-release-plugin](https://github.com/apache/commons-release-plugin) | [commons-release-plugin](https://commons.apache.org/proper/commons-release-plugin) |
+| Apache Commons Rng | [commons-rng](https://github.com/apache/commons-rng) | [commons-rng](https://commons.apache.org/proper/commons-rng) |
+| Apache Commons Scxml | [commons-scxml](https://github.com/apache/commons-scxml) | [commons-scxml](https://commons.apache.org/proper/commons-scxml) |
+| Apache Commons Signing | [commons-signing](https://github.com/apache/commons-signing) | [commons-signing](https://commons.apache.org/proper/commons-signing) |
+| Apache Commons Skin | [commons-skin](https://github.com/apache/commons-skin) | [commons-skin](https://commons.apache.org/proper/commons-skin) |
+| Apache Commons Statistics | [commons-statistics](https://github.com/apache/commons-statistics) | [commons-statistics](https://commons.apache.org/proper/commons-statistics) |
+| Apache Commons Testing | [commons-testing](https://github.com/apache/commons-testing) | [commons-testing](https://commons.apache.org/proper/commons-testing) |
+| Apache Commons Text | [commons-text](https://github.com/apache/commons-text) | [commons-text](https://commons.apache.org/proper/commons-text) |
+| Apache Commons Validator | [commons-validator](https://github.com/apache/commons-validator) | [commons-validator](https://commons.apache.org/proper/commons-validator) |
+| Apache Commons VFS | [commons-vfs](https://github.com/apache/commons-vfs) | [commons-vfs](https://commons.apache.org/proper/commons-vfs) |
+| Apache Commons Weaver | [commons-weaver](https://github.com/apache/commons-weaver) | [commons-weaver](https://commons.apache.org/proper/commons-weaver) |
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
new file mode 100644
index 000000000..06faa9caa
--- /dev/null
+++ b/RELEASE-NOTES.txt
@@ -0,0 +1,1610 @@
+ Apache Commons Lang
+ Version 3.12.0
+ Release Notes
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.12.0 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.9 and onwards now targets Java 8, making use of features that arrived with Java 8.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes.
+
+Changes in this version include:
+
+New features:
+o Add BooleanUtils.booleanValues(). Thanks to Gary Gregory.
+o Add BooleanUtils.primitiveValues(). Thanks to Gary Gregory.
+o LANG-1535: Add StringUtils.containsAnyIgnoreCase(CharSequence, CharSequence...). Thanks to Gary Gregory, Isira Seneviratne.
+o LANG-1359: Add StopWatch.getStopTime(). Thanks to Gary Gregory, Keegan Witt.
+o More test coverage for CharSequenceUtils. #631. Thanks to Edgar Asatryan.
+o Add fluent-style ArraySorter. Thanks to Gary Gregory.
+o Add and use LocaleUtils.toLocale(Locale) to avoid NPEs. Thanks to Gary Gregory.
+o Add FailableShortSupplier, handy for JDBC APIs. Thanks to Gary Gregory.
+o Add JavaVersion.JAVA_17. Thanks to Gary Gregory.
+o LANG-1636: Add missing boolean[] join method #686. Thanks to .
+o Add StringUtils.substringBefore(String, int). Thanks to Gary Gregory.
+o Add Range.INTEGER. Thanks to Gary Gregory.
+o Add DurationUtils. Thanks to Gary Gregory.
+o Introduce the use of @Nonnull, and @Nullable, and the Objects class as a helper tool.
+o Add and use true and false String constants #714. Thanks to Arturo Bernal, Gary Gregory.
+o Add and use ObjectUtils.requireNonEmpty() #716. Thanks to Arturo Bernal, Gary Gregory.
+
+Fixed Bugs:
+o LANG-1592: Correct implementation of RandomUtils.nextLong(long, long) Thanks to Huang Pingcai, Alex Herbert.
+o LANG-1600: Restore handling of collections for non-JSON ToStringStyle #610. Thanks to Michael F.
+o ContextedException Javadoc add missing semicolon #581. Thanks to iamchao1129.
+o LANG-1608: Resolve JUnit pioneer transitive dependencies using JUnit BOM. Thanks to Edgar Asatryan.
+o NumberUtilsTest - incorrect types in min/max tests #634. Thanks to HubertWo, Gary Gregory.
+o LANG-1579: Improve StringUtils.stripAccents conversion of remaining accents. Thanks to XenoAmess.
+o LANG-1606: StringUtils.countMatches - clarify Javadoc. Thanks to Rustem Galiev.
+o LANG-1591: Remove redundant argument from substring call. Thanks to bhawna94.
+o LANG-1613: BigDecimal is created when you pass it the min and max values, #642. Thanks to Arturo Bernal, Gary Gregory.
+o LANG-1541: ArrayUtils.contains() and indexOf() fail to handle Double.NaN #647. Thanks to Arturo Bernal, Gary Gregory.
+o LANG-1615: ArrayUtils contains() and indexOf() fail to handle Float.NaN # #561. Thanks to Arturo Bernal, Gary Gregory.
+o Fix potential NPE in TypeUtils.isAssignable(Type, ParameterizedType, Map, Type>). Thanks to Gary Gregory.
+o LANG-1420: TypeUtils.isAssignable returns wrong result for GenericArrayType and ParameterizedType, #643. Thanks to Gordon Fraser, Rostislav Krasny, Arturo Bernal, Gary Gregory.
+o LANG-1612: testGetAllFields and testGetFieldsWithAnnotation sometimes fail. Thanks to XinT, Gary Gregory.
+o Fix Javadoc for SystemUtils.isJavaVersionAtMost() #638. Thanks to John R. D'Orazio.
+o LANG-1610: Fix StringUtils.unwrap throws StringIndexOutOfBoundsException #636. Thanks to Tony Liang.
+o Fix formatting of isAnyBlank() and isAnyEmpty(). #513. Thanks to Isira Seneviratne.
+o LANG-1618: TypeUtils. containsTypeVariables does not support GenericArrayType #661. Thanks to Arturo Bernal.
+o LANG-1622: Javadoc of some methods incorrectly refers to another method, #667, #668. #670. Thanks to Kanak Sony, anomen-s.
+o LANG-1620: Refine StringUtils.lastIndexOfIgnoreCase #664. Thanks to Arturo Bernal.
+o LANG-1619: Refine StringUtils.abbreviate #663. Thanks to Arturo Bernal.
+o LANG-1584: Refine StringUtils.isNumericSpace #573. Thanks to Arturo Bernal.
+o LANG-1580: Refine StringUtils.deleteWhitespace #569. Thanks to Arturo Bernal.
+o LANG-1626: Correction in Javadoc of some methods. #673 Thanks to Kanak Sony.
+o LANG-1628: Javadoc for RandomStringUtils.random() letters, numbers parameters is wrong. Thanks to Jarkko Rantavuori.
+o Correct markup in Javadoc for unbalanced braces #679. Thanks to Felix Schumacher.
+o LANG-1544: MethodUtils.invokeMethod NullPointerException in case of null in args list #680. Thanks to Peter Nagy, Michael Buck, Gary Gregory.
+o LANG-1637: Fix 2 digit week year formatting #688. Thanks to Uri Gonen, Gary Gregory, Michael Osipov.
+o Fix broken Javadoc links to commons-text #712. Thanks to Chris Smowton.
+o Add and use ThreadUtils.sleep(Duration). Thanks to Gary Gregory.
+o Add and use ThreadUtils.join(Thread, Duration). Thanks to Gary Gregory.
+o Add ObjectUtils.wait(Duration). Thanks to Gary Gregory.
+
+Changes:
+o LANG-1596: ArrayUtils.toPrimitive(Object) does not support boolean and other types #607. Thanks to Richard Eckart de Castilho.
+o Enable Dependabot #587. Thanks to Gary Gregory.
+o Bump junit-jupiter from 5.6.2 to 5.7.0.
+o Bump spotbugs from 4.1.2 to 4.2.1, #627, #671, #708. Thanks to chtompki, Dependabot.
+o Bump spotbugs-maven-plugin from 4.0.0 to 4.2.0, #593, #596, #609, #623, #632, #692. Thanks to Dependabot.
+o Bump biz.aQute.bndlib from 5.1.1 to 5.3.0 #592, #628, #715. Thanks to Dependabot.
+o Bump junit-pioneer from 0.6.0 to 1.1.0, #589, #597, #600, #624, #625, #662. Thanks to Dependabot.
+o Bump checkstyle from 8.34 to 8.40, #594, #614, #637, #665, #706. Thanks to Dependabot.
+o Bump actions/checkout from v2.3.1 to v2.3.4 #601, #639. Thanks to Dependabot.
+o Bump actions/setup-java from v1.4.0 to v1.4.2 #612. Thanks to Dependabot.
+o Update commons.jacoco.version 0.8.5 to 0.8.6 (Fixes Java 15 builds). Thanks to Gary Gregory.
+o Update maven-surefire-plugin 2.22.2 -> 3.0.0-M5. Thanks to Gary Gregory.
+o Bump maven-pmd-plugin from 3.13.0 to 3.14.0 #660. Thanks to Dependabot.
+o Bump jmh.version from 1.21 to 1.27 #674. Thanks to Dependabot.
+o Update commons.japicmp.version 0.14.3 -> 0.15.2. Thanks to Gary Gregory.
+o Processor.java: check enum equality with == instead of .equals() method #690. Thanks to Ali K. Nouri.
+o Bump junit-pioneer from 1.1.0 to 1.3.0 #702. Thanks to Dependabot.
+o Bump maven-checkstyle-plugin from 3.1.1 to 3.1.2 #705. Thanks to Dependabot.
+o Bump actions/cache from v2 to v2.1.4 #710. Thanks to Dependabot.
+o Bump junit-bom from 5.7.0 to 5.7.1 #707. Thanks to Dependabot.
+o Minor Improvements #701. Thanks to Arturo Bernal.
+o Minor Improvement: Add final variable.try to make the code read-only #700. Thanks to Arturo Bernal.
+o Minor Improvement: Remove redundant initializer #699. Thanks to Arturo Bernal.
+o Use own validator ObjectUtils.anyNull to check null String input #718. Thanks to Arturo Bernal.
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+Download page: https://commons.apache.org/proper/commons-lang/download_lang.cgi
+
+Have fun!
+-Apache Commons Team
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.11
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.11 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.9 and onwards now targets Java 8, making use of features that arrived with Java 8.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes.
+
+Changes in this version include:
+
+New features:
+o Add ArrayUtils.isSameLength() to compare more array types #430. Thanks to XenoAmess, Gary Gregory.
+o Added the Locks class as a convenient possibility to deal with locked objects.
+o LANG-1568: Add to Functions: FailableBooleanSupplier, FailableIntSupplier, FailableLongSupplier, FailableDoubleSupplier, and so on.
+o LANG-1569: Add ArrayUtils.get(T[], index, T) to provide an out-of-bounds default value.
+o LANG-1570: Add JavaVersion enum constants for Java 14 and 15. #553. Thanks to Edgar Asatryan.
+o Add JavaVersion enum constants for Java 16. Thanks to Gary Gregory.
+o LANG-1556: Use Java 8 lambdas and Map operations. Thanks to XenoAmess.
+o LANG-1565: Change removeLastFieldSeparator to use endsWith #550. Thanks to XenoAmess.
+o LANG-1557: Change a Pattern to a static final field, for not letting it compile each time the function invoked. #542. Thanks to XenoAmess, Gary Gregory.
+o Add ImmutablePair factory methods left() and right().
+o Add ObjectUtils.toString(Object, Supplier<String>).
+o Add org.apache.commons.lang3.StringUtils.substringAfter(String, int).
+o Add org.apache.commons.lang3.StringUtils.substringAfterLast(String, int).
+
+Fixed Bugs:
+o Fix Javadoc for StringUtils.appendIfMissingIgnoreCase() #507. Thanks to contextshuffling.
+o LANG-1560: Refine Javadoc #545. Thanks to XenoAmess.
+o LANG-1554: Fix typos #539. Thanks to XenoAmess.
+o LANG-1555: Ignored exception `ignored`, should not be called so #540. Thanks to XenoAmess.
+o LANG-1528: StringUtils.replaceEachRepeatedly gives IllegalStateException #505. Thanks to Edwin Delgado H.
+o LANG-1543: [JSON string for maps] ToStringBuilder.reflectionToString doesnt render nested maps correctly. Thanks to Swaraj Pal, Wander Costa, Gary Gregory.
+o Correct Javadocs of methods that use Validate.notNull() and replace some uses of Validate.isTrue() with Validate.notNull(). #525. Thanks to Isira Seneviratne.
+o LANG-1539: Add allNull() and anyNull() methods to ObjectUtils. #522. Thanks to Isira Seneviratne.
+
+Changes:
+o Refine test output for FastDateParserTest Thanks to Jin Xu.
+o LANG-1549: CharSequenceUtils.lastIndexOf : remake it Thanks to Jin Xu.
+o remove encoding and docEncoding and use inherited values from commons-parent Thanks to XenoAmess.
+o Simplify null checks in Pair.hashCode() using Objects.hashCode(). #517. Thanks to Isira Seneviratne, Bruno P. Kinoshita.
+o Simplify null checks in Triple.hashCode() using Objects.hashCode(). #516. Thanks to Isira Seneviratne, Bruno P. Kinoshita.
+o Simplify some if statements in StringUtils. #521. Thanks to Isira Seneviratne, Bruno P. Kinoshita.
+o LANG-1537: Simplify a null check in the private replaceEach() method of StringUtils. #514. Thanks to Isira Seneviratne, Bruno P. Kinoshita.
+o LANG-1534: Replace some usages of the ternary operator with calls to Math.max() and Math.min() #512. Thanks to Isira Seneviratne, Bruno P. Kinoshita.
+o (Javadoc) Fix return tag for throwableOf*() methods #518. Thanks to Arend v. Reinersdorff, Bruno P. Kinoshita.
+o LANG-1545: CharSequenceUtils.regionMatches is wrong dealing with Georgian. Thanks to XenoAmess, Gary Gregory.
+o LANG-1550: Optimize ArrayUtils::isArrayIndexValid method. #551. Thanks to Edgar Asatryan.
+o LANG-1561: Use List.sort instead of Collection.sort #546. Thanks to XenoAmess.
+o LANG-1563: Use StandardCharsets.UTF_8 #548. Thanks to XenoAmess.
+o LANG-1564: Use Collections.singletonList insteadof Arrays.asList when there be only one element. #549. Thanks to XenoAmess.
+o LANG-1553: Change array style from `int a[]` to `int[] a` #537. Thanks to XenoAmess.
+o LANG-1552: Change from addAll to constructors for some List #536. Thanks to XenoAmess.
+o LANG-1558: Simplify if as some conditions are covered by others #543. Thanks to XenoAmess.
+o LANG-1567: Fixed Javadocs for setTestRecursive() #556. Thanks to Miguel Muoz, Bruno P. Kinoshita, Gary Gregory.
+o LANG-1542: ToStringBuilder.reflectionToString - Wrong JSON format when object has a List of Enum. Thanks to Tr?n Ng?c Khoa, Gary Gregory.
+o Make org.apache.commons.lang3.CharSequenceUtils.toCharArray(CharSequence) public.
+o org.apache.commons:commons-parent 50 -> 51.
+o org.junit-pioneer:junit-pioneer 0.5.4 -> 0.6.0.
+o org.junit.jupiter:junit-jupiter 5.6.0 -> 5.6.2.
+o com.github.spotbugs:spotbugs 4.0.0 -> 4.0.6.
+o com.puppycrawl.tools:checkstyle 8.29 -> 8.34.
+o commons.surefire.version 3.0.0-M4 -> 3.0.0-M5..
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+Download page: https://commons.apache.org/proper/commons-lang/download_csv.cgi
+
+Have fun!
+-Apache Commons Team
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.10
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.10 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.9 and onwards now targets Java 8, making use of features that arrived with Java 8.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 8, supports Java 9, 10, 11.
+
+Changes in this version include:
+
+New features:
+o LANG-1457: Add ExceptionUtils.throwableOfType(Throwable, Class) and friends.
+o LANG-1458: Add EMPTY_ARRAY constants to classes in org.apache.commons.lang3.tuple.
+o LANG-1461: Add null-safe StringUtils APIs to wrap String#getBytes([Charset|String]).
+o LANG-1467: Add zero arg constructor for org.apache.commons.lang3.NotImplementedException.
+o LANG-1470: Add ArrayUtils.addFirst() methods.
+o LANG-1479: Add Range.fit(T) to fit a value into a range.
+o LANG-1477: Added Functions.as*, and tests thereof, as suggested by Peter Verhas
+o LANG-1485: Add getters for lhs and rhs objects in DiffResult #451. Thanks to nicolasbd.
+o LANG-1486: Generify builder classes Diffable, DiffBuilder, and DiffResult #452. Thanks to Gary Gregory.
+o LANG-1487: Add ClassLoaderUtils with toString() implementations #453. Thanks to Gary Gregory.
+o LANG-1489: Add null-safe APIs as StringUtils.toRootLowerCase(String) and StringUtils.toRootUpperCase(String) #456. Thanks to Gary Gregory.
+o LANG-1494: Add org.apache.commons.lang3.time.Calendars. Thanks to Gary Gregory.
+o LANG-1495: Add EnumUtils getEnum() methods with default values #475. Thanks to Cheong Voon Leong.
+o LANG-1177: Added indexesOf methods and simplified removeAllOccurences #471. Thanks to Liel Fridman.
+o LANG-1498: Add support of lambda value evaluation for defaulting methods #416. Thanks to Lysergid, Gary Gregory.
+o LANG-1503: Add factory methods to Pair classes with Map.Entry input. #454. Thanks to XenoAmess, Gary Gregory.
+o LANG-1505: Add StopWatch convenience APIs to format times and create a simple instance. Thanks to Gary Gregory.
+o LANG-1506: Allow a StopWatch to carry an optional message. Thanks to Gary Gregory.
+o LANG-1507: Add ComparableUtils #398. Thanks to Sam Kruglov, Mark Dacek, Marc Magon, Pascal Schumacher, Rob Tompkins, Bruno P. Kinoshita, Amey Jadiye, Gary Gregory.
+o LANG-1508: Add org.apache.commons.lang3.SystemUtils.getUserName(). Thanks to Gary Gregory.
+o LANG-1509: Add ObjectToStringComparator. #483. Thanks to Gary Gregory.
+o LANG-1510: Add org.apache.commons.lang3.arch.Processor.Arch.getLabel(). Thanks to Gary Gregory.
+o LANG-1512: Add IS_JAVA_14 and IS_JAVA_15 to org.apache.commons.lang3.SystemUtils. Thanks to Gary Gregory.
+o LANG-1513: ObjectUtils: Get first non-null supplier value. Thanks to Bernhard Bonigl, Gary Gregory.
+o Added the Streams class, and Functions.stream() as an accessor thereof.
+
+Fixed Bugs:
+o LANG-1514: Make test more stable by wrapping assertions in hashset. Thanks to contextshuffling.
+o LANG-1450: Generate Javadoc jar on build.
+o LANG-1460: Trivial: year of release for 3.9 says 2018, should be 2019 Thanks to Larry West.
+o LANG-1476: Use synchronize on a set created with Collections.synchronizedSet before iterating Thanks to emopers.
+o LANG-1475: StringUtils.unwrap incorrect throw StringIndexOutOfBoundsException. Thanks to stzx.
+o LANG-1406: StringIndexOutOfBoundsException in StringUtils.replaceIgnoreCase #423. Thanks to geratorres.
+o LANG-1453: StringUtils.removeIgnoreCase("?a", "a") throws IndexOutOfBoundsException #423. Thanks to geratorres.
+o LANG-1426: Corrected usage examples in Javadocs #458. Thanks to Brower, Mikko Maunu, Suraj Gautam.
+o LANG-1463: StringUtils abbreviate returns String of length greater than maxWidth #477. Thanks to bbeckercscc, Gary Gregory.
+o LANG-1500: Test may fail due to a different order of fields returned by reflection api #480. Thanks to contextshuffling.
+o LANG-1501: Sort fields in ReflectionToStringBuilder for deterministic order #481. Thanks to contextshuffling.
+o LANG-1433: MethodUtils will throw a NPE if invokeMethod() is called for a var-args method #407. Thanks to Christian Franzen.
+o LANG-1518: MethodUtils.getAnnotation() with searchSupers = true does not work if super is generic #494. Thanks to Michele Preti, Bruno P. Kinoshita, Gary Gregory.
+
+Changes:
+o LANG-1437: Remove redundant if statements in join methods #411. Thanks to Andrei Troie.
+o commons.japicmp.version 0.13.1 -> 0.14.1.
+o junit-jupiter 5.5.0 -> 5.5.1.
+o junit-jupiter 5.5.1 -> 5.5.2.
+o Improve Javadoc based on the discussion of the GitHub PR #459. Thanks to Jonathan Leitschuh, Bruno P. Kinoshita, Rob Tompkins, Gary Gregory.
+o maven-checkstyle-plugin 3.0.0 -> 3.1.0.
+o LANG-696: Update documentation related to the issue LANG-696 #449. Thanks to Peter Verhas.
+o AnnotationUtils little cleanup #467. Thanks to Peter Verhas.
+o Update test dependency: org.easymock:easymock 4.0.2 -> 4.1. Thanks to Gary Gregory.
+o Update test dependency: org.hamcrest:hamcrest 2.1 -> 2.2. Thanks to Gary Gregory.
+o Update test dependency: org.junit-pioneer:junit-pioneer 0.3.0 -> 0.4.2. Thanks to Gary Gregory.
+o Update build dependency: com.puppycrawl.tools:checkstyle 8.18 -> 8.27. Thanks to Gary Gregory.
+o Update POM parent: org.apache.commons:commons-parent 48 -> 50. Thanks to Gary Gregory.
+o BooleanUtils Javadoc #469. Thanks to Peter Verhas.
+o Functions Javadoc #466. Thanks to Peter Verhas.
+o org.easymock:easymock 4.1 -> 4.2. Thanks to Gary Gregory.
+o org.junit-pioneer:junit-pioneer 0.4.2 -> 0.5.4. Thanks to Gary Gregory.
+o org.junit.jupiter:junit-jupiter 5.5.2 -> 5.6.0. Thanks to Gary Gregory.
+o Use Javadoc {@code} instead of pre tags. #490. Thanks to Peter Verhas.
+o ExceptionUtilsTest to 100% #486. Thanks to Peter Verhas.
+o Reuse own code in Functions.java #493. Thanks to Peter Verhas.
+o LANG-1523: Avoid unnecessary allocation in StringUtils.wrapIfMissing. #496. Thanks to Edgar Asatryan, Bruno P. Kinoshita, Gary Gregory.
+o LANG-1525: Internally use Validate.notNull(foo, ...) instead of Validate.isTrue(foo != null, ...). Thanks to Edgar Asatryan, Bruno P. Kinoshita, Gary Gregory.
+o LANG-1526: Add 1 and 0 in toBooleanObject(final String str) #502. Thanks to Dominik Schramm.
+o LANG-1527: Remove an redundant argument check in NumberUtils #504. Thanks to Pengyu Nie.
+o LANG-1529: Deprecate org.apache.commons.lang3.ArrayUtils.removeAllOccurences(*) for org.apache.commons.lang3.ArrayUtils.removeAllOccurrences(*). Thanks to Gary Gregory, BillCindy, Bruno P. Kinoshita.
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+Download page: https://commons.apache.org/proper/commons-lang/download_lang.cgi
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.9
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.9 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.9 and onwards now targets Java 8, making use of features that arrived with Java 8.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 8, supports Java 9, 10, 11
+
+Changes in this version include:
+
+New features:
+o LANG-1442: Javadoc pointing to Commons RNG.
+o Adding the Functions class.
+o LANG-1411: Add isEmpty method to ObjectUtils Thanks to Alexander Tsvetkov.
+o LANG-1422: Add null-safe StringUtils.valueOf(char[]) to delegate to String.valueOf(char[])
+o LANG-1427: Add API org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost(JavaVersion)
+
+
+Changes:
+o LANG-1416: Add more SystemUtils.IS_JAVA_XX variants.
+o LANG-1416: Update to JUnit 5
+o LANG-1417: Add @FunctionalInterface to ThreadPredicate and ThreadGroupPredicate
+o LANG-1415: Update Java Language requirement to 1.8
+o LANG-1436: Consolidate the StringUtils equals and equalsIgnoreCase Javadoc and implementation
+o (doc) Fix javadoc for 'startIndex' parameter of StringUtils.join() methods. GitHub PR #412. Thanks to Andrei Troie aft90.
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.8.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.8.1 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 7.0, making use of features that arrived with Java 7.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+This release is a bugfix for Restoring Bundle-SymbolicName in the MANIFEST.mf file.
+
+Changes in this version include:
+
+
+Fixed Bugs:
+o LANG-1419: Restore BundleSymbolicName for OSGi
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.8
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.8 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 7.0, making use of features that arrived with Java 7.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1352: EnumUtils.getEnumIgnoreCase and isValidEnumIgnoreCase methods added Thanks to Ruslan Sibgatullin.
+o LANG-1372: Add ToStringSummary annotation Thanks to Srgio Ozaki.
+o LANG-1356: Add bypass option for classes to recursive and reflective EqualsBuilder Thanks to Yathos UG.
+o LANG-1391: Improve Javadoc for StringUtils.isAnyEmpty(null) Thanks to Sauro Matulli, Oleg Chubaryov.
+o LANG-1393: Add API SystemUtils.String getEnvironmentVariable(final String name, final String defaultValue) Thanks to Gary Gregory.
+o LANG-1394: org.apache.commons.lang3.SystemUtils should not write to System.err. Thanks to Sebb, Gary Gregory.
+o LANG-1238: Add RegexUtils class instead of overloading methods in StringUtils that take a regex to take precompiled Pattern. Thanks to Christopher Cordeiro, Gary Gregory, Bruno P. Kinoshita, Oleg Chubaryov.
+o LANG-1390: StringUtils.join() with support for List<?> with configurable start/end indices. Thanks to Jochen Schalanda.
+o LANG-1392: Methods for getting first non empty or non blank value Thanks to Jeff Nelson.
+o LANG-1408: Rounding utilities for converting to BigDecimal
+
+Fixed Bugs:
+o LANG-1380: FastDateParser too strict on abbreviated short month symbols Thanks to Markus Jelsma.
+o LANG-1396: JsonToStringStyle does not escape string names
+o LANG-1395: JsonToStringStyle does not escape double quote in a string value Thanks to Jim Gan.
+o LANG-1384: New Java version ("11") must be handled Thanks to Ian Young.
+o LANG-1364: ExceptionUtils#getRootCause(Throwable t) should return t if no lower level cause exists Thanks to Zheng Xie.
+o LANG-1060: NumberUtils.isNumber assumes number starting with Zero Thanks to Piotr Kosmala.
+o LANG-1375: defaultString(final String str) in StringUtils to reuse defaultString(final String str, final String defaultStr) Thanks to Jerry Zhao.
+o LANG-1374: Parsing Json Array failed Thanks to Jaswanth Bala.
+o LANG-1371: Fix TypeUtils#parameterize to work correctly with narrower-typed array Thanks to Dmitry Ovchinnikov.
+o LANG-1370: Fix EventCountCircuitBreaker increment batch Thanks to Andre Dieb.
+o LANG-1385: NumberUtils.createNumber() throws StringIndexOutOfBoundsException instead of NumberFormatException Thanks to Rohan Padhye.
+o LANG-1397: WordUtils.wrap throws StringIndexOutOfBoundsException when wrapLength is Integer.MAX_VALUE. Thanks to Takanobu Asanuma.
+o LANG-1401: Typo in JavaDoc for lastIndexOf Thanks to Roman Golyshev, Alex Mamedov.
+
+Changes:
+o LANG-1367: ObjectUtils.identityToString(Object) and friends should allocate builders and buffers with a size Thanks to Gary Gregory.
+o LANG-1405: Remove checks for java versions below the minimum supported one Thanks to Lars Grefer.
+o LANG-1402: Null/index safe get methods for ArrayUtils Thanks to Mark Dacek.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.7
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.7 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1355: TimeZone.getTimeZone() in FastDateParser causes resource contention (PR #296.) Thanks to Chas Honton.
+o LANG-1360: Add methods to ObjectUtils to get various forms of class names in a null-safe manner Thanks to Gary Gregory.
+
+Fixed Bugs:
+o LANG-1362: Fix tests DateUtilsTest for Java 9 with en_GB locale Thanks to Stephen Colebourne.
+o LANG-1365: Fix NullPointerException in isJavaVersionAtLeast on Java 10, add SystemUtils.IS_JAVA_10, add JavaVersion.JAVA_10 Thanks to Gary Gregory.
+o LANG-1348: StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf Thanks to mbusso.
+o LANG-1350: ConstructorUtils.invokeConstructor(Class, Object...) regression Thanks to Brett Kail.
+o LANG-1349: EqualsBuilder#isRegistered: swappedPair construction bug Thanks to Naman Nigam.
+o LANG-1357: org.apache.commons.lang3.time.FastDateParser should use toUpperCase(Locale) Thanks to BruceKuiLiu.
+
+Changes:
+o LANG-1358: Improve StringUtils#replace throughput Thanks to Stephane Landelle.
+o LANG-1346: Remove deprecation from RandomStringUtils
+o LANG-1361: ExceptionUtils.getThrowableList() is using deprecated ExceptionUtils.getCause() Thanks to Ana.
+
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.6
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.6 version of
+Apache Commons Lang as well as a history all changes in the Commons Lang 3.x
+release line. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.6 at
+least requires Java 7.0. Note that this has changed from Commons Lang 3.5, which
+only required Java 1.6.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o The class org.apache.commons.lang3.concurrent.Memoizer is an implementation
+ of the Memoizer pattern as shown in
+ Goetz, Brian et al. (2006) - Java Concurrency in Practice, p. 108.
+o The class org.apache.commons.lang3.ArchUtils has been added. ArchUtils is
+ a utility class for the "os.arch" system property.
+
+DEPRECATIONS
+============
+
+The Apache Commons Community has recently set up the Commons Text component
+as a home for algorithms working on strings. For this reason most of the string
+focused functionality in Commons Lang has been deprecated and moved to
+Commons Text. This includes:
+
+o All classes in the org.apache.commons.lang3.text and the
+ org.apache.commons.lang3.text.translate packages
+o org.apache.commons.lang3.StringEscapeUtils
+o org.apache.commons.lang3.RandomStringUtils
+o The methods org.apache.commons.lang3.StringUtils.getJaroWinklerDistance and
+ org.apache.commons.lang3.StringUtils.getLevenshteinDistance
+
+For more information see the Commons Text website:
+
+ https://commons.apache.org/text
+
+The class org.apache.commons.lang3.CharEncoding has been deprecated in favor of
+java.nio.charset.StandardCharsets.
+
+The following methods have been deprecated in
+org.apache.commons.lang3.ArrayUtils in favor of the corresponding insert
+methods. Note that the handling for null inputs differs between add and insert.
+
+o add(boolean[], int, boolean) -> insert(int, boolean[], boolean...)
+o add(byte[], int, boolean) -> insert(int, byte[], byte...)
+o add(char[], int, boolean) -> insert(int, char[], char...)
+o add(double[], int, boolean) -> insert(int, double[], double...)
+o add(float[], int, boolean) -> insert(int, float[], float...)
+o add(int[], int, boolean) -> insert(int, int[], int...)
+o add(long[], int, boolean) -> insert(int, long[], long...)
+o add(short[], int, boolean) -> insert(int, short[], short...)
+o add(T[], int, boolean) -> insert(int, T[], T...)
+
+
+COMPATIBILITY WITH JAVA 9
+==================
+
+The MANIFEST.MF now contains an additional entry:
+
+ Automatic-Module-Name: org.apache.commons.lang3
+
+This should make it possible to use Commons Lang 3.6 as a module in the Java 9
+module system. For more information see the corresponding issue and the
+referenced mailing list discussions:
+
+ https://issues.apache.org/jira/browse/LANG-1338
+
+The build problems present in the 3.5 release have been resolved. Building
+Commons Lang 3.6 should work out of the box with the latest Java 9 EA build.
+Please report any Java 9 related issues at:
+
+ https://issues.apache.org/jira/browse/LANG
+
+NEW FEATURES
+============
+
+o LANG-1336: Add NUL Byte To CharUtils. Thanks to Beluga Behr.
+o LANG-1304: Add method in StringUtils to determine if string contains both
+ mixed cased characters. Thanks to Andy Klimczak.
+o LANG-1325: Increase test coverage of ToStringBuilder class to 100%.
+ Thanks to Arshad Basha.
+o LANG-1307: Add a method in StringUtils to extract only digits out of input
+ string. Thanks to Arshad Basha.
+o LANG-1256: Add JMH maven dependencies. Thanks to C0rWin.
+o LANG-1167: Add null filter to ReflectionToStringBuilder.
+ Thanks to Mark Dacek.
+o LANG-1299: Add method for converting string to an array of code points.
+o LANG-660: Add methods to insert arrays into arrays at an index.
+o LANG-1034: Add support for recursive comparison to
+ EqualsBuilder#reflectionEquals. Thanks to Yathos UG.
+o LANG-1067: Add a reflection-based variant of DiffBuilder.
+o LANG-740: Implementation of a Memoizer. Thanks to James Sawle.
+o LANG-1258: Add ArrayUtils#toStringArray method.
+ Thanks to IG, Grzegorz Ro?niecki.
+o LANG-1160: StringUtils#abbreviate should support 'custom ellipses' parameter.
+o LANG-1293: Add StringUtils#isAllEmpty and #isAllBlank methods.
+ Thanks to Pierre Templier, Martin Tarjanyi.
+o LANG-1313: Add ArchUtils - An utility class for the "os.arch" system property.
+ Thanks to Tomschi.
+o LANG-1272: Add shuffle methods to ArrayUtils.
+o LANG-1317: Add MethodUtils#findAnnotation and extend
+ MethodUtils#getMethodsWithAnnotation for non-public, super-class
+ and interface methods. Thanks to Yasser Zamani.
+o LANG-1331: Add ImmutablePair.nullPair().
+o LANG-1332: Add ImmutableTriple.nullTriple().
+
+FIXED BUGS
+==========
+
+o LANG-1337: Fix test failures in IBM JDK 8 for ToStringBuilderTest.
+o LANG-1319: MultilineRecursiveToStringStyle StackOverflowError when object is
+ an array.
+o LANG-1320: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code followed by variant.
+o LANG-1300: Clarify or improve behavior of int-based indexOf methods in
+ StringUtils. Thanks to Mark Dacek.
+o LANG-1286: RandomStringUtils random method can overflow and return characters
+ outside of specified range.
+o LANG-1292: WordUtils.wrap throws StringIndexOutOfBoundsException.
+o LANG-1287: RandomStringUtils#random can enter infinite loop if end parameter
+ is to small. Thanks to Ivan Morozov.
+o LANG-1285: NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to Francesco Chicchiricc.
+o LANG-1281: Javadoc of StringUtils.ordinalIndexOf is contradictory.
+ Thanks to Andreas Lundblad.
+o LANG-1188: StringUtils#join(T...): warning: [unchecked] Possible heap
+ pollution from parameterized vararg type T.
+o LANG-1144: Multiple calls of
+ org.apache.commons.lang3.concurrent.LazyInitializer.initialize()
+ are possible. Thanks to Waldemar Maier, Gary Gregory.
+o LANG-1276: StrBuilder#replaceAll ArrayIndexOutOfBoundsException.
+ Thanks to Andy Klimczak.
+o LANG-1278: BooleanUtils javadoc issues. Thanks to Duke Yin.
+o LANG-1070: ArrayUtils#add confusing example in javadoc.
+ Thanks to Paul Pogonyshev.
+o LANG-1271: StringUtils#isAnyEmpty and #isAnyBlank should return false for an
+ empty array. Thanks to Pierre Templier.
+o LANG-1155: Add StringUtils#unwrap. Thanks to Saif Asif, Thiago Andrade.
+o LANG-1311: TypeUtils.toString() doesn't handle primitive and Object arrays
+ correctly. Thanks to Aaron Digulla.
+o LANG-1312: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code.
+o LANG-1265: Build failures when building with Java 9 EA.
+o LANG-1314: javadoc creation broken with Java 8. Thanks to Allon Murienik.
+o LANG-1310: MethodUtils.invokeMethod throws ArrayStoreException if using
+ varargs arguments and smaller types than the method defines.
+ Thanks to Don Jeba.
+
+CHANGES
+=======
+
+o LANG-1338: Add Automatic-Module-Name MANIFEST entry for Java 9
+ compatibility.
+o LANG-1334: Deprecate CharEncoding in favour of
+ java.nio.charset.StandardCharsets.
+o LANG-1110: Implement HashSetvBitSetTest using JMH.
+ Thanks to Bruno P. Kinoshita.
+o LANG-1290: Increase test coverage of org.apache.commons.lang3.ArrayUtils.
+ Thanks to Andrii Abramov.
+o LANG-1274: StrSubstitutor should state its thread safety.
+o LANG-1277: StringUtils#getLevenshteinDistance reduce memory consumption.
+ Thanks to yufcuy.
+o LANG-1279: Update Java requirement from Java 6 to 7.
+o LANG-1143: StringUtils should use toXxxxCase(int) rather than
+ toXxxxCase(char). Thanks to sebb.
+o LANG-1297: Add SystemUtils.getHostName() API.
+o LANG-1301: Moving apache-rat-plugin configuration into pluginManagement.
+ Thanks to Karl Heinz Marbaise.
+o LANG-1316: Deprecate classes/methods moved to commons-text.
+
+=============================================================================
+
+ Release Notes for version 3.5
+
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o Added Java 9 detection to org.apache.commons.lang3.SystemUtils.
+o Support for shifting and swapping elements in
+ org.apache.commons.lang3.ArrayUtils.
+o New methods for generating random strings from different character classes
+ including alphabetic, alpha-numeric and ASCII added to
+ org.apache.commons.lang3.RandomStringUtils.
+o Numerous extensions to org.apache.commons.lang3.StringUtils including
+ null safe compare variants, more remove and replace variants, rotation and
+ truncation.
+o Added org.apache.commons.lang3.ThreadUtils - a utility class to work with
+ instances of java.lang.Thread and java.lang.ThreadGroup.
+o Added annotations @EqualsExclude, @HashCodeExclude and @ToStringExclude to
+ mark fields which should be ignored by the reflective builders in the
+ org.apache.commons.lang3.builder package.
+o Support for various modify and retrieve value use cases added to the classes
+ in org.apache.commons.lang3.mutable.
+
+COMPATIBILITY
+=============
+
+Apache Commons Lang 3.5 is binary compatible with the 3.4 release. Users
+should not experience any problems when upgrading from 3.4 to 3.5.
+
+There has been an addition to the org.apache.commons.lang3.time.DatePrinter
+interface:
+
+o Added method 'public boolean parse(java.lang.String, java.text.ParsePosition,
+ java.util.Calendar)'
+o Added method 'public java.lang.Appendable format(long, java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Date,
+ java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Calendar,
+ java.lang.Appendable)'
+
+For this reason 3.5 is not strictly source compatible to 3.4. Since the
+DatePrinter interface is not meant to be implemented by clients, this
+change it not considered to cause any problems.
+
+JAVA 9 SUPPORT
+==============
+
+Java 9 introduces a new version-string scheme. Details of this new scheme are
+documented in JEP-223 (https://openjdk.org/jeps/223). In order to support
+JEP-223 two classes had to be changed:
+
+o org.apache.commons.lang3.JavaVersion
+ deprecated enum constant JAVA_1_9
+ introduced enum constant JAVA_9
+
+o org.apache.commons.lang3.SystemUtils
+ deprecated constant IS_JAVA_1_9
+ introduced constant IS_JAVA_9
+
+For more information see LANG-1197
+(https://issues.apache.org/jira/browse/LANG-1197). All other APIs are expected
+to work with Java 9.
+
+BUILDING ON JAVA 9
+==================
+
+Java 8 introduced the Unicode Consortium's Common Locale Data Repository as
+alternative source for locale data. Java 9 will use the CLDR provider as
+default provider for locale data (see https://openjdk.org/jeps/252). This
+causes an number of locale-sensitive test in Commons Lang to fail. In order
+to build Commons Lang 3.5 on Java 9, the locale provider has to be set to
+'JRE':
+
+ mvn -Djava.locale.providers=JRE clean install
+
+We are currently investigating ways to support building on Java 9 without
+further configuration. For more information see:
+https://issues.apache.org/jira/browse/LANG-1265
+
+
+NEW FEATURES
+==============
+
+o LANG-1275: Added a tryAcquire() method to TimedSemaphore.
+o LANG-1255: Add DateUtils.toCalendar(Date, TimeZone). Thanks to Kaiyuan Wang.
+o LANG-1023: Add WordUtils.wrap overload with customizable breakable character.
+ Thanks to Marko Bekhta.
+o LANG-787: Add method removeIgnoreCase(String, String) to StringUtils. Thanks
+ to Gokul Nanthakumar C.
+o LANG-1224: Extend RandomStringUtils with methods that generate strings
+ between a min and max length. Thanks to Caleb Cushing.
+o LANG-1257: Add APIs StringUtils.wrapIfMissing(String, char|String). Thanks to
+ Gary Gregory.
+o LANG-1253: Add RandomUtils#nextBoolean() method. Thanks to adilek.
+o LANG-1085: Add a circuit breaker implementation. Thanks to Oliver Heger and
+ Bruno P. Kinoshita.
+o LANG-1013: Add StringUtils.truncate(). Thanks to Thiago Andrade.
+o LANG-1195: Enhance MethodUtils to allow invocation of private methods. Thanks
+ to Derek C. Ashmore.
+o LANG-1189: Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/
+ decrementAndGet/addAndGet in Mutable* classes. Thanks to
+ Haiyang Li and Matthew Bartenschlag.
+o LANG-1225: Add RandomStringUtils#randomGraph and #randomPrint which match
+ corresponding regular expression class. Thanks to Caleb Cushing.
+o LANG-1223: Add StopWatch#getTime(TimeUnit). Thanks to Nick Manley.
+o LANG-781: Add methods to ObjectUtils class to check for null elements in the
+ array. Thanks to Krzysztof Wolny.
+o LANG-1228: Prefer Throwable.getCause() in ExceptionUtils.getCause().
+ Thanks to Brad Hess.
+o LANG-1233: DiffBuilder add method to allow appending from a DiffResult.
+ Thanks to Nick Manley.
+o LANG-1168: Add SystemUtils.IS_OS_WINDOWS_10 property.
+ Thanks to Pascal Schumacher.
+o LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and
+ MethodUtils. Thanks to Jim Lloyd and Joe Ferner.
+o LANG-1134: Add methods to check numbers against NaN and infinite to
+ Validate. Thanks to Alan Smithee.
+o LANG-1220: Add tests for missed branches in DateUtils.
+ Thanks to Casey Scarborough.
+o LANG-1146: z/OS identification in SystemUtils.
+ Thanks to Gabor Liptak.
+o LANG-1192: FastDateFormat support of the week-year component (uppercase 'Y').
+ Thanks to Dominik Stadler.
+o LANG-1169: Add StringUtils methods to compare a string to multiple strings.
+ Thanks to Rafal Glowinski, Robert Parr and Arman Sharif.
+o LANG-1185: Add remove by regular expression methods in StringUtils.
+o LANG-1139: Add replace by regular expression methods in StringUtils.
+o LANG-1171: Add compare methods in StringUtils.
+o LANG-1174: Add sugar to RandomUtils. Thanks to Punkratz312.
+o LANG-1154: FastDateFormat APIs that use a StringBuilder. Thanks to
+ Gary Gregory.
+o LANG-1149: Ability to throw checked exceptions without declaring them. Thanks
+ to Gregory Zak.
+o LANG-1153: Implement ParsePosition api for FastDateParser.
+o LANG-1137: Add check for duplicate event listener in EventListenerSupport.
+ Thanks to Matthew Aguirre.
+o LANG-1135: Add method containsAllWords to WordUtils. Thanks to
+ Eduardo Martins.
+o LANG-1132: ReflectionToStringBuilder doesn't throw IllegalArgumentException
+ when the constructor's object param is null. Thanks to Jack Tan.
+o LANG-701: StringUtils join with var args. Thanks to James Sawle.
+o LANG-1105: Add ThreadUtils - A utility class which provides helper methods
+ related to java.lang.Thread Issue: LANG-1105. Thanks to
+ Hendrik Saly.
+o LANG-1031: Add annotations to exclude fields from ReflectionEqualsBuilder,
+ ReflectionToStringBuilder and ReflectionHashCodeBuilder. Thanks
+ to Felipe Adorno.
+o LANG-1127: Use JUnit rules to set and reset the default Locale and TimeZone.
+o LANG-1119: Add rotate(string, int) method to StringUtils. Thanks to
+ Loic Guibert.
+o LANG-1099: Add swap and shift operations for arrays to ArrayUtils. Thanks to
+ Adrian Ber.
+o LANG-1050: Change nullToEmpty methods to generics. Thanks to James Sawle.
+o LANG-1074: Add a method to ArrayUtils for removing all occurrences of a given
+ element Issue: LANG-1074. Thanks to Haiyang Li.
+
+FIXED BUGS
+============
+
+o LANG-1261: ArrayUtils.contains returns false for instances of subtypes.
+o LANG-1252: Rename NumberUtils.isNumber, isCreatable to better reflect
+ createNumber. Also, accommodated for "+" symbol as prefix in
+ isCreatable and isNumber. Thanks to Rob Tompkins.
+o LANG-1230: Remove unnecessary synchronization from registry lookup in
+ EqualsBuilder and HashCodeBuilder. Thanks to Philippe Marschall.
+o LANG-1214: Handle "void" in ClassUtils.getClass(). Thanks to Henry Tung.
+o LANG-1250: SerializationUtils#deserialize has unnecessary code and a comment
+ for that. Thanks to Glease Wang.
+o LANG-1190: TypeUtils.isAssignable throws NullPointerException when fromType
+ has type variables and toType generic superclass specifies type
+ variable. Thanks to Pascal Schumacher.
+o LANG-1226: StringUtils#normalizeSpace does not trim the string anymore.
+ Thanks to Pascal Schumacher.
+o LANG-1251: SerializationUtils.ClassLoaderAwareObjectInputStream should use
+ static initializer to initialize primitiveTypes map. Thanks to
+ Takuya Ueshin.
+o LANG-1248: FastDatePrinter Memory allocation regression. Thanks to
+ Benoit Wiart.
+o LANG-1018: Fix precision loss on NumberUtils.createNumber(String). Thanks to
+ Nick Manley.
+o LANG-1199: Fix implementation of StringUtils.getJaroWinklerDistance(). Thanks
+ to M. Steiger.
+o LANG-1244: Fix dead links in StringUtils.getLevenshteinDistance() javadoc.
+ Thanks to jjbankert.
+o LANG-1242: "\u2284":"?" mapping missing from
+ EntityArrays#HTML40_EXTENDED_ESCAPE. Thanks to Neal Stewart.
+o LANG-901: StringUtils#startsWithAny/endsWithAny is case sensitive -
+ documented as case insensitive. Thanks to Matthew Bartenschlag.
+o LANG-1232: DiffBuilder: Add null check on fieldName when appending Object or
+ Object[]. Thanks to Nick Manley.
+o LANG-1178: ArrayUtils.removeAll(Object array, int... indices) should do the
+ clone, not its callers. Thanks to Henri Yandell.
+o LANG-1120: StringUtils.stripAccents should remove accents from "?" and "?".
+ Thanks to kaching88.
+o LANG-1205: NumberUtils.createNumber() behaves inconsistently with
+ NumberUtils.isNumber(). Thanks to pbrose.
+o LANG-1222: Fix for incorrect comment on StringUtils.containsIgnoreCase
+ method. Thanks to Adam J.
+o LANG-1221: Fix typo on appendIfMissing javadoc. Thanks to Pierre Templier.
+o LANG-1202: parseDateStrictly doesn't pass specified locale. Thanks to
+ Markus Jelsma.
+o LANG-1219: FastDateFormat doesn't respect summer daylight in some localized
+ strings. Thanks to Jarek.
+o LANG-1175: Remove Ant-based build.
+o LANG-1194: Limit max heap memory for consistent Travis CI build.
+o LANG-1186: Fix NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to NickManley.
+o LANG-1193: ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1
+ (correct answer should be 0); revert fix for LANG-1077. Thanks to
+ Qin Li.
+o LANG-1002: Several predefined ISO FastDateFormats in DateFormatUtils are
+ incorrect. Thanks to Michael Osipov.
+o LANG-1152: StringIndexOutOfBoundsException or field over-write for large year
+ fields in FastDateParser. Thanks to Pas Filip.
+o LANG-1141: StrLookup.systemPropertiesLookup() no longer reacts on changes on
+ system properties.
+o LANG-1147: EnumUtils *BitVector issue with more than 32 values Enum. Thanks
+ to Loic Guibert.
+o LANG-1059: Capitalize javadoc is incorrect. Thanks to Colin Casey.
+o LANG-1122: Inconsistent behavior of swap for malformed inputs. Thanks to
+ Adrian Ber.
+o LANG-1130: Fix critical issues reported by SonarQube.
+o LANG-1131: StrBuilder.equals(StrBuilder) doesn't check for null inputs.
+o LANG-1128: JsonToStringStyle doesn't handle chars and objects correctly.
+ Thanks to Jack Tan.
+o LANG-1126: DateFormatUtilsTest.testSMTP depends on the default Locale.
+o LANG-1123: Unit test FastDatePrinterTimeZonesTest needs a timezone set.
+ Thanks to Christian P. Momon.
+o LANG-916: DateFormatUtils.format does not correctly change Calendar
+ TimeZone in certain situations. Thanks to Christian P. Momon.
+o LANG-1116: DateUtilsTest.testLang530 fails for some timezones. Thanks to
+ Aaron Sheldon.
+o LANG-1114: TypeUtils.ParameterizedType#equals doesn't work with wildcard
+ types. Thanks to Andy Coates.
+o LANG-1118: StringUtils.repeat('z', -1) throws NegativeArraySizeException.
+ Thanks to Loic Guibert.
+o LANG-1111: Fix FindBugs warnings in DurationFormatUtils.
+o LANG-1162: StringUtils#equals fails with Index OOBE on non-Strings with
+ identical leading prefix..
+o LANG-1163: There are no tests for CharSequenceUtils.regionMatches.
+o LANG-1200: Fix Javadoc of StringUtils.ordinalIndexOf. Thanks to BarkZhang.
+o LANG-1191: Incorrect Javadoc
+ StringUtils.containsAny(CharSequence, CharSequence...). Thanks to
+ qed, Brent Worden and Gary Gregory.
+
+CHANGES
+=========
+o LANG-1197: Prepare Java 9 detection.
+o LANG-1262: CompareToBuilder.append(Object, Object, Comparator) method is too
+ big to be inlined. Thanks to Ruslan Cheremin.
+o LANG-1259: Javadoc for ArrayUtils.isNotEmpty() is slightly misleading. Thanks
+ to Dominik Stadler.
+o LANG-1247: FastDatePrinter generates extra Date objects. Thanks to
+ Benoit Wiart.
+o LANG-1229: HashCodeBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1243: Simplify ArrayUtils removeElements by using new decrementAndGet()
+ method.
+o LANG-1240: Optimize BitField constructor implementation. Thanks to zhanhb.
+o LANG-1206: Improve CharSetUtils.squeeze() performance. Thanks to
+ Mohammed Alfallaj.
+o LANG-1176: Improve ArrayUtils removeElements time complexity to O(n). Thanks
+ to Jeffery Yuan.
+o LANG-1234: getLevenshteinDistance with a threshold: optimize implementation
+ if the strings lengths differ more than the threshold. Thanks to
+ Jonatan Jnsson.
+o LANG-1151: Performance improvements for NumberUtils.isParsable. Thanks to
+ Juan Pablo Santos Rodrguez.
+o LANG-1218: EqualsBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1210: StringUtils#startsWithAny has error in Javadoc. Thanks to
+ Matthias Niehoff.
+o LANG-1208: StrSubstitutor can preserve escapes. Thanks to Samuel Karp.
+o LANG-1182: Clarify Javadoc of StringUtils.containsAny(). Thanks to
+ Larry West and Pascal Schumacher.
+o LANG-1183: Making replacePattern/removePattern methods null safe in
+ StringUtils.
+o LANG-1057: Replace StringBuilder with String concatenation for better
+ optimization. Thanks to Otvio Santana.
+o LANG-1075: Deprecate SystemUtils.FILE_SEPARATOR and
+ SystemUtils.PATH_SEPARATOR.
+o LANG-979: TypeUtils.parameterizeWithOwner - wrong format descriptor for
+ "invalid number of type parameters". Thanks to Bruno P. Kinoshita.
+o LANG-1112: MultilineRecursiveToStringStyle largely unusable due to being
+ package-private.
+o LANG-1058: StringUtils.uncapitalize performance improvement. Thanks to
+ Leo Wang.
+o LANG-1069: CharSet.getInstance documentation does not clearly explain how
+ to include negation character in set. Thanks to Arno Noordover.
+o LANG-1107: Fix parsing edge cases in FastDateParser.
+o LANG-1273: Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils. Thanks
+ to Jake Wang.
+
+=============================================================================
+
+ Release Notes for version 3.4
+
+
+COMPATIBILITY
+=============
+
+Commons Lang 3.4 is fully binary compatible to the last release and can
+therefore be used as a drop in replacement for 3.3.2. Note that the value of
+org.apache.commons.lang3.time.DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN
+has changed, which may affect clients using the constant. Furthermore the
+constant is used internally in
+o DurationFormatUtils.formatDurationISO(long)
+o DurationFormatUtils.formatPeriodISO(long, long)
+
+For more information see https://issues.apache.org/jira/browse/LANG-1000.
+
+NEW FEATURES
+==============
+
+o LANG-821: Support OS X versions in SystemUtils. Thanks to Timo Kockert.
+o LANG-1103: Add SystemUtils.IS_JAVA_1_9
+o LANG-1093: Add ClassUtils.getAbbreviatedName(). Thanks to Fabian Lange.
+o LANG-1082: Add option to disable the "objectsTriviallyEqual" test in
+ DiffBuilder. Thanks to Jonathan Baker.
+o LANG-1015: Add JsonToStringStyle implementation to ToStringStyle. Thanks to
+ Thiago Andrade.
+o LANG-1080: Add NoClassNameToStringStyle implementation of ToStringStyle.
+ Thanks to Innokenty Shuvalov.
+o LANG-883: Add StringUtils.containsAny(CharSequence, CharSequence...) method.
+ Thanks to Daniel Stewart.
+o LANG-1052: Multiline recursive to string style. Thanks to Jan Matrne.
+o LANG-536: Add isSorted() to ArrayUtils. Thanks to James Sawle.
+o LANG-1033: Add StringUtils.countMatches(CharSequence, char)
+o LANG-1021: Provide methods to retrieve all fields/methods annotated with a
+ specific type. Thanks to Alexander Mller.
+o LANG-1016: NumberUtils#isParsable method(s). Thanks to
+ Juan Pablo Santos Rodrguez.
+o LANG-999: Add fuzzy String matching logic to StringUtils. Thanks to
+ Ben Ripkens.
+o LANG-994: Add zero copy read method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-993: Add zero copy write method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-1044: Add method MethodUtils.invokeExactMethod(Object, String)
+o LANG-1045: Add method MethodUtils.invokeMethod(Object, String)
+
+FIXED BUGS
+============
+
+o LANG-794: SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect. Thanks to
+ Timo Kockert.
+o LANG-1104: Parse test fails for TimeZone America/Sao_Paulo
+o LANG-948: Exception while using ExtendedMessageFormat and escaping braces.
+ Thanks to Andrey Khobnya.
+o LANG-1092: Wrong formatting of time zones with daylight saving time in
+ FastDatePrinter
+o LANG-1090: FastDateParser does not set error indication in ParsePosition
+o LANG-1089: FastDateParser does not handle excess hours as per
+ SimpleDateFormat
+o LANG-1061: FastDateParser error - timezones not handled correctly. Thanks to
+ dmeneses.
+o LANG-1087: NumberUtils#createNumber() returns positive BigDecimal when
+ negative Float is expected. Thanks to Renat Zhilkibaev.
+o LANG-1081: DiffBuilder.append(String, Object left, Object right) does not do
+ a left.equals(right) check. Thanks to Jonathan Baker.
+o LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently.
+ Thanks to Jonathan Baker.
+o LANG-1083: Add (T) casts to get unit tests to pass in old JDK. Thanks to
+ Jonathan Baker.
+o LANG-1073: Read wrong component type of array in add in ArrayUtils.
+ Thanks to haiyang li.
+o LANG-1077: StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils.
+ Thanks to haiyang li.
+o LANG-1072: Duplicated "0x" check in createBigInteger in NumberUtils. Thanks
+ to haiyang li.
+o LANG-1064: StringUtils.abbreviate description doesn't agree with the
+ examples. Thanks to B.J. Herbison.
+o LANG-1041: Fix MethodUtilsTest so it does not depend on JDK method ordering.
+ Thanks to Alexandre Bartel.
+o LANG-1000: ParseException when trying to parse UTC dates with Z as zone
+ designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
+o LANG-1035: Javadoc for EqualsBuilder.reflectionEquals() is unclear
+o LANG-1001: ISO 8601 misspelled throughout the Javadocs. Thanks to
+ Michael Osipov.
+o LANG-1088: FastDateParser should be case insensitive
+o LANG-995: Fix bug with stripping spaces on last line in WordUtils.wrap().
+ Thanks to Andrey Khobnya.
+
+CHANGES
+=========
+
+o LANG-1102: Make logic for comparing OS versions in SystemUtils smarter
+o LANG-1091: Shutdown thread pools in test cases. Thanks to Fabian Lange.
+o LANG-1101: FastDateParser and FastDatePrinter support 'X' format
+o LANG-1100: Avoid memory allocation when using date formatting to StringBuffer.
+ Thanks to mbracher.
+o LANG-935: Possible performance improvement on string escape functions.
+ Thanks to Fabian Lange, Thomas Neidhart.
+o LANG-1098: Avoid String allocation in StrBuilder.append(CharSequence). Thanks
+ to Mikhail Mazurskiy, Fabian Lange.
+o LANG-1098: Update maven-checkstyle-plugin to 2.14. Thanks to Micha? Kordas.
+o LANG-1097: Update org.easymock:easymock to 3.3.1. Thanks to Micha? Kordas.
+o LANG-1096: Update maven-pmd-plugin to 3.4. Thanks to Micha? Kordas.
+o LANG-1095: Update maven-antrun-plugin to 1.8. Thanks to Micha? Kordas.
+o LANG-877: Performance improvements for StringEscapeUtils. Thanks to
+ Fabian Lange.
+o LANG-1071: Fix wrong examples in Javadoc of
+ StringUtils.replaceEachRepeatedly(...),
+ StringUtils.replaceEach(...) Thanks to Arno Noordover.
+o LANG-827: CompareToBuilder's doc doesn't specify precedence of fields it
+ uses in performing comparisons
+o LANG-1020: Improve performance of normalize space. Thanks to Libor Ondrusek.
+o LANG-1027: org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should
+ return true by default
+o LANG-1026: Bring static method references in StringUtils to consistent style.
+ Thanks to Alex Yursha.
+o LANG-1017: Use non-ASCII digits in Javadoc examples for
+ StringUtils.isNumeric. Thanks to Christoph Schneegans.
+o LANG-1008: Change min/max methods in NumberUtils/IEEE754rUtils from array
+ input parameters to varargs. Thanks to Thiago Andrade.
+o LANG-1006: Add wrap (with String or char) to StringUtils. Thanks to
+ Thiago Andrade.
+o LANG-1005: Extend DurationFormatUtils#formatDurationISO default pattern to
+ match #formatDurationHMS. Thanks to Michael Osipov.
+o LANG-1007: Fixing NumberUtils JAVADoc comments for max methods. Thanks to
+ Thiago Andrade.
+o LANG-731: Better Javadoc for BitField class
+o LANG-1004: DurationFormatUtils#formatDurationHMS implementation does not
+ correspond to Javadoc and vice versa. Thanks to Michael Osipov.
+o LANG-1003: DurationFormatUtils are not able to handle negative
+ durations/periods
+o LANG-998: Javadoc is not clear on preferred pattern to instantiate
+ FastDateParser / FastDatePrinter
+
+=============================================================================
+
+ Release Notes for version 3.3.2
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+=============================================================================
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+=============================================================================
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Gtz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernndez.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+=============================================================================
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+=============================================================================
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skytt.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+=============================================================================
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+=============================================================================
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12 hour clock and not
+ 24 hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..51943ba7b
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,17 @@
+<!---
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+The Apache Commons security page is [https://commons.apache.org/security.html](https://commons.apache.org/security.html).
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 000000000..44c538d36
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "OnDevicePersonalizationManagingServicesTests"
+ }
+ ]
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 000000000..c49fc224d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,1002 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-parent</artifactId>
+ <version>56</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.13.0-SNAPSHOT</version>
+ <name>Apache Commons Lang</name>
+
+ <inceptionYear>2001</inceptionYear>
+ <description>
+ Apache Commons Lang, a package of Java utility classes for the
+ classes that are in java.lang's hierarchy, or are considered to be so
+ standard as to justify existence in java.lang.
+ </description>
+
+ <url>https://commons.apache.org/proper/commons-lang/</url>
+
+ <issueManagement>
+ <system>jira</system>
+ <url>https://issues.apache.org/jira/browse/LANG</url>
+ </issueManagement>
+
+ <scm>
+ <connection>scm:git:http://gitbox.apache.org/repos/asf/commons-lang.git</connection>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/commons-lang.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=commons-lang.git</url>
+ <tag>commons-lang-3.12.0</tag>
+ </scm>
+
+ <developers>
+ <developer>
+ <name>Daniel Rall</name>
+ <id>dlr</id>
+ <email>dlr@finemaltcoding.com</email>
+ <organization>CollabNet, Inc.</organization>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Stephen Colebourne</name>
+ <id>scolebourne</id>
+ <email>scolebourne@joda.org</email>
+ <organization>SITA ATS Ltd</organization>
+ <timezone>0</timezone>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Henri Yandell</name>
+ <id>bayard</id>
+ <email>bayard@apache.org</email>
+ <organization />
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Steven Caswell</name>
+ <id>scaswell</id>
+ <email>stevencaswell@apache.org</email>
+ <organization />
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ <timezone>-5</timezone>
+ </developer>
+ <developer>
+ <name>Robert Burrell Donkin</name>
+ <id>rdonkin</id>
+ <email>rdonkin@apache.org</email>
+ <organization />
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <id>ggregory</id>
+ <name>Gary Gregory</name>
+ <email>ggregory at apache.org</email>
+ <url>https://www.garygregory.com</url>
+ <organization>The Apache Software Foundation</organization>
+ <organizationUrl>https://www.apache.org/</organizationUrl>
+ <roles>
+ <role>PMC Member</role>
+ </roles>
+ <timezone>America/New_York</timezone>
+ <properties>
+ <picUrl>https://people.apache.org/~ggregory/img/garydgregory80.png</picUrl>
+ </properties>
+ </developer>
+ <developer>
+ <name>Fredrik Westermarck</name>
+ <id>fredrik</id>
+ <email />
+ <organization />
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>James Carman</name>
+ <id>jcarman</id>
+ <email>jcarman@apache.org</email>
+ <organization>Carman Consulting, Inc.</organization>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Niall Pemberton</name>
+ <id>niallp</id>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Matt Benson</name>
+ <id>mbenson</id>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Joerg Schaible</name>
+ <id>joehni</id>
+ <email>joerg.schaible@gmx.de</email>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ <timezone>+1</timezone>
+ </developer>
+ <developer>
+ <name>Oliver Heger</name>
+ <id>oheger</id>
+ <email>oheger@apache.org</email>
+ <timezone>+1</timezone>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Paul Benedict</name>
+ <id>pbenedict</id>
+ <email>pbenedict@apache.org</email>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Benedikt Ritter</name>
+ <id>britter</id>
+ <email>britter@apache.org</email>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Duncan Jones</name>
+ <id>djones</id>
+ <email>djones@apache.org</email>
+ <timezone>0</timezone>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Loic Guibert</name>
+ <id>lguibert</id>
+ <email>lguibert@apache.org</email>
+ <timezone>+4</timezone>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <name>Rob Tompkins</name>
+ <id>chtompki</id>
+ <email>chtompki@apache.org</email>
+ <timezone>-5</timezone>
+ <roles>
+ <role>Java Developer</role>
+ </roles>
+ </developer>
+ </developers>
+ <contributors>
+ <contributor>
+ <name>C. Scott Ananian</name>
+ </contributor>
+ <contributor>
+ <name>Chris Audley</name>
+ </contributor>
+ <contributor>
+ <name>Stephane Bailliez</name>
+ </contributor>
+ <contributor>
+ <name>Michael Becke</name>
+ </contributor>
+ <contributor>
+ <name>Benjamin Bentmann</name>
+ </contributor>
+ <contributor>
+ <name>Ola Berg</name>
+ </contributor>
+ <contributor>
+ <name>Nathan Beyer</name>
+ </contributor>
+ <contributor>
+ <name>Stefan Bodewig</name>
+ </contributor>
+ <contributor>
+ <name>Janek Bogucki</name>
+ </contributor>
+ <contributor>
+ <name>Mike Bowler</name>
+ </contributor>
+ <contributor>
+ <name>Sean Brown</name>
+ </contributor>
+ <contributor>
+ <name>Alexander Day Chaffee</name>
+ </contributor>
+ <contributor>
+ <name>Al Chou</name>
+ </contributor>
+ <contributor>
+ <name>Greg Coladonato</name>
+ </contributor>
+ <contributor>
+ <name>Maarten Coene</name>
+ </contributor>
+ <contributor>
+ <name>Justin Couch</name>
+ </contributor>
+ <contributor>
+ <name>Michael Davey</name>
+ </contributor>
+ <contributor>
+ <name>Norm Deane</name>
+ </contributor>
+ <contributor>
+ <name>Morgan Delagrange</name>
+ </contributor>
+ <contributor>
+ <name>Ringo De Smet</name>
+ </contributor>
+ <contributor>
+ <name>Russel Dittmar</name>
+ </contributor>
+ <contributor>
+ <name>Steve Downey</name>
+ </contributor>
+ <contributor>
+ <name>Matthias Eichel</name>
+ </contributor>
+ <contributor>
+ <name>Christopher Elkins</name>
+ </contributor>
+ <contributor>
+ <name>Chris Feldhacker</name>
+ </contributor>
+ <contributor>
+ <name>Roland Foerther</name>
+ </contributor>
+ <contributor>
+ <name>Pete Gieser</name>
+ </contributor>
+ <contributor>
+ <name>Jason Gritman</name>
+ </contributor>
+ <contributor>
+ <name>Matthew Hawthorne</name>
+ </contributor>
+ <contributor>
+ <name>Michael Heuer</name>
+ </contributor>
+ <contributor>
+ <name>Chas Honton</name>
+ </contributor>
+ <contributor>
+ <name>Chris Hyzer</name>
+ </contributor>
+ <contributor>
+ <name>Paul Jack</name>
+ </contributor>
+ <contributor>
+ <name>Marc Johnson</name>
+ </contributor>
+ <contributor>
+ <name>Shaun Kalley</name>
+ </contributor>
+ <contributor>
+ <name>Tetsuya Kaneuchi</name>
+ </contributor>
+ <contributor>
+ <name>Nissim Karpenstein</name>
+ </contributor>
+ <contributor>
+ <name>Ed Korthof</name>
+ </contributor>
+ <contributor>
+ <name>Holger Krauth</name>
+ </contributor>
+ <contributor>
+ <name>Rafal Krupinski</name>
+ </contributor>
+ <contributor>
+ <name>Rafal Krzewski</name>
+ </contributor>
+ <contributor>
+ <name>David Leppik</name>
+ </contributor>
+ <contributor>
+ <name>Eli Lindsey</name>
+ </contributor>
+ <contributor>
+ <name>Sven Ludwig</name>
+ </contributor>
+ <contributor>
+ <name>Craig R. McClanahan</name>
+ </contributor>
+ <contributor>
+ <name>Rand McNeely</name>
+ </contributor>
+ <contributor>
+ <name>Hendrik Maryns</name>
+ </contributor>
+ <contributor>
+ <name>Dave Meikle</name>
+ </contributor>
+ <contributor>
+ <name>Nikolay Metchev</name>
+ </contributor>
+ <contributor>
+ <name>Kasper Nielsen</name>
+ </contributor>
+ <contributor>
+ <name>Tim O'Brien</name>
+ </contributor>
+ <contributor>
+ <name>Brian S O'Neill</name>
+ </contributor>
+ <contributor>
+ <name>Andrew C. Oliver</name>
+ </contributor>
+ <contributor>
+ <name>Alban Peignier</name>
+ </contributor>
+ <contributor>
+ <name>Moritz Petersen</name>
+ </contributor>
+ <contributor>
+ <name>Dmitri Plotnikov</name>
+ </contributor>
+ <contributor>
+ <name>Neeme Praks</name>
+ </contributor>
+ <contributor>
+ <name>Eric Pugh</name>
+ </contributor>
+ <contributor>
+ <name>Stephen Putman</name>
+ </contributor>
+ <contributor>
+ <name>Travis Reeder</name>
+ </contributor>
+ <contributor>
+ <name>Antony Riley</name>
+ </contributor>
+ <contributor>
+ <name>Valentin Rocher</name>
+ </contributor>
+ <contributor>
+ <name>Scott Sanders</name>
+ </contributor>
+ <contributor>
+ <name>James Sawle</name>
+ </contributor>
+ <contributor>
+ <name>Ralph Schaer</name>
+ </contributor>
+ <contributor>
+ <name>Henning P. Schmiedehausen</name>
+ </contributor>
+ <contributor>
+ <name>Sean Schofield</name>
+ </contributor>
+ <contributor>
+ <name>Robert Scholte</name>
+ </contributor>
+ <contributor>
+ <name>Reuben Sivan</name>
+ </contributor>
+ <contributor>
+ <name>Ville Skytta</name>
+ </contributor>
+ <contributor>
+ <name>David M. Sledge</name>
+ </contributor>
+ <contributor>
+ <name>Michael A. Smith</name>
+ </contributor>
+ <contributor>
+ <name>Jan Sorensen</name>
+ </contributor>
+ <contributor>
+ <name>Glen Stampoultzis</name>
+ </contributor>
+ <contributor>
+ <name>Scott Stanchfield</name>
+ </contributor>
+ <contributor>
+ <name>Jon S. Stevens</name>
+ </contributor>
+ <contributor>
+ <name>Sean C. Sullivan</name>
+ </contributor>
+ <contributor>
+ <name>Ashwin Suresh</name>
+ </contributor>
+ <contributor>
+ <name>Helge Tesgaard</name>
+ </contributor>
+ <contributor>
+ <name>Arun Mammen Thomas</name>
+ </contributor>
+ <contributor>
+ <name>Masato Tezuka</name>
+ </contributor>
+ <contributor>
+ <name>Daniel Trebbien</name>
+ </contributor>
+ <contributor>
+ <name>Jeff Varszegi</name>
+ </contributor>
+ <contributor>
+ <name>Chris Webb</name>
+ </contributor>
+ <contributor>
+ <name>Mario Winterer</name>
+ </contributor>
+ <contributor>
+ <name>Stepan Koltsov</name>
+ </contributor>
+ <contributor>
+ <name>Holger Hoffstatte</name>
+ </contributor>
+ <contributor>
+ <name>Derek C. Ashmore</name>
+ </contributor>
+ <contributor>
+ <name>Sebastien Riou</name>
+ </contributor>
+ <contributor>
+ <name>Allon Mureinik</name>
+ </contributor>
+ <contributor>
+ <name>Adam Hooper</name>
+ </contributor>
+ <contributor>
+ <name>Chris Karcher</name>
+ </contributor>
+ <contributor>
+ <name>Michael Osipov</name>
+ </contributor>
+ <contributor>
+ <name>Thiago Andrade</name>
+ </contributor>
+ <contributor>
+ <name>Jonathan Baker</name>
+ </contributor>
+ <contributor>
+ <name>Mikhail Mazursky</name>
+ </contributor>
+ <contributor>
+ <name>Fabian Lange</name>
+ </contributor>
+ <contributor>
+ <name>Michał Kordas</name>
+ </contributor>
+ <contributor>
+ <name>Felipe Adorno</name>
+ </contributor>
+ <contributor>
+ <name>Adrian Ber</name>
+ </contributor>
+ <contributor>
+ <name>Mark Dacek</name>
+ </contributor>
+ <contributor>
+ <name>Peter Verhas</name>
+ </contributor>
+ <contributor>
+ <name>Jin Xu</name>
+ </contributor>
+ <contributor>
+ <name>Arturo Bernal</name>
+ </contributor>
+ </contributors>
+
+ <!-- Lang should depend on very little -->
+ <dependencies>
+ <!-- testing -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit-pioneer</groupId>
+ <artifactId>junit-pioneer</artifactId>
+ <version>1.9.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest</artifactId>
+ <version>2.2</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>5.1.0</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- For Javadoc links -->
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-text</artifactId>
+ <version>1.10.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ <version>${jmh.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <version>${jmh.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <version>3.0.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <distributionManagement>
+ <site>
+ <id>apache.website</id>
+ <name>Apache Commons Site</name>
+ <url>scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-lang/</url>
+ </site>
+ </distributionManagement>
+
+ <properties>
+ <argLine>-Xmx512m</argLine>
+ <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <!--
+ This is also used to generate download_xxx file name.
+ To override this when generating the download page:
+
+ mvn commons:download-page -Dcommons.componentid=lang
+
+ The above seems to change the download page name but not any other
+ properties that depend on the componentid.
+
+ N.B. The componentid is also used by the parent pom as part of the OSGI symbolic name.
+ -->
+ <commons.componentid>lang</commons.componentid>
+ <commons.packageId>lang3</commons.packageId>
+ <commons.module.name>org.apache.commons.lang3</commons.module.name>
+ <!-- Current 3.x release series -->
+ <commons.release.version>3.13.0</commons.release.version>
+ <commons.release.desc>(Java 8+)</commons.release.desc>
+ <!-- Previous 2.x release series -->
+ <commons.release.2.version>2.6</commons.release.2.version>
+ <commons.release.2.desc>(Requires Java 1.2 or later)</commons.release.2.desc>
+ <!-- Override generated name -->
+ <commons.release.2.name>commons-lang-${commons.release.2.version}</commons.release.2.name>
+ <commons.jira.id>LANG</commons.jira.id>
+ <commons.jira.pid>12310481</commons.jira.pid>
+
+ <commons.site.path>lang</commons.site.path>
+ <commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-lang</commons.scmPubUrl>
+ <commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
+ <commons.encoding>utf-8</commons.encoding>
+
+ <checkstyle.configdir>src/site/resources/checkstyle</checkstyle.configdir>
+
+ <japicmp.skip>false</japicmp.skip>
+
+ <!-- JMH Benchmark related properties, version, target compiler and name of the benchmarking uber jar. -->
+ <jmh.version>1.36</jmh.version>
+ <uberjar.name>benchmarks</uberjar.name>
+
+ <!-- Commons Release Plugin -->
+ <commons.bc.version>3.12.0</commons.bc.version>
+ <commons.rc.version>RC1</commons.rc.version>
+ <commons.release.isDistModule>true</commons.release.isDistModule>
+ <commons.distSvnStagingUrl>scm:svn:https://dist.apache.org/repos/dist/dev/commons/lang</commons.distSvnStagingUrl>
+ <commons.releaseManagerName>Gary Gregory</commons.releaseManagerName>
+ <commons.releaseManagerKey>86fdc7e2a11262cb</commons.releaseManagerKey>
+ </properties>
+
+ <build>
+ <defaultGoal>clean verify apache-rat:check checkstyle:check japicmp:cmp spotbugs:check pmd:check javadoc:javadoc</defaultGoal>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${commons.pmd.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ <excludeFromFailureFile>src/conf/exclude-pmd.properties</excludeFromFailureFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>site-content/**</exclude>
+ <exclude>src/site/resources/.htaccess</exclude>
+ <exclude>src/site/resources/download_lang.cgi</exclude>
+ <exclude>src/site/resources/release-notes/RELEASE-NOTES-*.txt</exclude>
+ <exclude>src/test/resources/lang-708-input.txt</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <quiet>true</quiet>
+ <notimestamp>true</notimestamp>
+ <links>
+ <link>https://commons.apache.org/proper/commons-text/apidocs</link>
+ <link>https://docs.oracle.com/javase/8/docs/api</link>
+ <link>https://docs.oracle.com/javaee/6/api</link>
+ </links>
+ <validateLinks>true</validateLinks>
+ <archive>
+ <manifest>
+ <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+ <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+ </manifest>
+ </archive>
+ <doclint>all</doclint>
+ </configuration>
+ <executions>
+ <execution>
+ <id>create-javadoc-jar</id>
+ <goals>
+ <goal>javadoc</goal>
+ <goal>jar</goal>
+ </goals>
+ <phase>package</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>plain</id>
+ <configuration>
+ <includes>
+ <include>**/*Test.java</include>
+ </includes>
+ <runOrder>random</runOrder>
+ </configuration>
+ </execution>
+ <!-- <execution> <id>security-manager-test</id> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration>
+ <includes> <include>**/*Test.java</include> </includes> <argLine>-Djava.security.manager -Djava.security.policy=${basedir}/src/test/resources/java.policy</argLine>
+ </configuration> </execution> -->
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <descriptors>
+ <descriptor>src/assembly/bin.xml</descriptor>
+ <descriptor>src/assembly/src.xml</descriptor>
+ </descriptors>
+ <tarLongFileMode>gnu</tarLongFileMode>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <archive combine.children="append">
+ <manifestEntries>
+ <Automatic-Module-Name>${commons.module.name}</Automatic-Module-Name>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-scm-publish-plugin</artifactId>
+ <configuration>
+ <ignorePathsToDelete>
+ <ignorePathToDelete>javadocs</ignorePathToDelete>
+ </ignorePathsToDelete>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <configuration>
+ <configLocation>${checkstyle.configdir}/checkstyle.xml</configLocation>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ <enableRulesSummary>false</enableRulesSummary>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <excludeFilterFile>${basedir}/src/conf/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <quiet>true</quiet>
+ <notimestamp>true</notimestamp>
+ <links>
+ <link>https://commons.apache.org/proper/commons-text/apidocs</link>
+ <link>https://docs.oracle.com/javase/8/docs/api</link>
+ <link>https://docs.oracle.com/javaee/6/api</link>
+ </links>
+ <validateLinks>true</validateLinks>
+ <archive>
+ <manifest>
+ <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+ <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+ </manifest>
+ </archive>
+ <doclint>all</doclint>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <configuration>
+ <configLocation>${checkstyle.configdir}/checkstyle.xml</configLocation>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ <enableRulesSummary>false</enableRulesSummary>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>checkstyle</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <!-- Requires setting 'export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=128m" ' -->
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <excludeFilterFile>${basedir}/src/conf/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>taglist-maven-plugin</artifactId>
+ <version>3.0.0</version>
+ <configuration>
+ <tagListOptions>
+ <tagClasses>
+ <tagClass>
+ <displayName>Needs Work</displayName>
+ <tags>
+ <tag>
+ <matchString>TODO</matchString>
+ <matchType>exact</matchType>
+ </tag>
+ <tag>
+ <matchString>FIXME</matchString>
+ <matchType>exact</matchType>
+ </tag>
+ <tag>
+ <matchString>XXX</matchString>
+ <matchType>exact</matchType>
+ </tag>
+ </tags>
+ </tagClass>
+ <tagClass>
+ <displayName>Noteable Markers</displayName>
+ <tags>
+ <tag>
+ <matchString>NOTE</matchString>
+ <matchType>exact</matchType>
+ </tag>
+ <tag>
+ <matchString>NOPMD</matchString>
+ <matchType>exact</matchType>
+ </tag>
+ <tag>
+ <matchString>NOSONAR</matchString>
+ <matchType>exact</matchType>
+ </tag>
+ </tags>
+ </tagClass>
+ </tagClasses>
+ </tagListOptions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+
+ <profiles>
+ <profile>
+ <id>setup-checkout</id>
+ <activation>
+ <file>
+ <missing>site-content</missing>
+ </file>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>prepare-checkout</id>
+ <phase>pre-site</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <tasks>
+ <exec executable="svn">
+ <arg line="checkout --depth immediates ${commons.scmPubUrl} ${commons.scmPubCheckoutDirectory}" />
+ </exec>
+
+ <exec executable="svn">
+ <arg line="update --set-depth exclude ${commons.scmPubCheckoutDirectory}/javadocs" />
+ </exec>
+
+ <pathconvert pathsep=" " property="dirs">
+ <dirset dir="${commons.scmPubCheckoutDirectory}" includes="*" />
+ </pathconvert>
+ <exec executable="svn">
+ <arg line="update --set-depth infinity ${dirs}" />
+ </exec>
+ </tasks>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>java9+</id>
+ <activation>
+ <jdk>[9,)</jdk>
+ </activation>
+ <properties>
+ <!-- LANG-1265: allow tests to access private fields/methods of java.base classes via reflection -->
+ <!-- LANG-1667: allow tests to access private fields/methods of java.base/java.util such as ArrayList via reflection -->
+ <argLine>-Xmx512m --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED</argLine>
+ <!-- coverall version 4.3.0 does not work with java 9, see https://github.com/trautonen/coveralls-maven-plugin/issues/112 -->
+ <coveralls.skip>true</coveralls.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>java13+</id>
+ <activation>
+ <jdk>[13,)</jdk>
+ </activation>
+ <properties>
+ <!-- jacoco does not work with java 13 yet -->
+ <jacoco.skip>true</jacoco.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>java15</id>
+ <activation>
+ <!-- This is ONLY activated for Java 15 -->
+ <jdk>15</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>org/apache/commons/lang3/time/Java15BugFastDateParserTest.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>benchmark</id>
+ <properties>
+ <skipTests>true</skipTests>
+ <benchmark>org.apache</benchmark>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>3.1.0</version>
+ <executions>
+ <execution>
+ <id>benchmark</id>
+ <phase>test</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <classpathScope>test</classpathScope>
+ <executable>java</executable>
+ <arguments>
+ <argument>-classpath</argument>
+ <classpath/>
+ <argument>org.openjdk.jmh.Main</argument>
+ <argument>-rf</argument>
+ <argument>json</argument>
+ <argument>-rff</argument>
+ <argument>target/jmh-result.${benchmark}.json</argument>
+ <argument>${benchmark}</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/src/assembly/bin.xml b/src/assembly/bin.xml
new file mode 100644
index 000000000..fb1e07c9a
--- /dev/null
+++ b/src/assembly/bin.xml
@@ -0,0 +1,46 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<assembly>
+ <id>bin</id>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+ <includeSiteDirectory>false</includeSiteDirectory>
+ <fileSets>
+ <fileSet>
+ <includes>
+ <include>LICENSE.txt</include>
+ <include>NOTICE.txt</include>
+ <include>RELEASE-NOTES.txt</include>
+ <include>README.md</include>
+ <include>CONTRIBUTING.md</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>target</directory>
+ <outputDirectory></outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>target/site/apidocs</directory>
+ <outputDirectory>apidocs</outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/src/assembly/src.xml b/src/assembly/src.xml
new file mode 100644
index 000000000..1a9ad19f3
--- /dev/null
+++ b/src/assembly/src.xml
@@ -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.
+-->
+<assembly>
+ <id>src</id>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+ <baseDirectory>${project.artifactId}-${commons.release.version}-src</baseDirectory>
+ <fileSets>
+ <fileSet>
+ <includes>
+ <include>.travis.yml</include>
+ <include>checkstyle.xml</include>
+ <include>checkstyle-suppressions.xml</include>
+ <include>spotbugs-exclude-filter.xml</include>
+ <include>LICENSE.txt</include>
+ <include>NOTICE.txt</include>
+ <include>pom.xml</include>
+ <include>PROPOSAL.html</include>
+ <include>RELEASE-NOTES.txt</include>
+ <include>README.md</include>
+ <include>CONTRIBUTING.md</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>src</directory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
new file mode 100644
index 000000000..5161d0eb8
--- /dev/null
+++ b/src/changes/changes.xml
@@ -0,0 +1,1394 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!--
+This file is also used by the maven-changes-plugin to generate the release notes.
+Useful ways of finding items to add to this file are:
+
+1. Add items when you fix a bug or add a feature (this makes the
+release process easy :-).
+
+2. Do a JIRA search for tickets closed since the previous release.
+
+3. Use the report generated by the maven-changelog-plugin to see all
+SVN commits. TBA how to use this with SVN.
+
+To generate the release notes from this file:
+
+mvn changes:announcement-generate -Prelease-notes [-Dchanges.version=nnn]
+
+then tweak the formatting if necessary
+and commit
+
+The <action> type attribute can be add,update,fix,remove.
+-->
+
+<document>
+ <properties>
+ <title>Apache Commons Lang Release Notes</title>
+ </properties>
+ <body>
+
+ <release version="3.13.0" date="2021-MM-DD" description="New features and bug fixes (Java 8).">
+ <!-- FIX -->
+ <action issue="LANG-1645" type="fix" dev="aherbert" due-to="Alex Herbert">NumberUtils.createNumber() to recognize hex integers prefixed with +.</action>
+ <action issue="LANG-1646" type="fix" dev="aherbert" due-to="Alex Herbert">NumberUtils.createNumber() to return requested floating point type for zero.</action>
+ <action type="fix" dev="ggregory" due-to="SpotBugs, Gary Gregory">DMI: Random object created and used only once (DMI_RANDOM_USED_ONLY_ONCE); Better multi-threaded behavior.</action>
+ <action issue="LANG-1646" type="fix" dev="aherbert" due-to="Arturo Bernal">Redundant Collection operation. Use Collections.emptyIterator() #738.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Make Streams.stream(Collection) null-safe.</action>
+ <action issue="LANG-1667" type="fix" dev="ggregory" due-to="Andrew Thomas">Allow tests to access java.util classes such as ArrayList in Java 16 #788.</action>
+ <action issue="LANG-1669" type="fix" dev="ggregory" due-to="Andrew Thomas">OpenJDK 16 Day Period Parsing #791.</action>
+ <action issue="LANG-1663" type="fix" dev="ggregory" due-to="Andrew Thomas">Update documentation to list correct exception for null array parameters #785.</action>
+ <action type="fix" dev="ggregory" due-to="Thunderforge">Fixing reversed Javadoc descriptions in StopWatch #781.</action>
+ <action issue="LANG-1670" type="fix" dev="ggregory" due-to="Igor Shuvalov">Fix typos in JavaDoc #795.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Simplify assertions with equivalent but more simple. #792.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Avoid multiple equivalent occurrences of the same expression. #797.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Remove redundant initializers #800.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Fix ObjectUtils Javadocs #755.</action>
+ <action type="fix" dev="ggregory" due-to="Rushi98, Gary Gregory">Add test idea for RangeTest from PR #815 by Rushi98, but with a new comment.</action>
+ <action issue="LANG-1674" type="fix" dev="ggregory" due-to="singhbaljit, Gary Gregory">Make Range constructors more generic #810.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Use final and Remove redundant String. #813, #816.</action>
+ <action type="fix" dev="ggregory" due-to="CiprianBodnarescu">Use Set instead of List for checking the contains() method #734.</action>
+ <action type="fix" dev="kinow" due-to="Roland Kreuzer">Javadoc for StringUtils.substringBefore(String str, int separator) doesn't mention that the separator is an int.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix NullPointerException in ThreadUtils.getSystemThreadGroup() when the current thread is stopped.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">ArrayUtils.toPrimitive(Boolean...) null array elements map to false, like Boolean.parseBoolean(null) and its callers return false.</action>
+ <action type="fix" dev="ggregory" due-to="CodeQL, Gary Gregory">StrBuilder.StrBuilderReader.skip(long): Throw an exception when an implicit narrowing conversion in a compound assignment would result in information loss or a numeric error such as an overflows.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate Validate#notNull(Object) in favor of using Objects#requireNonNull(Object, String).</action>
+ <action issue="LANG-1462" type="fix" dev="ggregory" due-to="Lijun Liang, Arun Avanathan, Tai Dupree, Maria Buiakova, Gary Gregory">Use TimeZone from calendar in DateFormatUtils.</action>
+ <action type="fix" dev="ggregory" due-to="Diego Marcilio">Updating javadoc for NullPointerException when Validate.notNull() is called #870.</action>
+ <action type="fix" dev="ggregory" due-to="Diego Marcilio">Fixing and adding DateUtils exception Javadocs #871.</action>
+ <action issue="LANG-1679" type="fix" dev="ggregory" due-to="clover">Improve performance of StringUtils.unwrap(String, String) #844.</action>
+ <action issue="LANG-1675" type="fix" dev="ggregory" due-to="clover">Improve performance of StringUtils.join for primitives #812.</action>
+ <action issue="LANG-1675" type="fix" dev="ggregory" due-to="Arturo Bernal">Fixed NPE getting Stack Trace if Throwable is null #733.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory, Arturo Bernal">Make Validate.isAssignableFrom() check null inputs.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Fix Javadoc for Validate.isAssignableFrom().</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Make final mappingFunction variable #876.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Remove unnecessary variable creations #882.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Minor changes #769.</action>
+ <action issue="LANG-1680" type="fix" dev="ggregory" due-to="Michael Krause, Steve Bosman, Gary Gregory">FastDateFormat does not support the 'L'-Pattern from SimpleDateFormat.</action>
+ <action type="fix" dev="ggregory" due-to="Steve Bosman, Gary Gregory">Increase test coverage of ComparableUtils from 71% to 100% #898.</action>
+ <action type="fix" dev="ggregory" due-to="Steve Bosman">Increase method test coverage of MultilineRecursiveToStringStyle #899.</action>
+ <action type="fix" dev="ggregory" due-to="Steve Bosman">Fix unstable coverage of CharSequenceUtils tests noticed during merge of PRs 898 and 899 #901.</action>
+ <action type="fix" dev="aherbert" due-to="Arturo Bernal">Rewrite Conversion.binaryBeMsb0ToHexDigit to invert logic of binaryToHexDigit.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Allow extension of previously final classes ImmutablePair and ImmutableTriple.</action>
+ <action type="fix" dev="ggregory" due-to="shalk, Bruno P. Kinoshita, Gary Gregory">Update ClassUtils Javadoc with some missing throws NPE #912.</action>
+ <action type="fix" dev="ggregory" due-to="guicaiyue">Javadoc: StringUtils.repeat("", "x", 3) = "xx"; #918.</action>
+ <action type="fix" dev="ggregory" due-to="Marc Wrobel">Fix typos #920, #923.</action>
+ <action type="fix" dev="ggregory" due-to="Bhimantoro Suryo Admodjo">Simplify condition #925.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">StringUtils.join(Iterable, String) should only return null when the Iterable is null.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">StringUtils.join(Iterator, String) should only return null when the Iterator is null.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Add tests to increase coverage #904.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Extends Object clauses are redundant #937.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Simplify conditional expression. #941.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Fix some Javadoc comments #938.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal, Gary Gregory">Deprecate getNanosOfMiili() method with typo and create proper getNanosOfMilli() #940.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate ThreadUtils code that defines custom function interfaces in favor of stock java.util.function.Predicate usage.</action>
+ <action type="fix" dev="ggregory" due-to="Marc Wrobel">Fix links in Javadoc and documentation #926.</action>
+ <action issue="LANG-1604" type="fix" dev="ggregory" due-to="Gilles Sadowski, Maksym Bohachov, Gary Gregory">Deprecate RandomUtils in favor of Apache Commons RNG UniformRandomProvider #942.</action>
+ <action issue="LANG-1638" type="fix" dev="ggregory" due-to="Shailendra Soni, Michael Osipov, Arun Avanathan, Andrew Thomas, Bruno P. Kinoshita, Gary Gregory">Added docs regarding week year support #924.</action>
+ <action issue="LANG-1691" type="fix" dev="ggregory" due-to="Thiyagarajan, Gary Gregory">ClassUtils.getShortCanonicalName doesn't use the canonicalName #949.</action>
+ <action type="fix" dev="aherbert" due-to="Piotr Stawirej">Validate: Get error messages without using String.format when varargs is empty.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Simplify expression (length is never &lt; 0) #962.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Fix simple broken javadoc. #981.</action>
+ <action type="fix" dev="ggregory" due-to="LeeJuHyun">Fix typo #1001.</action>
+ <action type="fix" dev="ggregory" due-to="Arturo Bernal">Use Objects.requireNonNull() directly #1022.</action>
+ <!-- ADD -->
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add GitHub coverage.yml.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add EnumUtils.getEnumSystemProperty(...).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add TriConsumer.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use EnumUtils.getFirstEnumIgnoreCase(Class, String, Function, E).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use Suppliers.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ArrayUtils.getComponentType(T[]).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ClassUtils.getComponentType(Class&gt;T[]>).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ObjectUtils.getClass(T).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ArrayUtils.newInstance(Class&gt;T>, int).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use null-safe Streams.of(T...).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ClassUtils.comparator().</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ThreadUtils.sleepQuietly(Duration).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ArrayUtils.setAll(T[], IntFunction).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ArrayUtils.setAll(T[], Supplier).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add BooleanConsumer.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add IntToCharFunction.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add IntStreams.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add UncheckedFuture.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add UncheckedException.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add UncheckedExecutionException.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add UncheckedTimeoutException.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add UncheckedInterruptedException.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add TimeZones.GMT.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ObjectUtils.identityHashCodeHex(Object).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ObjectUtils.hashCodeHex(Object).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add StringUtils.removeStart(String, char).</action>
+ <action issue="LANG-1659" type="add" dev="ggregory" due-to="Arturo Bernal, Gary Gregory">Add null-safe ObjectUtils.isArray() #754.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ComparableUtils.max(A, A) and ComparableUtils.min(A, A).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add UncheckedReflectiveOperationException.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ClassUtils.isPublic(Class).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add UncheckedIllegalAccessException.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add MethodInvokers.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Streams.nullSafeStream(Collection).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Streams.toStream(Collection).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Streams.failableStream(Collection) and deprecate misnamed stream(Collection).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Streams.failableStream(Stream) and deprecate misnamed stream(Stream).</action>
+ <action type="add" dev="ggregory" due-to="Maxwell Cody, Gary Gregory">Add EnumUtils.getEnumMap(Class, Function). #730</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add FluentBitSet.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Streams.instancesOf(Class, Collection).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ImmutablePair.ofNonNull(L, R).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ImmutableTriple.ofNonNull(L, M, R).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add MutablePair.ofNonNull(L, R).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add MutableTriple.ofNonNull(L, M, R).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Pair.ofNonNull(L, R).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Triple.ofNonNull(L, M, R).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ArrayUtils.containsAny(Object[], Object...).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Processor.Type.AARCH_64.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Processor.isAarch64().</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Update ArchUtils.getProcessor(String) for "aarch64".</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add JavaVersion.JAVA_18.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add TimeZones.toTimeZone(TimeZone).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add FutureTasks.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Memoizer(Function) and Memoizer(Function, boolean).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Consumers.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add github/codeql-action.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add coverage.yml.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add DurationUtils.since(Temporal).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add DurationUtils.of(FailableConsumer|FailableRunnbale).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ExceptionUtils.forEach(Throwable, Consumer&lt;Throwable&gt;).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ExceptionUtils.stream(Throwable).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ExceptionUtils.getRootCauseStackTraceList(Throwable).</action>
+ <action type="add" dev="ggregory" due-to="Will Herrmann, Gary Gregory, Roland Kreuzer">Add SystemUtils.IS_OS_WINDOWS_11.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add SystemUtils.IS_JAVA_16.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add SystemUtils.IS_JAVA_17.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add SystemUtils.IS_JAVA_18.</action>
+ <action issue="LANG-1627" type="add" dev="ggregory" due-to="Alberto Scotto, Avijit Chakraborty, Steve Bosman, Bruno P. Kinoshita, Gary Gregory">Add ArrayUtils.oneHot().</action>
+ <action issue="LANG-1662" type="add" dev="ggregory" due-to="Daniel Augusto Veronezi Salvador, Gary Gregory, Bruno P. Kinoshita">Let ReflectionToStringBuilder only reflect given field names #849.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Streams.of(Enumeration&lt;E&gt;).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Streams.of(Iterable&lt;E&gt;).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Streams.of(Iterator&lt;E&gt;).</action>
+ <action issue="LANG-1689" type="add" dev="ggregory" due-to="Joseph Hendrix, Gary Gregory">Simple support for Optional in ObjectUtils#isEmpty() #933.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Processor.Type.getLabel().</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Processor.toString().</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add HashCodeBuilder.equals(Object).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add BooleanUtils.values() and forEach().</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add ClassPathUtils.packageToPath(String) and pathToPackage(String)</action>
+ <action type="add" dev="ggregory" due-to="Arturo Bernal">Add CalendarUtils#getDayOfYear() #968</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add NumberRange, DoubleRange, IntegerRange, LongRange.</action>
+ <action type="add" dev="ggregory" due-to="Diego Marcilio, Bruno P. Kinoshita, Gary Gregory">Add missing exception javadoc/tests for some null arguments #869.</action>
+ <!-- UPDATE -->
+ <action type="update" dev="ggregory" due-to="Dependabot, XenoAmess, Gary Gregory">Bump actions/cache from 2.1.4 to 3.0.10 #742, #752, #764, #833, #867, #959, #964.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump actions/checkout from 2 to 3.1.0 #819, #825, #859, #963.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Bump actions/setup-java from v1.4.3 to 3.5.1 #879.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump spotbugs-maven-plugin from 4.2.0 to 4.7.3.0 #735, #808, #822, #834, #868, #895, #919, #927, #946, #989.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump spotbugs from 4.2.2 to 4.7.3 #744, #917, #947, #973.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump maven-checkstyle-plugin from 3.1.2 to 3.2.0 #943.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump checkstyle from 8.41 to 9.3 #739, #768, #787, #811, #824, #843.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump easymock from 4.2 to 5.1.0 #746, #972, #986, #1012.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Bump commons.jacoco.version from 0.8.6 to 0.8.8.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Bump commons.japicmp.version from 0.15.2 to 0.16.0.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump junit-pioneer from 1.3.8 to 1.9.1 #749, #767, #832, #883, #988, #991, #995.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump junit-bom from 5.7.1 to 5.9.1 #761, #805, #807, #836, #928, #955.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump maven-javadoc-plugin from 3.2.0 to 3.4.1.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump jmh.version from 1.27 to 1.36 #794, #842, #872, #990.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump maven-pmd-plugin from 3.14.0 to 3.19.0 #802, #858, #909, #948.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump pmd from 6.40.0 to 6.52.0 #837, #861, #873, #905, #915, #932, #944.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump biz.aQute.bndlib from 5.3.0 to 6.3.1 #814, #835.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump maven-bundle-plugin from 5.1.1 to 5.1.2.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump animal-sniffer-maven-plugin from 1.19 to 1.21.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump exec-maven-plugin from 1.6.0 to 3.1.0 #590, #922.</action>
+ <action type="update" dev="kinow" due-to="Dependabot">Bump maven-surefire-plugin from 3.0.0-M5 to 3.0.0-M7 #880, #910.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Bump apache-rat from 0.13 to 0.14.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump commons-parent from 53 to 56 #954, #1000, #1011.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump commons-text from 1.9 to 1.10.0 #957.</action>
+ <action type="update" dev="kinow" due-to="Dependabot, Gary Gregory">Bump commons.pmd-impl.version from 6.49.0 to 6.51.0 #961.</action>
+ </release>
+
+ <release version="3.12.0" date="2021-02-26" description="New features and bug fixes (Java 8).">
+ <!-- FIX -->
+ <action issue="LANG-1592" type="fix" dev="aherbert" due-to="Huang Pingcai, Alex Herbert">Correct implementation of RandomUtils.nextLong(long, long)</action>
+ <action issue="LANG-1600" type="fix" dev="ggregory" due-to="Michael F">Restore handling of collections for non-JSON ToStringStyle #610.</action>
+ <action type="fix" dev="ggregory" due-to="iamchao1129">ContextedException Javadoc add missing semicolon #581.</action>
+ <action issue="LANG-1608" type="fix" dev="aherbert" due-to="Edgar Asatryan">Resolve JUnit pioneer transitive dependencies using JUnit BOM.</action>
+ <action type="fix" dev="aherbert" due-to="HubertWo, Gary Gregory">NumberUtilsTest - incorrect types in min/max tests #634.</action>
+ <action issue="LANG-1579" type="fix" dev="aherbert" due-to="XenoAmess">Improve StringUtils.stripAccents conversion of remaining accents.</action>
+ <action issue="LANG-1606" type="fix" dev="sebb" due-to="Rustem Galiev">StringUtils.countMatches - clarify Javadoc.</action>
+ <action issue="LANG-1591" type="fix" dev="kinow" due-to="bhawna94">Remove redundant argument from substring call.</action>
+ <action issue="LANG-1613" type="fix" dev="ggregory" due-to="Arturo Bernal, Gary Gregory">BigDecimal is created when you pass it the min and max values, #642.</action>
+ <action issue="LANG-1541" type="fix" dev="ggregory" due-to="Arturo Bernal, Gary Gregory">ArrayUtils.contains() and indexOf() fail to handle Double.NaN #647.</action>
+ <action issue="LANG-1615" type="fix" dev="ggregory" due-to="Arturo Bernal, Gary Gregory">ArrayUtils contains() and indexOf() fail to handle Float.NaN # #561.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix potential NPE in TypeUtils.isAssignable(Type, ParameterizedType, Map, Type>).</action>
+ <action issue="LANG-1420" type="fix" dev="ggregory" due-to="Gordon Fraser, Rostislav Krasny, Arturo Bernal, Gary Gregory">TypeUtils.isAssignable returns wrong result for GenericArrayType and ParameterizedType, #643.</action>
+ <action issue="LANG-1612" type="fix" dev="ggregory" due-to="XinT, Gary Gregory">testGetAllFields and testGetFieldsWithAnnotation sometimes fail.</action>
+ <action type="fix" dev="ggregory" due-to="John R. D'Orazio">Fix Javadoc for SystemUtils.isJavaVersionAtMost() #638.</action>
+ <action issue="LANG-1610" type="fix" dev="ggregory" due-to="Tony Liang">Fix StringUtils.unwrap throws StringIndexOutOfBoundsException #636.</action>
+ <action type="fix" dev="ggregory" due-to="Isira Seneviratne">Fix formatting of isAnyBlank() and isAnyEmpty(). #513.</action>
+ <action issue="LANG-1618" type="fix" dev="ggregory" due-to="Arturo Bernal">TypeUtils. containsTypeVariables does not support GenericArrayType #661.</action>
+ <action issue="LANG-1622" type="fix" dev="ggregory" due-to="Kanak Sony, anomen-s">Javadoc of some methods incorrectly refers to another method, #667, #668. #670.</action>
+ <action issue="LANG-1620" type="fix" dev="ggregory" due-to="Arturo Bernal">Refine StringUtils.lastIndexOfIgnoreCase #664.</action>
+ <action issue="LANG-1619" type="fix" dev="ggregory" due-to="Arturo Bernal">Refine StringUtils.abbreviate #663.</action>
+ <action issue="LANG-1584" type="fix" dev="ggregory" due-to="Arturo Bernal">Refine StringUtils.isNumericSpace #573.</action>
+ <action issue="LANG-1580" type="fix" dev="ggregory" due-to="Arturo Bernal">Refine StringUtils.deleteWhitespace #569.</action>
+ <action issue="LANG-1626" type="fix" dev="ggregory" due-to="Kanak Sony">Correction in Javadoc of some methods. #673</action>
+ <action issue="LANG-1628" type="fix" dev="kinow" due-to="Jarkko Rantavuori">Javadoc for RandomStringUtils.random() letters, numbers parameters is wrong.</action>
+ <action type="fix" dev="ggregory" due-to="Felix Schumacher">Correct markup in Javadoc for unbalanced braces #679.</action>
+ <action issue="LANG-1544" type="fix" dev="kinow" due-to="Peter Nagy, Michael Buck, Gary Gregory">MethodUtils.invokeMethod NullPointerException in case of null in args list #680.</action>
+ <action issue="LANG-1637" type="fix" dev="ggregory" due-to="Uri Gonen, Gary Gregory, Michael Osipov">Fix 2 digit week year formatting #688.</action>
+ <action type="fix" dev="ggregory" due-to="Chris Smowton">Fix broken Javadoc links to commons-text #712.</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Add and use ThreadUtils.sleep(Duration).</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Add and use ThreadUtils.join(Thread, Duration).</action>
+ <action type="fix" dev="ggregory" due-to="Gary Gregory">Add ObjectUtils.wait(Duration).</action>
+ <!-- ADD -->
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add BooleanUtils.booleanValues().</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add BooleanUtils.primitiveValues().</action>
+ <action issue="LANG-1535" type="add" dev="ggregory" due-to="Gary Gregory, Isira Seneviratne">Add StringUtils.containsAnyIgnoreCase(CharSequence, CharSequence...).</action>
+ <action issue="LANG-1359" type="add" dev="ggregory" due-to="Gary Gregory, Keegan Witt">Add StopWatch.getStopTime().</action>
+ <action type="add" dev="ggregory" due-to="Edgar Asatryan">More test coverage for CharSequenceUtils. #631.</action>
+ <action issue="LANG-1596" type="update" dev="aherbert" due-to="Richard Eckart de Castilho">ArrayUtils.toPrimitive(Object) does not support boolean and other types #607.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add fluent-style ArraySorter.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use LocaleUtils.toLocale(Locale) to avoid NPEs.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add FailableShortSupplier, handy for JDBC APIs.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add JavaVersion.JAVA_17.</action>
+ <action issue="LANG-1636" type="add" dev="ggregory" due-to="">Add missing boolean[] join method #686.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add StringUtils.substringBefore(String, int).</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add Range.INTEGER.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add DurationUtils.</action>
+ <action type="add" dev="jochen">Introduce the use of @Nonnull, and @Nullable, and the Objects class as a helper tool.</action>
+ <action type="add" dev="ggregory" due-to="Arturo Bernal, Gary Gregory">Add and use true and false String constants #714.</action>
+ <action type="add" dev="ggregory" due-to="Arturo Bernal, Gary Gregory">Add and use ObjectUtils.requireNonEmpty() #716.</action>
+ <!-- UPDATE -->
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Enable Dependabot #587.</action>
+ <action type="update" dev="chtompki">Bump junit-jupiter from 5.6.2 to 5.7.0.</action>
+ <action type="update" dev="chtompki" due-to="chtompki, Dependabot">Bump spotbugs from 4.1.2 to 4.2.2, #627, #671, #708, #726.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump spotbugs-maven-plugin from 4.0.0 to 4.2.0, #593, #596, #609, #623, #632, #692.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump biz.aQute.bndlib from 5.1.1 to 5.3.0 #592, #628, #715.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump junit-pioneer from 0.6.0 to 1.1.0, #589, #597, #600, #624, #625, #662.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump checkstyle from 8.34 to 8.41, #594, #614, #637, #665, #706, #722.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump actions/checkout from v2.3.1 to v2.3.4 #601, #639.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump actions/setup-java from v1.4.0 to v1.4.2 #612.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Update commons.jacoco.version 0.8.5 to 0.8.6 (Fixes Java 15 builds).</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Update maven-surefire-plugin 2.22.2 -> 3.0.0-M5.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump maven-pmd-plugin from 3.13.0 to 3.14.0 #660.</action>
+ <action type="update" dev="kinow" due-to="Dependabot">Bump jmh.version from 1.21 to 1.27 #674.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Update commons.japicmp.version 0.14.3 -> 0.15.2.</action>
+ <action type="update" dev="ggregory" due-to="Ali K. Nouri">Processor.java: check enum equality with == instead of .equals() method #690.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump junit-pioneer from 1.1.0 to 1.3.8, #702, #721.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump maven-checkstyle-plugin from 3.1.1 to 3.1.2 #705.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump actions/cache from v2 to v2.1.4 #710.</action>
+ <action type="update" dev="ggregory" due-to="Dependabot">Bump junit-bom from 5.7.0 to 5.7.1 #707.</action>
+ <action type="update" dev="ggregory" due-to="Arturo Bernal">Minor Improvements #701.</action>
+ <action type="update" dev="ggregory" due-to="Arturo Bernal">Minor Improvement: Add final variable.try to make the code read-only #700.</action>
+ <action type="update" dev="ggregory" due-to="Arturo Bernal">Minor Improvement: Remove redundant initializer #699.</action>
+ <action type="update" dev="ggregory" due-to="Arturo Bernal">Use own validator ObjectUtils.anyNull to check null String input #718.</action>
+ <action type="update" dev="kinow" due-to="Dependabot">Bump commons-parent from 52 to 53 #885.</action>
+ </release>
+ <release version="3.11" date="2020-07-12" description="New features and bug fixes (Java 8).">
+ <action type="update" dev="chtompki" due-to="Jin Xu">Refine test output for FastDateParserTest</action>
+ <action issue="LANG-1549" type="update" dev="chtompki" due-to="Jin Xu">CharSequenceUtils.lastIndexOf : remake it</action>
+ <action type="update" dev="kinow" due-to="XenoAmess">remove encoding and docEncoding and use inherited values from commons-parent</action>
+ <action type="fix" dev="kinow" due-to="contextshuffling">Fix Javadoc for StringUtils.appendIfMissingIgnoreCase() #507.</action>
+ <action type="update" dev="ggregory" due-to="Isira Seneviratne, Bruno P. Kinoshita">Simplify null checks in Pair.hashCode() using Objects.hashCode(). #517.</action>
+ <action type="update" dev="ggregory" due-to="Isira Seneviratne, Bruno P. Kinoshita">Simplify null checks in Triple.hashCode() using Objects.hashCode(). #516.</action>
+ <action type="update" dev="ggregory" due-to="Isira Seneviratne, Bruno P. Kinoshita">Simplify some if statements in StringUtils. #521.</action>
+ <action issue="LANG-1537" type="update" dev="ggregory" due-to="Isira Seneviratne, Bruno P. Kinoshita">Simplify a null check in the private replaceEach() method of StringUtils. #514.</action>
+ <action issue="LANG-1534" type="update" dev="ggregory" due-to="Isira Seneviratne, Bruno P. Kinoshita">Replace some usages of the ternary operator with calls to Math.max() and Math.min() #512.</action>
+ <action type="update" dev="ggregory" due-to="Arend v. Reinersdorff, Bruno P. Kinoshita">(Javadoc) Fix return tag for throwableOf*() methods #518.</action>
+ <action type="add" dev="ggregory" due-to="XenoAmess, Gary Gregory">Add ArrayUtils.isSameLength() to compare more array types #430.</action>
+ <action issue="LANG-1545" type="update" dev="ggregory" due-to="XenoAmess, Gary Gregory">CharSequenceUtils.regionMatches is wrong dealing with Georgian.</action>
+ <action type="add" dev="jochen">Added the Locks class as a convenient possibility to deal with locked objects.</action>
+ <action issue="LANG-1568" type="add" dev="ggregory">Add to Functions: FailableBooleanSupplier, FailableIntSupplier, FailableLongSupplier, FailableDoubleSupplier, and so on.</action>
+ <action issue="LANG-1569" type="add" dev="ggregory">Add ArrayUtils.get(T[], index, T) to provide an out-of-bounds default value.</action>
+ <action issue="LANG-1550" type="update" dev="ggregory" due-to="Edgar Asatryan">Optimize ArrayUtils::isArrayIndexValid method. #551.</action>
+ <action issue="LANG-1561" type="update" dev="ggregory" due-to="XenoAmess">Use List.sort instead of Collection.sort #546.</action>
+ <action issue="LANG-1563" type="update" dev="ggregory" due-to="XenoAmess">Use StandardCharsets.UTF_8 #548.</action>
+ <action issue="LANG-1564" type="update" dev="ggregory" due-to="XenoAmess">Use Collections.singletonList insteadof Arrays.asList when there be only one element. #549.</action>
+ <action issue="LANG-1560" type="fix" dev="ggregory" due-to="XenoAmess">Refine Javadoc #545.</action>
+ <action issue="LANG-1553" type="update" dev="ggregory" due-to="XenoAmess">Change array style from `int a[]` to `int[] a` #537.</action>
+ <action issue="LANG-1552" type="update" dev="ggregory" due-to="XenoAmess">Change from addAll to constructors for some List #536.</action>
+ <action issue="LANG-1554" type="fix" dev="ggregory" due-to="XenoAmess">Fix typos #539.</action>
+ <action issue="LANG-1555" type="fix" dev="ggregory" due-to="XenoAmess">Ignored exception `ignored`, should not be called so #540.</action>
+ <action issue="LANG-1558" type="update" dev="ggregory" due-to="XenoAmess">Simplify if as some conditions are covered by others #543.</action>
+ <action issue="LANG-1528" type="fix" dev="ggregory" due-to="Edwin Delgado H">StringUtils.replaceEachRepeatedly gives IllegalStateException #505.</action>
+ <action issue="LANG-1570" type="add" dev="ggregory" due-to="Edgar Asatryan">Add JavaVersion enum constants for Java 14 and 15. #553.</action>
+ <action type="add" dev="ggregory" due-to="Gary Gregory">Add JavaVersion enum constants for Java 16.</action>
+ <action issue="LANG-1556" type="add" dev="ggregory" due-to="XenoAmess">Use Java 8 lambdas and Map operations.</action>
+ <action issue="LANG-1565" type="add" dev="ggregory" due-to="XenoAmess">Change removeLastFieldSeparator to use endsWith #550.</action>
+ <action issue="LANG-1557" type="add" dev="ggregory" due-to="XenoAmess, Gary Gregory">Change a Pattern to a static final field, for not letting it compile each time the function invoked. #542.</action>
+ <action type="add" dev="ggregory">Add ImmutablePair factory methods left() and right().</action>
+ <action type="add" dev="ggregory">Add ObjectUtils.toString(Object, Supplier&lt;String&gt;).</action>
+ <action issue="LANG-1567" type="update" dev="ggregory" due-to="Miguel Muñoz, Bruno P. Kinoshita, Gary Gregory">Fixed Javadocs for setTestRecursive() #556.</action>
+ <action issue="LANG-1542" type="update" dev="ggregory" due-to=" Trần Ngọc Khoa, Gary Gregory">ToStringBuilder.reflectionToString - Wrong JSON format when object has a List of Enum.</action>
+ <action issue="LANG-1543" type="fix" dev="ggregory" due-to="Swaraj Pal, Wander Costa, Gary Gregory">[JSON string for maps] ToStringBuilder.reflectionToString doesn't render nested maps correctly.</action>
+ <action type="update" dev="ggregory">Make org.apache.commons.lang3.CharSequenceUtils.toCharArray(CharSequence) public.</action>
+ <action type="add" dev="ggregory">Add org.apache.commons.lang3.StringUtils.substringAfter(String, int).</action>
+ <action type="add" dev="ggregory">Add org.apache.commons.lang3.StringUtils.substringAfterLast(String, int).</action>
+ <action type="fix" dev="ggregory" due-to="Isira Seneviratne">Correct Javadocs of methods that use Validate.notNull() and replace some uses of Validate.isTrue() with Validate.notNull(). #525.</action>
+ <action issue="LANG-1539" type="fix" dev="ggregory" due-to="Isira Seneviratne">Add allNull() and anyNull() methods to ObjectUtils. #522.</action>
+ <action type="update" dev="ggregory">org.apache.commons:commons-parent 50 -> 51.</action>
+ <action type="update" dev="ggregory">org.junit-pioneer:junit-pioneer 0.5.4 -> 0.6.0.</action>
+ <action type="update" dev="ggregory">org.junit.jupiter:junit-jupiter 5.6.0 -> 5.6.2.</action>
+ <action type="update" dev="ggregory">com.github.spotbugs:spotbugs 4.0.0 -> 4.0.6.</action>
+ <action type="update" dev="ggregory">com.puppycrawl.tools:checkstyle 8.29 -> 8.34.</action>
+ <action type="update" dev="ggregory">commons.surefire.version 3.0.0-M4 -> 3.0.0-M5..</action>
+ </release>
+
+ <release version="3.10" date="2020-03-22" description="New features and bug fixes. Requires Java 8, supports Java 9, 10, 11.">
+ <action issue="LANG-1514" type="fix" dev="kinow" due-to="contextshuffling">Make test more stable by wrapping assertions in hashset.</action>
+ <action issue="LANG-1450" type="fix" dev="chtompki">Generate Javadoc jar on build.</action>
+ <action issue="LANG-1457" type="add" dev="ggregory">Add ExceptionUtils.throwableOfType(Throwable, Class) and friends.</action>
+ <action issue="LANG-1458" type="add" dev="ggregory">Add EMPTY_ARRAY constants to classes in org.apache.commons.lang3.tuple.</action>
+ <action issue="LANG-1461" type="add" dev="ggregory">Add null-safe StringUtils APIs to wrap String#getBytes([Charset|String]).</action>
+ <action issue="LANG-1467" type="add" dev="ggregory">Add zero arg constructor for org.apache.commons.lang3.NotImplementedException.</action>
+ <action issue="LANG-1470" type="add" dev="ggregory">Add ArrayUtils.addFirst() methods.</action>
+ <action issue="LANG-1437" type="update" dev="ggregory" due-to="Andrei Troie">Remove redundant if statements in join methods #411.</action>
+ <action issue="LANG-1460" type="fix" dev="kinow" due-to="Larry West">Trivial: year of release for 3.9 says 2018, should be 2019</action>
+ <action issue="LANG-1476" type="fix" dev="kinow" due-to="emopers">Use synchronize on a set created with Collections.synchronizedSet before iterating</action>
+ <action issue="LANG-1479" type="add" dev="ggregory">Add Range.fit(T) to fit a value into a range.</action>
+ <action type="update" dev="ggregory">commons.japicmp.version 0.13.1 -> 0.14.1.</action>
+ <action type="update" dev="ggregory">junit-jupiter 5.5.0 -> 5.5.1.</action>
+ <action issue="LANG-1477" type="add" dev="jochen">Added Functions.as*, and tests thereof, as suggested by Peter Verhas</action>
+ <action issue="LANG-1475" type="fix" dev="kinow" due-to="stzx">StringUtils.unwrap incorrect throw StringIndexOutOfBoundsException.</action>
+ <action issue="LANG-1485" type="add" dev="ggregory" due-to="nicolasbd">Add getters for lhs and rhs objects in DiffResult #451.</action>
+ <action issue="LANG-1486" type="add" dev="ggregory" due-to="Gary Gregory">Generify builder classes Diffable, DiffBuilder, and DiffResult #452.</action>
+ <action issue="LANG-1487" type="add" dev="ggregory" due-to="Gary Gregory">Add ClassLoaderUtils with toString() implementations #453.</action>
+ <action issue="LANG-1489" type="add" dev="ggregory" due-to="Gary Gregory">Add null-safe APIs as StringUtils.toRootLowerCase(String) and StringUtils.toRootUpperCase(String) #456.</action>
+ <action issue="LANG-1406" type="fix" dev="ggregory" due-to="geratorres">StringIndexOutOfBoundsException in StringUtils.replaceIgnoreCase #423.</action>
+ <action issue="LANG-1453" type="fix" dev="ggregory" due-to="geratorres">StringUtils.removeIgnoreCase("İa", "a") throws IndexOutOfBoundsException #423.</action>
+ <action type="update" dev="ggregory">junit-jupiter 5.5.1 -> 5.5.2.</action>
+ <action issue="LANG-1426" type="fix" dev="ggregory" due-to="Brower, Mikko Maunu, Suraj Gautam">Corrected usage examples in Javadocs #458.</action>
+ <action type="update" dev="ggregory" due-to="Jonathan Leitschuh, Bruno P. Kinoshita, Rob Tompkins, Gary Gregory">Improve Javadoc based on the discussion of the GitHub PR #459.</action>
+ <action type="update" dev="ggregory">maven-checkstyle-plugin 3.0.0 -> 3.1.0.</action>
+ <action issue="LANG-696" type="update" dev="ggregory" due-to="Peter Verhas">Update documentation related to the issue LANG-696 #449.</action>
+ <action type="update" dev="ggregory" due-to="Peter Verhas">AnnotationUtils little cleanup #467.</action>
+ <action issue="LANG-1494" type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.time.Calendars.</action>
+ <action issue="LANG-1495" type="add" dev="ggregory" due-to="Cheong Voon Leong">Add EnumUtils getEnum() methods with default values #475.</action>
+ <action issue="LANG-1177" type="add" dev="ggregory" due-to="Liel Fridman">Added indexesOf methods and simplified removeAllOccurences #471.</action>
+ <action issue="LANG-1498" type="add" dev="ggregory" due-to="Lysergid, Gary Gregory">Add support of lambda value evaluation for defaulting methods #416.</action>
+ <action issue="LANG-1463" type="fix" dev="ggregory" due-to="bbeckercscc, Gary Gregory">StringUtils abbreviate returns String of length greater than maxWidth #477.</action>
+ <action issue="LANG-1500" type="fix" dev="ggregory" due-to="contextshuffling">Test may fail due to a different order of fields returned by reflection api #480.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Update test dependency: org.easymock:easymock 4.0.2 -> 4.1.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Update test dependency: org.hamcrest:hamcrest 2.1 -> 2.2.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Update test dependency: org.junit-pioneer:junit-pioneer 0.3.0 -> 0.4.2.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Update build dependency: com.puppycrawl.tools:checkstyle 8.18 -> 8.27.</action>
+ <action issue="LANG-1501" type="fix" dev="ggregory" due-to="contextshuffling">Sort fields in ReflectionToStringBuilder for deterministic order #481.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">Update POM parent: org.apache.commons:commons-parent 48 -> 50.</action>
+ <action type="update" dev="ggregory" due-to="Peter Verhas">BooleanUtils Javadoc #469.</action>
+ <action type="update" dev="ggregory" due-to="Peter Verhas">Functions Javadoc #466.</action>
+ <action issue="LANG-1503" type="add" dev="ggregory" due-to="XenoAmess, Gary Gregory">Add factory methods to Pair classes with Map.Entry input. #454.</action>
+ <action issue="LANG-1505" type="add" dev="ggregory" due-to="Gary Gregory">Add StopWatch convenience APIs to format times and create a simple instance.</action>
+ <action issue="LANG-1506" type="add" dev="ggregory" due-to="Gary Gregory">Allow a StopWatch to carry an optional message.</action>
+ <action issue="LANG-1507" type="add" dev="ggregory" due-to="Sam Kruglov, Mark Dacek, Marc Magon, Pascal Schumacher, Rob Tompkins, Bruno P. Kinoshita, Amey Jadiye, Gary Gregory">Add ComparableUtils #398.</action>
+ <action issue="LANG-1508" type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.SystemUtils.getUserName().</action>
+ <action issue="LANG-1509" type="add" dev="ggregory" due-to="Gary Gregory">Add ObjectToStringComparator. #483.</action>
+ <action issue="LANG-1510" type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.arch.Processor.Arch.getLabel().</action>
+ <action issue="LANG-1512" type="add" dev="ggregory" due-to="Gary Gregory">Add IS_JAVA_14 and IS_JAVA_15 to org.apache.commons.lang3.SystemUtils.</action>
+ <action issue="LANG-1513" type="add" dev="ggregory" due-to="Bernhard Bonigl, Gary Gregory">ObjectUtils: Get first non-null supplier value.</action>
+ <action type="add" dev="jochen">Added the Streams class, and Functions.stream() as an accessor thereof.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">org.easymock:easymock 4.1 -> 4.2.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">org.junit-pioneer:junit-pioneer 0.4.2 -> 0.5.4.</action>
+ <action type="update" dev="ggregory" due-to="Gary Gregory">org.junit.jupiter:junit-jupiter 5.5.2 -> 5.6.0.</action>
+ <action type="update" dev="ggregory" due-to="Peter Verhas">Use Javadoc {@code} instead of pre tags. #490.</action>
+ <action type="update" dev="ggregory" due-to="Peter Verhas">ExceptionUtilsTest to 100% #486.</action>
+ <action issue="LANG-1433" type="fix" dev="ggregory" due-to="Christian Franzen">MethodUtils will throw a NPE if invokeMethod() is called for a var-args method #407.</action>
+ <action type="update" dev="ggregory" due-to="Peter Verhas">Reuse own code in Functions.java #493.</action>
+ <action issue="LANG-1518" type="fix" dev="ggregory" due-to="Michele Preti, Bruno P. Kinoshita, Gary Gregory">MethodUtils.getAnnotation() with searchSupers = true does not work if super is generic #494.</action>
+ <action issue="LANG-1523" type="update" dev="ggregory" due-to="Edgar Asatryan, Bruno P. Kinoshita, Gary Gregory">Avoid unnecessary allocation in StringUtils.wrapIfMissing. #496.</action>
+ <action issue="LANG-1525" type="update" dev="ggregory" due-to="Edgar Asatryan, Bruno P. Kinoshita, Gary Gregory">Internally use Validate.notNull(foo, ...) instead of Validate.isTrue(foo != null, ...).</action>
+ <action issue="LANG-1526" type="update" dev="ggregory" due-to="Dominik Schramm">Add 1 and 0 in toBooleanObject(final String str) #502.</action>
+ <action issue="LANG-1527" type="update" dev="ggregory" due-to="Pengyu Nie">Remove a redundant argument check in NumberUtils #504.</action>
+ <action issue="LANG-1529" type="update" dev="ggregory" due-to="Gary Gregory, BillCindy, Bruno P. Kinoshita">Deprecate org.apache.commons.lang3.ArrayUtils.removeAllOccurences(*) for org.apache.commons.lang3.ArrayUtils.removeAllOccurrences(*).</action>
+ </release>
+
+ <release version="3.9" date="2019-04-09" description="New features and bug fixes. Requires Java 8, supports Java 9, 10, 11.">
+ <action issue="LANG-1447" type="update" dev="chtompki">FieldUtils.removeFinalModifier(Field, boolean), in java 12
+ throw exception because the final modifier is no longer mutable.</action>
+ <action issue="LANG-1446" type="add" dev="chtompki">Switch coverage from cobertura to jacoco.</action>
+ <action issue="LANG-1442" type="add" dev="chtompki">Javadoc pointing to Commons RNG.</action>
+ <action issue="LANG-1416" type="update" dev="britter">Add more SystemUtils.IS_JAVA_XX variants.</action>
+ <action type="add" dev="jochen">Adding the Functions class.</action>
+ <action issue="LANG-1416" type="update" dev="britter">Update to JUnit 5</action>
+ <action issue="LANG-1417" type="update" dev="britter">Add @FunctionalInterface to ThreadPredicate and ThreadGroupPredicate</action>
+ <action issue="LANG-1415" type="update" dev="britter">Update Java Language requirement to 1.8</action>
+ <action issue="LANG-1411" type="add" dev="britter" due-to="Alexander Tsvetkov">Add isEmpty method to ObjectUtils</action>
+ <action issue="LANG-1422" type="add" dev="ggregory">Add null-safe StringUtils.valueOf(char[]) to delegate to String.valueOf(char[])</action>
+ <action issue="LANG-1427" type="add" dev="ggregory">Add API org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost(JavaVersion)</action>
+ <action issue="LANG-1436" type="update" dev="aherbert">Consolidate the StringUtils equals and equalsIgnoreCase Javadoc and implementation</action>
+ <action type="update" dev="ggregory" due-to="Andrei Troie aft90">(doc) Fix javadoc for 'startIndex' parameter of StringUtils.join() methods. GitHub PR #412.</action>
+ </release>
+
+ <release version="3.8.1" date="2018-09-19" description="This release is a bugfix for Restoring Bundle-SymbolicName in the MANIFEST.mf file.">
+ <action issue="LANG-1419" type="fix" dev="chtompki">Restore BundleSymbolicName for OSGi</action>
+ </release>
+
+ <release version="3.8" date="2018-08-15" description="New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.">
+ <action issue="LANG-1380" type="fix" dev="chas" due-to="Markus Jelsma">FastDateParser too strict on abbreviated short month symbols</action>
+ <action issue="LANG-1396" type="fix" dev="sebb">JsonToStringStyle does not escape string names</action>
+ <action issue="LANG-1395" type="fix" dev="sebb" due-to="Jim Gan">JsonToStringStyle does not escape double quote in a string value</action>
+ <action issue="LANG-1384" type="fix" dev="erans" due-to="Ian Young">New Java version ("11") must be handled</action>
+ <action issue="LANG-1364" type="fix" dev="pschumacher" due-to="Zheng Xie">ExceptionUtils#getRootCause(Throwable t) should return t if no lower level cause exists</action>
+ <action issue="LANG-1060" type="fix" dev="pschumacher" due-to="Piotr Kosmala">NumberUtils.isNumber assumes number starting with Zero</action>
+ <action issue="LANG-1375" type="fix" dev="kinow" due-to="Jerry Zhao">defaultString(final String str) in StringUtils to reuse defaultString(final String str, final String defaultStr)</action>
+ <action issue="LANG-1374" type="fix" dev="kinow" due-to="Jaswanth Bala">Parsing Json Array failed</action>
+ <action issue="LANG-1371" type="fix" dev="pschumacher" due-to="Dmitry Ovchinnikov">Fix TypeUtils#parameterize to work correctly with narrower-typed array</action>
+ <action issue="LANG-1370" type="fix" dev="kinow" due-to="Andre Dieb">Fix EventCountCircuitBreaker increment batch</action>
+ <action issue="LANG-1385" type="fix" dev="ggregory" due-to="Rohan Padhye">NumberUtils.createNumber() throws StringIndexOutOfBoundsException instead of NumberFormatException</action>
+ <action issue="LANG-1397" type="fix" dev="ggregory" due-to="Takanobu Asanuma">WordUtils.wrap throws StringIndexOutOfBoundsException when wrapLength is Integer.MAX_VALUE.</action>
+ <action issue="LANG-1401" type="fix" dev="pschumacher" due-to="Roman Golyshev, Alex Mamedov">Typo in JavaDoc for lastIndexOf</action>
+ <action issue="LANG-1367" type="update" dev="ggregory" due-to="Gary Gregory">ObjectUtils.identityToString(Object) and friends should allocate builders and buffers with a size</action>
+ <action issue="LANG-1352" type="add" dev="pschumacher" due-to="Ruslan Sibgatullin">EnumUtils.getEnumIgnoreCase and isValidEnumIgnoreCase methods added</action>
+ <action issue="LANG-1372" type="add" dev="pschumacher" due-to="Sérgio Ozaki">Add ToStringSummary annotation</action>
+ <action issue="LANG-1356" type="add" dev="pschumacher" due-to="Yathos UG">Add bypass option for classes to recursive and reflective EqualsBuilder</action>
+ <action issue="LANG-1391" type="add" dev="ggregory" due-to="Sauro Matulli, Oleg Chubaryov">Improve Javadoc for StringUtils.isAnyEmpty(null)</action>
+ <action issue="LANG-1393" type="add" dev="ggregory" due-to="Gary Gregory">Add API SystemUtils.String getEnvironmentVariable(final String name, final String defaultValue)</action>
+ <action issue="LANG-1394" type="add" dev="ggregory" due-to="Sebb, Gary Gregory">org.apache.commons.lang3.SystemUtils should not write to System.err.</action>
+ <action issue="LANG-1238" type="add" dev="ggregory" due-to="Christopher Cordeiro, Gary Gregory, Bruno P. Kinoshita, Oleg Chubaryov">Add RegexUtils class instead of overloading methods in StringUtils that take a regex to take precompiled Pattern.</action>
+ <action issue="LANG-1390" type="add" dev="ggregory" due-to="Jochen Schalanda">StringUtils.join() with support for List&lt;?> with configurable start/end indices.</action>
+ <action issue="LANG-1392" type="add" dev="pschumacher" due-to="Jeff Nelson">Methods for getting first non-empty or non-blank value</action>
+ <action issue="LANG-1405" type="update" dev="ggregory" due-to="Lars Grefer">Remove checks for java versions below the minimum supported one</action>
+ <action issue="LANG-1402" type="update" dev="chtompki" due-to="Mark Dacek">Null/index safe get methods for ArrayUtils</action>
+ <action issue="LANG-1408" type="add" dev="chtompki">Rounding utilities for converting to BigDecimal</action>
+ </release>
+
+ <release version="3.7" date="2017-11-04" description="New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.">
+ <action issue="LANG-1362" type="fix" dev="ggregory" due-to="Stephen Colebourne">Fix tests DateUtilsTest for Java 9 with en_GB locale</action>
+ <action issue="LANG-1365" type="fix" dev="ggregory" due-to="Gary Gregory">Fix NullPointerException in isJavaVersionAtLeast on Java 10, add SystemUtils.IS_JAVA_10, add JavaVersion.JAVA_10</action>
+ <action issue="LANG-1348" type="fix" dev="pschumacher" due-to="mbusso">StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf</action>
+ <action issue="LANG-1350" type="fix" dev="ggregory" due-to="Brett Kail">ConstructorUtils.invokeConstructor(Class, Object...) regression</action>
+ <action issue="LANG-1349" type="fix" dev="pschumacher" due-to="Naman Nigam">EqualsBuilder#isRegistered: swappedPair construction bug</action>
+ <action issue="LANG-1357" type="fix" dev="ggregory" due-to="BruceKuiLiu">org.apache.commons.lang3.time.FastDateParser should use toUpperCase(Locale)</action>
+ <action issue="LANG-1358" type="update" dev="pschumacher" due-to="Stephane Landelle">Improve StringUtils#replace throughput</action>
+ <action issue="LANG-1346" type="update" dev="pschumacher">Remove deprecation from RandomStringUtils</action>
+ <action issue="LANG-1361" type="update" dev="ggregory" due-to="Ana">ExceptionUtils.getThrowableList() is using deprecated ExceptionUtils.getCause()</action>
+ <action issue="LANG-1355" type="add" dev="ggregory" due-to="Chas Honton">TimeZone.getTimeZone() in FastDateParser causes resource contention (PR #296.)</action>
+ <action issue="LANG-1360" type="add" dev="ggregory" due-to="Gary Gregory">Add methods to ObjectUtils to get various forms of class names in a null-safe manner</action>
+ </release>
+
+ <release version="3.6" date="2017-06-08" description="New features and bug fixes. Requires Java 7.">
+ <action issue="LANG-1338" type="update" dev="britter">Add Automatic-Module-Name MANIFEST entry for Java 9 compatibility</action>
+ <action issue="LANG-1336" type="add" dev="britter" due-to="Beluga Behr">Add NUL Byte To CharUtils</action>
+ <action issue="LANG-1337" type="fix" dev="kinow">Fix test failures in IBM JDK 8 for ToStringBuilderTest</action>
+ <action issue="LANG-1304" type="add" dev="pschumacher" due-to="Andy Klimczak">Add method in StringUtils to determine if string contains both mixed cased characters</action>
+ <action issue="LANG-1334" type="update" dev="djones">Deprecate CharEncoding in favour of java.nio.charset.StandardCharsets</action>
+ <action issue="LANG-1319" type="fix" dev="djones">MultilineRecursiveToStringStyle StackOverflowError when object is an array</action>
+ <action issue="LANG-1325" type="add" dev="kinow" due-to="Arshad Basha">Increase test coverage of ToStringBuilder class to 100%</action>
+ <action issue="LANG-1307" type="add" dev="pschumacher" due-to="Arshad Basha">Add a method in StringUtils to extract only digits out of input string</action>
+ <action issue="LANG-1110" type="update" dev="pschumacher" due-to="Bruno P. Kinoshita">Implement HashSetvBitSetTest using JMH</action>
+ <action issue="LANG-1256" type="add" dev="pschumacher" due-to="C0rWin">Add JMH maven dependencies</action>
+ <action issue="LANG-1167" type="add" dev="chtompki" due-to="Mark Dacek">Add null filter to ReflectionToStringBuilder</action>
+ <action issue="LANG-1320" type="fix" dev="britter">LocaleUtils#toLocale does not support language followed by UN M.49 numeric-3 area code followed by variant</action>
+ <action issue="LANG-1300" type="fix" dev="chtompki" due-to="Mark Dacek">Clarify or improve behavior of int-based indexOf methods in StringUtils</action>
+ <action issue="LANG-1299" type="add" dev="djones">Add method for converting string to an array of code points</action>
+ <action issue="LANG-1286" type="fix" dev="djones">RandomStringUtils random method can overflow and return characters outside the specified range</action>
+ <action issue="LANG-660" type="add" dev="djones">Add methods to insert arrays into arrays at an index</action>
+ <action issue="LANG-1292" type="fix" dev="djones">WordUtils.wrap throws StringIndexOutOfBoundsException</action>
+ <action issue="LANG-1287" type="fix" dev="pschumacher" due-to="Ivan Morozov">RandomStringUtils#random can enter infinite loop if end parameter is to small</action>
+ <action issue="LANG-1285" type="fix" dev="pschumacher" due-to="Francesco Chicchiriccò">NullPointerException in FastDateParser$TimeZoneStrategy</action>
+ <action issue="LANG-1281" type="fix" dev="pschumacher" due-to="Andreas Lundblad">Javadoc of StringUtils.ordinalIndexOf is contradictory.</action>
+ <action issue="LANG-1188" type="fix" dev="pschumacher">StringUtils#join(T...): warning: [unchecked] Possible heap pollution from parameterized vararg type T</action>
+ <action issue="LANG-1144" type="fix" dev="ggregory" due-to="Waldemar Maier, Gary Gregory">Multiple calls of org.apache.commons.lang3.concurrent.LazyInitializer.initialize() are possible.</action>
+ <action issue="LANG-1276" type="fix" dev="pschumacher" due-to="Andy Klimczak">StrBuilder#replaceAll ArrayIndexOutOfBoundsException</action>
+ <action issue="LANG-1278" type="fix" dev="pschumacher" due-to="Duke Yin">BooleanUtils javadoc issues</action>
+ <action issue="LANG-1070" type="fix" dev="pschumacher" due-to="Paul Pogonyshev">ArrayUtils#add confusing example in javadoc</action>
+ <action issue="LANG-1271" type="fix" dev="pschumacher" due-to="Pierre Templier">StringUtils#isAnyEmpty and #isAnyBlank should return false for an empty array</action>
+ <action issue="LANG-1155" type="fix" dev="pschumacher" due-to="Saif Asif, Thiago Andrade">Add StringUtils#unwrap</action>
+ <action issue="LANG-1034" type="add" dev="pschumacher" due-to="Yathos UG">Add support for recursive comparison to EqualsBuilder#reflectionEquals</action>
+ <action issue="LANG-1067" type="add" dev="pschumacher">Add a reflection-based variant of DiffBuilder</action>
+ <action issue="LANG-740" type="add" dev="pschumacher" due-to="James Sawle">Implementation of a Memoizer</action>
+ <action issue="LANG-1258" type="add" dev="pschumacher" due-to="IG, Grzegorz Rożniecki">Add ArrayUtils#toStringArray method</action>
+ <action issue="LANG-1160" type="add" dev="kinow">StringUtils#abbreviate should support 'custom ellipses' parameter</action>
+ <action issue="LANG-1293" type="add" dev="pschumacher" due-to="Pierre Templier, Martin Tarjanyi">Add StringUtils#isAllEmpty and #isAllBlank methods</action>
+ <action issue="LANG-1290" type="update" dev="pschumacher" due-to="Andrii Abramov">Increase test coverage of org.apache.commons.lang3.ArrayUtils</action>
+ <action issue="LANG-1274" type="update" dev="pschumacher">StrSubstitutor should state its thread safety</action>
+ <action issue="LANG-1277" type="update" dev="pschumacher" due-to="yufcuy">StringUtils#getLevenshteinDistance reduce memory consumption</action>
+ <action issue="LANG-1279" type="update" dev="ggregory">Update Java requirement from Java 6 to 7.</action>
+ <action issue="LANG-1143" type="update" dev="pschumacher" due-to="sebb">StringUtils should use toXxxxCase(int) rather than toXxxxCase(char)</action>
+ <action issue="LANG-1297" type="update" dev="ggregory">Add SystemUtils.getHostName() API.</action>
+ <action issue="LANG-1301" type="update" dev="pschumacher" due-to="Karl Heinz Marbaise">Moving apache-rat-plugin configuration into pluginManagement</action>
+ <action issue="LANG-1311" type="fix" dev="pschumacher" due-to="Aaron Digulla">TypeUtils.toString() doesn't handle primitive and Object arrays correctly</action>
+ <action issue="LANG-1312" type="fix" dev="pschumacher">LocaleUtils#toLocale does not support language followed by UN M.49 numeric-3 area code</action>
+ <action issue="LANG-1265" type="fix" dev="pschumacher">Build failures when building with Java 9 EA</action>
+ <action issue="LANG-1314" type="fix" dev="pschumacher" due-to="Allon Murienik">javadoc creation broken with Java 8</action>
+ <action issue="LANG-1316" type="update" dev="pschumacher">Deprecate classes/methods moved to commons-text</action>
+ <action issue="LANG-1310" type="fix" dev="pschumacher" due-to="Don Jeba">MethodUtils.invokeMethod throws ArrayStoreException if using varargs arguments and smaller types than the method defines</action>
+ <action issue="LANG-1313" type="add" dev="pschumacher" due-to="Tomschi">Add ArchUtils - An utility class for the "os.arch" system property</action>
+ <action issue="LANG-1272" type="add" dev="ebourg">Add shuffle methods to ArrayUtils</action>
+ <action issue="LANG-1317" type="add" dev="pschumacher" due-to="Yasser Zamani">Add MethodUtils#findAnnotation and extend MethodUtils#getMethodsWithAnnotation for non-public, super-class and interface methods</action>
+ <action issue="LANG-1331" type="add" dev="ggregory">Add ImmutablePair.nullPair()</action>
+ <action issue="LANG-1332" type="add" dev="ggregory">Add ImmutableTriple.nullTriple()</action>
+ </release>
+
+ <release version="3.5" date="2016-10-13" description="New features including Java 9 detection">
+ <action issue="LANG-1275" type="add" dev="oheger">Added a tryAcquire() method to TimedSemaphore.</action>
+ <action issue="LANG-1273" type="add" dev="ebourg" due-to="Jake Wang">Added a new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils</action>
+ <action issue="LANG-1255" type="add" dev="britter" due-to="Kaiyuan Wang">Add DateUtils.toCalendar(Date, TimeZone)</action>
+ <action issue="LANG-1023" type="add" dev="britter" due-to="Marko Bekhta">Add WordUtils.wrap overload with customizable breakable character</action>
+ <action issue="LANG-787" type="add" dev="pschumacher" due-to="Gokul Nanthakumar C">Add method removeIgnoreCase(String, String) to StringUtils</action>
+ <action issue="LANG-1261" type="fix" dev="pschumacher" >ArrayUtils.contains returns false for instances of subtypes</action>
+ <action issue="LANG-1197" type="update" dev="pschumacher" >Prepare Java 9 detection</action>
+ <action issue="LANG-1252" type="fix" dev="chtompki" due-to="Rob Tompkins">Rename NumberUtils.isNumber, isCreatable to better reflect createNumber. Also, accommodated for "+" symbol as prefix in isCreatable and isNumber.</action>
+ <action issue="LANG-1262" type="update" dev="pschumacher" due-to="Ruslan Cheremin">CompareToBuilder.append(Object, Object, Comparator) method is too big to be inlined</action>
+ <action issue="LANG-1230" type="fix" dev="pschumacher" due-to="Philippe Marschall">Remove unnecessary synchronization from registry lookup in EqualsBuilder and HashCodeBuilder</action>
+ <action issue="LANG-1224" type="add" dev="pschumacher" due-to="Caleb Cushing">Extend RandomStringUtils with methods that generate strings between a min and max length</action>
+ <action issue="LANG-1214" type="fix" dev="pschumacher" due-to="Henry Tung">Handle "void" in ClassUtils.getClass()</action>
+ <action issue="LANG-1250" type="fix" dev="pschumacher" due-to="Glease Wang">SerializationUtils#deserialize has unnecessary code and a comment for that</action>
+ <action issue="LANG-1259" type="update" dev="britter" due-to="Dominik Stadler">Javadoc for ArrayUtils.isNotEmpty() is slightly misleading</action>
+ <action issue="LANG-1257" type="add" dev="ggregory" due-to="Gary Gregory">Add APIs StringUtils.wrapIfMissing(String, char|String)</action>
+ <action issue="LANG-1190" type="fix" dev="pschumacher" due-to="pschumacher">TypeUtils.isAssignable throws NullPointerException when fromType has type variables and toType generic superclass specifies type variable</action>
+ <action issue="LANG-1226" type="fix" dev="pschumacher" due-to="pschumacher">StringUtils#normalizeSpace does not trim the string anymore</action>
+ <action issue="LANG-1251" type="fix" dev="pschumacher" due-to="Takuya Ueshin">SerializationUtils.ClassLoaderAwareObjectInputStream should use static initializer to initialize primitiveTypes map</action>
+ <action issue="LANG-1253" type="add" dev="ggregory" due-to="adilek">[GitHub issue #170] Add RandomUtils#nextBoolean() method</action>
+ <action issue="LANG-1248" type="fix" dev="chas" due-to="Benoit Wiart">FastDatePrinter Memory allocation regression</action>
+ <action issue="LANG-1247" type="update" dev="chas" due-to="Benoit Wiart">FastDatePrinter generates extra Date objects</action>
+ <action issue="LANG-1018" type="fix" dev="pschumacher" due-to="Nick Manley">Fix precision loss on NumberUtils.createNumber(String)</action>
+ <action issue="LANG-1229" type="update" dev="pschumacher" due-to="Ruslan Cheremin">HashCodeBuilder.append(Object,Object) is too big to be inlined, which prevents whole builder to be scalarized</action>
+ <action issue="LANG-1085" type="add" dev="oheger" due-to="oheger / kinow">Add a circuit breaker implementation</action>
+ <action issue="LANG-1013" type="add" dev="pschumacher" due-to="Thiago Andrade">Add StringUtils.truncate()</action>
+ <action issue="LANG-1195" type="add" dev="pschumacher" due-to="Derek C. Ashmore">Enhance MethodUtils to allow invocation of private methods</action>
+ <action issue="LANG-1199" type="fix" dev="pschumacher" due-to="M. Steiger">Fix implementation of StringUtils.getJaroWinklerDistance()</action>
+ <action issue="LANG-1244" type="fix" dev="pschumacher" due-to="jjbankert">Fix dead links in StringUtils.getLevenshteinDistance() javadoc</action>
+ <action issue="LANG-1242" type="fix" dev="pschumacher" due-to="Neal Stewart">"\u2284":"nsub" mapping missing from EntityArrays#HTML40_EXTENDED_ESCAPE</action>
+ <action issue="LANG-1243" type="update" dev="sebb">Simplify ArrayUtils removeElements by using new decrementAndGet() method</action>
+ <action issue="LANG-1189" type="add" dev="sebb" due-to="haiyang li / Matthew Bartenschlag ">Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/decrementAndGet/addAndGet in Mutable* classes</action>
+ <action issue="LANG-1240" type="update" dev="pschumacher" due-to="zhanhb">Optimize BitField constructor implementation</action>
+ <action issue="LANG-1206" type="update" dev="pschumacher" due-to="Mohammed Alfallaj">Improve CharSetUtils.squeeze() performance</action>
+ <action issue="LANG-1225" type="add" dev="pschumacher" due-to="Caleb Cushing">Add RandomStringUtils#randomGraph and #randomPrint which match corresponding regular expression class</action>
+ <action issue="LANG-901" type="fix" dev="pschumacher" due-to="Matthew Bartenschlag">StringUtils#startsWithAny/endsWithAny is case-sensitive - documented as case insensitive</action>
+ <action issue="LANG-1223" type="add" dev="pschumacher" due-to="Nick Manley">Add StopWatch#getTime(TimeUnit)</action>
+ <action issue="LANG-781" type="add" dev="pschumacher" due-to="Krzysztof Wolny">Add methods to ObjectUtils class to check for null elements in the array</action>
+ <action issue="LANG-1228" type="add" dev="pschumacher" due-to="Brad Hess">Prefer Throwable.getCause() in ExceptionUtils.getCause()</action>
+ <action issue="LANG-1233" type="add" dev="pschumacher" due-to="Nick Manley">DiffBuilder add method to allow appending from a DiffResult</action>
+ <action issue="LANG-1176" type="update" dev="pschumacher" due-to="Jeffery Yuan">Improve ArrayUtils removeElements time complexity to O(n)</action>
+ <action issue="LANG-1234" type="update" dev="pschumacher" due-to="Jonatan Jönsson">getLevenshteinDistance with a threshold: optimize implementation if the strings lengths differ more than the threshold</action>
+ <action issue="LANG-1168" type="add" dev="pschumacher" due-to="pschumacher">Add SystemUtils.IS_OS_WINDOWS_10 property</action>
+ <action issue="LANG-1232" type="fix" dev="pschumacher" due-to="Nick Manley">DiffBuilder: Add null check on fieldName when appending Object or Object[]</action>
+ <action issue="LANG-1178" type="fix" dev="pschumacher" due-to="Henri Yandell">ArrayUtils.removeAll(Object array, int... indices) should do the clone, not its callers</action>
+ <action issue="LANG-1151" type="update" dev="pschumacher" due-to="Juan Pablo Santos Rodríguez">Performance improvements for NumberUtils.isParsable</action>
+ <action issue="LANG-1120" type="fix" dev="pschumacher" due-to="kaching88">StringUtils.stripAccents should remove accents from "Ł" and "ł".</action>
+ <action issue="LANG-1218" type="update" dev="ggregory" due-to="Ruslan Cheremin">EqualsBuilder.append(Object,Object) is too big to be inlined, which prevents whole builder to be scalarized</action>
+ <action issue="LANG-1205" type="fix" dev="chas" due-to="pbrose">NumberUtils.createNumber() behaves inconsistently with NumberUtils.isNumber()</action>
+ <action issue="LANG-1115" type="add" dev="chas" due-to="Jim Lloyd, Joe Ferner">Add support for varargs in ConstructorUtils, MemberUtils, and MethodUtils</action>
+ <action issue="LANG-1134" type="add" dev="chas" due-to="Alan Smithee">Add methods to check numbers against NaN and infinite to Validate</action>
+ <action issue="LANG-1222" type="fix" dev="ggregory" due-to="Adam J.">Fix for incorrect comment on StringUtils.containsIgnoreCase method</action>
+ <action issue="LANG-1221" type="fix" dev="ggregory" due-to="Pierre Templier">Fix typo on appendIfMissing javadoc</action>
+ <action issue="LANG-1220" type="add" dev="kinow" due-to="Casey Scarborough">Add tests for missed branches in DateUtils</action>
+ <action issue="LANG-1202" type="fix" dev="chas" due-to="Markus Jelsma">parseDateStrictly doesn't pass specified locale</action>
+ <action issue="LANG-1219" type="fix" dev="chas" due-to="Jarek">FastDateFormat doesn't respect summer daylight in some localized strings</action>
+ <action issue="LANG-1146" type="add" dev="ggregory" due-to="Gabor Liptak">z/OS identification in SystemUtils</action>
+ <action issue="LANG-1210" type="update" dev="ggregory" due-to="Matthias Niehoff">StringUtils#startsWithAny has error in Javadoc</action>
+ <action issue="LANG-1208" type="update" dev="bayard" due-to="Samuel Karp">StrSubstitutor can preserve escapes</action>
+ <action issue="LANG-1175" type="fix" dev="wikier" due-to="Benedikt Ritter">Remove Ant-based build</action>
+ <action issue="LANG-1192" type="add" dev="chas" due-to="Dominik Stadler">FastDateFormat support of the week-year component (uppercase 'Y')</action>
+ <action issue="LANG-1194" type="fix" dev="chas">Limit max heap memory for consistent Travis CI build</action>
+ <action issue="LANG-1186" type="fix" dev="chas" due-to="NickManley">Fix NullPointerException in FastDateParser$TimeZoneStrategy</action>
+ <action issue="LANG-1193" type="fix" dev="sebb" due-to="Qin Li">ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1 (correct answer should be 0); revert fix for LANG-1077</action>
+ <action issue="LANG-1182" type="update" dev="britter" due-to="Larry West, Pascal Schumacher">Clarify Javadoc of StringUtils.containsAny()</action>
+ <action issue="LANG-1169" type="add" dev="lguibert" due-to="Rafal Glowinski, Robert Parr, Arman Sharif">Add StringUtils methods to compare a string to multiple strings</action>
+ <action issue="LANG-1185" type="add" dev="lguibert">Add remove by regular expression methods in StringUtils</action>
+ <action issue="LANG-1183" type="update" dev="lguibert">Making replacePattern/removePattern methods null safe in StringUtils</action>
+ <action issue="LANG-1139" type="add" dev="lguibert">Add replace by regular expression methods in StringUtils</action>
+ <action issue="LANG-1171" type="add" dev="lguibert">Add compare methods in StringUtils</action>
+ <action issue="LANG-1174" type="add" dev="britter" due-to="Punkratz312">Add sugar to RandomUtils</action>
+ <action issue="LANG-1057" type="update" dev="chas" due-to="Otávio Santana">Replace StringBuilder with String concatenation for better optimization</action>
+ <action issue="LANG-1075" type="update" dev="chas">Deprecate SystemUtils.FILE_SEPARATOR and SystemUtils.PATH_SEPARATOR</action>
+ <action issue="LANG-1154" type="add" dev="chas" due-to="Gary Gregory">FastDateFormat APIs that use a StringBuilder</action>
+ <action issue="LANG-1149" type="add" dev="chas" due-to="Gregory Zak">Ability to throw checked exceptions without declaring them</action>
+ <action issue="LANG-1002" type="fix" dev="chas" due-to="Michael Osipov">Several predefined ISO FastDateFormats in DateFormatUtils are incorrect</action>
+ <action issue="LANG-1152" type="fix" dev="chas" due-to="Pas Filip">StringIndexOutOfBoundsException or field over-write for large year fields in FastDateParser</action>
+ <action issue="LANG-1153" type="add" dev="chas">Implement ParsePosition api for FastDateParser</action>
+ <action issue="LANG-1141" type="fix" dev="oheger">StrLookup.systemPropertiesLookup() no longer reacts on changes on system properties</action>
+ <action issue="LANG-1147" type="fix" dev="sebb" due-to="Loic Guibert">EnumUtils *BitVector issue with more than 32 values Enum</action>
+ <action issue="LANG-1059" type="fix" dev="sebb" due-to="Colin Casey">Capitalize javadoc is incorrect</action>
+ <action issue="LANG-1137" type="add" dev="britter" due-to="Matthew Aguirre">Add check for duplicate event listener in EventListenerSupport</action>
+ <action issue="LANG-1133" type="fix" dev="chas" due-to="Pascal Schumacher">FastDateParser_TimeZoneStrategyTest#testTimeZoneStrategyPattern fails on Windows with German Locale</action>
+ <action issue="LANG-1135" type="add" dev="britter" due-to="Eduardo Martins">Add method containsAllWords to WordUtils</action>
+ <action issue="LANG-1132" type="add" dev="britter" due-to="Jack Tan">ReflectionToStringBuilder doesn't throw IllegalArgumentException when the constructor's object param is null</action>
+ <action issue="LANG-1122" type="fix" dev="britter" due-to="Adrian Ber">Inconsistent behavior of swap for malformed inputs</action>
+ <action issue="LANG-701" type="add" dev="britter" due-to="James Sawle">StringUtils join with var args</action>
+ <action issue="LANG-1130" type="fix" dev="britter">Fix critical issues reported by SonarQube</action>
+ <action issue="LANG-1131" type="fix" dev="britter">StrBuilder.equals(StrBuilder) doesn't check for null inputs</action>
+ <action issue="LANG-1105" type="add" dev="britter" due-to="Hendrik Saly">Add ThreadUtils - A utility class which provides helper methods related to java.lang.Thread</action>
+ <action issue="LANG-1031" type="add" dev="britter" due-to="Felipe Adorno">Add annotations to exclude fields from ReflectionEqualsBuilder, ReflectionToStringBuilder and ReflectionHashCodeBuilder</action>
+ <action issue="LANG-1127" type="add" dev="chas, britter">Use JUnit rules to set and reset the default Locale and TimeZone</action>
+ <action issue="LANG-1128" type="fix" dev="britter" due-to="Jack Tan">JsonToStringStyle doesn't handle chars and objects correctly</action>
+ <action issue="LANG-1126" type="fix" dev="britter">DateFormatUtilsTest.testSMTP depends on the default Locale</action>
+ <action issue="LANG-1123" type="fix" dev="chas" due-to="Christian P. Momon">Unit test FastDatePrinterTimeZonesTest needs a timezone set</action>
+ <action issue="LANG-916" type="fix" dev="chas" due-to="Christian P. Momon">CLONE - DateFormatUtils.format does not correctly change Calendar TimeZone in certain situations</action>
+ <action issue="LANG-1116" type="fix" dev="chas" due-to="Aaron Sheldon">DateUtilsTest.testLang530 fails for some timezones</action>
+ <action issue="LANG-1114" type="fix" dev="britter" due-to="Andy Coates">TypeUtils.ParameterizedType#equals doesn't work with wildcard types</action>
+ <action issue="LANG-1119" type="add" dev="britter" due-to="Loic Guibert">Add rotate(string, int) method to StringUtils</action>
+ <action issue="LANG-1118" type="fix" dev="britter" due-to="Loic Guibert">StringUtils.repeat('z', -1) throws NegativeArraySizeException</action>
+ <action issue="LANG-1099" type="add" dev="britter" due-to="Adrian Ber">Add swap and shift operations for arrays to ArrayUtils</action>
+ <action issue="LANG-979" type="update" dev="britter" due-to="Bruno P. Kinoshita">TypeUtils.parameterizeWithOwner - wrong format descriptor for "invalid number of type parameters".</action>
+ <action issue="LANG-1112" type="update" dev="britter">MultilineRecursiveToStringStyle largely unusable due to being package-private.</action>
+ <action issue="LANG-1058" type="update" dev="djones" due-to="Leo Wang">StringUtils.uncapitalize performance improvement</action>
+ <action issue="LANG-1069" type="update" dev="djones" due-to="Arno Noordover">CharSet.getInstance documentation does not clearly explain how to include negation character in set</action>
+ <action issue="LANG-1050" type="add" dev="djones" due-to="James Sawle">Change nullToEmpty methods to generics</action>
+ <action issue="LANG-1111" type="fix" dev="chas">Fix FindBugs warnings in DurationFormatUtils</action>
+ <action issue="LANG-1074" type="add" dev="djones" due-to="Haiyang Li">Add a method to ArrayUtils for removing all occurrences of a given element</action>
+ <action issue="LANG-1107" type="update" dev="chas">Fix parsing edge cases in FastDateParser</action>
+ <action issue="LANG-1162" type="fix" dev="sebb">StringUtils#equals fails with Index OOBE on non-Strings with identical leading prefix</action>
+ <action issue="LANG-1163" type="fix" dev="sebb">There are no tests for CharSequenceUtils.regionMatches</action>
+ <action issue="LANG-1200" type="fix" dev="ggregory" due-to="BarkZhang">StringUtils.ordinalIndexOf: Add missing right parenthesis in Javadoc example</action>
+ <action issue="LANG-1191" type="fix" dev="ggregory" due-to="qed, Brent Worden, Gary Gregory">Incorrect Javadoc StringUtils.containsAny(CharSequence, CharSequence...)</action>
+ <action type="update" dev="ebourg" due-to="Jake Wang">Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils</action>
+ </release>
+
+ <release version="3.4" date="2014-04-06" description="Feature and bugfix release">
+ <action issue="LANG-821" type="add" dev="britter" due-to="Timo Kockert">Support OS X versions in SystemUtils</action>
+ <action issue="LANG-794" type="fix" dev="britter" due-to="Timo Kockert">SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect</action>
+ <action issue="LANG-1104" type="fix" dev="chas">Parse test fails for TimeZone America/Sao_Paulo</action>
+ <action issue="LANG-1103" type="add" dev="britter">Add SystemUtils.IS_JAVA_1_9</action>
+ <action issue="LANG-1102" type="update" dev="britter">Make logic for comparing OS versions in SystemUtils smarter</action>
+ <action issue="LANG-1091" type="update" dev="britter" due-to="Fabian Lange">Shutdown thread pools in test cases</action>
+ <action issue="LANG-1101" type="update" dev="chas">FastDateParser and FastDatePrinter support 'X' format</action>
+ <action issue="LANG-1100" type="update" dev="chas" due-to="mbracher">Avoid memory allocation when using date formatting to StringBuffer</action>
+ <action issue="LANG-935" type="update" dev="britter" due-to="Fabian Lange, Thomas Neidhart">Possible performance improvement on string escape functions</action>
+ <action issue="LANG-948" type="fix" dev="britter" due-to="Andrey Khobnya">Exception while using ExtendedMessageFormat and escaping braces</action>
+ <action issue="LANG-1098" type="update" dev="britter" due-to="Mikhail Mazurskiy, Fabian Lange">Avoid String allocation in StrBuilder.append(CharSequence)</action>
+ <action issue="LANG-1098" type="update" dev="britter" due-to="Michał Kordas">Update maven-checkstyle-plugin to 2.14</action>
+ <action issue="LANG-1097" type="update" dev="britter" due-to="Michał Kordas">Update org.easymock:easymock to 3.3.1</action>
+ <action issue="LANG-1096" type="update" dev="britter" due-to="Michał Kordas">Update maven-pmd-plugin to 3.4</action>
+ <action issue="LANG-1095" type="update" dev="britter" due-to="Michał Kordas">Update maven-antrun-plugin to 1.8</action>
+ <action issue="LANG-1092" type="fix" dev="britter">Wrong formatting of time zones with daylight saving time in FastDatePrinter</action>
+ <action issue="LANG-877" type="update" dev="britter" due-to="Fabian Lange">Performance improvements for StringEscapeUtils</action>
+ <action issue="LANG-1093" type="add" dev="britter" due-to="Fabian Lange">Add ClassUtils.getAbbreviatedName()</action>
+ <action issue="LANG-1090" type="fix" dev="sebb">FastDateParser does not set error indication in ParsePosition</action>
+ <action issue="LANG-1089" type="fix" dev="sebb">FastDateParser does not handle excess hours as per SimpleDateFormat</action>
+ <action issue="LANG-1061" type="fix" dev="sebb" due-to="dmeneses">FastDateParser error - timezones not handled correctly</action>
+ <action issue="LANG-1087" type="fix" dev="britter" due-to="Renat Zhilkibaev">NumberUtils#createNumber() returns positive BigDecimal when negative Float is expected</action>
+ <action issue="LANG-1081" type="fix" dev="britter" due-to="Jonathan Baker">DiffBuilder.append(String, Object left, Object right) does not do a left.equals(right) check</action>
+ <action issue="LANG-1055" type="fix" dev="britter" due-to="Jonathan Baker">StrSubstitutor.replaceSystemProperties does not work consistently</action>
+ <action issue="LANG-1082" type="add" dev="britter" due-to="Jonathan Baker">Add option to disable the "objectsTriviallyEqual" test in DiffBuilder</action>
+ <action issue="LANG-1083" type="fix" dev="britter" due-to="Jonathan Baker">Add (T) casts to get unit tests to pass in old JDK</action>
+ <action issue="LANG-1015" type="add" dev="britter" due-to="Thiago Andrade">Add JsonToStringStyle implementation to ToStringStyle</action>
+ <action issue="LANG-1080" type="add" dev="britter" due-to="Innokenty Shuvalov">Add NoClassNameToStringStyle implementation of ToStringStyle</action>
+ <action issue="LANG-1071" type="update" dev="britter" due-to="Arno Noordover">Fix wrong examples in Javadoc of StringUtils.replaceEachRepeatedly(...), StringUtils.replaceEach(...)</action>
+ <action issue="LANG-883" type="add" dev="britter" due-to="Daniel Stewart">Add StringUtils.containsAny(CharSequence, CharSequence...) method</action>
+ <action issue="LANG-1073" type="fix" dev="kinow" due-to="haiyang li">Read wrong component type of array in add in ArrayUtils</action>
+ <action issue="LANG-1077" type="fix" dev="kinow" due-to="haiyang li">StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils</action>
+ <action issue="LANG-1072" type="fix" dev="sebb" due-to="haiyang li">Duplicated "0x" check in createBigInteger in NumberUtils</action>
+ <action issue="LANG-1064" type="fix" dev="djones" due-to="B.J. Herbison">StringUtils.abbreviate description doesn't agree with the examples</action>
+ <action issue="LANG-1052" type="add" dev="britter" due-to="Jan Matèrne">Multiline recursive to string style</action>
+ <action issue="LANG-536" type="add" dev="djones" due-to="James Sawle">Add isSorted() to ArrayUtils</action>
+ <action issue="LANG-1041" type="fix" dev="britter" due-to="Alexandre Bartel">Fix MethodUtilsTest so it does not depend on JDK method ordering</action>
+ <action issue="LANG-827" type="update" dev="djones">CompareToBuilder's doc doesn't specify precedence of fields it uses in performing comparisons</action>
+ <action issue="LANG-1000" type="fix" dev="djones">ParseException when trying to parse UTC dates with Z as zone designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT</action>
+ <action issue="LANG-1035" type="fix" dev="djones">Javadoc for EqualsBuilder.reflectionEquals() is unclear</action>
+ <action issue="LANG-1020" type="update" dev="britter" due-to="Libor Ondrusek">Improve performance of normalize space</action>
+ <action issue="LANG-1033" type="add" dev="ggregory">Add StringUtils.countMatches(CharSequence, char)</action>
+ <action issue="LANG-1027" type="update" dev="rmannibucau">org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should return true by default</action>
+ <action issue="LANG-1021" type="add" dev="britter" due-to="Alexander Müller">Provide methods to retrieve all fields/methods annotated with a specific type</action>
+ <action issue="LANG-1026" type="update" dev="britter" due-to="Alex Yursha">Bring static method references in StringUtils to consistent style</action>
+ <action issue="LANG-1016" type="add" dev="britter" due-to="Juan Pablo Santos Rodríguez">NumberUtils#isParsable method(s)</action>
+ <action issue="LANG-1017" type="update" dev="britter" due-to="Christoph Schneegans">Use non-ASCII digits in Javadoc examples for StringUtils.isNumeric</action>
+ <action issue="LANG-1008" type="update" dev="britter" due-to="Thiago Andrade">Change min/max methods in NumberUtils/IEEE754rUtils from array input parameters to varargs</action>
+ <action issue="LANG-999" type="add" dev="britter" due-to="Ben Ripkens">Add fuzzy String matching logic to StringUtils</action>
+ <action issue="LANG-1006" type="update" dev="britter" due-to="Thiago Andrade">Add wrap (with String or char) to StringUtils</action>
+ <action issue="LANG-1005" type="update" dev="britter" due-to="Michael Osipov">Extend DurationFormatUtils#formatDurationISO default pattern to match #formatDurationHMS</action>
+ <action issue="LANG-1007" type="update" dev="britter" due-to="Thiago Andrade">Fixing NumberUtils JAVADoc comments for max methods</action>
+ <action issue="LANG-731" type="update" dev="djones">Better Javadoc for BitField class</action>
+ <action issue="LANG-1004" type="update" dev="britter" due-to="Michael Osipov">DurationFormatUtils#formatDurationHMS implementation does not correspond to Javadoc and vice versa</action>
+ <action issue="LANG-1003" type="update" dev="britter">DurationFormatUtils are not able to handle negative durations/periods</action>
+ <action issue="LANG-1001" type="fix" dev="ggregory" due-to="Michael Osipov">ISO 8601 misspelled throughout the Javadocs</action>
+ <action issue="LANG-994" type="add" dev="britter" due-to="Mikhail Mazursky">Add zero copy read method to StrBuilder</action>
+ <action issue="LANG-993" type="add" dev="britter" due-to="Mikhail Mazursky">Add zero copy write method to StrBuilder</action>
+ <action issue="LANG-998" type="update" dev="chas">Javadoc is not clear on preferred pattern to instantiate FastDateParser / FastDatePrinter</action>
+ <action issue="LANG-1088" type="fix" dev="chas">FastDateParser should be case insensitive</action>
+ <action issue="LANG-995" type="fix" dev="britter" due-to="Andrey Khobnya">Fix bug with stripping spaces on last line in WordUtils.wrap()</action>
+ <action issue="LANG-1044" type="add" dev="ggregory">Add method org.apache.commons.lang3.reflect.MethodUtils.invokeExactMethod(Object, String)</action>
+ <action issue="LANG-1045" type="add" dev="ggregory">Add method org.apache.commons.lang3.reflect.MethodUtils.invokeMethod(Object, String)</action>
+ </release>
+
+ <release version="3.3.2" date="2014-04-09" description="Bugfix for a bug in NumberUtils introduced in 3.3.1">
+ <action issue="LANG-992" type="fix" dev="niallp">NumberUtils#isNumber() returns false for "0.0", "0.4790", et al</action>
+ <action issue="LANG-989" type="add" dev="ggregory">Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8</action>
+ </release>
+
+ <release version="3.3.1" date="2014-03-18" description="Bugfix release for 3.3">
+ <action issue="LANG-987" type="fix" dev="djones">DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong days</action>
+ <action issue="LANG-983" type="fix" dev="sebb">DurationFormatUtils does not describe format string fully</action>
+ <action issue="LANG-981" type="fix" dev="sebb">DurationFormatUtils#lexx does not detect unmatched quote char</action>
+ <action issue="LANG-984" type="fix" dev="sebb">DurationFormatUtils does not handle large durations correctly</action>
+ <action issue="LANG-982" type="fix" dev="sebb">DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field size should be 4 digits</action>
+ <action issue="LANG-978" type="fix" dev="sebb">Failing tests with Java 8 b128</action>
+ </release>
+
+ <release version="3.3" date="2014-03-04" description="Bug fixes and new features including: DifferenceBuilder, ClassPathUtils, RandomUtils and Jaro-Winkler String distance metric">
+ <action issue="LANG-621" type="fix" dev="kinow" due-to="Philip Hodges, Thomas Neidhart">ReflectionToStringBuilder.toString does not debug 3rd party object fields within 3rd party object</action>
+ <action issue="LANG-955" type="add" dev="britter" due-to="Adam Hooper">Add methods for removing all invalid characters according to XML 1.0 and XML 1.1 in an input string to StringEscapeUtils</action>
+ <action issue="LANG-977" type="fix" dev="britter" due-to="Chris Karcher">NumericEntityEscaper incorrectly encodes supplementary characters</action>
+ <action issue="LANG-973" type="fix" dev="sebb">Make some private fields final</action>
+ <action issue="LANG-971" type="fix" dev="sebb">NumberUtils#isNumber(String) fails to reject invalid Octal numbers</action>
+ <action issue="LANG-972" type="fix" dev="sebb">NumberUtils#isNumber does not allow for hex 0XABCD</action>
+ <action issue="LANG-969" type="fix" dev="ggregory" due-to="Matt Bishop">StringUtils.toEncodedString(byte[], Charset) needlessly throws UnsupportedEncodingException</action>
+ <action issue="LANG-970" type="add" dev="ggregory">Add APIs MutableBoolean setTrue() and setFalse()</action>
+ <action issue="LANG-946" type="fix" dev="britter">ConstantInitializerTest fails when building with IBM JDK 7</action>
+ <action issue="LANG-962" type="add" dev="ggregory">Add SerializationUtils.roundtrip(T extends Serializable) to serialize then deserialize</action>
+ <action issue="LANG-961" type="update" dev="ggregory">org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field) does not clean up after itself</action>
+ <action issue="LANG-958" type="update" dev="chas">FastDateParser javadoc incorrectly states that SimpleDateFormat is used internally</action>
+ <action issue="LANG-637" type="add" dev="djones">There should be a DifferenceBuilder with a ReflectionDifferenceBuilder implementation</action>
+ <action issue="LANG-954" type="fix" due-to="Michael Keppler" dev="sebb">uncaught PatternSyntaxException in FastDateFormat on Android</action>
+ <action issue="LANG-956" type="update" dev="britter">Improve Javadoc of WordUtils.wrap methods</action>
+ <action issue="LANG-944" type="add" dev="britter" due-to="Rekha Joshi">Add the Jaro-Winkler string distance algorithm to StringUtils</action>
+ <action issue="LANG-936" type="fix" dev="bayard" due-to="Yaniv Kunda, Eli Lindsey">StringUtils.getLevenshteinDistance with too big of a threshold returns wrong result</action>
+ <action issue="LANG-943" type="fix" dev="kinow">Test DurationFormatUtilsTest.testEdgeDuration fails in JDK 1.6, 1.7 and 1.8, BRST time zone</action>
+ <action issue="LANG-613" type="fix" dev="mbenson">ConstructorUtils.getAccessibleConstructor() Does Not Check the Accessibility of Enclosing Classes</action>
+ <action issue="LANG-951" type="fix" dev="britter" due-to="Sebastian Götz">Fragments are wrong by 1 day when using fragment YEAR or MONTH</action>
+ <action issue="LANG-417" type="add" dev="britter">New class ClassPathUtils with methods for turning FQN into resource path</action>
+ <action issue="LANG-939" type="update" dev="britter">Move Documentation from user guide to package-info files</action>
+ <action issue="LANG-953" type="update" dev="britter">Convert package.html files to package-info.java files</action>
+ <action issue="LANG-950" type="fix" dev="chas">FastDateParser does not handle two digit year parsing like SimpleDateFormat</action>
+ <action issue="LANG-949" type="fix" dev="chas">FastDateParserTest.testParses does not test FastDateParser</action>
+ <action issue="LANG-940" type="update" dev="britter">Fix deprecation warnings</action>
+ <action issue="LANG-819" type="update" dev="mbenson">EnumUtils.generateBitVector needs a "? extends"</action>
+ <action issue="LANG-834" type="add" dev="britter">Validate: add inclusiveBetween and exclusiveBetween overloads for primitive types</action>
+ <action issue="LANG-900" type="add" dev="britter" due-to="Duncan Jones">New RandomUtils class</action>
+ <action issue="LANG-915" type="fix" dev="britter" due-to="Sergio Fernández">Wrong locale handling in LocaleUtils.toLocale()</action>
+ <action issue="LANG-966" type="add" dev="ggregory">Add IBM OS/400 detection</action>
+ </release>
+
+ <release version="3.2.1" date="2014-01-05" description="Bug fix for 3.2">
+ <action issue="LANG-937" type="fix" dev="britter">Fix missing Hamcrest dependency in Ant Build</action>
+ <action issue="LANG-941" type="fix" dev="britter">Test failure in LocaleUtilsTest when building with JDK 8</action>
+ <action issue="LANG-942" type="fix" dev="britter" due-to="Bruno P. Kinoshita, Henri Yandell">Test failure in FastDateParserTest and FastDateFormat_ParserTest when building with JDK8</action>
+ <action issue="LANG-938" type="fix">Build fails with test failures when building with JDK 8</action>
+ </release>
+
+ <release version="3.2" date="2014-01-01" description="Bug fixes and new features, at least requires Java 6.0">
+ <action issue="LANG-934" type="add" dev="mcucchiara">Add removeFinalModifier to FieldUtils</action>
+ <action issue="LANG-863" type="add" due-to="Daneel S. Yaitskov" dev="sebb">Method returns number of inheritance hops between parent and subclass</action>
+ <action issue="LANG-932" type="fix" due-to="Ville Skyttä" dev="sebb">Spelling fixes</action>
+ <action issue="LANG-931" type="update" dev="britter" due-to="Christoph Schneegans">Misleading Javadoc comment in StrBuilderReader class</action>
+ <action issue="LANG-929" type="fix">OctalUnescaper tried to parse all of \279</action>
+ <action issue="LANG-928" type="fix">OctalUnescaper had bugs when parsing octals starting with a zero</action>
+ <action issue="LANG-905" type="fix">EqualsBuilder returned true when comparing arrays, even when the elements are different</action>
+ <action issue="LANG-774" type="add" due-to="Erhan Bagdemir">Added isStarted, isSuspended and isStopped to StopWatch</action>
+ <action issue="LANG-917" type="fix" due-to="Arne Burmeister">Fixed exception when combining custom and choice format in ExtendedMessageFormat</action>
+ <action issue="LANG-848" type="add" due-to="Alexander Muthmann">Added StringUtils.isBlank/isEmpty CharSequence... methods</action>
+ <action issue="LANG-926" type="add" dev="ggregory">Added ArrayUtils.reverse(array, from, to) methods</action>
+ <action issue="LANG-795" type="add" due-to="Aaron Digulla">StringUtils.toString(byte[], String) deprecated in favour of a new StringUtils.toString(byte[], CharSet)</action>
+ <action issue="LANG-902" type="fix" due-to="Andrzej Winnicki">RandomStringUtils.random javadoc was incorrectly promising letters and numbers would, as opposed to may, appear</action>
+ <action issue="LANG-921" type="fix" dev="britter">BooleanUtils.xor(boolean...) produces wrong results</action>
+ <action issue="LANG-910" type="update" due-to="Timur Yarosh">StringUtils.normalizeSpace now handles non-breaking spaces (Unicode 00A0)</action>
+ <action issue="LANG-804" type="update" dev="britter" due-to="Allon Mureinik">Redundant check for zero in HashCodeBuilder ctor</action>
+ <action issue="LANG-893" type="add" dev="oheger" due-to="Woonsan Ko">StrSubstitutor now supports default values for variables</action>
+ <action issue="LANG-913" type="add" dev="britter" due-to="Allon Mureinik">Adding .gitignore to commons-lang</action>
+ <action issue="LANG-837" type="add">Add ObjectUtils.toIdentityString methods that support StringBuilder, StrBuilder, and Appendable</action>
+ <action issue="LANG-896" type="fix" due-to="Mark Bryan Yu">BooleanUtils.toBoolean(String str) javadoc is not updated</action>
+ <action issue="LANG-879" type="fix">LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese" of JDK7</action>
+ <action issue="LANG-836" type="fix" due-to="Arnaud Brunet">StrSubstitutor does not support StringBuilder or CharSequence</action>
+ <action issue="LANG-693" type="fix" due-to="Calvin Echols">Method createNumber from NumberUtils doesn't work for floating point numbers other than Float</action>
+ <action issue="LANG-887" type="fix">FastDateFormat does not use the locale specific cache correctly</action>
+ <action issue="LANG-884" type="update">Simplify FastDateFormat; eliminate boxing</action>
+ <action issue="LANG-882" type="update">LookupTranslator now works with implementations of CharSequence other than String</action>
+ <action issue="LANG-754" type="fix">ClassUtils.getShortName(String) will now only do a reverse lookup for array types</action>
+ <action issue="LANG-886" type="add">Added CharSetUtils.containsAny(String, String)</action>
+ <action issue="LANG-846" type="update">Provide CharSequenceUtils.regionMatches with a proper green implementation instead of inefficiently converting to Strings</action>
+ <action issue="LANG-797" type="add">Added escape/unescapeJson to StringEscapeUtils</action>
+ <action issue="LANG-875" type="add">Added appendIfMissing and prependIfMissing methods to StringUtils</action>
+ <action issue="LANG-881" type="fix">NumberUtils.createNumber() Javadoc says it does not work for octal numbers</action>
+ <action type="fix">Fixed URLs in javadoc to point to new oracle.com pages</action>
+ <action issue="LANG-870" type="add">Add StringUtils.LF and StringUtils.CR values</action>
+ <action issue="LANG-873" type="add">Add FieldUtils getAllFields() to return all the fields defined in the given class and super classes</action>
+ <action issue="LANG-865" type="fix">LocaleUtils.toLocale does not parse strings starting with an underscore</action>
+ <action issue="LANG-835" type="add">StrBuilder should support StringBuilder as an input parameter</action>
+ <action issue="LANG-858" type="fix">StringEscapeUtils.escapeJava() and escapeEcmaScript() do not output the escaped surrogate pairs that are Java parsable</action>
+ <action issue="LANG-857" type="add">StringIndexOutOfBoundsException in CharSequenceTranslator</action>
+ <action issue="LANG-856" type="add">Code refactoring in NumberUtils</action>
+ <action issue="LANG-855" type="add">NumberUtils#createBigInteger does not allow for hex and octal numbers</action>
+ <action issue="LANG-854" type="add">NumberUtils#createNumber - does not allow for hex numbers to be larger than Long</action>
+ <action issue="LANG-853" type="add">StringUtils join APIs for primitives</action>
+ <action issue="LANG-849" type="fix">FastDateFormat and FastDatePrinter generates Date objects wastefully</action>
+ <action issue="LANG-845" type="fix">Spelling fixes</action>
+ <action issue="LANG-844" type="fix">Fix examples contained in javadoc of StringUtils.center methods</action>
+ <action issue="LANG-841" type="add">Add StringUtils API to call String.replaceAll in DOTALL a.k.a. single-line mode</action>
+ <action issue="LANG-839" type="update">ArrayUtils removeElements methods use unnecessary HashSet</action>
+ <action issue="LANG-838" type="update">ArrayUtils removeElements methods clone temporary index arrays unnecessarily</action>
+ <action issue="LANG-832" type="fix">FastDateParser does not handle unterminated quotes correctly</action>
+ <action issue="LANG-831" type="fix">FastDateParser does not handle white-space properly</action>
+ <action issue="LANG-830" type="fix">FastDateParser could use \Q \E to quote regexes</action>
+ <action issue="LANG-828" type="fix">FastDateParser does not handle non-Gregorian calendars properly</action>
+ <action issue="LANG-826" type="fix">FastDateParser does not handle non-ASCII digits correctly</action>
+ <action issue="LANG-825" type="add">Create StrBuilder APIs similar to String.format(String, Object...)</action>
+ <action issue="LANG-822" type="fix">NumberUtils#createNumber - bad behavior for leading "--"</action>
+ <action issue="LANG-818" type="fix">FastDateFormat's "z" pattern does not respect timezone of Calendar instances passed to format()</action>
+ <action issue="LANG-817" type="fix">Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8</action>
+ <action issue="LANG-813" type="fix">StringUtils.equalsIgnoreCase doesn't check string reference equality</action>
+ <action issue="LANG-810" type="fix">StringUtils.join() endIndex, bugged for loop</action>
+ <action issue="LANG-807" type="fix">RandomStringUtils throws confusing IAE when end &lt;= start</action>
+ <action issue="LANG-805" type="fix">RandomStringUtils.random(count, 0, 0, false, false, universe, random) always throws java.lang.ArrayIndexOutOfBoundsException</action>
+ <action issue="LANG-802" type="fix">LocaleUtils - unnecessary recursive call in SyncAvoid class.</action>
+ <action issue="LANG-800" type="fix">Javadoc bug in DateUtils#ceiling for Calendar and Object versions.</action>
+ <action issue="LANG-799" type="update">DateUtils#parseDate uses default locale; add Locale support</action>
+ <action issue="LANG-798" type="update">Use generics in SerializationUtils</action>
+ <action issue="LANG-788" type="fix">SerializationUtils throws ClassNotFoundException when cloning primitive classes</action>
+ <action issue="LANG-786" type="fix">StringUtils equals() relies on undefined behavior</action>
+ <action issue="LANG-783" type="fix">Documentation bug: StringUtils.split</action>
+ <action issue="LANG-777" type="fix">jar contains velocity template of release notes</action>
+ <action issue="LANG-776" type="fix">TypeUtilsTest contains incorrect type assignability assertion</action>
+ <action issue="LANG-775" type="fix">TypeUtils.getTypeArguments() misses type arguments for partially-assigned classes</action>
+ <action issue="LANG-773" type="fix">ImmutablePair doc contains nonsense text</action>
+ <action issue="LANG-772" type="fix">ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text</action>
+ <action issue="LANG-765" type="fix">EventListenerSupport.ProxyInvocationHandler no longer defines serialVersionUID</action>
+ <action issue="LANG-764" type="fix">StrBuilder is now serializable</action>
+ <action issue="LANG-761" type="fix">Fix Javadoc Ant warnings</action>
+ <action issue="LANG-747" type="fix">NumberUtils does not handle Long Hex numbers</action>
+ <action issue="LANG-743" type="fix">Javadoc bug in static inner class DateIterator</action>
+ <action issue="LANG-675" type="add">Add Triple class (ternary version of Pair)</action>
+ <action issue="LANG-462" type="add">FastDateFormat supports parse methods</action>
+ </release>
+
+ <release version="3.1" date="2011-11-14" description="November release">
+ <action type="add" issue="LANG-760">Add API StringUtils.toString(byte[] input, String charsetName)</action>
+ <action type="update" issue="LANG-758">Add an example with whitespace in StringUtils.defaultIfEmpty</action>
+ <action type="add" issue="LANG-756">Add APIs ClassUtils.isPrimitiveWrapper(Class&lt;?&gt;) and isPrimitiveOrWrapper(Class&lt;?&gt;)</action>
+ <action type="update" issue="LANG-752">Fix createLong() so it behaves like createInteger()</action>
+ <action type="update" issue="LANG-751">Include the actual type in the Validate.isInstance and isAssignableFrom exception messages</action>
+ <action type="fix" issue="LANG-749">Incorrect Bundle-SymbolicName in Manifest</action>
+ <action type="update" issue="LANG-748">Deprecating chomp(String, String)</action>
+ <action type="fix" issue="LANG-746">NumberUtils does not handle upper-case hex: 0X and -0X</action>
+ <action type="fix" issue="LANG-744">StringUtils throws java.security.AccessControlException on Google App Engine</action>
+ <action type="fix" issue="LANG-741">Ant build has wrong component.name</action>
+ <action type="update" issue="LANG-736">CharUtils static final array CHAR_STRING is not needed to compute CHAR_STRING_ARRAY</action>
+ <action type="fix" issue="LANG-698">Document that the Mutable numbers don't work as expected with String.format</action>
+ <action type="add" issue="LANG-695">SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system</action>
+ </release>
+
+ <release version="3.0.1" date="2011-08-09" description="August release">
+ <action type="fix" issue="LANG-626">SerializationUtils.clone: Fallback to context classloader if class not found in current classloader.</action>
+ <action type="fix" issue="LANG-727">ToStringBuilderTest.testReflectionHierarchyArrayList fails with IBM JDK 6.</action>
+ <action type="fix" issue="LANG-720">StringEscapeUtils.escapeXml(input) wrong when input contains characters in Supplementary Planes.</action>
+ <action type="fix" issue="LANG-708">StringEscapeUtils.escapeEcmaScript from lang3 cuts off long unicode string.</action>
+ <action type="update" issue="LANG-686">Improve exception message when StringUtils.replaceEachRepeatedly detects recursion.</action>
+ <action type="update" issue="LANG-717">Specify source encoding for Ant build.</action>
+ <action type="add" issue="LANG-721">Complement ArrayUtils.addAll() variants with by-index and by-value removal methods.</action>
+ <action type="add" issue="LANG-726">Add Range&lt;T&gt; Range&lt;T&gt;.intersectionWith(Range&lt;T&gt;).</action>
+ <action type="add" issue="LANG-723">Add mode and median Comparable... methods to ObjectUtils.</action>
+ <action type="add" issue="LANG-722">Add BooleanUtils.and + or varargs methods.</action>
+ <action type="add" issue="LANG-730">EnumSet -&gt; bit vector.</action>
+ <action type="fix" issue="LANG-734">The CHAR_ARRAY cache in CharUtils duplicates the cache in java.lang.Character.</action>
+ <action type="update" issue="LANG-735">Deprecate CharUtils.toCharacterObject(char) in favor of java.lang.Character.valueOf(char).</action>
+ <action type="add" issue="LANG-737">Missing method getRawMessage for ContextedException and ContextedRuntimeException.</action>
+ <action type="fix" issue="LANG-738">Use internal Java's Number caches instead creating new objects.</action>
+ </release>
+
+ <release version="3.0" date="2011-07-18" description="Backwards incompatible update of Commons Lang to Java 5">
+ <action type="fix" issue="LANG-720">StringEscapeUtils.escapeXml(input) outputs wrong results when an input contains characters in Supplementary Planes.</action>
+ <action type="update" issue="LANG-718">build.xml Java 1.5+ updates.</action>
+ <action type="fix" issue="LANG-716">swapCase and *capitalize speedups.</action>
+ <action type="fix" issue="LANG-715">CharSetUtils.squeeze() speedup.</action>
+ <action type="fix" issue="LANG-714">StringUtils doc/comment spelling fixes.</action>
+ <action type="update" issue="LANG-713">Increase test coverage of FieldUtils read methods and tweak Javadoc.</action>
+ <action type="fix" issue="LANG-711">Add includeantruntime=false to javac targets to quell warnings in ant 1.8.1 and better (and modest performance gain).</action>
+ <action type="fix" issue="LANG-710">StringIndexOutOfBoundsException when calling unescapeHtml4("&amp;#03").</action>
+ <action type="fix" issue="LANG-708">StringEscapeUtils.escapeEcmaScript from lang3 cuts off long Unicode string.</action>
+ <action type="fix" issue="LANG-703">StringUtils.join throws NPE when toString returns null for one of objects in collection.</action>
+ <action type="add" issue="LANG-697">Add FormattableUtils class.</action>
+ <action type="add">Add ClassUtils.getSimpleName() methods.</action>
+ <action type="add" issue="LANG-692">Add hashCodeMulti varargs method.</action>
+ <action type="remove" issue="LANG-691">Removed DateUtils.UTC_TIME_ZONE.</action>
+ <action type="update" issues="LANG-687">Convert more of the StringUtils API to take CharSequence.</action>
+ <action type="fix" issue="LANG-685">EqualsBuilder synchronizes on HashCodeBuilder.</action>
+ <action type="fix" issue="LANG-428">StringUtils.isAlpha, isAlphanumeric and isNumeric now return false for "".</action>
+ <action type="add" issue="LANG-678">Add support for ConcurrentMap.putIfAbsent().</action>
+ <action type="add" issue="LANG-676">Documented potential NPE if auto-boxing occurs for some BooleanUtils methods.</action>
+ <action type="fix" issue="LANG-677">DateUtils.isSameLocalTime compares using 12-hour clock and not 24-hour.</action>
+ <action type="add" issue="LANG-610">Extend exception handling in ConcurrentUtils to runtime exceptions.</action>
+ <action type="fix" issue="LANG-624">SystemUtils.getJavaVersionAsFloat throws StringIndexOutOfBoundsException on Android runtime/Dalvik VM.</action>
+ <action type="remove" issue="LANG-673">WordUtils.abbreviate() removed.</action>
+ <action type="fix" issue="LANG-672">Doc bug in DateUtils#ceiling.</action>
+ <action type="fix" issue="LANG-646">StringEscapeUtils.unescapeJava doesn't handle octal escapes and Unicode with extra u.</action>
+ <action type="fix" issue="LANG-662">org.apache.commons.lang3.math.Fraction does not reduce (Integer.MIN_VALUE, 2^k).</action>
+ <action type="fix" issue="LANG-663">org.apache.commons.lang3.math.Fraction does not always succeed in multiplyBy and divideBy.</action>
+ <action type="update" issue="LANG-668">Change ObjectUtils min() &amp; max() functions to use varargs rather than just two parameters.</action>
+ <action type="add" issue="LANG-667">Add a Null-safe compare() method to ObjectUtils.</action>
+ <action type="fix" issue="LANG-664">NumberUtils.isNumber(String) is not right when the String is "1.1L".</action>
+ <action type="fix" issue="LANG-659">EntityArrays typo: {"\u2122", "&amp;minus;"}, // minus sign, U+2212 ISOtech.</action>
+ <action type="fix" issue="LANG-658">Some entities like &amp;Ouml; are not matched properly against its ISO8859-1 representation.</action>
+ <action type="fix" issue="LANG-656">Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect.</action>
+ <action type="add" issue="LANG-655">Add StringUtils.defaultIfBlank().</action>
+ <action type="add" issue="LANG-653">Provide a very basic ConcurrentInitializer implementation.</action>
+ <action type="add" issue="LANG-609">Support lazy initialization using atomic variables.</action>
+ <action type="add" issue="LANG-482">Enhance StrSubstitutor to support nested ${var-${subvr}} expansion.</action>
+ <action type="add" issue="LANG-644">Provide documentation about the new concurrent package.</action>
+ <action type="fix" issue="LANG-629">Charset may not be threadsafe, because the HashSet is not synch.</action>
+ <action type="fix" issue="LANG-617">StringEscapeUtils.escapeXML() can't process UTF-16 supplementary characters.</action>
+ <action type="add" issue="LANG-614">StringUtils.endsWithAny method.</action>
+ <action type="add" issue="LANG-651">Add AnnotationUtils.</action>
+ <action type="add" issue="LANG-649">BooleanUtils.toBooleanObject to support single character input.</action>
+ <action type="fix" issue="LANG-645">FastDateFormat.format() outputs incorrect week of year because locale isn't respected.</action>
+ <action type="fix" issue="LANG-596">StrSubstitutor should also handle the default properties of a java.util.Properties class.</action>
+ <action type="fix" issue="LANG-643">Javadoc StringUtils.left() claims to throw on negative len, but doesn't.</action>
+ <action type="add" issue="LANG-640">Add normalizeSpace to StringUtils.</action>
+ <action type="fix" issue="LANG-638">NumberUtils createNumber throws a StringIndexOutOfBoundsException when argument containing "e" and "E" is passed in.</action>
+ <!-- 3.0 beta below here -->
+ <action>NOTE: The below were included in the Commons Lang 3.0-beta release.</action>
+ <action type="update" issues="LANG-510">Convert StringUtils API to take CharSequence.</action>
+ <action type="update">Push down WordUtils to "text" sub-package.</action>
+ <action type="add" issue="LANG-610">Extend exception handling in ConcurrentUtils to runtime exceptions.</action>
+ <action type="fix" issue="LANG-608">Some StringUtils methods should take an int character instead of char to use String API features.</action>
+ <action type="fix" issue="LANG-606">EqualsBuilder causes StackOverflowException.</action>
+ <action type="update" issue="LANG-605">DefaultExceptionContext overwrites values in recursive situations.</action>
+ <action type="fix" issue="LANG-602">ContextedRuntimeException no longer an 'unchecked' exception.</action>
+ <action type="add" issue="LANG-601">Add Builder Interface / Update Builders to Implement It.</action>
+ <action type="fix" issue="LANG-600">Javadoc is incorrect for public static int lastIndexOf(String str, String searchStr).</action>
+ <action type="update" issue="LANG-599">ClassUtils.getClass(): Allow Dots as Inner Class Separators.</action>
+ <action type="add" issue="LANG-594">DateUtils equal &amp; compare functions up to most significant field.</action>
+ <action type="remove" issue="LANG-590">Remove JDK 1.2/1.3 bug handling in StringUtils.indexOf(String, String, int).</action>
+ <action type="add" issue="LANG-588">Create a basic Pair&lt;L, R&gt; class.</action>
+ <action type="fix" issue="LANG-585">exception.DefaultExceptionContext.getFormattedExceptionMessage catches Throwable.</action>
+ <action type="add" issue="LANG-582">Provide an implementation of the ThreadFactory interface.</action>
+ <action type="update" issue="LANG-579">Add new Validate methods.</action>
+ <action type="fix" issue="LANG-571">ArrayUtils.add(T[] array, T element) can create unexpected ClassCastException.</action>
+ <action type="update" issue="LANG-570">Do the test cases really still require main() and suite() methods?.</action>
+ <action type="fix" issue="LANG-568">@SuppressWarnings("unchecked") is used too generally.</action>
+ <action type="fix" issue="LANG-564">Improve StrLookup API documentation.</action>
+ <action type="update" issue="LANG-563">Change Java package name.</action>
+ <action type="update" issue="LANG-562">Change Maven groupId.</action>
+ <action type="add" issue="LANG-560">New TimedSemaphore class.</action>
+ <action type="add" issue="LANG-559">Added validState validation method.</action>
+ <action type="add" issue="LANG-559">Added isAssignableFrom and isInstanceOf validation methods.</action>
+ <action type="add" issue="LANG-553">Add TypeUtils class to provide utility code for working with generic types.</action>
+ <action type="update" issue="LANG-551">Replace Range classes with generic version.</action>
+ <action type="update" issue="LANG-548">Use Iterable on API instead of Collection.</action>
+ <action type="add" issue="LANG-546">Add methods to Validate to check whether the index is valid for the array/list/string.</action>
+ <action type="add" issue="LANG-545">Add ability to create a Future for a constant.</action>
+ <action type="update" issue="LANG-541">Replace StringBuffer with StringBuilder.</action>
+ <action type="update" issue="LANG-540">Make NumericEntityEscaper immutable.</action>
+ <action type="update" issue="LANG-539">Compile commons.lang for CDC 1.1/Foundation 1.1.</action>
+ <action type="add" issue="LANG-537">Add ArrayUtils.toArray to create generic arrays.</action>
+ <action type="add" issue="LANG-533">Validate: support for validating blank strings.</action>
+ <action type="add" issue="LANG-529">Add a concurrent package.</action>
+ <action type="update" issue="LANG-528">Mutable classes should implement an appropriately typed Mutable interface.</action>
+ <action type="update" issue="LANG-513">Better EnumUtils.</action>
+ <action type="update" issue="LANG-507">StringEscapeUtils.unescapeJava should support \u+ notation.</action>
+ <action type="update" issue="LANG-505">Rewrite StringEscapeUtils.</action>
+ <action type="update" issue="LANG-504">bring ArrayUtils.isEmpty to the generics world.</action>
+ <action type="add" issue="LANG-501">Add support for background initialization.</action>
+ <action type="add" issue="LANG-499">Add support for the handling of ExecutionExceptions.</action>
+ <action type="add" issue="LANG-498">Add StringEscapeUtils.escapeText() methods.</action>
+ <action type="add" issue="LANG-497">Addition of ContextedException and ContextedRuntimeException.</action>
+ <action type="add" issue="LANG-496">A generic implementation of the Lazy initialization pattern.</action>
+ <action type="remove" issue="LANG-493">Remove code that does not hold enough value to remain.</action>
+ <action type="remove" issue="LANG-492">Remove code handled now by the JDK.</action>
+ <action type="add" issue="LANG-482">StrSubstitutor now supports substitution in variable names.</action>
+ <action type="fix" issue="LANG-481">Possible race-conditions in hashCode of the range classes.</action>
+ <action type="fix" issue="LANG-480">StringEscapeUtils.escapeHtml incorrectly converts Unicode characters above U+00FFFF into 2 characters.</action>
+ <action type="update" issue="LANG-479">Document where in SVN trunk is.</action>
+ <action type="fix" issue="LANG-478">StopWatch does not resist to system time changes.</action>
+ <action type="fix" issue="LANG-474">Fixes for thread safety.</action>
+ <action type="update" issue="LANG-458">Refactor Validate.java to eliminate code redundancy.</action>
+ <action type="fix" issue="LANG-448">Lower Ascii Characters don't get encoded by Entities.java.</action>
+ <action type="add" issue="LANG-444">StringUtils.emptyToNull.</action>
+ <action type="fix" issue="LANG-439">StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).</action>
+ <action type="remove" issue="LANG-438">Remove @deprecateds.</action>
+ <action type="add" issue="LANG-435">Add ClassUtils.isAssignable() variants with autoboxing.</action>
+ <action type="update" issue="LANG-424">Improve Javadoc for StringUtils class.</action>
+ <action type="fix" issue="LANG-418">Javadoc incorrect for StringUtils.endsWithIgnoreCase.</action>
+ <action type="update" issue="LANG-396">Investigate for vararg usages.</action>
+ <action type="fix" issue="LANG-468">JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).</action>
+ <action type="add" issue="LANG-386">LeftOf/RightOfNumber in Range convenience methods necessary.</action>
+ <action type="fix" issue="LANG-369">ExceptionUtils not thread-safe.</action>
+ <action type="add" issue="LANG-358">ObjectUtils.coalesce.</action>
+ <action type="update" issue="LANG-355">StrBuilder should implement CharSequence and Appendable.</action>
+ <action type="fix" issue="LANG-339">StringEscapeUtils.escapeHtml() escapes multibyte characters like Chinese, Japanese, etc.</action>
+ <action type="update" issue="LANG-336">Finally start using generics.</action>
+ <action type="fix" issue="LANG-302">StrBuilder does not implement clone().</action>
+ <action type="update" issue="LANG-290">EnumUtils for JDK 5.0.</action>
+ <action type="add" issue="LANG-285">Wish : method unaccent.</action>
+ <action type="add" issue="LANG-276">MutableBigDecimal and MutableBigInteger.</action>
+ <action type="fix" issue="LANG-66">StringEscaper.escapeXml() escapes characters &gt; 0x7f.</action>
+ <action type="fix" issue="LANG-11">Depend on JDK 1.5+.</action>
+ </release>
+
+ <release version="2.6" date="2011-01-16" description="Bug Fixes/Enhancements for the 2.6 release (requires minimum of Java 1.3)">
+ <action type="update" issue="LANG-633">BooleanUtils: use same optimization in toBooleanObject(String) as in toBoolean(String).</action>
+ <action type="update" issue="LANG-599">ClassUtils: allow Dots as Inner Class Separators in getClass().</action>
+ <action type="add" issue="LANG-594">DateUtils: equal and compare functions up to most significant field.</action>
+ <action type="add" issue="LANG-632">DateUtils: provide a Date to Calendar convenience method.</action>
+ <action type="add" issue="LANG-576">ObjectUtils: add clone methods to ObjectUtils.</action>
+ <action type="add" issue="LANG-667">ObjectUtils: add a Null-safe compare() method.</action>
+ <action type="add" issue="LANG-670">ObjectUtils: add notEqual() method.</action>
+ <action type="add" issue="LANG-302">StrBuilder: implement clone() method.</action>
+ <action type="add" issue="LANG-640">StringUtils: add a normalizeSpace() method.</action>
+ <action type="add" issue="LANG-614">StringUtils: add endsWithAny() method.</action>
+ <action type="add" issue="LANG-655">StringUtils: add defaultIfBlank() method.</action>
+ <action type="add" issue="LANG-596">StrSubstitutor: add a replace(String, Properties) variant.</action>
+ <action type="add" issue="LANG-482">StrSubstitutor: support substitution in variable names.</action>
+ <action type="update" issue="LANG-669">Use StrBuilder instead of StringBuffer to improve performance where sync. is not an issue.</action>
+ <action type="fix" issue="LANG-629">CharSet: make the underlying set synchronized.</action>
+ <action type="fix" issue="LANG-635">CompareToBuilder: fix passing along compareTransients to the reflectionCompare method.</action>
+ <action type="fix" issue="LANG-636">ExtendedMessageFormat doesn't override equals(Object).</action>
+ <action type="fix" issue="LANG-645">FastDateFormat: fix to properly include the locale when formatting a Date.</action>
+ <action type="fix" issue="LANG-638">NumberUtils: createNumber() throws a StringIndexOutOfBoundsException when argument containing "e" and "E" is passed in.</action>
+ <action type="fix" issue="LANG-607">StringUtils methods do not handle Unicode 2.0+ supplementary characters correctly.</action>
+ <action type="fix" issue="LANG-624">SystemUtils: getJavaVersionAsFloat throws StringIndexOutOfBoundsException on Android runtime/Dalvik VM.</action>
+ <action type="fix" issue="BEANUTILS-381">MemberUtils: getMatchingAccessibleMethod does not correctly handle inheritance and method overloading.</action>
+ <action type="update" issue="LANG-600">Javadoc is incorrect for lastIndexOf() method.</action>
+ <action type="update" issue="LANG-628">Javadoc for HashCodeBuilder.append(boolean) does not match implementation.</action>
+ <action type="update" issue="LANG-643">Javadoc StringUtils.left() claims to throw an exception on negative length, but doesn't.</action>
+ <action type="update" issue="LANG-370">Javadoc - document thread safety.</action>
+ <action type="update" issue="LANG-623">Test for StringUtils replaceChars() icelandic characters.</action>
+ </release>
+
+ <release version="2.5" date="2010-02-25" description="">
+ <action type="add" issue="LANG-583">ArrayUtils - add isNotEmpty() methods.</action>
+ <action type="add" issue="LANG-534">ArrayUtils - add nullToEmpty() methods.</action>
+ <action type="add" issue="LANG-454">CharRange - provide an iterator that lets you walk the chars in the range.</action>
+ <action type="add" issue="LANG-514">CharRange - add more readable static builder methods.</action>
+ <action type="add">ClassUtils - new isAssignable() methods with autoboxing.</action>
+ <action type="add" issue="LANG-535">ClassUtils - add support to getShortClassName and getPackageName for arrays.</action>
+ <action type="add" issue="LANG-434">DateUtils - add ceiling() method.</action>
+ <action type="add" issue="LANG-486">DateUtils - add parseDateStrictly() method.</action>
+ <action type="add" issue="LANG-466">EqualsBuilder - add reset() method.</action>
+ <action type="add" issue="LANG-461">NumberUtils - add toByte() and toShort() methods.</action>
+ <action type="add" issue="LANG-522">Mutable numbers - add string constructors.</action>
+ <action type="add">MutableBoolean - add toBoolean(), isTrue() and isFalse() methods.</action>
+ <action type="add" issue="LANG-422">StrBuilder - add appendSeparator() methods with an alternative default separator if the StrBuilder is currently empty.</action>
+ <action type="add" issue="LANG-555">SystemUtils - add IS_OS_WINDOWS_7 constant.</action>
+ <action type="add" issue="LANG-554">SystemUtils - add IS_JAVA_1_7 constant for JDK 1.7.</action>
+ <action type="add" issue="LANG-405">StringUtils - add abbreviateMiddle() method.</action>
+ <action type="add" issue="LANG-569">StringUtils - add indexOfIgnoreCase() and lastIndexOfIgnoreCase() methods.</action>
+ <action type="add" issue="LANG-471">StringUtils - add isAllUpperCase() and isAllLowerCase() methods.</action>
+ <action type="add" issue="LANG-469">StringUtils - add lastOrdinalIndexOf() method to complement the existing ordinalIndexOf() method.</action>
+ <action type="add" issue="LANG-348">StringUtils - add repeat() method.</action>
+ <action type="add" issue="LANG-445">StringUtils - add startsWithAny() method.</action>
+ <action type="add" issue="LANG-430">StringUtils - add upperCase(String, Locale) and lowerCase(String, Locale) methods.</action>
+ <action type="add" issue="LANG-416">New Reflection package containing ConstructorUtils, FieldUtils, MemberUtils and MethodUtils.</action>
+ <action type="fix" issue="LANG-567">ArrayUtils - addAll() does not handle mixed types very well.</action>
+ <action type="fix" issue="LANG-494">CharSet - Synchronizing the COMMON Map so that getInstance doesn't miss a put from a subclass in another thread.</action>
+ <action type="fix" issue="LANG-500">ClassUtils - improving performance of getAllInterfaces.</action>
+ <action type="fix" issue="LANG-587">ClassUtils - toClass() throws NullPointerException on null array element.</action>
+ <action type="fix" issue="LANG-530">DateUtils - Fix parseDate() cannot parse ISO8601 dates produced by FastDateFormat.</action>
+ <action type="fix" issue="LANG-440">DateUtils - round() doesn't work correct for Calendar.AM_PM.</action>
+ <action type="fix" issue="LANG-443">DateUtils - improve tests.</action>
+ <action type="fix" issue="LANG-204">Entities - multithreaded initialization.</action>
+ <action type="fix" issue="LANG-506">Entities - missing final modifiers; thread-safety issues.</action>
+ <action type="fix" issue="LANG-76">EnumUtils - getEnum() doesn't work well in 1.5+.</action>
+ <action type="fix" issue="LANG-584">ExceptionUtils - use immutable lock target.</action>
+ <action type="fix" issue="LANG-477">ExtendedMessageFormat - OutOfMemory with a pattern containing single quotes.</action>
+ <action type="fix" issue="LANG-538">FastDateFormat - call getTime() on a calendar to ensure timezone is in the right state.</action>
+ <action type="fix" issue="LANG-547">FastDateFormat - Remove unused field.</action>
+ <action type="fix" issue="LANG-511">LocaleUtils - Initialization of available locales in LocaleUtils can be deferred.</action>
+ <action type="fix" issue="LANG-457">NumberUtils - createNumber() throws a StringIndexOutOfBoundsException when only an "l" is passed in.</action>
+ <action type="fix" issue="LANG-521">NumberUtils - isNumber(String) and createNumber(String) both modified to support '2.'.</action>
+ <action type="fix" issue="LANG-432">StringUtils - improve handling of case-insensitive Strings.</action>
+ <action type="fix" issue="LANG-552">StringUtils - replaceEach() no longer NPEs when null appears in the last String[].</action>
+ <action type="fix" issue="LANG-460">StringUtils - correct Javadoc for startsWith() and startsWithIgnoreCase().</action>
+ <action type="fix" issue="LANG-421">StringEscapeUtils - escapeJava() escapes '/' characters.</action>
+ <action type="fix" issue="LANG-450">StringEscapeUtils - change escapeJavaStyleString() to throw UnhandledException instead swallowing IOException and returning null.</action>
+ <action type="fix" issue="LANG-419">WordUtils - fix StringIndexOutOfBoundsException when lower is greater than the String length.</action>
+ <action type="fix" issue="LANG-523">StrBuilder - Performance improvement by doubling the size of the String in ensureCapacity.</action>
+ <action type="fix" issue="LANG-575">Compare, Equals and HashCode builders - use ArrayUtils to avoid creating a temporary List.</action>
+ <action type="fix" issue="LANG-467">EqualsBuilder - removing the special handling of BigDecimal (LANG-393) to use compareTo instead of equals because it creates an inequality with HashCodeBuilder.</action>
+ <action type="fix" issue="LANG-574">HashCodeBuilder - Performance improvement: check for isArray to short-circuit the 9 instanceof checks.</action>
+ <action type="fix" issue="LANG-520">HashCodeBuilder - Changing the hashCode() method to return toHashCode().</action>
+ <action type="fix" issue="LANG-459">HashCodeBuilder - reflectionHashCode() can generate incorrect hashcodes.</action>
+ <action type="fix" issue="LANG-586">HashCodeBuilder and ToStringStyle - use of ThreadLocal causes memory leaks in container environments.</action>
+ <action type="fix" issue="LANG-487">ToStringBuilder - make default style thread-safe.</action>
+ <action type="fix" issue="LANG-472">RandomUtils - nextLong() always produces even numbers.</action>
+ <action type="fix" issue="LANG-592">RandomUtils - RandomUtils tests are failing frequently.</action>
+ </release>
+
+ <release version="2.4" date="2008-03-18" description="">
+ <action type="add" issue="LANG-322">ClassUtils.getShortClassName(String) inefficient.</action>
+ <action type="add" issue="LANG-269">Shouldn't Commons Lang's StringUtils have a "common" string method?.</action>
+ <action type="fix" issue="LANG-368">FastDateFormat getDateInstance() and getDateTimeInstance() assume Locale.getDefault() won't change.</action>
+ <action type="add" issue="LANG-402">OSGi-ify Lang.</action>
+ <action type="fix" issue="LANG-412">StrBuilder appendFixedWidth does not handle nulls.</action>
+ <action type="fix" issue="LANG-380">infinite loop in Fraction.reduce when numerator == 0.</action>
+ <action type="fix" issue="LANG-367">FastDateFormat thread safety.</action>
+ <action type="add" issue="LANG-298">ClassUtils.getShortClassName and ClassUtils.getPackageName and class of array.</action>
+ <action type="fix" issue="LANG-328">LocaleUtils.toLocale() rejects strings with only language+variant.</action>
+ <action type="fix" issue="LANG-334">Enum is not thread-safe.</action>
+ <action type="fix" issue="LANG-365">BooleanUtils.toBoolean() - invalid drop-thru in case statement causes StringIndexOutOfBoundsException.</action>
+ <action type="add" issue="LANG-333">ArrayUtils.toClass.</action>
+ <action type="fix" issue="LANG-360">Why does appendIdentityToString return null?.</action>
+ <action type="fix" issue="LANG-381">NumberUtils.min(floatArray) returns wrong value if floatArray[0] happens to be Float.NaN.</action>
+ <action type="fix" issue="LANG-346">Dates.round() behaves incorrectly for minutes and seconds.</action>
+ <action type="add" issue="LANG-407">StringUtils.length(String) returns null-safe length.</action>
+ <action type="add" issue="LANG-180">adding a StringUtils.replace method that takes an array or List of replacement strings.</action>
+ <action type="add" issue="LANG-383">Adding functionality to DateUtils to allow direct setting of various fields.</action>
+ <action type="add" issue="LANG-374">Add escaping for CSV columns to StringEscapeUtils.</action>
+ <action type="add" issue="LANG-326">StringUtils: startsWith / endsWith / startsWithIgnoreCase / endsWithIgnoreCase / removeStartIgnoreCase / removeEndIgnoreCase methods.</action>
+ <action type="add" issue="LANG-351">Extension to ClassUtils: Obtain the primitive class from a wrapper.</action>
+ <action type="fix" issue="LANG-399">Javadoc bugs - cannot find object.</action>
+ <action type="add" issue="LANG-345">Optimize HashCodeBuilder.append(Object).</action>
+ <action type="fix" issue="LANG-385">https://commons.apache.org/proper/commons-lang/developerguide.html "Building" section is incorrect and incomplete.</action>
+ <action type="fix" issue="LANG-410">Ambiguous / confusing names in StringUtils replace* methods.</action>
+ <action type="add" issue="LANG-257">Add new splitByWholeSeparatorPreserveAllTokens() methods to StringUtils.</action>
+ <action type="add" issue="LANG-356">Add getStartTime to StopWatch.</action>
+ <action type="add" issue="LANG-377">Perhaps add containsAny() methods?.</action>
+ <action type="fix" issue="LANG-353">Javadoc Example for EqualsBuilder is questionable.</action>
+ <action type="fix" issue="LANG-393">EqualsBuilder don't compare BigDecimals correctly.</action>
+ <action type="add" issue="LANG-192">Split camel case strings.</action>
+ <action type="add" issue="LANG-404">Add Calendar flavour format methods to DateFormatUtils.</action>
+ <action type="add" issue="LANG-379">Calculating A date fragment in any time-unit.</action>
+ <action type="add" issue="LANG-413">Memory usage improvement for StringUtils#getLevenshteinDistance().</action>
+ <action type="add" issue="LANG-362">Add ExtendedMessageFormat to org.apache.commons.lang.text.</action>
+ <action type="fix" issue="LANG-363">StringEscapeUtils.escapeJavaScript() method did not escape '/' into '\/', it will make IE render page incorrectly.</action>
+ <action type="add" issue="LANG-321">Add toArray() method to IntRange and LongRange classes.</action>
+ <action type="add" issue="LANG-375">add SystemUtils.IS_OS_WINDOWS_VISTA field.</action>
+ <action type="add" issue="LANG-329">Pointless synchronized in ThreadLocal.initialValue should be removed.</action>
+ <action type="add" issue="LANG-371">ToStringStyle Javadoc should show examples of styles.</action>
+ <action type="fix" issue="LANG-364">Documentation bug for ignoreEmptyTokens accessors in StrTokenizer.</action>
+ <action type="fix" issue="LANG-361">BooleanUtils toBooleanObject Javadoc does not match implementation.</action>
+ <action type="add" issue="LANG-338">truncateNicely method which avoids truncating in the middle of a word.</action>
+ </release>
+
+ <release version="2.3" date="2007-02-13" description="">
+ <action type="fix" issue="LANG-262">Use of enum prevents a classloader from being garbage collected resulting in out of memory exceptions.</action>
+ <action type="add" issue="LANG-289">NumberUtils.max(byte[]) and NumberUtils.min(byte[]) are missing.</action>
+ <action type="add" issue="LANG-291">Null-safe comparison methods for finding the most recent / least recent dates.</action>
+ <action type="fix" issue="LANG-315">StopWatch: suspend() acts as split(), if followed by stop().</action>
+ <action type="fix" issue="LANG-294">StrBuilder.replaceAll and StrBuilder.deleteAll can throw ArrayIndexOutOfBoundsException.</action>
+ <action type="fix" issue="LANG-299">Bug in method appendFixedWidthPadRight of class StrBuilder causes an ArrayIndexOutOfBoundsException.</action>
+ <action type="fix" issue="LANG-69"> ToStringBuilder throws StackOverflowError when an Object cycle exists.</action>
+ <action type="add" issue="LANG-282">Create more tests to test out the +=31 replacement code in DurationFormatUtils.</action>
+ <action type="fix" issue="LANG-295">StrBuilder contains usages of thisBuf.length when they should use size.</action>
+ <action type="add" issue="LANG-258">Enum Javadoc: 1) outline 5.0 native Enum migration 2) warn not to use the switch() , 3) point out approaches for persistence and gui.</action>
+ <action type="fix" issue="LANG-313">Wrong behavior of Entities.unescape.</action>
+ <action type="fix" issue="LANG-300">NumberUtils.createNumber throws NumberFormatException for one digit long.</action>
+ <action type="fix" issue="LANG-304">NullPointerException in isAvailableLocale(Locale).</action>
+ <action type="fix" issue="LANG-303">FastDateFormat.mRules is not transient or serializable.</action>
+ <action type="add" issue="LANG-268">StringUtils.join should allow you to pass a range for it (so it only joins a part of the array).</action>
+ <action type="fix" issue="LANG-102">Refactor Entities methods.</action>
+ <action type="fix" issue="LANG-314">Tests fail to pass when building with Maven 2.</action>
+ <action type="fix" issue="LANG-281">DurationFormatUtils returns wrong result.</action>
+ <action type="fix" issue="LANG-292">unescapeXml("&amp;12345678;") should be "&amp;12345678;".</action>
+ <action type="add" issue="LANG-287">Optimize StringEscapeUtils.unescapeXml(String).</action>
+ <action type="add" issue="LANG-310">BooleanUtils isNotTrue/isNotFalse.</action>
+ <action type="add" issue="LANG-306">Extra StrBuilder methods.</action>
+ <action type="add" issue="LANG-275">Add a pair of StringUtils.substringsBetween;String[] methods.</action>
+ <action type="fix" issue="LANG-279">HashCodeBuilder throws java.lang.StackOverflowError when an object contains a cycle.</action>
+ <action type="add" issue="LANG-266">Wish for StringUtils.join(Collection, *).</action>
+ </release>
+
+ <release version="2.2" date="2006-10-04" description="">
+ <action type="fix" issue="LANG-45">StrBuilderTest#testReplaceStringString fails.</action>
+ <action type="fix" issue="LANG-42">EqualsBuilder.append(Object[], Object[]) crashes with a NullPointerException if an element of the first array is null.</action>
+ <action type="fix" issue="LANG-286">Serialization - not backwards compatible.</action>
+ <action type="fix" issue="LANG-50"> Replace Clover with Cobertura.</action>
+ <action type="fix" issue="LANG-259">ValuedEnum.compareTo(Object other) not typesafe - it easily could be...</action>
+ <action type="fix" issue="LANG-271">LocaleUtils test fails under Mustang.</action>
+ <action type="fix" issue="LANG-2">Javadoc example for StringUtils.splitByWholeSeparator incorrect.</action>
+ <action type="fix" issue="LANG-3">PADDING array in StringUtils overflows on '\uffff'.</action>
+ <action type="fix" issue="LANG-10">ClassUtils.primitiveToWrapper and Void.</action>
+ <action type="fix" issue="LANG-37">unit test for org.apache.commons.lang.text.StrBuilder.</action>
+ <action type="fix" issue="LANG-59">DateUtils.truncate method is buggy when dealing with DST switching hours.</action>
+ <action type="fix" issue="LANG-100">RandomStringUtils.random() family of methods create invalid Unicode sequences.</action>
+ <action type="fix" issue="LANG-106">StringUtils#getLevenshteinDistance() performance is sub-optimal.</action>
+ <action type="fix" issue="LANG-112">Wrong length check in StrTokenizer.StringMatcher.</action>
+ <action type="fix" issue="LANG-105">ExceptionUtils goes into infinite loop in getThrowables is throwable.getCause() == throwable.</action>
+ <action type="fix" issue="LANG-117">FastDateFormat: wrong format for date "01.01.1000".</action>
+ <action type="fix" issue="LANG-123">Unclear Javadoc for DateUtils.iterator().</action>
+ <action type="fix" issue="LANG-130">Memory "leak" in StringUtils.</action>
+ <action type="add" issue="LANG-260">StringEscapeUtils should expose escape*() methods taking Writer argument.</action>
+ <action type="fix" issue="LANG-141">Fraction.toProperString() returns -1/1 for -1.</action>
+ <action type="fix" issue="LANG-152">DurationFormatUtils.formatDurationWords "11 &lt;unit&gt;s" gets converted to "11 &lt;unit&gt;".</action>
+ <action type="fix" issue="LANG-148">Performance modifications on StringUtils.replace.</action>
+ <action type="fix" issue="LANG-150">StringEscapeUtils.unescapeHtml skips first entity after standalone ampersand.</action>
+ <action type="fix" issue="LANG-140">DurationFormatUtils.formatPeriod() returns the wrong result.</action>
+ <action type="add" issue="LANG-186">Request for MutableBoolean implementation.</action>
+ <action type="add" issue="LANG-198">New method for EqualsBuilder.</action>
+ <action type="add" issue="LANG-212">New ExceptionUtils method setCause().</action>
+ <action type="add" issue="LANG-217">Add Mutable&lt;Type&gt; to&lt;Type&gt;() methods.</action>
+ <action type="add" issue="LANG-216">Provides a Class.getPublicMethod which returns public invocable Method.</action>
+ <action type="add" issue="LANG-226">Using ReflectionToStringBuilder and excluding secure fields.</action>
+ <action type="add" issue="LANG-194">add generic add method to DateUtils.</action>
+ <action type="add" issue="LANG-220">Tokenizer Enhancements: reset input string, static CSV/TSV factories.</action>
+ <action type="add" issue="LANG-242">Trivial cleanup of Javadoc in various files.</action>
+ <action type="add" issue="LANG-246">CompositeFormat.</action>
+ <action type="add" issue="LANG-250">Performance boost for RandomStringUtils.</action>
+ <action type="add" issue="LANG-254">Enhanced Class.forName version.</action>
+ <action type="add" issue="LANG-263">Add StringUtils.containsIgnoreCase(...).</action>
+ <action type="add" issue="LANG-267">Support char array converters on ArrayUtils.</action>
+ <action type="fix" issue="LANG-25">DurationFormatUtils.formatDurationISO() Javadoc is missing T in duration string between date and time part.</action>
+ <action type="fix" issue="LANG-272">Minor build and checkstyle changes.</action>
+ <action type="fix" issue="LANG-277">Javadoc errors on StringUtils.splitPreserveAllTokens(String, char).</action>
+ <action type="fix" issue="LANG-122">EscapeUtil.escapeHtml() should clarify that it does not escape ' chars to &amp;apos;.</action>
+ <action type="add" issue="LANG-161">Add methods and tests to StrBuilder.</action>
+ <action type="add" issue="LANG-162">replace() length calculation improvement.</action>
+ <action type="add" issue="LANG-166">New interpolation features.</action>
+ <action type="add" issue="LANG-169">Implementation of escape/unescapeHtml methods with Writer.</action>
+ <action type="add" issue="LANG-176">CompareToBuilder excludeFields for reflection method.</action>
+ <action type="add" issue="LANG-159">Add WordUtils.getInitials(String).</action>
+ <action type="fix" issue="LANG-261">Error in an example in the Javadoc of the StringUtils.splitPreserveAllTokens() method.</action>
+ <action type="fix" issue="LANG-264">ToStringBuilder/HashCodeBuilder Javadoc code examples.</action>
+ <action type="fix" issue="LANG-265">Cannot build tests from latest SVN.</action>
+ <action type="add" issue="LANG-270">minor Javadoc improvements for StringUtils.stripXxx() methods.</action>
+ <action type="fix" issue="LANG-278">Javadoc for StringUtils.removeEnd is incorrect.</action>
+ <action type="fix" issue="LANG-127">Minor tweak to fix of bug # 26616.</action>
+ </release>
+
+ <release version="2.1" date="2005-06-13" description="">
+ <action type="fix" issue="LANG-103">make optional parameters in FastDateFormat really optional.</action>
+ <action type="fix" issue="LANG-149">Nestable.indexOfThrowable(Class) uses Class.equals() to match.</action>
+ <action type="fix" issue="LANG-30">buffer under/overrun on Strings.strip, stripStart &amp; stripEnd.</action>
+ <action type="fix" issue="LANG-19">ToStringStyle.setArrayEnd(String) doesn't replace null with empty string.</action>
+ <action type="fix" issue="LANG-80">New class proposal: CharacterEncoding.</action>
+ <action type="fix" issue="LANG-43">SystemUtils fails init on HP-UX.</action>
+ <action type="fix" issue="LANG-134">Javadoc - 'four basic XML entities' should be 5 (apos is missing).</action>
+ <action type="fix" issue="LANG-156">o.a.c.lang.enum.ValuedEnum: 'enum' is a keyword in JDK 1.5.0.</action>
+ <action type="fix" issue="LANG-131">StringEscapeUtils.unescapeHtml() doesn't handle an empty entity.</action>
+ <action type="fix" issue="LANG-6">EqualsBuilder.append(Object[], Object[]) incorrectly checks that rhs[i] is instance of lhs[i]'s class.</action>
+ <action type="fix" issue="LANG-33">Method enums.Enum.equals(Object o) doesn't work correctly.</action>
+ <action type="fix" issue="LANG-31">ExceptionUtils.addCauseMethodName(String) does not check for duplicates.</action>
+ <action type="fix" issue="LANG-136">Make StopWatch validate state transitions.</action>
+ <action type="fix" issue="LANG-124">enum package is not compatible with 1.5 jdk.</action>
+ <action type="fix" issue="LANG-128">WordUtils capitalizeFully() throws a null pointer exception.</action>
+ <action type="fix" issue="LANG-138">ValuedEnum.</action>
+ <action type="fix" issue="LANG-133">parseDate class from HttpClient's DateParser class.</action>
+ <action type="fix" issue="LANG-62">ArrayUtils.isEquals() throws ClassCastException when array1 and array2 are different dimension.</action>
+ <action type="fix" issue="LANG-57">ClassCastException in Enum.equals(Object).</action>
+ <action type="fix" issue="LANG-107">FastDateFormat year bug.</action>
+ <action type="fix" issue="LANG-77">unbalanced ReflectionToStringBuilder.</action>
+ <action type="fix" issue="LANG-86">FastDateFormat.getDateInstance(int, Locale) always uses the pattern from the first invocation.</action>
+ <action type="fix" issue="LANG-79">ReflectionToStringBuilder.toString(null) throws exception by design.</action>
+ <action type="fix" issue="LANG-126">Make ClassUtils methods null-safe and not throw an IAE.</action>
+ <action type="fix" issue="LANG-5">StringUtils.split ignores empty items.</action>
+ <action type="fix" issue="LANG-144">EqualsBuilder.append(Object[], Object[]) throws NPE.</action>
+ <action type="fix" issue="LANG-74">ArrayUtils.addAll doesn't always return new array.</action>
+ <action type="fix" issue="LANG-81">Enum.equals does not handle different class loaders.</action>
+ <action type="fix" issue="LANG-27">Add SystemUtils.AWT_TOOLKIT and others.</action>
+ <action type="fix" issue="LANG-14">Throwable cause for NotImplementedException.</action>
+ <action type="fix" issue="LANG-28">ClassUtils.primitivesToWrappers method.</action>
+ <action type="fix" issue="LANG-120">public static boolean DateUtils.equals(Date dt1, Date dt2) ?.</action>
+ <action type="fix" issue="LANG-7">Documentation error in StringUtils.replace.</action>
+ <action type="fix" issue="LANG-125">DateUtils constants should be long.</action>
+ <action type="fix" issue="LANG-13">DateUtils.truncate() is off by one hour when using a date in DST switch 'zone'.</action>
+ <action type="fix" issue="LANG-118">StringEscapeUtils.unescapeHtml() doesn't handle hex entities.</action>
+ <action type="fix" issue="LANG-99">new StringUtils.replaceChars behaves differently from old CharSetUtils.translate.</action>
+ <action type="fix" issue="LANG-41">last substring returned by StringUtils.split( String, String, int ) is too long.</action>
+ <action type="fix" issue="LANG-119">Can't subclass EqualsBuilder because isEquals is private.</action>
+ <action type="fix" issue="LANG-158">new StringUtils.split methods that split on the whole separator string.</action>
+ <action type="add" issue="LANG-172">New method for converting a primitive Class to its corresponding wrapper Class.</action>
+ <action type="add" issue="LANG-222">Add convenience format(long) methods to FastDateFormat.</action>
+ <action type="fix" issue="LANG-116">Enum's outer class may not be loaded for EnumUtils.</action>
+ <action type="add" issue="LANG-219">WordUtils.capitalizeFully(String str) should take a delimiter.</action>
+ <action type="add" issue="LANG-183">Make Javadoc cross-linking configurable.</action>
+ <action type="fix" issue="LANG-82">Minor Javadoc fixes for StringUtils.contains(String, String).</action>
+ <action type="fix" issue="LANG-32">Error in Javadoc for StringUtils.chomp(String, String).</action>
+ <action type="fix" issue="LANG-95">StringUtils.defaultString: Documentation error.</action>
+ <action type="add" issue="LANG-233">Add hashCode-support to class ObjectUtils.</action>
+ <action type="add" issue="LANG-202">add another "known method" to ExceptionUtils.</action>
+ <action type="add" issue="LANG-235">Enhancement of ExceptionUtils.CAUSE_METHOD_NAMES.</action>
+ <action type="fix" issue="LANG-24">DateUtils.truncate oddity at the far end of the Date spectrum.</action>
+ <action type="add" issue="LANG-232">add getLength() method to ArrayUtils.</action>
+ <action type="add" issue="LANG-171">Validate.java: fixes comment skew, removes unused loop counter.</action>
+ <action type="add" issue="LANG-179">StringUtils.isAsciiPrintable().</action>
+ <action type="add" issue="LANG-167">ExceptionUtils: new getCause() methodname (for tomcat-exception).</action>
+ <action type="fix" issue="LANG-85">fixes 75 typos.</action>
+ <action type="add" issue="LANG-230">mutable numbers.</action>
+ <action type="add" issue="LANG-191">Javadoc fixes for ClassUtils.</action>
+ <action type="add" issue="LANG-184">Add StringUtils.nIndexOf?.</action>
+ <action type="fix" issue="LANG-135">Javadoc fixes for CharSetUtils.</action>
+ <action type="fix" issue="LANG-154">Remove redundant check for null separator in StringUtils#join.</action>
+ <action type="add" issue="LANG-247">Class and Package Comparators for ClassUtils.</action>
+ <action type="add" issue="LANG-256">add remove methods to ArrayUtils.</action>
+ <action type="add" issue="LANG-185">WordUtils capitalize improvement.</action>
+ <action type="add" issue="LANG-173">add isEmpty method to ArrayUtils.</action>
+ <action type="add" issue="LANG-168">lang.math.Fraction class deficiencies.</action>
+ <action type="add" issue="LANG-207">Add methods to ArrayUtils: add at end and insert-like ops.</action>
+ <action type="add" issue="LANG-239">Add SystemUtils methods for directory properties.</action>
+ <action type="add" issue="LANG-189">Add method that validates Collection elements are a certain type.</action>
+ <action type="add" issue="LANG-224">elapsed time formatting utility method.</action>
+ </release>
+
+ <release version="2.0" date="2003-09-02" description="">
+ <action type="fix" issue="LANG-20">Infinite loop in ToStringBuilder.reflectionToString for inner classes.</action>
+ <action type="fix" issue="LANG-75">NumberUtils.createBigDecimal("") NPE in Sun 1.3.1_08.</action>
+ <action type="fix" issue="LANG-38">Rationalize StringUtils slice functions.</action>
+ <action type="fix" issue="LANG-53">SystemUtils.IS_OS_OS2 Javadoc is wrong.</action>
+ <action type="fix" issue="LANG-142">A small, but important Javadoc fix for Fraction proper whole and numerator.</action>
+ <action type="fix" issue="LANG-70">Adding tolerance to double[] search methods in ArrayUtils.</action>
+ <action type="fix" issue="LANG-9">lang.builder classes Javadoc edits (mostly typo fixes).</action>
+ <action type="fix" issue="LANG-63">StringUtils Javadoc and test enhancements.</action>
+ <action type="fix" issue="LANG-132">SystemUtils.IS_OS_*, IS_JAVA_* are always false.</action>
+ <action type="fix" issue="LANG-143">Improve util.Validate tests.</action>
+ <action type="fix" issue="LANG-155">maven-beta10 checkstyle problem.</action>
+ <action type="fix" issue="LANG-147">StringUtils.chopNewLine - StringIndexOutOfBoundsException.</action>
+ <action type="fix" issue="LANG-73">ToStringBuilder doesn't work well in subclasses.</action>
+ <action type="fix" issue="LANG-48">static option for reversing the stacktrace.</action>
+ <action type="fix" issue="LANG-87">NullPointerException in CompareToBuilder.</action>
+ <action type="fix" issue="LANG-84">RandomStringUtils.randomAlpha methods omit 'z'.</action>
+ <action type="fix" issue="LANG-129">test.time fails in Japanese (non-us) locale.</action>
+ <action type="fix" issue="LANG-94">NumberUtils.isNumber allows illegal trailing characters.</action>
+ <action type="fix" issue="LANG-137">Improve Javadoc and overflow behavior of Fraction.</action>
+ <action type="fix" issue="LANG-55">RandomStringUtils infinite loop with length &gt; 1.</action>
+ <action type="fix" issue="LANG-47">test.lang fails if compiled with non iso-8859-1 locales.</action>
+ <action type="fix" issue="LANG-113">SystemUtils does not play nice in an Applet.</action>
+ <action type="fix" issue="LANG-111">time unit tests fail on Sundays.</action>
+ <action type="fix" issue="LANG-90">java.lang.ExceptionInInitializerError thrown by JVMRandom constructor.</action>
+ <action type="fix" issue="LANG-78">StringUtils.chomp does not match Perl.</action>
+ <action type="fix" issue="LANG-36">patch and test case fixing problem with RandomStringUtils.random().</action>
+ <action type="fix" issue="LANG-151">General case: infinite loop: ToStringBuilder.reflectionToString.</action>
+ <action type="fix" issue="LANG-35">Should ToStringBuilder.reflectionToString handle arrays?.</action>
+ <action type="fix" issue="LANG-83">EnumUtils nit: The import java.io.Serializable is never used.</action>
+ <action type="fix" issue="LANG-12">Example in Javadoc for ToStringBuilder wrong for append.</action>
+ <action type="fix" issue="LANG-110">Added class hierarchy support to HashCodeBuilder.reflectionHashCode().</action>
+ <action type="fix" issue="LANG-71">ExceptionUtils new methods.</action>
+ <action type="fix" issue="LANG-15">Infinite loop in StringUtils.replace(text, repl, with) + FIX.</action>
+ <action type="fix" issue="LANG-93">StackOverflow due to ToStringBuilder.</action>
+ <action type="fix" issue="LANG-39">No Javadoc for NestableDelegate.</action>
+ <action type="fix" issue="LANG-49">Specify initial size for Enum's HashMap.</action>
+ <action type="fix" issue="LANG-146">Enum does not support inner sub-classes.</action>
+ <action type="fix" issue="LANG-157">Removed compile warning in ObjectUtils.</action>
+ <action type="fix" issue="LANG-96">SystemUtils.IS_JAVA_1_5 Javadoc is wrong.</action>
+ <action type="fix" issue="LANG-16">NumberRange inaccurate for Long, etc.</action>
+ <action type="fix" issue="LANG-4">Hierarchy support in ToStringBuilder.reflectionToString().</action>
+ <action type="fix" issue="LANG-56">StringUtils.countMatches loops forever if substring empty.</action>
+ <action type="add" issue="LANG-209">Javadoc fixes (remove @links to non-public identifiers).</action>
+ <action type="add" issue="LANG-210">Add Javadoc examples and tests for StringUtils.</action>
+ <action type="add" issue="LANG-170">Make NumberUtils null handling consistent.</action>
+ <action type="fix" issue="LANG-145">Unused field 'startFinal' in DateIterator.</action>
+ <action type="add" issue="LANG-214">reduce object creation in ToStringBuilder.</action>
+ <action type="add" issue="LANG-228">Improved tests, Javadoc for CharSetUtils, StringEscapeUtils.</action>
+ <action type="add" issue="LANG-252">NumberUtils min/max, BooleanUtils.xor, and ArrayUtils toPrimitive and toObject.</action>
+ <action type="add" issue="LANG-208">Javadoc, tests improvements for CharSet, CharSetUtils.</action>
+ <action type="add" issue="LANG-205">StringUtil enhancement.</action>
+ <action type="add" issue="LANG-164">Javadoc nit.</action>
+ <action type="add" issue="LANG-206">Additional Lang Method Suggestions.</action>
+ <action type="add" issue="LANG-178">Make NestableDelegate methods public instead of package private.</action>
+ <action type="add" issue="LANG-174">Missing @since tags.</action>
+ <action type="add" issue="LANG-245">Refactored reflection feature of ToStringBuilder into new ReflectionToStringBuilder.</action>
+ <action type="fix" issue="LANG-51">Typo in documentation.</action>
+ <action type="fix" issue="LANG-1">Patch for Javadoc.</action>
+ <action type="add" issue="LANG-244">Add join(..., char c) to StringUtils (and some performance fixes). Even contains tests!.</action>
+ <action type="add" issue="LANG-231">Resurrect the WordWrapUtils from commons-sandbox/utils.</action>
+ <action type="fix" issue="LANG-139">EnumTest fails on Linux Sun JDK 1.3.0.</action>
+ <action type="add" issue="LANG-234">What to do with FastDateFormat unused private constructors.</action>
+ <action type="add" issue="LANG-240">Added class hierarchy support to CompareToBuilder.reflectionCompare().</action>
+ <action type="add" issue="LANG-190">Removed compile warning in FastDateFormat.</action>
+ <action type="fix" issue="LANG-97">typo in the Javadoc example code.</action>
+ <action type="add" issue="LANG-249">MethodUtils: Removed unused code/unused local vars.</action>
+ <action type="add" issue="LANG-237">Hierarchy support in EqualsBuilder.reflectionEquals().</action>
+ <action type="fix" issue="LANG-91">Javadoc Errata.</action>
+ <action type="add" issue="LANG-215">ArrayUtils.contains().</action>
+ <action type="add" issue="LANG-221">More flexibility for getRootCause in ExceptionUtils.</action>
+ </release>
+
+ <release version="1.0.1" date="2002-11-25" description="Quick bugfix to 1.0">
+ <action type="fix">NumberRange.getMaximum returns minimum.</action>
+ <action type="fix">Enum constructor validations.</action>
+ <action type="fix">NestableException/Delegate is not serializable.</action>
+ <action type="fix">split using null and max less than actual token count adds "null".</action>
+ <action type="add">ExceptionUtils cannot handle J2EE-Exception in a default way.</action>
+ </release>
+
+ <release version="1.0" date="2002-10-04" description="First release of Commons Lang">
+ </release>
+
+ </body>
+</document>
diff --git a/src/changes/release-notes.vm b/src/changes/release-notes.vm
new file mode 100644
index 000000000..7c8d90d67
--- /dev/null
+++ b/src/changes/release-notes.vm
@@ -0,0 +1,144 @@
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements. See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership. The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied. See the License for the
+## specific language governing permissions and limitations
+## under the License.
+##
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+ ${project.name}
+ Version ${version}
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the ${version} version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.9 and onwards now targets Java 8, making use of features that arrived with Java 8.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+$introduction.replaceAll("(?<!\015)\012", "
+").replaceAll("(?m)^ +","")
+
+## N.B. the available variables are described here:
+## https://maven.apache.org/plugins/maven-changes-plugin/examples/using-a-custom-announcement-template.html
+##
+## Hack to improve layout: replace all pairs of spaces with a single new-line
+$release.description.replaceAll(" ", "
+")
+
+## set up indent sizes. Only change indent1
+#set($props=${project.properties})
+#set($jiralen=$props.get("commons.jira.id").length())
+## indent1 = POOL-nnnn:
+#set($blanklen=$jiralen+6)## +6 for "-nnnn:"
+## must be at least as long as the longest JIRA id
+#set($blanks=" ")
+#set($indent1=$blanks.substring(0,$blanklen))
+## indent2 allows for issue wrapper
+#set($indent2="$indent1 ")
+##
+#macro ( processaction )
+## Use replaceAll to fix up LF-only line ends on Windows.
+#set($action=$actionItem.getAction().replaceAll("\n","
+"))
+## Fix up indentation for multi-line action descriptions
+#set($action=$action.replaceAll("(?m)^ +",$indent2))
+#if ($actionItem.getIssue())
+#set($issue="$actionItem.getIssue():")
+## Pad shorter issue numbers
+#if ($issue.length() < $indent1.length())#set ($issue="$issue ")#end
+#if ($issue.length() < $indent1.length())#set ($issue="$issue ")#end
+#if ($issue.length() < $indent1.length())#set ($issue="$issue ")#end
+#else
+#set($issue=$indent1)
+#end
+#if ($actionItem.getDueTo())
+#set($dueto=" Thanks to $actionItem.getDueTo().")
+#else
+#set($dueto="")
+#end
+o $issue ${action}$dueto
+#set($action="")
+#set($issue="")
+#set($dueto="")
+#end
+##
+#if ($release.getActions().size() == 0)
+No changes defined in this version.
+#else
+Changes in this version include:
+
+#if ($release.getActions('add').size() !=0)
+New features:
+#foreach($actionItem in $release.getActions('add'))
+#processaction()
+#end
+#end
+
+#if ($release.getActions('fix').size() !=0)
+Fixed Bugs:
+#foreach($actionItem in $release.getActions('fix'))
+#processaction()
+#end
+#end
+
+#if ($release.getActions('update').size() !=0)
+Changes:
+#foreach($actionItem in $release.getActions('update'))
+#processaction()
+#end
+#end
+
+#if ($release.getActions('remove').size() !=0)
+Removed:
+#foreach($actionItem in $release.getActions('remove'))
+#processaction()
+#end
+#end
+## End of main loop
+#end
+
+Historical list of changes: ${project.url}changes-report.html
+
+For complete information on ${project.name}, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the ${project.name} website:
+
+${project.url}
+
+Download page: ${project.url}download_lang.cgi
+
+Have fun!
+-Apache Commons Team
diff --git a/src/conf/exclude-pmd.properties b/src/conf/exclude-pmd.properties
new file mode 100644
index 000000000..73c6f9f0b
--- /dev/null
+++ b/src/conf/exclude-pmd.properties
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# We have ThreadGroup utilities
+org.apache.commons.lang3.ThreadUtils = AvoidThreadGroup
+
+# TODO?
+# violation beginline="2900" endline="2900" begincolumn="13" endcolumn="21" rule="AvoidBranchingStatementAsLastInLoop" ruleset="Error Prone" package="org.apache.commons.lang3" class="StringUtils" method="indexOfAnyBut"
+org.apache.commons.lang3.StringUtils = AvoidBranchingStatementAsLastInLoop
+
+# Bug in PMD when the same class name exists in different packages
+# Unnecessary use of fully qualified name 'org.apache.commons.lang3.function.FailableRunnable' due to existing same package import 'org.apache.commons.lang3.*'
+org.apache.commons.lang3.Functions = UnnecessaryFullyQualifiedName\
diff --git a/src/conf/spotbugs-exclude-filter.xml b/src/conf/spotbugs-exclude-filter.xml
new file mode 100644
index 000000000..4d609e71e
--- /dev/null
+++ b/src/conf/spotbugs-exclude-filter.xml
@@ -0,0 +1,197 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ This file contains some false positive bugs detected by findbugs. Their
+ false positive nature has been analyzed individually and they have been
+ put here to instruct findbugs it must ignore them.
+-->
+<FindBugsFilter
+ xmlns="https://github.com/spotbugs/filter/3.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
+
+ <!-- TODO Can any of these be done without breaking binary compatibility? -->
+ <Match>
+ <Class name="~.*" />
+ <Or>
+ <Bug pattern="EI_EXPOSE_REP" />
+ <Bug pattern="EI_EXPOSE_REP2" />
+ <Bug pattern="MS_EXPOSE_REP" />
+ </Or>
+ </Match>
+
+ <!-- TODO Can any of these be done without breaking binary compatibility? -->
+ <Match>
+ <Class name="org.apache.commons.lang3.reflect.FieldUtils" />
+ <Bug pattern="REFLF_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_FIELD" />
+ </Match>
+
+ <!-- https://github.com/spotbugs/spotbugs/issues/1504 -->
+ <Match>
+ <Class name="org.apache.commons.lang3.ArrayUtils" />
+ <Method name="shuffle" />
+ <Bug pattern="DMI_RANDOM_USED_ONLY_ONCE" />
+ </Match>
+
+ <!-- https://github.com/spotbugs/spotbugs/issues/1504 -->
+ <Match>
+ <Class name="org.apache.commons.lang3.RandomStringUtils" />
+ <Method name="random" />
+ <Bug pattern="DMI_RANDOM_USED_ONLY_ONCE" />
+ </Match>
+
+ <Match>
+ <Class name="org.apache.commons.lang3.ArrayUtils" />
+ <Method name="addFirst" />
+ <Bug pattern="NP_LOAD_OF_KNOWN_NULL_VALUE" />
+ </Match>
+
+ <!-- Reason: Optimization to use == -->
+ <Match>
+ <Class name="org.apache.commons.lang3.BooleanUtils" />
+ <Or>
+ <Method name="toBoolean" />
+ <Method name="toBooleanObject" />
+ </Or>
+ <Bug pattern="ES_COMPARING_PARAMETER_STRING_WITH_EQ" />
+ </Match>
+ <Match>
+ <Class name="org.apache.commons.lang3.BooleanUtils" />
+ <Method name="toBoolean" />
+ <Bug pattern="RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN" />
+ </Match>
+
+ <!-- Reason: Behavior documented in javadoc -->
+ <Match>
+ <Class name="org.apache.commons.lang3.BooleanUtils" />
+ <Or>
+ <Method name="negate" />
+ <Method name="toBooleanObject" />
+ </Or>
+ <Bug pattern="NP_BOOLEAN_RETURN_NULL" />
+ </Match>
+
+ <!-- Reason: base class cannot be changed and field is properly checked against null so behavior is OK -->
+ <Match>
+ <Class name="org.apache.commons.lang3.text.ExtendedMessageFormat" />
+ <Method name="applyPattern" />
+ <Bug pattern="UR_UNINIT_READ_CALLED_FROM_SUPER_CONSTRUCTOR" />
+ </Match>
+
+ <!-- Reason: Optimization to use == -->
+ <Match>
+ <Class name="org.apache.commons.lang3.StringUtils" />
+ <Or>
+ <Method name="indexOfDifference"/>
+ <Method name="compare" params="java.lang.String,java.lang.String,boolean"/>
+ <Method name="compareIgnoreCase" params="java.lang.String,java.lang.String,boolean"/>
+ </Or>
+ <Bug pattern="ES_COMPARING_PARAMETER_STRING_WITH_EQ" />
+ </Match>
+
+ <!-- Reason: Very much intended to do a fall through on the switch -->
+ <Match>
+ <Class name="org.apache.commons.lang3.math.NumberUtils" />
+ <Method name="createNumber"/>
+ <Bug pattern="SF_SWITCH_FALLTHROUGH" />
+ </Match>
+
+ <!-- Reason: Very much intended to do a fall through on the switch -->
+ <Match>
+ <Class name="org.apache.commons.lang3.time.DateUtils" />
+ <Method name="getFragment"/>
+ <Bug pattern="SF_SWITCH_FALLTHROUGH" />
+ </Match>
+
+ <!-- Reason: toProperString is lazily loaded -->
+ <Match>
+ <Class name="org.apache.commons.lang3.math.Fraction" />
+ <Field name="toProperString" />
+ <Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED" />
+ </Match>
+
+ <!-- Reason: It does call super.clone(), but via a subsequent method -->
+ <Match>
+ <Class name="org.apache.commons.lang3.text.StrTokenizer" />
+ <Method name="clone"/>
+ <Bug pattern="CN_IDIOM_NO_SUPER_CALL" />
+ </Match>
+
+ <!-- Reason: FindBugs 2.0.2 used in maven-findbugs-plugin 2.5.2 seems to have problems with detection of default cases
+ in switch statements. All the excluded methods have switch statements that contain a default case. -->
+ <Match>
+ <Class name="org.apache.commons.lang3.math.NumberUtils"/>
+ <Method name="createNumber" />
+ <Bug pattern="SF_SWITCH_NO_DEFAULT" />
+ </Match>
+ <!-- Reason: FindBugs does not correctly recognize default branches in switch statements without break statements.
+ See, e.g., the report at https://sourceforge.net/p/findbugs/bugs/1298 -->
+ <Match>
+ <Class name="org.apache.commons.lang3.time.FastDateParser"/>
+ <Or>
+ <Method name="getStrategy" />
+ <Method name="simpleQuote" params="java.lang.StringBuilder, java.lang.String"/>
+ </Or>
+ <Bug pattern="SF_SWITCH_NO_DEFAULT" />
+ </Match>
+
+ <!-- Reason: FindBugs cannot correctly recognize default branches in switch statements without break statements.
+ See, e.g., the report at https://sourceforge.net/p/findbugs/bugs/1298 -->
+ <Match>
+ <Class name="org.apache.commons.lang3.time.FastDatePrinter"/>
+ <Method name="appendFullDigits" params="java.lang.Appendable, int, int"/>
+ <Bug pattern="SF_SWITCH_NO_DEFAULT" />
+ </Match>
+
+ <!-- Reason: The fallthrough on the switch statement is intentional -->
+ <Match>
+ <Class name="org.apache.commons.lang3.time.FastDatePrinter"/>
+ <Method name="appendFullDigits" params="java.lang.Appendable, int, int"/>
+ <Bug pattern="SF_SWITCH_FALLTHROUGH" />
+ </Match>
+
+ <!-- Reason: Internal class that is used only as a key for an internal FormatCache. For this reason we can
+ be sure, that equals will never be called with null or types other than MultipartKey.
+ -->
+ <Match>
+ <Class name="org.apache.commons.lang3.time.FormatCache$MultipartKey" />
+ <Method name="equals" />
+ <Bug pattern="BC_EQUALS_METHOD_SHOULD_WORK_FOR_ALL_OBJECTS" />
+ </Match>
+ <Match>
+ <Class name="org.apache.commons.lang3.time.FormatCache$MultipartKey" />
+ <Method name="equals" />
+ <Bug pattern="NP_EQUALS_SHOULD_HANDLE_NULL_ARGUMENT" />
+ </Match>
+
+ <!-- Reason: toString() can return null! -->
+ <Match>
+ <Class name="org.apache.commons.lang3.compare.ObjectToStringComparator" />
+ <Method name="compare" />
+ <Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" />
+ </Match>
+
+ <!-- Reason: requireNonNull is supposed to take a nullable parameter,
+ whatever Spotbugs thinks of it. -->
+ <Match>
+ <Class name="org.apache.commons.lang3.function.Objects" />
+ <Method name="requireNonNull" />
+ <Bug pattern="NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" />
+ </Match>
+</FindBugsFilter>
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;
diff --git a/src/media/logo.xcf b/src/media/logo.xcf
new file mode 100644
index 000000000..e73d3710d
--- /dev/null
+++ b/src/media/logo.xcf
Binary files differ
diff --git a/src/site/resources/.htaccess b/src/site/resources/.htaccess
new file mode 100644
index 000000000..750861987
--- /dev/null
+++ b/src/site/resources/.htaccess
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+AddCharset utf-8 .txt .html
diff --git a/src/site/resources/checkstyle/checkstyle-suppressions.xml b/src/site/resources/checkstyle/checkstyle-suppressions.xml
new file mode 100644
index 000000000..e62645c4f
--- /dev/null
+++ b/src/site/resources/checkstyle/checkstyle-suppressions.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!DOCTYPE suppressions PUBLIC "-//Checkstyle//DTD SuppressionFilter Configuration 1.0//EN" "https://checkstyle.org/dtds/suppressions_1_0.dtd">
+<suppressions>
+ <suppress checks="JavadocMethod" files=".*[/\\]test[/\\].*"/>
+ <suppress checks="JavadocPackage" files=".*[/\\]test[/\\].*"/>
+ <!-- exclude generated JMH classes from all checks -->
+ <suppress checks="[a-zA-Z0-9]*" files=".*[/\\]generated-test-sources[/\\].*"/>
+ <suppress checks="RedundantModifier" files="ConstructorUtilsTest" lines="0-99999"/>
+ <!-- Windows-only workaround -->
+ <suppress checks="NewlineAtEndOfFile" files="target[/\\]maven-archiver[/\\]pom.properties"/>
+</suppressions>
diff --git a/src/site/resources/checkstyle/checkstyle.xml b/src/site/resources/checkstyle/checkstyle.xml
new file mode 100644
index 000000000..ea572b9ed
--- /dev/null
+++ b/src/site/resources/checkstyle/checkstyle.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<!DOCTYPE module PUBLIC
+ "-//Checkstyle//DTD Checkstyle Configuration 1.2//EN"
+ "https://checkstyle.org/dtds/configuration_1_2.dtd">
+
+<!-- Apache Commons Lang customization of default Checkstyle behavior -->
+<module name="Checker">
+ <property name="localeLanguage" value="en"/>
+ <module name="JavadocPackage"/>
+ <module name="NewlineAtEndOfFile">
+ <property name="lineSeparator" value="lf" />
+ </module>
+ <module name="FileTabCharacter">
+ <property name="fileExtensions" value="java,xml"/>
+ </module>
+ <module name="RegexpSingleline">
+ <!-- \s matches whitespace character, $ matches end of line. -->
+ <property name="format" value="\s+$"/>
+ <property name="message" value="Line has trailing spaces."/>
+ </module>
+ <module name="SuppressionFilter">
+ <property name="file" value="src/site/resources/checkstyle/checkstyle-suppressions.xml"/>
+ </module>
+ <module name="TreeWalker">
+ <module name="AvoidStarImport"/>
+ <module name="IllegalImport"/>
+ <module name="RedundantImport"/>
+ <module name="UnusedImports"/>
+ <module name="NeedBraces"/>
+ <module name="JavadocMethod">
+ <property name="accessModifiers" value="public" />
+ </module>
+ <module name="ModifierOrder"/>
+ <module name="RedundantModifier"/>
+ <module name="UpperEll" />
+ <module name="LeftCurly"/>
+ <module name="NeedBraces"/>
+ <module name="RightCurly"/>
+ <module name="GenericWhitespace"/>
+ <module name="WhitespaceAfter"/>
+ <module name="NoWhitespaceBefore"/>
+ </module>
+</module>
diff --git a/src/site/resources/download_lang.cgi b/src/site/resources/download_lang.cgi
new file mode 100755
index 000000000..495cde12d
--- /dev/null
+++ b/src/site/resources/download_lang.cgi
@@ -0,0 +1,4 @@
+#!/bin/sh
+# Just call the standard mirrors.cgi script. It will use download.html
+# as the input template.
+exec /www/www.apache.org/dyn/mirrors/mirrors.cgi $* \ No newline at end of file
diff --git a/src/site/resources/images/logo.png b/src/site/resources/images/logo.png
new file mode 100644
index 000000000..847238bc6
--- /dev/null
+++ b/src/site/resources/images/logo.png
Binary files differ
diff --git a/src/site/resources/lang2-lang3-clirr-report.html b/src/site/resources/lang2-lang3-clirr-report.html
new file mode 100644
index 000000000..783077600
--- /dev/null
+++ b/src/site/resources/lang2-lang3-clirr-report.html
@@ -0,0 +1,265 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!-- Generated by Apache Maven Doxia at Jul 13, 2011 ( $Revision: 1080083 $ ) -->
+<!-- $HeadURL: https://svn.apache.org/repos/asf/commons/proper/commons-skin/trunk/src/main/resources/META-INF/maven/site.vm $ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <title>Lang - Clirr Results</title>
+ <style type="text/css" media="all">
+ @import url("./css/maven-base.css");
+ @import url("./css/maven-theme.css");
+ @import url("./css/site.css");
+ </style>
+ <link rel="stylesheet" href="./css/print.css" type="text/css" media="print" />
+ <meta name="Date-Revision-yyyymmdd" content="20110713" />
+ <meta http-equiv="Content-Language" content="en" />
+
+<link rel="stylesheet" type="text/css" href="./css/prettify.css" media="all"/>
+<script src="./js/prettify.js" type="text/javascript"></script>
+<script type="text/javascript">window.onload=function() {
+ prettyPrint();
+ }</script>
+ </head>
+ <body class="composite">
+ <div id="banner">
+ <div id="bannerLeft">
+ <a href="https://commons.apache.org/" title="Apache Commons logo">
+ <img src="./images/commons-logo.png" alt="Apache Commons logo"/>
+ </a>
+ </div><!-- id="bannerLeft" -->
+ <div id="bannerRight">
+ <a href="index.html">
+ <img src="images/logo.png" alt="Commons Lang"/>
+ </a>
+ </div><!-- id="bannerRight" -->
+ <div class="clear">
+ <hr/>
+ </div>
+ </div>
+ <div id="breadcrumbs">
+
+
+ <div class="xleft">
+ <span id="publishDate">Last Published: 13 July 2011</span>
+ &nbsp;| <span id="projectVersion">Version: 3.0</span>
+ </div>
+ <div class="xright"> <a href="https://www.apachecon.com/" class="externalLink" title="ApacheCon">ApacheCon</a>
+ |
+ <a href="https://www.apache.org" class="externalLink" title="Apache">Apache</a>
+ |
+ <a href="../" title="Commons">Commons</a>
+
+
+ </div>
+ <div class="clear">
+ <hr/>
+ </div>
+ </div>
+ <div id="leftColumn">
+ <div id="navcolumn">
+
+
+ <h5>Lang</h5>
+ <ul>
+ <li class="none">
+ <a href="index.html" title="Overview">Overview</a>
+ </li>
+ <li class="none">
+ <a href="download_lang.cgi" title="Download">Download</a>
+ </li>
+ <li class="none">
+ <a href="userguide.html" title="Users guide">Users guide</a>
+ </li>
+ <li class="none">
+ <a href="changes-report.html" title="Release History">Release History</a>
+ </li>
+ <li class="none">
+ <a href="api-release/index.html" title="Javadoc (3.0 release)">Javadoc (3.0 release)</a>
+ </li>
+ </ul>
+ <h5>Development</h5>
+ <ul>
+ <li class="none">
+ <a href="building.html" title="Building">Building</a>
+ </li>
+ <li class="none">
+ <a href="mail-lists.html" title="Mailing Lists">Mailing Lists</a>
+ </li>
+ <li class="none">
+ <a href="issue-tracking.html" title="Issue Tracking">Issue Tracking</a>
+ </li>
+ <li class="none">
+ <a href="proposal.html" title="Proposal">Proposal</a>
+ </li>
+ <li class="none">
+ <a href="developerguide.html" title="Developer guide">Developer guide</a>
+ </li>
+ <li class="none">
+ <a href="scm.html" title="Source Repository">Source Repository</a>
+ </li>
+ <li class="none">
+ <a href="apidocs/index.html" title="Javadoc (SVN latest)">Javadoc (SVN latest)</a>
+ </li>
+ </ul>
+ <h5>Project Documentation</h5>
+ <ul>
+ <li class="collapsed">
+ <a href="project-info.html" title="Project Information">Project Information</a>
+ </li>
+ <li class="expanded">
+ <a href="project-reports.html" title="Project Reports">Project Reports</a>
+ <ul>
+ <li class="none">
+ <a href="changes-report.html" title="Changes Report">Changes Report</a>
+ </li>
+ <li class="none">
+ <a href="checkstyle.html" title="Checkstyle">Checkstyle</a>
+ </li>
+ <li class="none">
+ <strong>Clirr</strong>
+ </li>
+ <li class="none">
+ <a href="cobertura/index.html" title="Cobertura Test Coverage">Cobertura Test Coverage</a>
+ </li>
+ <li class="none">
+ <a href="cpd.html" title="CPD Report">CPD Report</a>
+ </li>
+ <li class="none">
+ <a href="findbugs.html" title="FindBugs Report">FindBugs Report</a>
+ </li>
+ <li class="none">
+ <a href="apidocs/index.html" title="Javadocs">Javadocs</a>
+ </li>
+ <li class="none">
+ <a href="javancss.html" title="JavaNCSS Report">JavaNCSS Report</a>
+ </li>
+ <li class="none">
+ <a href="jdepend-report.html" title="JDepend">JDepend</a>
+ </li>
+ <li class="none">
+ <a href="pmd.html" title="PMD Report">PMD Report</a>
+ </li>
+ <li class="none">
+ <a href="rat-report.html" title="RAT Report">RAT Report</a>
+ </li>
+ <li class="none">
+ <a href="xref/index.html" title="Source Xref">Source Xref</a>
+ </li>
+ <li class="none">
+ <a href="surefire-report.html" title="Surefire Report">Surefire Report</a>
+ </li>
+ <li class="none">
+ <a href="taglist.html" title="Tag List">Tag List</a>
+ </li>
+ <li class="none">
+ <a href="testapidocs/index.html" title="Test Javadocs">Test Javadocs</a>
+ </li>
+ <li class="none">
+ <a href="xref-test/index.html" title="Test Source Xref">Test Source Xref</a>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ <h5>Commons</h5>
+ <ul>
+ <li class="none">
+ <a href="../" title="Home">Home</a>
+ </li>
+ <li class="none">
+ <a href="https://www.apache.org/licenses/" class="externalLink" title="License">License</a>
+ </li>
+ <li class="collapsed">
+ <a href="../components.html" title="Components">Components</a>
+ </li>
+ <li class="collapsed">
+ <a href="../sandbox/index.html" title="Sandbox">Sandbox</a>
+ </li>
+ <li class="collapsed">
+ <a href="../dormant/index.html" title="Dormant">Dormant</a>
+ </li>
+ </ul>
+ <h5>General Information</h5>
+ <ul>
+ <li class="none">
+ <a href="../volunteering.html" title="Volunteering">Volunteering</a>
+ </li>
+ <li class="none">
+ <a href="../patches.html" title="Contributing Patches">Contributing Patches</a>
+ </li>
+ <li class="none">
+ <a href="../building.html" title="Building Components">Building Components</a>
+ </li>
+ <li class="none">
+ <a href="../releases/index.html" title="Releasing Components">Releasing Components</a>
+ </li>
+ <li class="none">
+ <a href="https://cwiki.apache.org/confluence/display/commons/FrontPage" class="externalLink" title="Wiki">Wiki</a>
+ </li>
+ </ul>
+ <h5>ASF</h5>
+ <ul>
+ <li class="none">
+ <a href="https://www.apache.org/foundation/how-it-works.html" class="externalLink" title="How the ASF works">How the ASF works</a>
+ </li>
+ <li class="none">
+ <a href="https://www.apache.org/foundation/getinvolved.html" class="externalLink" title="Get Involved">Get Involved</a>
+ </li>
+ <li class="none">
+ <a href="https://www.apache.org/dev/" class="externalLink" title="Developer Resources">Developer Resources</a>
+ </li>
+ <li class="none">
+ <a href="https://www.apache.org/foundation/sponsorship.html" class="externalLink" title="Sponsorship">Sponsorship</a>
+ </li>
+ <li class="none">
+ <a href="https://www.apache.org/foundation/thanks.html" class="externalLink" title="Thanks">Thanks</a>
+ </li>
+ </ul>
+ <a href="https://apachecon.com/" title="ApacheCon" class="poweredBy">
+ <img class="poweredBy" alt="ApacheCon" src="https://www.apache.org/events/current-event-125x125.png" />
+ </a>
+ <a href="https://maven.apache.org/" title="Maven" class="poweredBy">
+ <img class="poweredBy" alt="Maven" src="https://maven.apache.org/images/logos/maven-feather.png" />
+ </a>
+
+
+ </div>
+ </div>
+ <div id="bodyColumn">
+ <div id="contentBox">
+ <div class="section"><h2>Clirr Results<a name="Clirr_Results"></a></h2><p>The following document contains the results of <a class="externalLink" href="http://clirr.sourceforge.net/">Clirr</a>.</p><ul><li>Current Version: 3.0</li><li>Comparison Version: (2.6,3.0)</li></ul><div class="section"><h2>Summary<a name="Summary"></a></h2><table border="0" class="bodyTable"><tr class="a"><th>Severity</th><th>Number</th></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" />&#160;Error</td><td>268</td></tr><tr class="a"><td><img alt="Warning" src="images/icon_warning_sml.gif" />&#160;Warning</td><td>0</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" />&#160;Info</td><td>146</td></tr></table></div><div class="section"><h2>Details<a name="Details"></a></h2><table border="0" class="bodyTable"><tr class="a"><th>Severity</th><th>Message</th><th>Class</th><th>Method / Field</th></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.Boolean toBooleanObject(boolean)' has been removed</td><td><a href="./xref/org/apache/commons/lang/BooleanUtils.html">org.apache.commons.lang.BooleanUtils</a></td><td>public java.lang.Boolean toBooleanObject(boolean)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Decreased visibility of class from public to package</td><td><a href="./xref/org/apache/commons/lang/CharRange.html">org.apache.commons.lang.CharRange</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'protected CharSet(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/CharSet.html">org.apache.commons.lang.CharSet</a></td><td>protected CharSet(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Accessibility of method 'public org.apache.commons.lang.CharRange[] getCharRanges()' has been decreased from public to package</td><td><a href="./xref/org/apache/commons/lang/CharSet.html">org.apache.commons.lang.CharSet</a></td><td>public org.apache.commons.lang.CharRange[] getCharRanges()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public org.apache.commons.lang.CharSet getInstance(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/CharSet.html">org.apache.commons.lang.CharSet</a></td><td>public org.apache.commons.lang.CharSet getInstance(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public int count(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/CharSetUtils.html">org.apache.commons.lang.CharSetUtils</a></td><td>public int count(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String delete(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/CharSetUtils.html">org.apache.commons.lang.CharSetUtils</a></td><td>public java.lang.String delete(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public org.apache.commons.lang.CharSet evaluateSet(java.lang.String[])' has been removed</td><td><a href="./xref/org/apache/commons/lang/CharSetUtils.html">org.apache.commons.lang.CharSetUtils</a></td><td>public org.apache.commons.lang.CharSet evaluateSet(java.lang.String[])</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String keep(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/CharSetUtils.html">org.apache.commons.lang.CharSetUtils</a></td><td>public java.lang.String keep(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String squeeze(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/CharSetUtils.html">org.apache.commons.lang.CharSetUtils</a></td><td>public java.lang.String squeeze(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String translate(java.lang.String, java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/CharSetUtils.html">org.apache.commons.lang.CharSetUtils</a></td><td>public java.lang.String translate(java.lang.String, java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.IllegalClassException removed</td><td><a href="./xref/org/apache/commons/lang/IllegalClassException.html">org.apache.commons.lang.IllegalClassException</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.IncompleteArgumentException removed</td><td><a href="./xref/org/apache/commons/lang/IncompleteArgumentException.html">org.apache.commons.lang.IncompleteArgumentException</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.NotImplementedException removed</td><td><a href="./xref/org/apache/commons/lang/NotImplementedException.html">org.apache.commons.lang.NotImplementedException</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.NullArgumentException removed</td><td><a href="./xref/org/apache/commons/lang/NullArgumentException.html">org.apache.commons.lang.NullArgumentException</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.NumberRange removed</td><td><a href="./xref/org/apache/commons/lang/NumberRange.html">org.apache.commons.lang.NumberRange</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.NumberUtils removed</td><td><a href="./xref/org/apache/commons/lang/NumberUtils.html">org.apache.commons.lang.NumberUtils</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.StringBuffer appendIdentityToString(java.lang.StringBuffer, java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/ObjectUtils.html">org.apache.commons.lang.ObjectUtils</a></td><td>public java.lang.StringBuffer appendIdentityToString(java.lang.StringBuffer, java.lang.Object)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public java.lang.Object max(java.lang.Comparable, java.lang.Comparable)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/ObjectUtils.html">org.apache.commons.lang.ObjectUtils</a></td><td>public java.lang.Object max(java.lang.Comparable, java.lang.Comparable)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object max(java.lang.Comparable, java.lang.Comparable)' has been changed to java.lang.Comparable</td><td><a href="./xref/org/apache/commons/lang/ObjectUtils.html">org.apache.commons.lang.ObjectUtils</a></td><td>public java.lang.Object max(java.lang.Comparable, java.lang.Comparable)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public java.lang.Object min(java.lang.Comparable, java.lang.Comparable)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/ObjectUtils.html">org.apache.commons.lang.ObjectUtils</a></td><td>public java.lang.Object min(java.lang.Comparable, java.lang.Comparable)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object min(java.lang.Comparable, java.lang.Comparable)' has been changed to java.lang.Comparable</td><td><a href="./xref/org/apache/commons/lang/ObjectUtils.html">org.apache.commons.lang.ObjectUtils</a></td><td>public java.lang.Object min(java.lang.Comparable, java.lang.Comparable)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed org.apache.commons.lang.exception.Nestable from the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/SerializationException.html">org.apache.commons.lang.SerializationException</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed org.apache.commons.lang.exception.NestableRuntimeException from the list of superclasses</td><td><a href="./xref/org/apache/commons/lang/SerializationException.html">org.apache.commons.lang.SerializationException</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object clone(java.io.Serializable)' has been changed to java.io.Serializable</td><td><a href="./xref/org/apache/commons/lang/SerializationUtils.html">org.apache.commons.lang.SerializationUtils</a></td><td>public java.lang.Object clone(java.io.Serializable)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String escapeCsv(java.lang.String)' is now final</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeCsv(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void escapeCsv(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void escapeCsv(java.io.Writer, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String escapeHtml(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeHtml(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void escapeHtml(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void escapeHtml(java.io.Writer, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String escapeJava(java.lang.String)' is now final</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeJava(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void escapeJava(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void escapeJava(java.io.Writer, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String escapeJavaScript(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeJavaScript(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void escapeJavaScript(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void escapeJavaScript(java.io.Writer, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String escapeSql(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeSql(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String escapeXml(java.lang.String)' is now final</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeXml(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void escapeXml(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void escapeXml(java.io.Writer, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String unescapeCsv(java.lang.String)' is now final</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String unescapeCsv(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void unescapeCsv(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void unescapeCsv(java.io.Writer, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String unescapeHtml(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String unescapeHtml(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void unescapeHtml(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void unescapeHtml(java.io.Writer, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String unescapeJava(java.lang.String)' is now final</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String unescapeJava(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void unescapeJava(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void unescapeJava(java.io.Writer, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String unescapeJavaScript(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String unescapeJavaScript(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void unescapeJavaScript(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void unescapeJavaScript(java.io.Writer, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String unescapeXml(java.lang.String)' is now final</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String unescapeXml(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void unescapeXml(java.io.Writer, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public void unescapeXml(java.io.Writer, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String capitalise(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String capitalise(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String capitaliseAllWords(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String capitaliseAllWords(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String chompLast(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String chompLast(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String chompLast(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String chompLast(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String chopNewline(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String chopNewline(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String clean(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String clean(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String concatenate(java.lang.Object[])' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String concatenate(java.lang.Object[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean contains(java.lang.String, char)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean contains(java.lang.String, char)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean contains(java.lang.String, char)' has changed its type to int</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean contains(java.lang.String, char)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean contains(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean contains(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean contains(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean contains(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean containsAny(java.lang.String, char[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsAny(java.lang.String, char[])</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean containsAny(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsAny(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean containsAny(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsAny(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean containsIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean containsIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean containsNone(java.lang.String, char[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsNone(java.lang.String, char[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean containsNone(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsNone(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean containsOnly(java.lang.String, char[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsOnly(java.lang.String, char[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean containsOnly(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsOnly(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int countMatches(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int countMatches(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int countMatches(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int countMatches(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public java.lang.String defaultIfBlank(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String defaultIfBlank(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public java.lang.String defaultIfBlank(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String defaultIfBlank(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.String defaultIfBlank(java.lang.String, java.lang.String)' has been changed to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String defaultIfBlank(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public java.lang.String defaultIfEmpty(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String defaultIfEmpty(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public java.lang.String defaultIfEmpty(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String defaultIfEmpty(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.String defaultIfEmpty(java.lang.String, java.lang.String)' has been changed to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String defaultIfEmpty(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String deleteSpaces(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String deleteSpaces(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean endsWith(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean endsWith(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean endsWith(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean endsWith(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean endsWithAny(java.lang.String, java.lang.String[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean endsWithAny(java.lang.String, java.lang.String[])</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean endsWithAny(java.lang.String, java.lang.String[])' has changed its type to java.lang.CharSequence[]</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean endsWithAny(java.lang.String, java.lang.String[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean endsWithIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean endsWithIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean endsWithIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean endsWithIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean equals(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean equals(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean equals(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean equals(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean equalsIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean equalsIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean equalsIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean equalsIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String escape(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String escape(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String getChomp(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String getChomp(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int getLevenshteinDistance(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int getLevenshteinDistance(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int getLevenshteinDistance(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int getLevenshteinDistance(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String getNestedString(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String getNestedString(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String getNestedString(java.lang.String, java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String getNestedString(java.lang.String, java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String getPrechomp(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String getPrechomp(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOf(java.lang.String, char)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOf(java.lang.String, char)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOf(java.lang.String, char)' has changed its type to int</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOf(java.lang.String, char)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOf(java.lang.String, char, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOf(java.lang.String, char, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOf(java.lang.String, char, int)' has changed its type to int</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOf(java.lang.String, char, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOf(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOf(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOf(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOf(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOf(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOf(java.lang.String, java.lang.String, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOf(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOf(java.lang.String, java.lang.String, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfAny(java.lang.String, char[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfAny(java.lang.String, char[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfAny(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfAny(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfAny(java.lang.String, java.lang.String[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfAny(java.lang.String, java.lang.String[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOfAny(java.lang.String, java.lang.String[])' has changed its type to java.lang.CharSequence[]</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfAny(java.lang.String, java.lang.String[])</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfAnyBut(java.lang.String, char[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfAnyBut(java.lang.String, char[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfAnyBut(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfAnyBut(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOfAnyBut(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfAnyBut(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfDifference(java.lang.String[])' has changed its type to java.lang.CharSequence[]</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfDifference(java.lang.String[])</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfDifference(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfDifference(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOfDifference(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfDifference(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOfIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int indexOfIgnoreCase(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfIgnoreCase(java.lang.String, java.lang.String, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int indexOfIgnoreCase(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int indexOfIgnoreCase(java.lang.String, java.lang.String, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isAllLowerCase(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isAllLowerCase(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isAllUpperCase(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isAllUpperCase(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isAlpha(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isAlpha(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isAlphaSpace(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isAlphaSpace(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isAlphanumeric(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isAlphanumeric(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isAlphanumericSpace(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isAlphanumericSpace(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isAsciiPrintable(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isAsciiPrintable(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isBlank(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isBlank(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isEmpty(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isEmpty(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isNotBlank(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isNotBlank(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isNotEmpty(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isNotEmpty(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isNumeric(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isNumeric(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isNumericSpace(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isNumericSpace(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isWhitespace(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean isWhitespace(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public java.lang.String join(java.util.Collection, char)' has changed its type to java.lang.Iterable</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String join(java.util.Collection, char)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public java.lang.String join(java.util.Collection, java.lang.String)' has changed its type to java.lang.Iterable</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String join(java.util.Collection, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int lastIndexOf(java.lang.String, char)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOf(java.lang.String, char)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int lastIndexOf(java.lang.String, char)' has changed its type to int</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOf(java.lang.String, char)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int lastIndexOf(java.lang.String, char, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOf(java.lang.String, char, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int lastIndexOf(java.lang.String, char, int)' has changed its type to int</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOf(java.lang.String, char, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int lastIndexOf(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOf(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int lastIndexOf(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOf(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int lastIndexOf(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOf(java.lang.String, java.lang.String, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int lastIndexOf(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOf(java.lang.String, java.lang.String, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int lastIndexOfAny(java.lang.String, java.lang.String[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOfAny(java.lang.String, java.lang.String[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int lastIndexOfAny(java.lang.String, java.lang.String[])' has changed its type to java.lang.CharSequence[]</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOfAny(java.lang.String, java.lang.String[])</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int lastIndexOfIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOfIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int lastIndexOfIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOfIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int lastIndexOfIgnoreCase(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOfIgnoreCase(java.lang.String, java.lang.String, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int lastIndexOfIgnoreCase(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastIndexOfIgnoreCase(java.lang.String, java.lang.String, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int lastOrdinalIndexOf(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastOrdinalIndexOf(java.lang.String, java.lang.String, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int lastOrdinalIndexOf(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int lastOrdinalIndexOf(java.lang.String, java.lang.String, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int length(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int length(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public int ordinalIndexOf(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int ordinalIndexOf(java.lang.String, java.lang.String, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public int ordinalIndexOf(java.lang.String, java.lang.String, int)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int ordinalIndexOf(java.lang.String, java.lang.String, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String overlayString(java.lang.String, java.lang.String, int, int)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String overlayString(java.lang.String, java.lang.String, int, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String prechomp(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String prechomp(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String reverseDelimitedString(java.lang.String, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String reverseDelimitedString(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean startsWith(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean startsWith(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean startsWith(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean startsWith(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean startsWithAny(java.lang.String, java.lang.String[])' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean startsWithAny(java.lang.String, java.lang.String[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean startsWithAny(java.lang.String, java.lang.String[])' has changed its type to java.lang.CharSequence[]</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean startsWithAny(java.lang.String, java.lang.String[])</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean startsWithIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean startsWithIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 2 of 'public boolean startsWithIgnoreCase(java.lang.String, java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean startsWithIgnoreCase(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String uncapitalise(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String uncapitalise(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed field JAVA_VERSION_FLOAT</td><td><a href="./xref/org/apache/commons/lang/SystemUtils.html">org.apache.commons.lang.SystemUtils</a></td><td>JAVA_VERSION_FLOAT</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed field JAVA_VERSION_INT</td><td><a href="./xref/org/apache/commons/lang/SystemUtils.html">org.apache.commons.lang.SystemUtils</a></td><td>JAVA_VERSION_INT</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed field JAVA_VERSION_TRIMMED</td><td><a href="./xref/org/apache/commons/lang/SystemUtils.html">org.apache.commons.lang.SystemUtils</a></td><td>JAVA_VERSION_TRIMMED</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public float getJavaVersion()' has been removed</td><td><a href="./xref/org/apache/commons/lang/SystemUtils.html">org.apache.commons.lang.SystemUtils</a></td><td>public float getJavaVersion()</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public boolean isJavaVersionAtLeast(float)' has changed its type to org.apache.commons.lang.JavaVersion</td><td><a href="./xref/org/apache/commons/lang/SystemUtils.html">org.apache.commons.lang.SystemUtils</a></td><td>public boolean isJavaVersionAtLeast(float)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean isJavaVersionAtLeast(int)' has been removed</td><td><a href="./xref/org/apache/commons/lang/SystemUtils.html">org.apache.commons.lang.SystemUtils</a></td><td>public boolean isJavaVersionAtLeast(int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.UnhandledException removed</td><td><a href="./xref/org/apache/commons/lang/UnhandledException.html">org.apache.commons.lang.UnhandledException</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void allElementsOfType(java.util.Collection, java.lang.Class, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void allElementsOfType(java.util.Collection, java.lang.Class, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void allElementsOfType(java.util.Collection, java.lang.Class)' has been removed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void allElementsOfType(java.util.Collection, java.lang.Class)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 3 of 'public void isTrue(boolean, java.lang.String, java.lang.Object)' has changed its type to java.lang.Object[]</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void isTrue(boolean, java.lang.String, java.lang.Object)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void isTrue(boolean, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void isTrue(boolean, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void noNullElements(java.lang.Object[])' has been changed to java.lang.Object[]</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void noNullElements(java.lang.Object[])</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public void noNullElements(java.util.Collection)' has changed its type to java.lang.Iterable</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void noNullElements(java.util.Collection)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void noNullElements(java.util.Collection)' has been changed to java.lang.Iterable</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void noNullElements(java.util.Collection)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public void noNullElements(java.lang.Object[], java.lang.String)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void noNullElements(java.lang.Object[], java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void noNullElements(java.lang.Object[], java.lang.String)' has been changed to java.lang.Object[]</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void noNullElements(java.lang.Object[], java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public void noNullElements(java.util.Collection, java.lang.String)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void noNullElements(java.util.Collection, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void noNullElements(java.util.Collection, java.lang.String)' has been changed to java.lang.Iterable</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void noNullElements(java.util.Collection, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notEmpty(java.lang.Object[])' has been changed to java.lang.Object[]</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.lang.Object[])</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notEmpty(java.util.Collection)' has been changed to java.util.Collection</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.util.Collection)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notEmpty(java.util.Map)' has been changed to java.util.Map</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.util.Map)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public void notEmpty(java.lang.String)' has changed its type to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notEmpty(java.lang.String)' has been changed to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public void notEmpty(java.lang.Object[], java.lang.String)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.lang.Object[], java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notEmpty(java.lang.Object[], java.lang.String)' has been changed to java.lang.Object[]</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.lang.Object[], java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public void notEmpty(java.util.Collection, java.lang.String)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.util.Collection, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notEmpty(java.util.Collection, java.lang.String)' has been changed to java.util.Collection</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.util.Collection, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public void notEmpty(java.util.Map, java.lang.String)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.util.Map, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notEmpty(java.util.Map, java.lang.String)' has been changed to java.util.Map</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.util.Map, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public void notEmpty(java.lang.String, java.lang.String)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.lang.String, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notEmpty(java.lang.String, java.lang.String)' has been changed to java.lang.CharSequence</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notEmpty(java.lang.String, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notNull(java.lang.Object)' has been changed to java.lang.Object</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notNull(java.lang.Object)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>In method 'public void notNull(java.lang.Object, java.lang.String)' the number of arguments has changed</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notNull(java.lang.Object, java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public void notNull(java.lang.Object, java.lang.String)' has been changed to java.lang.Object</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void notNull(java.lang.Object, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.WordUtils removed</td><td><a href="./xref/org/apache/commons/lang/WordUtils.html">org.apache.commons.lang.WordUtils</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public int reflectionCompare(java.lang.Object, java.lang.Object, boolean, java.lang.Class)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/CompareToBuilder.html">org.apache.commons.lang.builder.CompareToBuilder</a></td><td>public int reflectionCompare(java.lang.Object, java.lang.Object, boolean, java.lang.Class)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean reflectionEquals(java.lang.Object, java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/EqualsBuilder.html">org.apache.commons.lang.builder.EqualsBuilder</a></td><td>public boolean reflectionEquals(java.lang.Object, java.lang.Object)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean reflectionEquals(java.lang.Object, java.lang.Object, boolean, java.lang.Class)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/EqualsBuilder.html">org.apache.commons.lang.builder.EqualsBuilder</a></td><td>public boolean reflectionEquals(java.lang.Object, java.lang.Object, boolean, java.lang.Class)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public int reflectionHashCode(int, int, java.lang.Object, boolean, java.lang.Class)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/HashCodeBuilder.html">org.apache.commons.lang.builder.HashCodeBuilder</a></td><td>public int reflectionHashCode(int, int, java.lang.Object, boolean, java.lang.Class)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public int reflectionHashCode(java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/HashCodeBuilder.html">org.apache.commons.lang.builder.HashCodeBuilder</a></td><td>public int reflectionHashCode(java.lang.Object)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public ReflectionToStringBuilder(java.lang.Object, org.apache.commons.lang.builder.ToStringStyle, java.lang.StringBuffer, java.lang.Class, boolean)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/ReflectionToStringBuilder.html">org.apache.commons.lang.builder.ReflectionToStringBuilder</a></td><td>public ReflectionToStringBuilder(java.lang.Object, org.apache.commons.lang.builder.ToStringStyle, java.lang.StringBuffer, java.lang.Class, boolean)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public org.apache.commons.lang.builder.ToStringBuilder reflectionAppendArray(java.lang.Object)' has been changed to org.apache.commons.lang.builder.ReflectionToStringBuilder</td><td><a href="./xref/org/apache/commons/lang/builder/ReflectionToStringBuilder.html">org.apache.commons.lang.builder.ReflectionToStringBuilder</a></td><td>public org.apache.commons.lang.builder.ToStringBuilder reflectionAppendArray(java.lang.Object)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String toString(java.lang.Object, org.apache.commons.lang.builder.ToStringStyle, boolean, java.lang.Class)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/ReflectionToStringBuilder.html">org.apache.commons.lang.builder.ReflectionToStringBuilder</a></td><td>public java.lang.String toString(java.lang.Object, org.apache.commons.lang.builder.ToStringStyle, boolean, java.lang.Class)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String toStringExclude(java.lang.Object, java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/ReflectionToStringBuilder.html">org.apache.commons.lang.builder.ReflectionToStringBuilder</a></td><td>public java.lang.String toStringExclude(java.lang.Object, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean isShortClassName()' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/StandardToStringStyle.html">org.apache.commons.lang.builder.StandardToStringStyle</a></td><td>public boolean isShortClassName()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void setShortClassName(boolean)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/StandardToStringStyle.html">org.apache.commons.lang.builder.StandardToStringStyle</a></td><td>public void setShortClassName(boolean)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'protected boolean isShortClassName()' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/ToStringStyle.html">org.apache.commons.lang.builder.ToStringStyle</a></td><td>protected boolean isShortClassName()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'protected void setShortClassName(boolean)' has been removed</td><td><a href="./xref/org/apache/commons/lang/builder/ToStringStyle.html">org.apache.commons.lang.builder.ToStringStyle</a></td><td>protected void setShortClassName(boolean)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.enum.Enum removed</td><td><a href="./xref/org/apache/commons/lang/enum/Enum.html">org.apache.commons.lang.enum.Enum</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.enum.EnumUtils removed</td><td><a href="./xref/org/apache/commons/lang/enum/EnumUtils.html">org.apache.commons.lang.enum.EnumUtils</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.enum.ValuedEnum removed</td><td><a href="./xref/org/apache/commons/lang/enum/ValuedEnum.html">org.apache.commons.lang.enum.ValuedEnum</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.enums.Enum removed</td><td><a href="./xref/org/apache/commons/lang/enums/Enum.html">org.apache.commons.lang.enums.Enum</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.enums.EnumUtils removed</td><td><a href="./xref/org/apache/commons/lang/enums/EnumUtils.html">org.apache.commons.lang.enums.EnumUtils</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.enums.ValuedEnum removed</td><td><a href="./xref/org/apache/commons/lang/enums/ValuedEnum.html">org.apache.commons.lang.enums.ValuedEnum</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed org.apache.commons.lang.exception.Nestable from the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/exception/CloneFailedException.html">org.apache.commons.lang.exception.CloneFailedException</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed org.apache.commons.lang.exception.NestableRuntimeException from the list of superclasses</td><td><a href="./xref/org/apache/commons/lang/exception/CloneFailedException.html">org.apache.commons.lang.exception.CloneFailedException</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void addCauseMethodName(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public void addCauseMethodName(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.String getFullStackTrace(java.lang.Throwable)' has been removed</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public java.lang.String getFullStackTrace(java.lang.Throwable)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean isCauseMethodName(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public boolean isCauseMethodName(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean isNestedThrowable(java.lang.Throwable)' has been removed</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public boolean isNestedThrowable(java.lang.Throwable)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean isThrowableNested()' has been removed</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public boolean isThrowableNested()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public void removeCauseMethodName(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public void removeCauseMethodName(java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean setCause(java.lang.Throwable, java.lang.Throwable)' has been removed</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public boolean setCause(java.lang.Throwable, java.lang.Throwable)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.exception.Nestable removed</td><td><a href="./xref/org/apache/commons/lang/exception/Nestable.html">org.apache.commons.lang.exception.Nestable</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.exception.NestableDelegate removed</td><td><a href="./xref/org/apache/commons/lang/exception/NestableDelegate.html">org.apache.commons.lang.exception.NestableDelegate</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.exception.NestableError removed</td><td><a href="./xref/org/apache/commons/lang/exception/NestableError.html">org.apache.commons.lang.exception.NestableError</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.exception.NestableException removed</td><td><a href="./xref/org/apache/commons/lang/exception/NestableException.html">org.apache.commons.lang.exception.NestableException</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.exception.NestableRuntimeException removed</td><td><a href="./xref/org/apache/commons/lang/exception/NestableRuntimeException.html">org.apache.commons.lang.exception.NestableRuntimeException</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.math.DoubleRange removed</td><td><a href="./xref/org/apache/commons/lang/math/DoubleRange.html">org.apache.commons.lang.math.DoubleRange</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.math.FloatRange removed</td><td><a href="./xref/org/apache/commons/lang/math/FloatRange.html">org.apache.commons.lang.math.FloatRange</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.math.IntRange removed</td><td><a href="./xref/org/apache/commons/lang/math/IntRange.html">org.apache.commons.lang.math.IntRange</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.math.JVMRandom removed</td><td><a href="./xref/org/apache/commons/lang/math/JVMRandom.html">org.apache.commons.lang.math.JVMRandom</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.math.LongRange removed</td><td><a href="./xref/org/apache/commons/lang/math/LongRange.html">org.apache.commons.lang.math.LongRange</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.math.NumberRange removed</td><td><a href="./xref/org/apache/commons/lang/math/NumberRange.html">org.apache.commons.lang.math.NumberRange</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public int compare(double, double)' has been removed</td><td><a href="./xref/org/apache/commons/lang/math/NumberUtils.html">org.apache.commons.lang.math.NumberUtils</a></td><td>public int compare(double, double)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public int compare(float, float)' has been removed</td><td><a href="./xref/org/apache/commons/lang/math/NumberUtils.html">org.apache.commons.lang.math.NumberUtils</a></td><td>public int compare(float, float)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public int stringToInt(java.lang.String)' has been removed</td><td><a href="./xref/org/apache/commons/lang/math/NumberUtils.html">org.apache.commons.lang.math.NumberUtils</a></td><td>public int stringToInt(java.lang.String)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public int stringToInt(java.lang.String, int)' has been removed</td><td><a href="./xref/org/apache/commons/lang/math/NumberUtils.html">org.apache.commons.lang.math.NumberUtils</a></td><td>public int stringToInt(java.lang.String, int)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.math.RandomUtils removed</td><td><a href="./xref/org/apache/commons/lang/math/RandomUtils.html">org.apache.commons.lang.math.RandomUtils</a></td><td></td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Class org.apache.commons.lang.math.Range removed</td><td><a href="./xref/org/apache/commons/lang/math/Range.html">org.apache.commons.lang.math.Range</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object getValue()' has been changed to java.lang.Boolean</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableBoolean.html">org.apache.commons.lang.mutable.MutableBoolean</a></td><td>public java.lang.Object getValue()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object getValue()' has been changed to java.lang.Byte</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableByte.html">org.apache.commons.lang.mutable.MutableByte</a></td><td>public java.lang.Object getValue()</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object getValue()' has been changed to java.lang.Double</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableDouble.html">org.apache.commons.lang.mutable.MutableDouble</a></td><td>public java.lang.Object getValue()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object getValue()' has been changed to java.lang.Float</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableFloat.html">org.apache.commons.lang.mutable.MutableFloat</a></td><td>public java.lang.Object getValue()</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object getValue()' has been changed to java.lang.Integer</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableInt.html">org.apache.commons.lang.mutable.MutableInt</a></td><td>public java.lang.Object getValue()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object getValue()' has been changed to java.lang.Long</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableLong.html">org.apache.commons.lang.mutable.MutableLong</a></td><td>public java.lang.Object getValue()</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object getValue()' has been changed to java.lang.Short</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableShort.html">org.apache.commons.lang.mutable.MutableShort</a></td><td>public java.lang.Object getValue()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.reflect.Constructor getAccessibleConstructor(java.lang.Class, java.lang.Class)' has been removed</td><td><a href="./xref/org/apache/commons/lang/reflect/ConstructorUtils.html">org.apache.commons.lang.reflect.ConstructorUtils</a></td><td>public java.lang.reflect.Constructor getAccessibleConstructor(java.lang.Class, java.lang.Class)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.Object invokeConstructor(java.lang.Class, java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/reflect/ConstructorUtils.html">org.apache.commons.lang.reflect.ConstructorUtils</a></td><td>public java.lang.Object invokeConstructor(java.lang.Class, java.lang.Object)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.Object invokeExactConstructor(java.lang.Class, java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/reflect/ConstructorUtils.html">org.apache.commons.lang.reflect.ConstructorUtils</a></td><td>public java.lang.Object invokeExactConstructor(java.lang.Class, java.lang.Object)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.reflect.Method getAccessibleMethod(java.lang.Class, java.lang.String, java.lang.Class)' has been removed</td><td><a href="./xref/org/apache/commons/lang/reflect/MethodUtils.html">org.apache.commons.lang.reflect.MethodUtils</a></td><td>public java.lang.reflect.Method getAccessibleMethod(java.lang.Class, java.lang.String, java.lang.Class)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.Object invokeExactMethod(java.lang.Object, java.lang.String, java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/reflect/MethodUtils.html">org.apache.commons.lang.reflect.MethodUtils</a></td><td>public java.lang.Object invokeExactMethod(java.lang.Object, java.lang.String, java.lang.Object)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.Object invokeExactStaticMethod(java.lang.Class, java.lang.String, java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/reflect/MethodUtils.html">org.apache.commons.lang.reflect.MethodUtils</a></td><td>public java.lang.Object invokeExactStaticMethod(java.lang.Class, java.lang.String, java.lang.Object)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.Object invokeMethod(java.lang.Object, java.lang.String, java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/reflect/MethodUtils.html">org.apache.commons.lang.reflect.MethodUtils</a></td><td>public java.lang.Object invokeMethod(java.lang.Object, java.lang.String, java.lang.Object)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.Object invokeStaticMethod(java.lang.Class, java.lang.String, java.lang.Object)' has been removed</td><td><a href="./xref/org/apache/commons/lang/reflect/MethodUtils.html">org.apache.commons.lang.reflect.MethodUtils</a></td><td>public java.lang.Object invokeStaticMethod(java.lang.Class, java.lang.String, java.lang.Object)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed java.lang.Cloneable from the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td></td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public org.apache.commons.lang.text.StrBuilder appendAll(java.util.Collection)' has changed its type to java.lang.Iterable</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public org.apache.commons.lang.text.StrBuilder appendAll(java.util.Collection)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Parameter 1 of 'public org.apache.commons.lang.text.StrBuilder appendWithSeparators(java.util.Collection, java.lang.String)' has changed its type to java.lang.Iterable</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public org.apache.commons.lang.text.StrBuilder appendWithSeparators(java.util.Collection, java.lang.String)</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public java.lang.Object clone()' has been removed</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public java.lang.Object clone()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object next()' has been changed to java.lang.String</td><td><a href="./xref/org/apache/commons/lang/text/StrTokenizer.html">org.apache.commons.lang.text.StrTokenizer</a></td><td>public java.lang.Object next()</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Return type of method 'public java.lang.Object previous()' has been changed to java.lang.String</td><td><a href="./xref/org/apache/commons/lang/text/StrTokenizer.html">org.apache.commons.lang.text.StrTokenizer</a></td><td>public java.lang.Object previous()</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Field MILLIS_IN_DAY has been removed, but it was previously a constant</td><td><a href="./xref/org/apache/commons/lang/time/DateUtils.html">org.apache.commons.lang.time.DateUtils</a></td><td>MILLIS_IN_DAY</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Field MILLIS_IN_HOUR has been removed, but it was previously a constant</td><td><a href="./xref/org/apache/commons/lang/time/DateUtils.html">org.apache.commons.lang.time.DateUtils</a></td><td>MILLIS_IN_HOUR</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Field MILLIS_IN_MINUTE has been removed, but it was previously a constant</td><td><a href="./xref/org/apache/commons/lang/time/DateUtils.html">org.apache.commons.lang.time.DateUtils</a></td><td>MILLIS_IN_MINUTE</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Field MILLIS_IN_SECOND has been removed, but it was previously a constant</td><td><a href="./xref/org/apache/commons/lang/time/DateUtils.html">org.apache.commons.lang.time.DateUtils</a></td><td>MILLIS_IN_SECOND</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Removed field UTC_TIME_ZONE</td><td><a href="./xref/org/apache/commons/lang/time/DateUtils.html">org.apache.commons.lang.time.DateUtils</a></td><td>UTC_TIME_ZONE</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Accessibility of method 'public java.util.Date add(java.util.Date, int, int)' has been decreased from public to private</td><td><a href="./xref/org/apache/commons/lang/time/DateUtils.html">org.apache.commons.lang.time.DateUtils</a></td><td>public java.util.Date add(java.util.Date, int, int)</td></tr><tr class="b"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Method 'public boolean getTimeZoneOverridesCalendar()' has been removed</td><td><a href="./xref/org/apache/commons/lang/time/FastDateFormat.html">org.apache.commons.lang.time.FastDateFormat</a></td><td>public boolean getTimeZoneOverridesCalendar()</td></tr><tr class="a"><td><img alt="Error" src="images/icon_error_sml.gif" /></td><td>Accessibility of method 'protected void init()' has been decreased from protected to private</td><td><a href="./xref/org/apache/commons/lang/time/FastDateFormat.html">org.apache.commons.lang.time.FastDateFormat</a></td><td>protected void init()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.AnnotationUtils added</td><td><a href="./xref/org/apache/commons/lang/AnnotationUtils.html">org.apache.commons.lang.AnnotationUtils</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object[] toArray(java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/ArrayUtils.html">org.apache.commons.lang.ArrayUtils</a></td><td>public java.lang.Object[] toArray(java.lang.Object[])</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.CharSequenceUtils added</td><td><a href="./xref/org/apache/commons/lang/CharSequenceUtils.html">org.apache.commons.lang.CharSequenceUtils</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String getSimpleName(java.lang.Class)' has been added</td><td><a href="./xref/org/apache/commons/lang/ClassUtils.html">org.apache.commons.lang.ClassUtils</a></td><td>public java.lang.String getSimpleName(java.lang.Class)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String getSimpleName(java.lang.Object, java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/ClassUtils.html">org.apache.commons.lang.ClassUtils</a></td><td>public java.lang.String getSimpleName(java.lang.Object, java.lang.String)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.EnumUtils added</td><td><a href="./xref/org/apache/commons/lang/EnumUtils.html">org.apache.commons.lang.EnumUtils</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.JavaVersion added</td><td><a href="./xref/org/apache/commons/lang/JavaVersion.html">org.apache.commons.lang.JavaVersion</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object firstNonNull(java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/ObjectUtils.html">org.apache.commons.lang.ObjectUtils</a></td><td>public java.lang.Object firstNonNull(java.lang.Object[])</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int hashCodeMulti(java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/ObjectUtils.html">org.apache.commons.lang.ObjectUtils</a></td><td>public int hashCodeMulti(java.lang.Object[])</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.Range added</td><td><a href="./xref/org/apache/commons/lang/Range.html">org.apache.commons.lang.Range</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field ESCAPE_CSV</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>ESCAPE_CSV</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field ESCAPE_ECMASCRIPT</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>ESCAPE_ECMASCRIPT</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field ESCAPE_HTML3</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>ESCAPE_HTML3</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field ESCAPE_HTML4</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>ESCAPE_HTML4</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field ESCAPE_JAVA</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>ESCAPE_JAVA</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field ESCAPE_XML</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>ESCAPE_XML</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field UNESCAPE_CSV</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>UNESCAPE_CSV</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field UNESCAPE_ECMASCRIPT</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>UNESCAPE_ECMASCRIPT</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field UNESCAPE_HTML3</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>UNESCAPE_HTML3</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field UNESCAPE_HTML4</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>UNESCAPE_HTML4</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field UNESCAPE_JAVA</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>UNESCAPE_JAVA</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added public field UNESCAPE_XML</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>UNESCAPE_XML</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String escapeEcmaScript(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeEcmaScript(java.lang.String)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String escapeHtml3(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeHtml3(java.lang.String)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String escapeHtml4(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String escapeHtml4(java.lang.String)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String unescapeEcmaScript(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String unescapeEcmaScript(java.lang.String)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String unescapeHtml3(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String unescapeHtml3(java.lang.String)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String unescapeHtml4(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringEscapeUtils.html">org.apache.commons.lang.StringEscapeUtils</a></td><td>public java.lang.String unescapeHtml4(java.lang.String)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public boolean containsWhitespace(java.lang.CharSequence)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public boolean containsWhitespace(java.lang.CharSequence)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int getLevenshteinDistance(java.lang.CharSequence, java.lang.CharSequence, int)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public int getLevenshteinDistance(java.lang.CharSequence, java.lang.CharSequence, int)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String repeat(char, int)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String repeat(char, int)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String stripAccents(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/StringUtils.html">org.apache.commons.lang.StringUtils</a></td><td>public java.lang.String stripAccents(java.lang.String)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void exclusiveBetween(java.lang.Object, java.lang.Object, java.lang.Comparable)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void exclusiveBetween(java.lang.Object, java.lang.Object, java.lang.Comparable)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void exclusiveBetween(java.lang.Object, java.lang.Object, java.lang.Comparable, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void exclusiveBetween(java.lang.Object, java.lang.Object, java.lang.Comparable, java.lang.String, java.lang.Object[])</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void inclusiveBetween(java.lang.Object, java.lang.Object, java.lang.Comparable)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void inclusiveBetween(java.lang.Object, java.lang.Object, java.lang.Comparable)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void inclusiveBetween(java.lang.Object, java.lang.Object, java.lang.Comparable, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void inclusiveBetween(java.lang.Object, java.lang.Object, java.lang.Comparable, java.lang.String, java.lang.Object[])</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void isAssignableFrom(java.lang.Class, java.lang.Class)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void isAssignableFrom(java.lang.Class, java.lang.Class)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void isAssignableFrom(java.lang.Class, java.lang.Class, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void isAssignableFrom(java.lang.Class, java.lang.Class, java.lang.String, java.lang.Object[])</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void isInstanceOf(java.lang.Class, java.lang.Object)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void isInstanceOf(java.lang.Class, java.lang.Object)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void isInstanceOf(java.lang.Class, java.lang.Object, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void isInstanceOf(java.lang.Class, java.lang.Object, java.lang.String, java.lang.Object[])</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void matchesPattern(java.lang.CharSequence, java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void matchesPattern(java.lang.CharSequence, java.lang.String)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void matchesPattern(java.lang.CharSequence, java.lang.String, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void matchesPattern(java.lang.CharSequence, java.lang.String, java.lang.String, java.lang.Object[])</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.CharSequence notBlank(java.lang.CharSequence, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public java.lang.CharSequence notBlank(java.lang.CharSequence, java.lang.String, java.lang.Object[])</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.CharSequence notBlank(java.lang.CharSequence)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public java.lang.CharSequence notBlank(java.lang.CharSequence)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object[] validIndex(java.lang.Object[], int, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public java.lang.Object[] validIndex(java.lang.Object[], int, java.lang.String, java.lang.Object[])</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object[] validIndex(java.lang.Object[], int)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public java.lang.Object[] validIndex(java.lang.Object[], int)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.util.Collection validIndex(java.util.Collection, int, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public java.util.Collection validIndex(java.util.Collection, int, java.lang.String, java.lang.Object[])</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.util.Collection validIndex(java.util.Collection, int)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public java.util.Collection validIndex(java.util.Collection, int)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.CharSequence validIndex(java.lang.CharSequence, int, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public java.lang.CharSequence validIndex(java.lang.CharSequence, int, java.lang.String, java.lang.Object[])</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.CharSequence validIndex(java.lang.CharSequence, int)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public java.lang.CharSequence validIndex(java.lang.CharSequence, int)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void validState(boolean)' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void validState(boolean)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void validState(boolean, java.lang.String, java.lang.Object[])' has been added</td><td><a href="./xref/org/apache/commons/lang/Validate.html">org.apache.commons.lang.Validate</a></td><td>public void validState(boolean, java.lang.String, java.lang.Object[])</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.builder.Builder added</td><td><a href="./xref/org/apache/commons/lang/builder/Builder.html">org.apache.commons.lang.builder.Builder</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added org.apache.commons.lang.builder.Builder to the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/builder/CompareToBuilder.html">org.apache.commons.lang.builder.CompareToBuilder</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Integer build()' has been added</td><td><a href="./xref/org/apache/commons/lang/builder/CompareToBuilder.html">org.apache.commons.lang.builder.CompareToBuilder</a></td><td>public java.lang.Integer build()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object build()' has been added</td><td><a href="./xref/org/apache/commons/lang/builder/CompareToBuilder.html">org.apache.commons.lang.builder.CompareToBuilder</a></td><td>public java.lang.Object build()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added org.apache.commons.lang.builder.Builder to the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/builder/EqualsBuilder.html">org.apache.commons.lang.builder.EqualsBuilder</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Boolean build()' has been added</td><td><a href="./xref/org/apache/commons/lang/builder/EqualsBuilder.html">org.apache.commons.lang.builder.EqualsBuilder</a></td><td>public java.lang.Boolean build()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object build()' has been added</td><td><a href="./xref/org/apache/commons/lang/builder/EqualsBuilder.html">org.apache.commons.lang.builder.EqualsBuilder</a></td><td>public java.lang.Object build()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added org.apache.commons.lang.builder.Builder to the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/builder/HashCodeBuilder.html">org.apache.commons.lang.builder.HashCodeBuilder</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Integer build()' has been added</td><td><a href="./xref/org/apache/commons/lang/builder/HashCodeBuilder.html">org.apache.commons.lang.builder.HashCodeBuilder</a></td><td>public java.lang.Integer build()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object build()' has been added</td><td><a href="./xref/org/apache/commons/lang/builder/HashCodeBuilder.html">org.apache.commons.lang.builder.HashCodeBuilder</a></td><td>public java.lang.Object build()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added org.apache.commons.lang.builder.Builder to the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/builder/ReflectionToStringBuilder.html">org.apache.commons.lang.builder.ReflectionToStringBuilder</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Accessibility of field excludeFieldNames has been increased from private to protected</td><td><a href="./xref/org/apache/commons/lang/builder/ReflectionToStringBuilder.html">org.apache.commons.lang.builder.ReflectionToStringBuilder</a></td><td>excludeFieldNames</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added org.apache.commons.lang.builder.Builder to the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/builder/ToStringBuilder.html">org.apache.commons.lang.builder.ToStringBuilder</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String build()' has been added</td><td><a href="./xref/org/apache/commons/lang/builder/ToStringBuilder.html">org.apache.commons.lang.builder.ToStringBuilder</a></td><td>public java.lang.String build()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object build()' has been added</td><td><a href="./xref/org/apache/commons/lang/builder/ToStringBuilder.html">org.apache.commons.lang.builder.ToStringBuilder</a></td><td>public java.lang.Object build()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.AtomicInitializer added</td><td><a href="./xref/org/apache/commons/lang/concurrent/AtomicInitializer.html">org.apache.commons.lang.concurrent.AtomicInitializer</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.AtomicSafeInitializer added</td><td><a href="./xref/org/apache/commons/lang/concurrent/AtomicSafeInitializer.html">org.apache.commons.lang.concurrent.AtomicSafeInitializer</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.BackgroundInitializer added</td><td><a href="./xref/org/apache/commons/lang/concurrent/BackgroundInitializer.html">org.apache.commons.lang.concurrent.BackgroundInitializer</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.BasicThreadFactory added</td><td><a href="./xref/org/apache/commons/lang/concurrent/BasicThreadFactory.html">org.apache.commons.lang.concurrent.BasicThreadFactory</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.BasicThreadFactory$Builder added</td><td><a href="./xref/org/apache/commons/lang/concurrent/BasicThreadFactory$Builder.html">org.apache.commons.lang.concurrent.BasicThreadFactory$Builder</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.CallableBackgroundInitializer added</td><td><a href="./xref/org/apache/commons/lang/concurrent/CallableBackgroundInitializer.html">org.apache.commons.lang.concurrent.CallableBackgroundInitializer</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.ConcurrentException added</td><td><a href="./xref/org/apache/commons/lang/concurrent/ConcurrentException.html">org.apache.commons.lang.concurrent.ConcurrentException</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.ConcurrentInitializer added</td><td><a href="./xref/org/apache/commons/lang/concurrent/ConcurrentInitializer.html">org.apache.commons.lang.concurrent.ConcurrentInitializer</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.ConcurrentRuntimeException added</td><td><a href="./xref/org/apache/commons/lang/concurrent/ConcurrentRuntimeException.html">org.apache.commons.lang.concurrent.ConcurrentRuntimeException</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.ConcurrentUtils added</td><td><a href="./xref/org/apache/commons/lang/concurrent/ConcurrentUtils.html">org.apache.commons.lang.concurrent.ConcurrentUtils</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.ConstantInitializer added</td><td><a href="./xref/org/apache/commons/lang/concurrent/ConstantInitializer.html">org.apache.commons.lang.concurrent.ConstantInitializer</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.LazyInitializer added</td><td><a href="./xref/org/apache/commons/lang/concurrent/LazyInitializer.html">org.apache.commons.lang.concurrent.LazyInitializer</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.MultiBackgroundInitializer added</td><td><a href="./xref/org/apache/commons/lang/concurrent/MultiBackgroundInitializer.html">org.apache.commons.lang.concurrent.MultiBackgroundInitializer</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.MultiBackgroundInitializer$MultiBackgroundInitializerResults added</td><td><a href="./xref/org/apache/commons/lang/concurrent/MultiBackgroundInitializer$MultiBackgroundInitializerResults.html">org.apache.commons.lang.concurrent.MultiBackgroundInitializer$MultiBackgroundInitializerResults</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.concurrent.TimedSemaphore added</td><td><a href="./xref/org/apache/commons/lang/concurrent/TimedSemaphore.html">org.apache.commons.lang.concurrent.TimedSemaphore</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.event.EventListenerSupport added</td><td><a href="./xref/org/apache/commons/lang/event/EventListenerSupport.html">org.apache.commons.lang.event.EventListenerSupport</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.event.EventListenerSupport$ProxyInvocationHandler added</td><td><a href="./xref/org/apache/commons/lang/event/EventListenerSupport$ProxyInvocationHandler.html">org.apache.commons.lang.event.EventListenerSupport$ProxyInvocationHandler</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.event.EventUtils added</td><td><a href="./xref/org/apache/commons/lang/event/EventUtils.html">org.apache.commons.lang.event.EventUtils</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.exception.ContextedException added</td><td><a href="./xref/org/apache/commons/lang/exception/ContextedException.html">org.apache.commons.lang.exception.ContextedException</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.exception.ContextedRuntimeException added</td><td><a href="./xref/org/apache/commons/lang/exception/ContextedRuntimeException.html">org.apache.commons.lang.exception.ContextedRuntimeException</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.exception.DefaultExceptionContext added</td><td><a href="./xref/org/apache/commons/lang/exception/DefaultExceptionContext.html">org.apache.commons.lang.exception.DefaultExceptionContext</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.exception.ExceptionContext added</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionContext.html">org.apache.commons.lang.exception.ExceptionContext</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Throwable getCause(java.lang.Throwable)' has been deprecated</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public java.lang.Throwable getCause(java.lang.Throwable)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Throwable getCause(java.lang.Throwable, java.lang.String[])' has been deprecated</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public java.lang.Throwable getCause(java.lang.Throwable, java.lang.String[])</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.String[] getDefaultCauseMethodNames()' has been added</td><td><a href="./xref/org/apache/commons/lang/exception/ExceptionUtils.html">org.apache.commons.lang.exception.ExceptionUtils</a></td><td>public java.lang.String[] getDefaultCauseMethodNames()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int compareTo(org.apache.commons.lang.math.Fraction)' has been added</td><td><a href="./xref/org/apache/commons/lang/math/Fraction.html">org.apache.commons.lang.math.Fraction</a></td><td>public int compareTo(org.apache.commons.lang.math.Fraction)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int compareTo(org.apache.commons.lang.mutable.MutableBoolean)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableBoolean.html">org.apache.commons.lang.mutable.MutableBoolean</a></td><td>public int compareTo(org.apache.commons.lang.mutable.MutableBoolean)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object getValue()' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableBoolean.html">org.apache.commons.lang.mutable.MutableBoolean</a></td><td>public java.lang.Object getValue()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void setValue(java.lang.Boolean)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableBoolean.html">org.apache.commons.lang.mutable.MutableBoolean</a></td><td>public void setValue(java.lang.Boolean)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int compareTo(org.apache.commons.lang.mutable.MutableByte)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableByte.html">org.apache.commons.lang.mutable.MutableByte</a></td><td>public int compareTo(org.apache.commons.lang.mutable.MutableByte)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object getValue()' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableByte.html">org.apache.commons.lang.mutable.MutableByte</a></td><td>public java.lang.Object getValue()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void setValue(java.lang.Number)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableByte.html">org.apache.commons.lang.mutable.MutableByte</a></td><td>public void setValue(java.lang.Number)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int compareTo(org.apache.commons.lang.mutable.MutableDouble)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableDouble.html">org.apache.commons.lang.mutable.MutableDouble</a></td><td>public int compareTo(org.apache.commons.lang.mutable.MutableDouble)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object getValue()' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableDouble.html">org.apache.commons.lang.mutable.MutableDouble</a></td><td>public java.lang.Object getValue()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void setValue(java.lang.Number)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableDouble.html">org.apache.commons.lang.mutable.MutableDouble</a></td><td>public void setValue(java.lang.Number)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int compareTo(org.apache.commons.lang.mutable.MutableFloat)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableFloat.html">org.apache.commons.lang.mutable.MutableFloat</a></td><td>public int compareTo(org.apache.commons.lang.mutable.MutableFloat)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object getValue()' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableFloat.html">org.apache.commons.lang.mutable.MutableFloat</a></td><td>public java.lang.Object getValue()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void setValue(java.lang.Number)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableFloat.html">org.apache.commons.lang.mutable.MutableFloat</a></td><td>public void setValue(java.lang.Number)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int compareTo(org.apache.commons.lang.mutable.MutableInt)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableInt.html">org.apache.commons.lang.mutable.MutableInt</a></td><td>public int compareTo(org.apache.commons.lang.mutable.MutableInt)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object getValue()' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableInt.html">org.apache.commons.lang.mutable.MutableInt</a></td><td>public java.lang.Object getValue()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void setValue(java.lang.Number)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableInt.html">org.apache.commons.lang.mutable.MutableInt</a></td><td>public void setValue(java.lang.Number)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int compareTo(org.apache.commons.lang.mutable.MutableLong)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableLong.html">org.apache.commons.lang.mutable.MutableLong</a></td><td>public int compareTo(org.apache.commons.lang.mutable.MutableLong)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object getValue()' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableLong.html">org.apache.commons.lang.mutable.MutableLong</a></td><td>public java.lang.Object getValue()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void setValue(java.lang.Number)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableLong.html">org.apache.commons.lang.mutable.MutableLong</a></td><td>public void setValue(java.lang.Number)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public int compareTo(org.apache.commons.lang.mutable.MutableShort)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableShort.html">org.apache.commons.lang.mutable.MutableShort</a></td><td>public int compareTo(org.apache.commons.lang.mutable.MutableShort)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object getValue()' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableShort.html">org.apache.commons.lang.mutable.MutableShort</a></td><td>public java.lang.Object getValue()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void setValue(java.lang.Number)' has been added</td><td><a href="./xref/org/apache/commons/lang/mutable/MutableShort.html">org.apache.commons.lang.mutable.MutableShort</a></td><td>public void setValue(java.lang.Number)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.reflect.TypeUtils added</td><td><a href="./xref/org/apache/commons/lang/reflect/TypeUtils.html">org.apache.commons.lang.reflect.TypeUtils</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.FormattableUtils added</td><td><a href="./xref/org/apache/commons/lang/text/FormattableUtils.html">org.apache.commons.lang.text.FormattableUtils</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added java.lang.Appendable to the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Added java.lang.CharSequence to the set of implemented interfaces</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public org.apache.commons.lang.text.StrBuilder append(java.lang.CharSequence)' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public org.apache.commons.lang.text.StrBuilder append(java.lang.CharSequence)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public org.apache.commons.lang.text.StrBuilder append(java.lang.CharSequence, int, int)' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public org.apache.commons.lang.text.StrBuilder append(java.lang.CharSequence, int, int)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Appendable append(char)' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public java.lang.Appendable append(char)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Appendable append(java.lang.CharSequence, int, int)' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public java.lang.Appendable append(java.lang.CharSequence, int, int)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Appendable append(java.lang.CharSequence)' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public java.lang.Appendable append(java.lang.CharSequence)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.CharSequence subSequence(int, int)' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrBuilder.html">org.apache.commons.lang.text.StrBuilder</a></td><td>public java.lang.CharSequence subSequence(int, int)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void add(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrTokenizer.html">org.apache.commons.lang.text.StrTokenizer</a></td><td>public void add(java.lang.String)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object next()' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrTokenizer.html">org.apache.commons.lang.text.StrTokenizer</a></td><td>public java.lang.Object next()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.lang.Object previous()' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrTokenizer.html">org.apache.commons.lang.text.StrTokenizer</a></td><td>public java.lang.Object previous()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public void set(java.lang.String)' has been added</td><td><a href="./xref/org/apache/commons/lang/text/StrTokenizer.html">org.apache.commons.lang.text.StrTokenizer</a></td><td>public void set(java.lang.String)</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.WordUtils added</td><td><a href="./xref/org/apache/commons/lang/text/WordUtils.html">org.apache.commons.lang.text.WordUtils</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.AggregateTranslator added</td><td><a href="./xref/org/apache/commons/lang/text/translate/AggregateTranslator.html">org.apache.commons.lang.text.translate.AggregateTranslator</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.CharSequenceTranslator added</td><td><a href="./xref/org/apache/commons/lang/text/translate/CharSequenceTranslator.html">org.apache.commons.lang.text.translate.CharSequenceTranslator</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.CodePointTranslator added</td><td><a href="./xref/org/apache/commons/lang/text/translate/CodePointTranslator.html">org.apache.commons.lang.text.translate.CodePointTranslator</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.EntityArrays added</td><td><a href="./xref/org/apache/commons/lang/text/translate/EntityArrays.html">org.apache.commons.lang.text.translate.EntityArrays</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.LookupTranslator added</td><td><a href="./xref/org/apache/commons/lang/text/translate/LookupTranslator.html">org.apache.commons.lang.text.translate.LookupTranslator</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.NumericEntityEscaper added</td><td><a href="./xref/org/apache/commons/lang/text/translate/NumericEntityEscaper.html">org.apache.commons.lang.text.translate.NumericEntityEscaper</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.NumericEntityUnescaper added</td><td><a href="./xref/org/apache/commons/lang/text/translate/NumericEntityUnescaper.html">org.apache.commons.lang.text.translate.NumericEntityUnescaper</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.NumericEntityUnescaper$OPTION added</td><td><a href="./xref/org/apache/commons/lang/text/translate/NumericEntityUnescaper$OPTION.html">org.apache.commons.lang.text.translate.NumericEntityUnescaper$OPTION</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.OctalUnescaper added</td><td><a href="./xref/org/apache/commons/lang/text/translate/OctalUnescaper.html">org.apache.commons.lang.text.translate.OctalUnescaper</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.UnicodeEscaper added</td><td><a href="./xref/org/apache/commons/lang/text/translate/UnicodeEscaper.html">org.apache.commons.lang.text.translate.UnicodeEscaper</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.text.translate.UnicodeUnescaper added</td><td><a href="./xref/org/apache/commons/lang/text/translate/UnicodeUnescaper.html">org.apache.commons.lang.text.translate.UnicodeUnescaper</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public java.util.Date add(java.util.Date, int, int)' is no longer deprecated</td><td><a href="./xref/org/apache/commons/lang/time/DateUtils.html">org.apache.commons.lang.time.DateUtils</a></td><td>public java.util.Date add(java.util.Date, int, int)</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public long getNanoTime()' has been added</td><td><a href="./xref/org/apache/commons/lang/time/StopWatch.html">org.apache.commons.lang.time.StopWatch</a></td><td>public long getNanoTime()</td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Method 'public long getSplitNanoTime()' has been added</td><td><a href="./xref/org/apache/commons/lang/time/StopWatch.html">org.apache.commons.lang.time.StopWatch</a></td><td>public long getSplitNanoTime()</td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.tuple.ImmutablePair added</td><td><a href="./xref/org/apache/commons/lang/tuple/ImmutablePair.html">org.apache.commons.lang.tuple.ImmutablePair</a></td><td></td></tr><tr class="b"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.tuple.MutablePair added</td><td><a href="./xref/org/apache/commons/lang/tuple/MutablePair.html">org.apache.commons.lang.tuple.MutablePair</a></td><td></td></tr><tr class="a"><td><img alt="Info" src="images/icon_info_sml.gif" /></td><td>Class org.apache.commons.lang.tuple.Pair added</td><td><a href="./xref/org/apache/commons/lang/tuple/Pair.html">org.apache.commons.lang.tuple.Pair</a></td><td></td></tr></table></div>
+ </div>
+ </div>
+ <div class="clear">
+ <hr/>
+ </div>
+ <div id="footer">
+ <div class="center">Copyright &#169; 2001-2011
+ <a href="https://www.apache.org/">The Apache Software Foundation</a>.
+ All Rights Reserved.
+
+ </div>
+
+<div class="center">Apache Commons, Apache Commons Lang, Apache, the Apache feather logo, and the Apache Commons project logos are trademarks of The Apache Software Foundation.
+ All other marks mentioned may be trademarks or registered trademarks of their respective owners.</div>
+ <div class="clear">
+ <hr/>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/src/site/resources/profile.jacoco b/src/site/resources/profile.jacoco
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/site/resources/profile.jacoco
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-1.0.1.txt b/src/site/resources/release-notes/RELEASE-NOTES-1.0.1.txt
new file mode 100644
index 000000000..975849225
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-1.0.1.txt
@@ -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.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 1.0.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for this version of the Commons
+Lang package. Commons Lang is a set of utility functions and reusable
+components that should be a help in any Java environment.
+
+NEW FEATURES:
+
+This release is a bugfix release for the Lang 1.0 release. There are no new features.
+
+BUG FIXES:
+
+#14062: StringUtils.split fails to handle (String, null, int) correctly.
+ This results in the String "null" appearing in the split text, when
+ the text is not entirely consumed in the split, ie) int is less
+ than the number of whitespace tokens in the String.
+ Fix is courtesy of Mark McDowell.
+
+#- : SystemUtils.isJavaVersionAtLeast made static.
+
+#- : NumberUtils test fails in JDK 1.2 due to lack of 1.2 support for
+ "1.1E-700F". Fix is to use SystemUtils to protect it for the moment.
+
+#- : ToStringStyle did not compile under JDK 1.2 due to inner class
+ issues. Added explicit 'this.' prefixes to make this so.
+
+#14566: NumberRange.getMaximum was returning the minimum.
+ Bug reported by Kasper Ronning.
+
+#13527: ExceptionUtils now handles getCausedByException and getRootCause
+ from EJBException and ServletException, as reported by Lars Beuster.
+
+#14334: NestableDelegate now implements Serializable, as reported by
+ Max Rydahl Andersen.
+
+#13568: Enums cannot now be created with the same name as an already
+ existing Enum. Enum now compiles under JDK 1.2.
+
+DEPRECATIONS:
+
+Solely a bugfix version.
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-1.0.txt b/src/site/resources/release-notes/RELEASE-NOTES-1.0.txt
new file mode 100644
index 000000000..10ee7e89c
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-1.0.txt
@@ -0,0 +1,161 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 1.0
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for this version of the Commons
+Lang package. Commons Lang is a set of utility functions and reusable
+components that should be a help in any Java environment.
+
+NEW FEATURES:
+
+Since the release of the b1 package the following have been added:
+
+lang.
+ SystemUtils:
+ Brings together many system specific variables under one easy component.
+
+exception.
+ ExceptionUtils:
+ Provides helpful static functions for dealing with Exceptions.
+ NestableError :
+ Adds nesting ability to Errors.
+
+enum sub-package:
+ A solid version of the typical Java translation of a C enum.
+
+builder sub-package:
+ A series of helpers for handling standard Object methods such as equals,
+ toString, compareTo and hashCode in a professional manner.
+
+
+BUG FIXES:
+
+StringUtils.stripStart and stripEnd were improved to match their Javadoc.
+StringUtils.convertUnicodeToNative and convertNativeToUnicode both removed.
+ Both methods did not work properly.
+
+
+DEPRECATIONS:
+
+Much of the exception subpackage was reworked between 1.0-b1 and 1.0. Apart
+from this the API should have a high level of backward compatibility.
+
+
+CHANGES: [In 'diff' format]
+
+Jar changes
+===========
+> org.apache.commons.lang.exception.ExceptionUtils
+> org.apache.commons.lang.exception.NestableError
+> org.apache.commons.lang.ObjectUtils$Null
+> org.apache.commons.lang.ObjectUtils$1
+> org.apache.commons.lang.enum.Enum$Entry
+> org.apache.commons.lang.enum.Enum$1
+> org.apache.commons.lang.enum.Enum
+> org.apache.commons.lang.enum.EnumUtils
+> org.apache.commons.lang.enum.ValuedEnum
+> org.apache.commons.lang.builder.CompareToBuilder
+> org.apache.commons.lang.builder.EqualsBuilder
+> org.apache.commons.lang.builder.HashCodeBuilder
+> org.apache.commons.lang.builder.StandardToStringStyle
+> org.apache.commons.lang.builder.ToStringStyle$DefaultToStringStyle
+> org.apache.commons.lang.builder.ToStringStyle$NoFieldNameToStringStyle
+> org.apache.commons.lang.builder.ToStringStyle$SimpleToStringStyle
+> org.apache.commons.lang.builder.ToStringStyle$MultiLineToStringStyle
+> org.apache.commons.lang.builder.ToStringStyle$1
+> org.apache.commons.lang.builder.ToStringStyle
+> org.apache.commons.lang.builder.ToStringBuilder
+> org.apache.commons.lang.SystemUtils
+
+
+Class changes
+=============
+org.apache.commons.lang.exception.Nestable
+--------------------
+< public abstract int getLength();
+> public abstract int getThrowableCount();
+< public abstract int indexOfThrowable(int, java.lang.Class);
+---
+> public abstract int indexOfThrowable(java.lang.Class, int);
+> public abstract void printStackTrace(java.io.PrintStream);
+
+org.apache.commons.lang.exception.NestableDelegate
+--------------------
+< int getLength();
+< java.lang.String getMessage(java.lang.String);
+> java.lang.String getMessage(java.lang.String);
+> java.lang.String getMessages()[];
+> int getThrowableCount();
+< java.lang.String getMessages()[];
+< int indexOfThrowable(int, java.lang.Class);
+---
+> int indexOfThrowable(java.lang.Class, int);
+
+org.apache.commons.lang.exception.NestableException
+--------------------
+< public int getLength();
+> public java.lang.String getMessage(int);
+> public int getThrowableCount();
+< public java.lang.String getMessage(int);
+< public int indexOfThrowable(int, java.lang.Class);
+---
+> public int indexOfThrowable(java.lang.Class, int);
+
+org.apache.commons.lang.exception.NestableRuntimeException
+--------------------
+< public int getLength();
+> public java.lang.String getMessage(int);
+> public int getThrowableCount();
+< public java.lang.String getMessage(int);
+< public int indexOfThrowable(int, java.lang.Class);
+---
+> public int indexOfThrowable(java.lang.Class, int);
+
+org.apache.commons.lang.NumberUtils
+--------------------
+> public static long minimum(long, long, long);
+> public static long maximum(long, long, long);
+> public static int compare(double, double);
+> public static int compare(float, float);
+
+org.apache.commons.lang.ObjectUtils
+--------------------
+> public static final org.apache.commons.lang.ObjectUtils.Null NULL;
+> public org.apache.commons.lang.ObjectUtils();
+> public static java.lang.String identityToString(java.lang.Object);
+> static {};
+> public static class org.apache.commons.lang.ObjectUtils. Null extends java.lang.Object implements java.io.Serializable
+
+org.apache.commons.lang.RandomStringUtils
+--------------------
+> public org.apache.commons.lang.RandomStringUtils();
+
+org.apache.commons.lang.StringUtils
+--------------------
+> public org.apache.commons.lang.StringUtils();
+< public static java.lang.String stackTrace(java.lang.Throwable);
+< public static java.lang.String convertUnicodeToNative(java.lang.String, java.lang.String) throws java.io.IOException;
+< public static java.lang.String convertNativeToUnicode(java.lang.String, java.lang.String) throws java.io.IOException;
+---
+> public static boolean containsOnly(java.lang.String, char[]);
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-2.0.txt b/src/site/resources/release-notes/RELEASE-NOTES-2.0.txt
new file mode 100644
index 000000000..000c90c8a
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-2.0.txt
@@ -0,0 +1,676 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 2.0
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for this version of the Commons
+Lang package. Commons Lang is a set of utility functions and reusable
+components that should be a help in any Java environment.
+
+This release has involved a major clean and tidy exercise.
+Javadoc and Tests are now much more thorough.
+All methods should now be much clearer in what they do in unusual cases.
+
+
+INCOMPATIBLE CHANGES:
+Some StringUtils methods have changed functionality from 1.0:
+ isEmpty()
+ chomp(String)
+ chomp(String,String)
+ swapCase(String)
+Numerous other methods have changed null handling to accept nulls gracefully.
+As with all major version releases, check your code for incompatibilities.
+
+
+NEW FEATURES:
+
+Since the release of the 1.0 package the following classes have been added:
+
+lang package:
+ ArrayUtils
+ BitField
+ BooleanUtils
+ CharRange (previously package scoped)
+ ClassUtils
+ StringEscapeUtils
+ WordUtils
+ IllegalClassException
+ IncompleteArgumentException
+ NotImplementedException
+ NullArgumentException
+ SerializationException
+ UnhandledException
+ Validate
+
+
+math sub-package:
+ IntRange
+ LongRange
+ Range
+ DoubleRange
+ JVMRandom
+ NumberRange
+ FloatRange
+ NumberUtils
+ Fraction
+ RandomUtils
+
+time sub-package:
+ DateFormatUtils
+ FastDateFormat
+ DateUtils
+ StopWatch
+
+Since the release of the 1.0 package the following classes have been changed:
+
+lang:
+ CharSet:
+ Added factory method, equals and hashCode().
+ Better defined and tested the set syntax.
+ CharSetUtils:
+ added keep method: keep any characters specified in the CharSet string
+ RandomStringUtils:
+ random method: overloaded to allow passing in of a Random class
+ SerializationUtils:
+ added empty constructor
+ StringUtils:
+ isEmpty() changed to not trim
+ chomp() changed to be more like Perl.
+ swapCase() no longer word based, but no difference if you pass in ASCII
+ Various methods changed in the handling of null (less exceptions).
+ Many new methods.
+ Various methods deprecated.
+ SystemUtils:
+ isJavaVersionAtLeast(int) added. getJavaVersion() deprecated.
+ host of new constants.
+
+enum:
+ Enum:
+ getEnumClass(Class) added
+ EnumUtils:
+ Removed irrelevant Comparable/Serializable interfaces.
+
+exception:
+ NestableDelegate:
+ Gained many new methods for dissecting an Exception.
+ ExceptionUtils:
+ Gained many new methods to improve handling of nested stack traces.
+
+builder:
+ ReflectionToStringBuilder:
+ Handy class added for creating default toStrings.
+ All other builder classes received a set of new methods.
+
+
+BUG FIXES:
+
+ID Sev Pri Plt Owner State Result Summary
+13367 [PATCH] StringUtil enhancement
+13391 Javadoc nit
+13771 Additional Lang Method Suggestions
+14306 NullPointerException in CompareToBuilder
+14357 static option for reversing the stacktrace
+14447 ToStringBuilder doesn't work well in subclasses
+14883 StringUtils.countMatches loops forever if substring empty
+14884 NumberRange inaccurate for Long, etc.
+14985 More flexibility for getRootCause in ExceptionUtils
+15154 SystemUtils.IS_JAVA_1_5 Javadoc is wrong
+15257 Hierarchy support in ToStringBuilder.reflectionToString()
+15438 ArrayUtils.contains()
+15439 Enum does not support inner sub-classes
+15986 Infinite loop in ToStringBuilder.reflectionToString for inne
+16076 Example in Javadoc for ToStringBuilder wrong for append.
+16193 Hierarchy support in EqualsBuilder.reflectionEquals()
+16202 typo in the javadoc example code
+16204 Infinite loop in StringUtils.replace(text, repl, with) + FIX
+16227 Added class hierarchy support to CompareToBuilder.reflectionC
+16228 Added class hierarchy support to HashCodeBuilder.reflectionHa
+16284 MethodUtils: Removed unused code/unused local vars.
+16341 No Javadoc for NestableDelegate
+16622 Removed compile warning in FastDateFormat
+16669 Javadoc Errata
+16676 StackOverflow due to ToStringBuilder
+16689 ExceptionUtils new methods.
+16690 Specify initial size for Enum's HashMap.
+16787 Removed compile warning in ObjectUtils
+17250 [Lang] Should ToStringBuilder.reflectionToString handle arra
+17654 EnumUtils nit: The import java.io.Serializable is never used
+17882 Add join(..., char c) to StringUtils (and some performance f
+18077 StringUtils.chomp does not match Perl
+18723 RandomStringUtils infinite loop with length < 1
+18836 test.lang fails if compiled with non iso-8859-1 locales
+18948 Resurrect the WordWrapUtils from commons-sandbox/utils
+19296 [Lang] What to do with FastDateFormat unused private constru
+19364 [Lang] time unit tests fail on Sundays
+19756 [lang] java.lang.ExceptionInInitializerError thrown by JVMRa
+19880 [lang] patch and test case fixing problem with RandomStringU
+20165 [LANG] SystemUtils does not play nice in an Applet
+20538 [lang] NumberUtils.isNumber allows illegal trailing characte
+20592 [lang] RandomStringUtils.randomAlpha methods omit 'z'
+20603 [lang] Make NestableDelegate methods public instead of packa
+20632 Refactored reflection feature of ToStringBuilder into new Re
+20652 StringUtils.chopNewLine - StringIndexOutOfBoundsException
+21021 [PATCH] reduce object creation in ToStringBuilder
+21068 [lang] [PATCH] NumberUtils min/max, BooleanUtils.xor, and Ar
+21099 [lang][PATCH] Unused field 'startFinal' in DateIterator
+21715 The javadoc says "Mac" instead of "OS/2"
+21734 [PATCH] all NumberUtils.createXXX(String) methods handle null
+21750 [lang] StringUtils javadoc and test enhancements
+21758 [lang] lang.builder classes javadoc edits (mostly typo fixes)
+21797 [lang] Add javadoc examples and tests for StringUtils
+21809 [lang] maven-beta10 checkstyle problem
+21904 NumberUtils.createBigDecimal("") NPE in Sun 1.3.1_08
+21952 [lang] Improved tests, javadoc for CharSetUtils, StringEscapeUtils
+22091 Adding tolerance to double[] search methods in ArrayUtils
+22094 A small, but important javadoc fix for Fraction proper whole/numerator
+22095 [lang] Javadoc, tests improvements for CharSet, CharSetUtils
+22098 [lang] Improve util.Validate tests
+22245 [lang] test.time fails in Japanese (non-us) locale.
+22286 [lang] Missing @since tags
+22367 Typo in documentation
+22386 [lang] Improve javadoc and overflow behavior of Fraction
+
+
+DEPRECATIONS:
+
+lang:
+ NumberRange:
+ now deprecated, see math subpackage
+ NumberUtils:
+ now deprecated, see math subpackage
+
+
+CHANGES: [In 'diff' format]
+
+Jar changes
+===========
+> org.apache.commons.lang.math.Range
+> org.apache.commons.lang.math.FloatRange
+> org.apache.commons.lang.math.NumberUtils
+> org.apache.commons.lang.math.JVMRandom
+> org.apache.commons.lang.math.IntRange
+> org.apache.commons.lang.math.LongRange
+> org.apache.commons.lang.math.DoubleRange
+> org.apache.commons.lang.math.NumberRange
+> org.apache.commons.lang.math.Fraction
+> org.apache.commons.lang.math.RandomUtils
+> org.apache.commons.lang.time.FastDateFormat
+> org.apache.commons.lang.time.DateUtils$DateIterator
+> org.apache.commons.lang.time.DateUtils
+> org.apache.commons.lang.time.FastDateFormat$UnpaddedMonthField
+> org.apache.commons.lang.time.FastDateFormat$StringLiteral
+> org.apache.commons.lang.time.FastDateFormat$TwelveHourField
+> org.apache.commons.lang.time.FastDateFormat$NumberRule
+> org.apache.commons.lang.time.FastDateFormat$CharacterLiteral
+> org.apache.commons.lang.time.FastDateFormat$TimeZoneNumberRule
+> org.apache.commons.lang.time.FastDateFormat$TimeZoneNameRule
+> org.apache.commons.lang.time.DateFormatUtils
+> org.apache.commons.lang.time.FastDateFormat$TwoDigitMonthField
+> org.apache.commons.lang.time.DurationFormatUtils
+> org.apache.commons.lang.time.FastDateFormat$TimeZoneDisplayKey
+> org.apache.commons.lang.time.FastDateFormat$UnpaddedNumberField
+> org.apache.commons.lang.time.FastDateFormat$PaddedNumberField
+> org.apache.commons.lang.time.StopWatch
+> org.apache.commons.lang.time.FastDateFormat$TwentyFourHourField
+> org.apache.commons.lang.time.FastDateFormat$Rule
+> org.apache.commons.lang.time.FastDateFormat$TwoDigitNumberField
+> org.apache.commons.lang.time.FastDateFormat$TextField
+> org.apache.commons.lang.time.FastDateFormat$Pair
+> org.apache.commons.lang.time.FastDateFormat$TwoDigitYearField
+> org.apache.commons.lang.util.IdentifierUtils$StringNumericIdentifierFactory
+> org.apache.commons.lang.util.IdentifierUtils$StringSessionIdentifierFactory
+> org.apache.commons.lang.util.IdentifierUtils$LongNumericIdentifierFactory
+> org.apache.commons.lang.util.IdentifierUtils$StringAlphanumericIdentifierFactory
+> org.apache.commons.lang.util.Validate
+> org.apache.commons.lang.util.LongIdentifierFactory
+> org.apache.commons.lang.util.IdentifierUtils$1
+> org.apache.commons.lang.util.StringIdentifierFactory
+> org.apache.commons.lang.util.IdentifierUtils
+> org.apache.commons.lang.util.IdentifierFactory
+> org.apache.commons.lang.util.BitField
+> org.apache.commons.lang.Entities
+> org.apache.commons.lang.Entities$LookupEntityMap
+> org.apache.commons.lang.NotImplementedException
+> org.apache.commons.lang.NullArgumentException
+< org.apache.commons.lang.ObjectUtils$1
+---
+> org.apache.commons.lang.StringPrintWriter
+> org.apache.commons.lang.UnhandledException
+> org.apache.commons.lang.Entities$HashEntityMap
+> org.apache.commons.lang.Entities$ArrayEntityMap
+> org.apache.commons.lang.Entities$EntityMap
+> org.apache.commons.lang.IntHashMap
+> org.apache.commons.lang.BooleanUtils
+> org.apache.commons.lang.IncompleteArgumentException
+> org.apache.commons.lang.Entities$PrimitiveEntityMap
+> org.apache.commons.lang.Entities$TreeEntityMap
+> org.apache.commons.lang.WordUtils
+> org.apache.commons.lang.StringEscapeUtils
+> org.apache.commons.lang.ArrayUtils
+> org.apache.commons.lang.Entities$BinaryEntityMap
+> org.apache.commons.lang.ClassUtils
+> org.apache.commons.lang.IntHashMap$Entry
+> org.apache.commons.lang.IllegalClassException
+> org.apache.commons.lang.builder.ReflectionToStringBuilder$1
+> org.apache.commons.lang.builder.ReflectionToStringBuilder
+> org.apache.commons.lang.Entities$MapIntMap
+
+
+Class changes
+=============
+org.apache.commons.lang.enum.EnumUtils
+--------------------
+< public abstract class org.apache.commons.lang.enum.EnumUtils extends java.lang.Object implements java.lang.Comparable, java.io.Serializable {
+---
+> public class org.apache.commons.lang.enum.EnumUtils extends java.lang.Object {
+> public org.apache.commons.lang.enum.EnumUtils();
+
+org.apache.commons.lang.enum.Enum$Entry
+--------------------
+> final java.util.Map unmodifiableMap;
+> final java.util.List unmodifiableList;
+
+org.apache.commons.lang.enum.Enum
+--------------------
+> protected transient java.lang.String iToString;
+> static java.lang.Class class$org$apache$commons$lang$enum$ValuedEnum;
+> public java.lang.Class getEnumClass();
+
+org.apache.commons.lang.enum.ValuedEnum
+--------------------
+> static {};
+
+org.apache.commons.lang.StringUtils
+--------------------
+> public static final java.lang.String EMPTY;
+> public static boolean isEmpty(java.lang.String);
+> public static boolean isNotEmpty(java.lang.String);
+> public static boolean isBlank(java.lang.String);
+> public static boolean isNotBlank(java.lang.String);
+< public static java.lang.String deleteSpaces(java.lang.String);
+< public static java.lang.String deleteWhitespace(java.lang.String);
+< public static boolean isNotEmpty(java.lang.String);
+< public static boolean isEmpty(java.lang.String);
+---
+> public static java.lang.String trimToNull(java.lang.String);
+> public static java.lang.String trimToEmpty(java.lang.String);
+> public static java.lang.String strip(java.lang.String);
+> public static java.lang.String stripToNull(java.lang.String);
+> public static java.lang.String stripToEmpty(java.lang.String);
+> public static java.lang.String strip(java.lang.String, java.lang.String);
+> public static java.lang.String stripStart(java.lang.String, java.lang.String);
+> public static java.lang.String stripEnd(java.lang.String, java.lang.String);
+> public static java.lang.String stripAll(java.lang.String[])[];
+> public static java.lang.String stripAll(java.lang.String[], java.lang.String)[];
+> public static int indexOf(java.lang.String, char);
+> public static int indexOf(java.lang.String, char, int);
+> public static int indexOf(java.lang.String, java.lang.String);
+> public static int indexOf(java.lang.String, java.lang.String, int);
+> public static int lastIndexOf(java.lang.String, char);
+> public static int lastIndexOf(java.lang.String, char, int);
+> public static int lastIndexOf(java.lang.String, java.lang.String);
+> public static int lastIndexOf(java.lang.String, java.lang.String, int);
+> public static boolean contains(java.lang.String, char);
+> public static boolean contains(java.lang.String, java.lang.String);
+> public static int indexOfAny(java.lang.String, char[]);
+> public static int indexOfAny(java.lang.String, java.lang.String);
+> public static int indexOfAnyBut(java.lang.String, char[]);
+> public static int indexOfAnyBut(java.lang.String, java.lang.String);
+> public static boolean containsOnly(java.lang.String, char[]);
+> public static boolean containsOnly(java.lang.String, java.lang.String);
+> public static boolean containsNone(java.lang.String, char[]);
+> public static boolean containsNone(java.lang.String, java.lang.String);
+> public static java.lang.String substringBefore(java.lang.String, java.lang.String);
+> public static java.lang.String substringAfter(java.lang.String, java.lang.String);
+> public static java.lang.String substringBeforeLast(java.lang.String, java.lang.String);
+> public static java.lang.String substringAfterLast(java.lang.String, java.lang.String);
+> public static java.lang.String substringBetween(java.lang.String, java.lang.String);
+> public static java.lang.String substringBetween(java.lang.String, java.lang.String, java.lang.String);
+> public static java.lang.String getNestedString(java.lang.String, java.lang.String);
+> public static java.lang.String getNestedString(java.lang.String, java.lang.String, java.lang.String);
+> public static java.lang.String split(java.lang.String, char)[];
+> public static java.lang.String join(java.lang.Object[]);
+> public static java.lang.String join(java.lang.Object[], char);
+> public static java.lang.String join(java.util.Iterator, char);
+> public static java.lang.String deleteSpaces(java.lang.String);
+> public static java.lang.String deleteWhitespace(java.lang.String);
+> public static java.lang.String replaceChars(java.lang.String, char, char);
+> public static java.lang.String replaceChars(java.lang.String, java.lang.String, java.lang.String);
+< public static java.lang.String center(java.lang.String, int);
+< public static java.lang.String center(java.lang.String, int, java.lang.String);
+---
+> public static java.lang.String overlay(java.lang.String, java.lang.String, int, int);
+> public static java.lang.String rightPad(java.lang.String, int, char);
+> public static java.lang.String leftPad(java.lang.String, int, char);
+< public static java.lang.String strip(java.lang.String);
+< public static java.lang.String strip(java.lang.String, java.lang.String);
+< public static java.lang.String stripAll(java.lang.String[])[];
+< public static java.lang.String stripAll(java.lang.String[], java.lang.String)[];
+< public static java.lang.String stripEnd(java.lang.String, java.lang.String);
+< public static java.lang.String stripStart(java.lang.String, java.lang.String);
+---
+> public static java.lang.String center(java.lang.String, int);
+> public static java.lang.String center(java.lang.String, int, char);
+> public static java.lang.String center(java.lang.String, int, java.lang.String);
+< public static java.lang.String uncapitalise(java.lang.String);
+---
+> public static java.lang.String capitalize(java.lang.String);
+> public static java.lang.String uncapitalize(java.lang.String);
+> public static java.lang.String uncapitalise(java.lang.String);
+< public static java.lang.String getNestedString(java.lang.String, java.lang.String);
+< public static java.lang.String getNestedString(java.lang.String, java.lang.String, java.lang.String);
+> public static boolean isWhitespace(java.lang.String);
+> public static java.lang.String reverseDelimited(java.lang.String, char);
+> public static java.lang.String abbreviate(java.lang.String, int);
+> public static java.lang.String abbreviate(java.lang.String, int, int);
+> public static java.lang.String difference(java.lang.String, java.lang.String);
+> public static int differenceAt(java.lang.String, java.lang.String);
+< public static boolean containsOnly(java.lang.String, char[]);
+---
+> static {};
+
+org.apache.commons.lang.ObjectUtils
+--------------------
+> public static java.lang.StringBuffer appendIdentityToString(java.lang.StringBuffer, java.lang.Object);
+> public static java.lang.String toString(java.lang.Object);
+> public static java.lang.String toString(java.lang.Object, java.lang.String);
+< org.apache.commons.lang.ObjectUtils.Null(org.apache.commons.lang.ObjectUtils$1);
+---
+> org.apache.commons.lang.ObjectUtils.Null();
+> static {};
+
+org.apache.commons.lang.exception.NestableDelegate
+--------------------
+> public static boolean topDown;
+> public static boolean trimStackFrames;
+< org.apache.commons.lang.exception.NestableDelegate(org.apache.commons.lang.exception.Nestable);
+< java.lang.String getMessage(int);
+< java.lang.String getMessage(java.lang.String);
+< java.lang.String getMessages()[];
+< java.lang.Throwable getThrowable(int);
+< int getThrowableCount();
+< java.lang.Throwable getThrowables()[];
+< int indexOfThrowable(java.lang.Class, int);
+---
+> public org.apache.commons.lang.exception.NestableDelegate(org.apache.commons.lang.exception.Nestable);
+> public java.lang.String getMessage(int);
+> public java.lang.String getMessage(java.lang.String);
+> public java.lang.String getMessages()[];
+> public java.lang.Throwable getThrowable(int);
+> public int getThrowableCount();
+> public java.lang.Throwable getThrowables()[];
+> public int indexOfThrowable(java.lang.Class, int);
+> protected java.lang.String getStackFrames(java.lang.Throwable)[];
+> protected void trimStackFrames(java.util.List);
+
+org.apache.commons.lang.exception.ExceptionUtils
+--------------------
+< protected static final java.lang.String CAUSE_METHOD_NAMES[];
+< protected static final java.lang.Object CAUSE_METHOD_PARAMS[];
+---
+> static final java.lang.String WRAPPED_MARKER;
+< protected org.apache.commons.lang.exception.ExceptionUtils();
+---
+> public org.apache.commons.lang.exception.ExceptionUtils();
+> public static void addCauseMethodName(java.lang.String);
+> public static boolean isThrowableNested();
+> public static boolean isNestedThrowable(java.lang.Throwable);
+> public static void printRootCauseStackTrace(java.lang.Throwable);
+> public static void printRootCauseStackTrace(java.lang.Throwable, java.io.PrintStream);
+> public static void printRootCauseStackTrace(java.lang.Throwable, java.io.PrintWriter);
+> public static java.lang.String getRootCauseStackTrace(java.lang.Throwable)[];
+> public static void removeCommonFrames(java.util.List, java.util.List);
+> public static java.lang.String getFullStackTrace(java.lang.Throwable);
+> static java.util.List getStackFrameList(java.lang.Throwable);
+
+org.apache.commons.lang.CharRange
+--------------------
+< class org.apache.commons.lang.CharRange extends java.lang.Object {
+---
+> public final class org.apache.commons.lang.CharRange extends java.lang.Object implements java.io.Serializable {
+> public org.apache.commons.lang.CharRange(char,boolean);
+< public org.apache.commons.lang.CharRange(java.lang.String,java.lang.String);
+---
+> public org.apache.commons.lang.CharRange(char,char,boolean);
+< public void setStart(char);
+< public void setEnd(char);
+< public boolean isRange();
+< public boolean inRange(char);
+< public void setNegated(boolean);
+---
+> public boolean contains(char);
+> public boolean contains(org.apache.commons.lang.CharRange);
+> public boolean equals(java.lang.Object);
+> public int hashCode();
+> static {};
+
+org.apache.commons.lang.ObjectUtils$1
+--------------------
+< Compiled from ObjectUtils.java
+< class org.apache.commons.lang.ObjectUtils$1 extends java.lang.Object {
+< }
+---
+> Class 'org.apache.commons.lang.ObjectUtils$1' has been removed
+
+org.apache.commons.lang.ObjectUtils$Null
+--------------------
+< org.apache.commons.lang.ObjectUtils.Null(org.apache.commons.lang.ObjectUtils$1);
+---
+> org.apache.commons.lang.ObjectUtils.Null();
+> static {};
+
+org.apache.commons.lang.SystemUtils
+--------------------
+> public static final java.lang.String FILE_ENCODING;
+> public static final java.lang.String JAVA_RUNTIME_NAME;
+> public static final java.lang.String JAVA_RUNTIME_VERSION;
+> public static final java.lang.String JAVA_VM_INFO;
+> public static final java.lang.String USER_COUNTRY;
+> public static final java.lang.String USER_LANGUAGE;
+> public static final float JAVA_VERSION_FLOAT;
+> public static final int JAVA_VERSION_INT;
+> public static final boolean IS_OS_AIX;
+> public static final boolean IS_OS_HP_UX;
+> public static final boolean IS_OS_IRIX;
+> public static final boolean IS_OS_LINUX;
+> public static final boolean IS_OS_MAC;
+> public static final boolean IS_OS_MAC_OSX;
+> public static final boolean IS_OS_OS2;
+> public static final boolean IS_OS_SOLARIS;
+> public static final boolean IS_OS_SUN_OS;
+> public static final boolean IS_OS_WINDOWS;
+> public static final boolean IS_OS_WINDOWS_2000;
+> public static final boolean IS_OS_WINDOWS_95;
+> public static final boolean IS_OS_WINDOWS_98;
+> public static final boolean IS_OS_WINDOWS_ME;
+> public static final boolean IS_OS_WINDOWS_NT;
+> public static final boolean IS_OS_WINDOWS_XP;
+> public static boolean isJavaVersionAtLeast(int);
+
+org.apache.commons.lang.SerializationUtils
+--------------------
+> public org.apache.commons.lang.SerializationUtils();
+
+org.apache.commons.lang.RandomStringUtils
+--------------------
+> public static java.lang.String random(int, int, int, boolean, boolean, char[], java.util.Random);
+
+org.apache.commons.lang.CharSet
+--------------------
+< public class org.apache.commons.lang.CharSet extends java.lang.Object {
+---
+> public class org.apache.commons.lang.CharSet extends java.lang.Object implements java.io.Serializable {
+> public static final org.apache.commons.lang.CharSet EMPTY;
+> public static final org.apache.commons.lang.CharSet ASCII_ALPHA;
+> public static final org.apache.commons.lang.CharSet ASCII_ALPHA_LOWER;
+> public static final org.apache.commons.lang.CharSet ASCII_ALPHA_UPPER;
+> public static final org.apache.commons.lang.CharSet ASCII_NUMERIC;
+> protected static final java.util.Map COMMON;
+> public static org.apache.commons.lang.CharSet getInstance(java.lang.String);
+> protected org.apache.commons.lang.CharSet(java.lang.String);
+< public boolean contains(char);
+> public org.apache.commons.lang.CharRange getCharRanges()[];
+> public boolean contains(char);
+> public boolean equals(java.lang.Object);
+> public int hashCode();
+> static {};
+
+org.apache.commons.lang.CharSetUtils
+--------------------
+> public static java.lang.String keep(java.lang.String, java.lang.String);
+> public static java.lang.String keep(java.lang.String, java.lang.String[]);
+
+org.apache.commons.lang.builder.ToStringBuilder
+--------------------
+< public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object);
+< public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object,org.apache.commons.lang.builder.ToStringStyle);
+< public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object,org.apache.commons.lang.builder.ToStringStyle,java.lang.StringBuffer);
+< public static void setDefaultStyle(org.apache.commons.lang.builder.ToStringStyle);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.Object);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object, boolean);
+< public org.apache.commons.lang.builder.ToStringBuilder append(long);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long);
+< public org.apache.commons.lang.builder.ToStringBuilder append(int);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int);
+< public org.apache.commons.lang.builder.ToStringBuilder append(short);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short);
+< public org.apache.commons.lang.builder.ToStringBuilder append(char);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char);
+---
+> public static java.lang.String reflectionToString(java.lang.Object, org.apache.commons.lang.builder.ToStringStyle, boolean, java.lang.Class);
+> public static void setDefaultStyle(org.apache.commons.lang.builder.ToStringStyle);
+> public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object);
+> public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object,org.apache.commons.lang.builder.ToStringStyle);
+> public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object,org.apache.commons.lang.builder.ToStringStyle,java.lang.StringBuffer);
+> public org.apache.commons.lang.builder.ToStringBuilder append(boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder append(boolean[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, byte);
+---
+> public org.apache.commons.lang.builder.ToStringBuilder append(byte[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(char);
+> public org.apache.commons.lang.builder.ToStringBuilder append(char[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, double);
+---
+> public org.apache.commons.lang.builder.ToStringBuilder append(double[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, float);
+< public org.apache.commons.lang.builder.ToStringBuilder append(boolean);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.Object[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object[], boolean);
+< public org.apache.commons.lang.builder.ToStringBuilder append(long[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long[], boolean);
+---
+> public org.apache.commons.lang.builder.ToStringBuilder append(float[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(int);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int[], boolean);
+---
+> public org.apache.commons.lang.builder.ToStringBuilder append(long);
+> public org.apache.commons.lang.builder.ToStringBuilder append(long[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.Object);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.Object[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(short);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short[], boolean);
+< public org.apache.commons.lang.builder.ToStringBuilder append(char[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char[], boolean);
+< public org.apache.commons.lang.builder.ToStringBuilder append(byte[]);
+---
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean[], boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, byte);
+< public org.apache.commons.lang.builder.ToStringBuilder append(double[]);
+---
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char[], boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, double);
+< public org.apache.commons.lang.builder.ToStringBuilder append(float[]);
+---
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, float);
+< public org.apache.commons.lang.builder.ToStringBuilder append(boolean[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean[]);
+< public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean[], boolean);
+---
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int[], boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long[], boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object, boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object[], boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short[]);
+> public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short[], boolean);
+> public org.apache.commons.lang.builder.ToStringBuilder appendAsObjectToString(java.lang.Object);
+> public org.apache.commons.lang.builder.ToStringBuilder appendSuper(java.lang.String);
+> public org.apache.commons.lang.builder.ToStringBuilder appendToString(java.lang.String);
+> public org.apache.commons.lang.builder.ToStringStyle getStyle();
+> public java.lang.Object getObject();
+
+org.apache.commons.lang.builder.StandardToStringStyle
+--------------------
+> public boolean isUseShortClassName();
+> public void setUseShortClassName(boolean);
+> public boolean isFieldSeparatorAtStart();
+> public void setFieldSeparatorAtStart(boolean);
+> public boolean isFieldSeparatorAtEnd();
+> public void setFieldSeparatorAtEnd(boolean);
+
+org.apache.commons.lang.builder.ToStringStyle
+--------------------
+> public void appendSuper(java.lang.StringBuffer, java.lang.String);
+> public void appendToString(java.lang.StringBuffer, java.lang.String);
+> protected void removeLastFieldSeparator(java.lang.StringBuffer);
+> protected void reflectionAppendArrayDetail(java.lang.StringBuffer, java.lang.String, java.lang.Object);
+> protected boolean isUseShortClassName();
+> protected void setUseShortClassName(boolean);
+> protected boolean isFieldSeparatorAtStart();
+> protected void setFieldSeparatorAtStart(boolean);
+> protected boolean isFieldSeparatorAtEnd();
+> protected void setFieldSeparatorAtEnd(boolean);
+
+org.apache.commons.lang.builder.HashCodeBuilder
+--------------------
+> public static int reflectionHashCode(int, int, java.lang.Object, boolean, java.lang.Class);
+> public org.apache.commons.lang.builder.HashCodeBuilder appendSuper(int);
+
+org.apache.commons.lang.builder.CompareToBuilder
+--------------------
+> public static int reflectionCompare(java.lang.Object, java.lang.Object, boolean, java.lang.Class);
+> public org.apache.commons.lang.builder.CompareToBuilder appendSuper(int);
+> public org.apache.commons.lang.builder.CompareToBuilder append(java.lang.Object, java.lang.Object, java.util.Comparator);
+> public org.apache.commons.lang.builder.CompareToBuilder append(java.lang.Object[], java.lang.Object[], java.util.Comparator);
+
+org.apache.commons.lang.builder.EqualsBuilder
+--------------------
+> public static boolean reflectionEquals(java.lang.Object, java.lang.Object, boolean, java.lang.Class);
+> public org.apache.commons.lang.builder.EqualsBuilder appendSuper(boolean);
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-2.1.txt b/src/site/resources/release-notes/RELEASE-NOTES-2.1.txt
new file mode 100644
index 000000000..d208a7f6b
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-2.1.txt
@@ -0,0 +1,150 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 2.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 2.1 version of Apache Jakarta Commons Lang.
+Commons Lang is a set of utility functions and reusable components that
+should be of use in any Java environment.
+
+
+INCOMPATIBLE CHANGES:
+
+- The Nestable interface defines the method indexOfThrowable(Class).
+Previously the implementations checked only for a specific Class.
+Now they check for subclasses of that Class as well.
+For most situations this will be the expected behavior (i.e. it's a bug fix).
+If it causes problems, please use the ExceptionUtils.indexOfThrowable(Class) method instead.
+Note that the ExceptionUtils method is available in v1.0 and v2.0 of commons-lang and has not been changed.
+(An alternative to this is to change the public static matchSubclasses flag on NestableDelegate.
+However, we don't recommend that as a long-term solution.)
+
+- The StopWatch class has had much extra validation added.
+If your code previously relied on unusual aspects, it may no longer work.
+
+- Starting with version 2.1, Ant version 1.6.x is required to build. Copy
+junit.jar to ANT_HOME/lib. You can get JUnit from https://www.junit.org. See the developer's guide
+for more details.
+
+
+DEPRECATIONS:
+
+- The enum package has been renamed to enums for JDK 1.5 compliance.
+All functionality is identical, just the package has changed.
+This package will be removed in v3.0.
+
+- NumberUtils.stringToInt - renamed to toInt
+
+- DateUtils - four constants, MILLIS_IN_* have been deprecated as they were defined
+as int not long. The replacements are MILLIS_PER_*.
+
+
+NEW FEATURES:
+
+New:
+- Mutable package - contains basic classes that hold an Object or primitive
+and provide both get and set methods.
+- DurationFormatUtils - provides various methods for formatting durations
+- CharEncoding - definitions of constants for character encoding work
+- CharUtils - utilities for working with characters
+
+Updated:
+- ArrayUtils - many more methods, especially List-like methods
+- BooleanUtils - isTrue and isFalse methods that handle null
+- ClassUtils - primitive to wrapper class conversion methods
+- ClassUtils - class name comparator
+- IllegalClassException - extra constructor for common instanceof case
+- NotImplementedException - supports nested exceptions
+- ObjectUtils - hashcode method handling null
+- StringUtils - isAsciiPrintable to check the contents of a string
+ -- ordinalIndexOf to find the nth index of a string
+ -- various remove methods to remove parts of a string
+ -- various split methods to provide more control over splitting a string
+ -- defaultIfEmpty to default a string if null or empty
+- SystemUtils - methods to get system properties as File objects
+ -- extra constants representing system properties
+- Validate - new methods to check whether all elements in a collection are of a specific type
+- WordUtils - new methods to capitalize based on a set of specified delimiters
+
+- EqualsBuilder - now provides setter to internal state
+- ToStringStyle - new style, short prefix style
+- ReflectionToStringBuilder - more flags to control the output with regards to statics
+
+- ExceptionUtils - added indexOfType methods that check subclasses, thus leaving the existing
+indexOfThrowable method untouched (see incompatible changes section)
+
+- NumberUtils - various string to number parsing methods added
+
+- DateUtils - methods added to compare dates in various ways
+ -- method to parse a date string using multiple patterns
+- FastDateFormat - extra formatting methods that take in a millisecond long value
+ -- additional static factory methods
+- StopWatch - new methods for split behavior
+
+
+BUG FIXES:
+
+19331 General case: infinite loop: ToStringBuilder.reflectionToString
+23174 EqualsBuilder.append(Object[], Object[]) throws NPE
+23356 Make DurationFormatUtils public!
+23557 WordUtils.capitalizeFully(String str) should take a delimiters
+23683 New method for converting a primitive Class to its corresponding wrapper
+23430 Minor javadoc fixes for StringUtils.contains(String, String)
+23590 make optional parameters in FastDateFormat really optional
+24056 Documentation error in StringUtils.replace
+25227 StringEscapeUtils.unescapeHtml() doesn't handle hex entities
+25454 new StringUtils.replaceChars behaves differently from old Ch
+25560 DateUtils.truncate() is off by one hour when using a date in DST switch 'zone'
+25627 DateUtils constants should be long
+25683 Add method that validates Collection elements are a correct
+25849 Add SystemUtils methods for directory properties.
+26616 ClassCastException in Enum.equals(Object)
+26699 Tokenizer Enhancements: reset input string, static CSV
+26734 NullPointerException in EqualsBuilder.append(Object[], Object[])
+26877 Add SystemUtils.AWT_TOOLKIT and others.
+26922 public static boolean DateUtils.equals(Date dt1, Date dt2)
+27592 WordUtils capitalize improvement
+27876 ReflectionToStringBuilder.toString(null) throws exception by design
+27877 Make ClassUtils methods null-safe and not throw an IAE.
+28468 StringUtils.defaultString: Documentation error
+28554 Add hashCode-support to class ObjectUtils
+29082 Enhancement of ExceptionUtils.CAUSE_METHOD_NAMES
+29149 StringEscapeUtils.unescapeHtml() doesn't handle an empty entity
+29294 lang.math.Fraction class deficiencies
+29673 ExceptionUtils: new getCause() methodname (for tomcat)
+29794 Add convenience format(long) methods to FastDateForma
+30328 HashCodeBuilder does not use the same values as Boolean (fixed as documentation)
+30334 New class proposal: CharacterEncoding
+30674 parseDate class from HttpClient's DateParser class
+30815 ArrayUtils.isEquals() throws ClassCastException when array1
+30929 Nestable.indexOfThrowable(Class) uses Class.equals() to match
+31395 DateUtils.truncate oddity at the far end of the Date spectrum
+31478 Compile error with JDK 5 "enum" is a keyword
+31572 o.a.c.lang.enum.ValuedEnum: 'enum' is a keyword in JDK 1.5.0
+31933 ToStringStyle setArrayEnd handled null incorrectly
+32133 SystemUtils fails init on HP-UX
+32198 Error in Javadoc for StringUtils.chomp(String, String)
+32625 Can't subclass EqualsBuilder because isEquals is private
+33067 EqualsBuilder.append(Object[], Object[]) crashes with a NullPointerException if an element of the first array is null
+33069 EqualsBuilder.append(Object[], Object[]) incorrectly checks that rhs[i] is instance of lhs[i]'s class
+33574 unbalanced ReflectionToStringBuilder
+33737 ExceptionUtils.addCauseMethodName(String) does not check for duplicates.
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-2.2.txt b/src/site/resources/release-notes/RELEASE-NOTES-2.2.txt
new file mode 100644
index 000000000..cec550f35
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-2.2.txt
@@ -0,0 +1,127 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 2.2
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 2.2 version of Apache Jakarta Commons Lang.
+Commons Lang is a set of utility functions and reusable components that
+should be of use in any Java environment.
+
+INCOMPATIBLE CHANGES WITH VERSION 2.1:
+
+- None
+
+ADDITIONAL INCOMPATIBLE CHANGES WITH VERSION 2.0:
+
+- The Nestable interface defines the method indexOfThrowable(Class).
+Previously the implementations checked only for a specific Class.
+Now they check for subclasses of that Class as well.
+For most situations this will be the expected behavior (i.e. it's a bug fix).
+If it causes problems, please use the ExceptionUtils.indexOfThrowable(Class) method instead.
+Note that the ExceptionUtils method is available in v1.0 and v2.0 of commons-lang and has not been changed.
+(An alternative to this is to change the public static matchSubclasses flag on NestableDelegate.
+However, we don't recommend that as a long-term solution.)
+
+- The StopWatch class has had much extra validation added.
+If your code previously relied on unusual aspects, it may no longer work.
+
+- Starting with version 2.1, Ant version 1.6.x is required to build. Copy
+junit.jar to ANT_HOME/lib. You can get JUnit from https://www.junit.org. See the developer's guide
+for more details.
+
+DEPRECATIONS FROM 2.1 to 2.2:
+
+- None
+
+DEPRECATIONS FROM 2.0 to 2.1:
+
+- The enum package has been renamed to enums for JDK 1.5 compliance.
+All functionality is identical, just the package has changed.
+This package will be removed in v3.0.
+
+- NumberUtils.stringToInt - renamed to toInt
+
+- DateUtils - four constants, MILLIS_IN_* have been deprecated as they were defined
+as int not long. The replacements are MILLIS_PER_*.
+
+
+BUG FIXES IN 2.2:
+
+LANG-2 javadoc example for StringUtils.splitByWholeSeparator incorrect
+LANG-3 PADDING array in StringUtils overflows on '\uffff'
+LANG-10 [patch] ClassUtils.primitiveToWrapper and Void
+LANG-21 escapeXML() -> Not escaping low characters
+LANG-25 DurationFormatUtils.formatDurationISO() javadoc is missing T in duration string between date and time part
+LANG-37 unit test for org.apache.commons.lang.text.StrBuilder
+LANG-42 EqualsBuilder.append(Object[], Object[]) crashes with a NullPointerException if an element of the first array is null
+LANG-45 StrBuilderTest#testReplaceStringString fails.
+LANG-50 Replace Clover with Cobertura
+LANG-59 DateUtils.truncate method is buggy when dealing with DST switching hours
+LANG-100 RandomStringUtils.random() family of methods create invalid unicode sequences
+LANG-105 ExceptionUtils goes into infinite loop in getThrowables is throwable.getCause() == throwable
+LANG-106 StringUtils#getLevenshteinDistance() performance is suboptimal
+LANG-112 Wrong length check in StrTokenizer.StringMatcher
+LANG-117 FastDateFormat: wrong format for date "01.01.1000"
+LANG-122 EscapeUtil.escapeHtml() should clarify that it does not escape ' chars to &apos;
+LANG-123 Unclear javadoc for DateUtils.iterator()
+LANG-127 Minor tweak to fix of bug # 26616
+LANG-130 Memory "leak" in StringUtils
+LANG-140 DurationFormatUtils.formatPeriod() returns the wrong result
+LANG-141 Fraction.toProperString() returns -1/1 for -1
+LANG-148 Performance modifications on StringUtils.replace
+LANG-150 StringEscapeUtils.unescapeHtml skips first entity after standalone ampersand
+LANG-152 DurationFormatUtils.formatDurationWords "11 <unit>s" gets converted to "11 <unit>"
+LANG-259 ValuedEnum.compareTo(Object other) not typesafe - it easily could be...
+LANG-261 Error in an example in the javadoc of the StringUtils.splitPreserveAllTokens() method
+LANG-264 ToStringBuilder/HashCodeBuilder javadoc code examples
+LANG-271 LocaleUtils test fails under Mustang
+LANG-272 Minor build and checkstyle changes
+LANG-277 Javadoc errors on StringUtils.splitPreserveAllTokens(String, char)
+LANG-278 javadoc for StringUtils.removeEnd is incorrect
+
+IMPROVEMENTS IN 2.2:
+
+LANG-159 Add WordUtils.getInitials(String)
+LANG-161 Add methods and tests to StrBuilder
+LANG-162 replace() length calculation improvement
+LANG-165 parseDate with TimeZone
+LANG-166 New interpolation features
+LANG-169 Implementation of escape/unescapeHtml methods with Writer
+LANG-176 CompareToBuilder excludeFields for reflection method
+LANG-186 Request for MutableBoolean implementation
+LANG-194 add generic add method to DateUtils
+LANG-198 New method for EqualsBuilder
+LANG-212 New ExceptionUtils method setCause()
+LANG-216 Provides a Class.getPublicMethod which returns public invocable Method
+LANG-217 Add Mutable<Type> to<Type>() methods.
+LANG-220 Tokenizer Enhancements: reset input string, static CSV/TSV factories
+LANG-226 Using ReflectionToStringBuilder and excluding secure fields
+LANG-242 Trivial cleanup of javadoc in various files
+LANG-246 CompositeFormat
+LANG-250 Performance boost for RandomStringUtils
+LANG-254 Enhanced Class.forName version
+LANG-260 StringEscapeUtils should expose escape*() methods taking Writer argument
+LANG-263 Add StringUtils.containsIgnoreCase(...)
+LANG-267 Support char array converters on ArrayUtils
+LANG-270 minor javadoc improvements for StringUtils.stripXxx() methods
+ New ExceptionUtils methods getMessage/getRootCauseMessage
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-2.3.txt b/src/site/resources/release-notes/RELEASE-NOTES-2.3.txt
new file mode 100644
index 000000000..04697723b
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-2.3.txt
@@ -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.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 2.3
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 2.3 version of Apache
+Jakarta Commons Lang.
+Commons Lang is a set of utility functions and reusable components that
+should be of use in any Java environment.
+
+INCOMPATIBLE CHANGES WITH VERSION 2.2:
+
+- Calling stop on a suspended StopWatch will no longer change the underlying time.
+ It's very unlikely anyone was relying on that bug as a feature.
+
+ADDITIONAL INCOMPATIBLE CHANGES WITH VERSION 2.0:
+
+- The Nestable interface defines the method indexOfThrowable(Class).
+Previously the implementations checked only for a specific Class.
+Now they check for subclasses of that Class as well.
+For most situations this will be the expected behavior (i.e. it's a bug fix).
+If it causes problems, please use the ExceptionUtils.indexOfThrowable(Class) method instead.
+Note that the ExceptionUtils method is available in v1.0 and v2.0 of commons-lang and has not been changed.
+(An alternative to this is to change the public static matchSubclasses flag on NestableDelegate.
+However, we don't recommend that as a long-term solution.)
+
+- The StopWatch class has had much extra validation added.
+If your code previously relied on unusual aspects, it may no longer work.
+
+- Starting with version 2.1, Ant version 1.6.x is required to build. Copy
+junit.jar to ANT_HOME/lib. You can get JUnit from https://www.junit.org. See the developer's guide
+for more details.
+
+DEPRECATIONS FROM 2.2 to 2.3:
+
+- None
+
+DEPRECATIONS FROM 2.1 to 2.2:
+
+- None
+
+DEPRECATIONS FROM 2.0 to 2.1:
+
+- The enum package has been renamed to enums for JDK 1.5 compliance.
+All functionality is identical, just the package has changed.
+This package will be removed in v3.0.
+
+- NumberUtils.stringToInt - renamed to toInt
+
+- DateUtils - four constants, MILLIS_IN_* have been deprecated as they were defined
+as int not long. The replacements are MILLIS_PER_*.
+
+
+BUG FIXES IN 2.3:
+
+ * [LANG-69 ] - ToStringBuilder throws StackOverflowError when an Object cycle exists
+ * [LANG-102] - Refactor Entities methods
+ * [LANG-153] - Can't XMLDecode an Enum
+ * [LANG-262] - Use of enum prevents a classloader from being garbage collected resulting in out of memory exceptions.
+ * [LANG-279] - HashCodeBuilder throws java.lang.StackOverflowError when an object contains a cycle.
+ * [LANG-281] - DurationFormatUtils returns wrong result
+ * [LANG-286] - Serialization - not backwards compatible
+ * [LANG-292] - unescapeXml("&12345678;") should be "&12345678;"
+ * [LANG-294] - StrBuilder.replaceAll and StrBuilder.deleteAll can throw ArrayIndexOutOfBoundsException.
+ * [LANG-295] - StrBuilder contains usages of thisBuf.length when they should use size
+ * [LANG-299] - Bug in method appendFixedWidthPadRight of class StrBuilder causes an ArrayIndexOutOfBoundsException
+ * [LANG-300] - NumberUtils.createNumber throws NumberFormatException for one digit long
+ * [LANG-303] - FastDateFormat.mRules is not transient or serializable
+ * [LANG-304] - NullPointerException in isAvailableLocale(Locale)
+ * [LANG-313] - Wrong behavior of Entities.unescape
+ * [LANG-315] - StopWatch: suspend() acts as split(), if followed by stop()
+
+IMPROVEMENTS IN 2.3:
+
+ * [LANG-258] - Enum Javadoc
+ * [LANG-266] - Wish for StringUtils.join(Collection, *)
+ * [LANG-268] - StringUtils.join should allow you to pass a range for it (so it only joins a part of the array)
+ * [LANG-275] - StringUtils substringsBetween
+ * [LANG-282] - Create more tests to test out the +=31 replacement code in DurationFormatUtils.
+ * [LANG-287] - Optimize StringEscapeUtils.unescapeXml(String)
+ * [LANG-289] - NumberUtils.max(byte[]) and NumberUtils.min(byte[]) are missing
+ * [LANG-291] - Null-safe comparison methods for finding the most recent / least recent dates.
+ * [LANG-306] - StrBuilder appendln/appendAll/appendSeparator
+ * [LANG-310] - BooleanUtils isNotTrue/isNotFalse
+ * [LANG-314] - Tests fail to pass when building with Maven 2
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-2.4.txt b/src/site/resources/release-notes/RELEASE-NOTES-2.4.txt
new file mode 100644
index 000000000..769c640f4
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-2.4.txt
@@ -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.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 2.4
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 2.4 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any Java environment.
+
+Lang 2.4 no longer attempts to target the Java 1.1 environment and now targets Java 1.2. While previous versions
+were built for 1.1, some parts were using methods that were only available in 1.2, and the Enum class had
+become dependent on Java 1.3.
+
+INCOMPATIBLE CHANGES WITH VERSION 2.3:
+
+- None
+
+INCOMPATIBLE CHANGES WITH VERSION 2.2:
+
+- Calling stop on a suspended StopWatch will no longer change the underlying time.
+ It's very unlikely anyone was relying on that bug as a feature.
+
+ADDITIONAL INCOMPATIBLE CHANGES WITH VERSION 2.0:
+
+- The Nestable interface defines the method indexOfThrowable(Class).
+Previously the implementations checked only for a specific Class.
+Now they check for subclasses of that Class as well.
+For most situations this will be the expected behavior (i.e. it's a bug fix).
+If it causes problems, please use the ExceptionUtils.indexOfThrowable(Class) method instead.
+Note that the ExceptionUtils method is available in v1.0 and v2.0 of commons-lang and has not been changed.
+(An alternative to this is to change the public static matchSubclasses flag on NestableDelegate.
+However, we don't recommend that as a long-term solution.)
+
+- The StopWatch class has had much extra validation added.
+If your code previously relied on unusual aspects, it may no longer work.
+
+- Starting with version 2.1, Ant version 1.6.x is required to build. Copy
+junit.jar to ANT_HOME/lib. You can get JUnit from https://www.junit.org. See the developer's guide
+for more details.
+
+DEPRECATIONS FROM 2.3 to 2.4:
+
+- ObjectUtils.appendIdentityToString(StringBuffer, Object) - has very odd semantics, use
+ ObjectUtils.identityToString(StringBuffer, Object) instead.
+
+- public static java.util.Date add(java.util.Date, int, int) - it is not intended for this
+ method to be public. Please let us know if you use this.
+
+DEPRECATIONS FROM 2.2 to 2.3:
+
+- None
+
+DEPRECATIONS FROM 2.1 to 2.2:
+
+- None
+
+DEPRECATIONS FROM 2.0 to 2.1:
+
+- The enum package has been renamed to enums for JDK 1.5 compliance.
+All functionality is identical, just the package has changed.
+This package will be removed in v3.0.
+
+- NumberUtils.stringToInt - renamed to toInt
+
+- DateUtils - four constants, MILLIS_IN_* have been deprecated as they were defined
+as int not long. The replacements are MILLIS_PER_*.
+
+
+BUG FIXES IN 2.4:
+
+ * [LANG-76 ] - EnumUtils.getEnum() doesn't work well in 1.5
+ * [LANG-328] - LocaleUtils.toLocale() rejects strings with only language+variant
+ * [LANG-334] - Enum is not thread-safe
+ * [LANG-346] - Dates.round() behaves incorrectly for minutes and seconds
+ * [LANG-349] - Deadlock using ReflectionToStringBuilder
+ * [LANG-353] - Javadoc Example for EqualsBuilder is questionable
+ * [LANG-360] - Why does appendIdentityToString return null?
+ * [LANG-361] - BooleanUtils toBooleanObject javadoc does not match implementation
+ * [LANG-363] - StringEscapeUtils..escapeJavaScript() method did not escape '/' into '\/', it will make IE render page incorrectly
+ * [LANG-364] - Documentation bug for ignoreEmptyTokens accessors in StrTokenizer
+ * [LANG-365] - BooleanUtils.toBoolean() - invalid drop-thru in case statement causes StringIndexOutOfBoundsException
+ * [LANG-367] - FastDateFormat thread safety
+ * [LANG-368] - FastDateFormat getDateInstance() and getDateTimeInstance() assume Locale.getDefault() won't change
+ * [LANG-369] - ExceptionUtils not thread-safe
+ * [LANG-372] - ToStringBuilder: MULTI_LINE_STYLE does not print anything from appendToString methods.
+ * [LANG-380] - infinite loop in Fraction.reduce when numerator == 0
+ * [LANG-381] - NumberUtils.min(floatArray) returns wrong value if floatArray[0] happens to be Float.NaN
+ * [LANG-385] - https://commons.apache.org/proper/commons-lang/developerguide.html "Building" section is incorrect and incomplete
+ * [LANG-393] - EqualsBuilder don't compare BigDecimals correctly
+ * [LANG-399] - Javadoc bugs - cannot find object
+ * [LANG-410] - Ambiguous / confusing names in StringUtils replace* methods
+ * [LANG-412] - StrBuilder appendFixedWidth does not handle nulls
+ * [LANG-414] - DateUtils.round() often fails
+
+IMPROVEMENTS IN 2.4:
+
+ * [LANG-180] - adding a StringUtils.replace method that takes an array or List of replacement strings
+ * [LANG-192] - Split camel case strings
+ * [LANG-257] - Add new splitByWholeSeparatorPreserveAllTokens() methods to StringUtils
+ * [LANG-269] - Shouldn't Commons Lang's StringUtils have a "common" string method?
+ * [LANG-298] - ClassUtils.getShortClassName and ClassUtils.getPackageName and class of array
+ * [LANG-321] - Add toArray() method to IntRange and LongRange classes
+ * [LANG-322] - ClassUtils.getShortClassName(String) inefficient
+ * [LANG-326] - StringUtils: startsWith / endsWith / startsWithIgnoreCase / endsWithIgnoreCase / removeStartIgnoreCase / removeEndIgnoreCase methods
+ * [LANG-329] - Pointless synchronized in ThreadLocal.initialValue should be removed
+ * [LANG-333] - ArrayUtils.toClass
+ * [LANG-337] - Utility class constructor javadocs should acknowledge that they may sometimes be used, e.g. with Velocity.
+ * [LANG-338] - truncateNicely method which avoids truncating in the middle of a word
+ * [LANG-345] - Optimize HashCodeBuilder.append(Object)
+ * [LANG-351] - Extension to ClassUtils: Obtain the primitive class from a wrapper
+ * [LANG-356] - Add getStartTime to StopWatch
+ * [LANG-362] - Add ExtendedMessageFormat to org.apache.commons.lang.text
+ * [LANG-371] - ToStringStyle javadoc should show examples of styles
+ * [LANG-374] - Add escaping for CSV columns to StringEscapeUtils
+ * [LANG-375] - add SystemUtils.IS_OS_WINDOWS_VISTA field
+ * [LANG-379] - Calculating A date fragment in any time-unit
+ * [LANG-383] - Adding functionality to DateUtils to allow direct setting of various fields.
+ * [LANG-402] - OSGi-ify Lang
+ * [LANG-404] - Add Calendar flavour format methods to DateFormatUtils
+ * [LANG-407] - StringUtils.length(String) returns null-safe length
+ * [LANG-413] - Memory usage improvement for StringUtils#getLevenshteinDistance()
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-2.5.txt b/src/site/resources/release-notes/RELEASE-NOTES-2.5.txt
new file mode 100644
index 000000000..c489ae479
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-2.5.txt
@@ -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.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 2.5
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 2.5 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any Java environment.
+
+
+COMPATIBILITY WITH 2.4
+======================
+Lang 2.5 is binary compatible release with Lang 2.4, containing bug fixes and small enhancements.
+
+Lang 2.5 requires a minimum of JDK 1.3.
+
+
+IMPROVEMENTS IN 2.5
+===================
+
+ * [LANG-583] - ArrayUtils - add isNotEmpty() methods
+ * [LANG-534] - ArrayUtils - add nullToEmpty() methods
+ * [LANG-454] - CharRange - provide an iterator that lets you walk the chars in the range
+ * [LANG-514] - CharRange - add more readable static builder methods
+ * [ ] - ClassUtils - new isAssignable() methods with autoboxing
+ * [LANG-535] - ClassUtils - add support to getShortClassName and getPackageName for arrays
+ * [LANG-434] - DateUtils - add ceiling() method
+ * [LANG-486] - DateUtils - add parseDateStrictly() method
+ * [LANG-466] - EqualsBuilder - add reset() method
+ * [LANG-461] - NumberUtils - add toByte() and toShort() methods
+ * [LANG-522] - Mutable numbers - add string constructors
+ * [ ] - MutableBoolean - add toBoolean(), isTrue() and isFalse() methods
+ * [LANG-422] - StrBuilder - add appendSeparator() methods with an alternative default separator if the StrBuilder is currently empty
+ * [LANG-555] - SystemUtils - add IS_OS_WINDOWS_7 constant
+ * [LANG-554] - SystemUtils - add IS_JAVA_1_7 constant for JDK 1.7
+ * [LANG-405] - StringUtils - add abbreviateMiddle() method
+ * [LANG-569] - StringUtils - add indexOfIgnoreCase() and lastIndexOfIgnoreCase() methods
+ * [LANG-471] - StringUtils - add isAllUpperCase() and isAllLowerCase() methods
+ * [LANG-469] - StringUtils - add lastOrdinalIndexOf() method to complement the existing ordinalIndexOf() method
+ * [LANG-438] - StringUtils - add repeat() method
+ * [LANG-445] - StringUtils - add startsWithAny() method
+ * [LANG-430] - StringUtils - add upperCase(String, Locale) and lowerCase(String, Locale) methods
+ * [LANG-416] - New Reflection package containing ConstructorUtils, FieldUtils, MemberUtils and MethodUtils
+
+BUG FIXES IN 2.5
+================
+
+ * [LANG-494] - CharSet - Synchronizing the COMMON Map so that getInstance doesn't miss a put from a subclass in another thread
+ * [LANG-500] - ClassUtils - improving performance of getAllInterfaces
+ * [LANG-587] - ClassUtils - toClass() throws NullPointerException on null array element
+ * [LANG-530] - DateUtils - Fix parseDate() cannot parse ISO8601 dates produced by FastDateFormat
+ * [LANG-440] - DateUtils - round() doesn't work correct for Calendar.AM_PM
+ * [LANG-443] - DateUtils - improve tests
+ * [LANG-204] - Entities - multithreaded initialization
+ * [LANG-506] - Entities - missing final modifiers; thread-safety issues
+ * [LANG-76] - EnumUtils - getEnum() doesn't work well in 1.5+
+ * [LANG-584] - ExceptionUtils - use immutable lock target
+ * [LANG-477] - ExtendedMessageFormat - OutOfMemory with a pattern containing single quotes
+ * [LANG-538] - FastDateFormat - call getTime() on a calendar to ensure timezone is in the right state
+ * [LANG-547] - FastDateFormat - Remove unused field
+ * [LANG-511] - LocaleUtils - initialization of available locales can be deferred
+ * [LANG-457] - NumberUtils - createNumber() throws a StringIndexOutOfBoundsException for "l"
+ * [LANG-521] - NumberUtils - isNumber(String) and createNumber(String) both modified to support '2.'
+ * [LANG-432] - StringUtils - improve handling of case-insensitive Strings
+ * [LANG-552] - StringUtils - replaceEach() no longer NPEs when null appears in the last String[]
+ * [LANG-460] - StringUtils - correct Javadocs for startsWith() and startsWithIgnoreCase()
+ * [LANG-421] - StringEscapeUtils - escapeJava() escapes '/' characters
+ * [LANG-450] - StringEscapeUtils - change escapeJavaStyleString() to throw UnhandledException instead swallowing IOException
+ * [LANG-419] - WordUtils - fix StringIndexOutOfBoundsException when lower is greater than the String length
+ * [LANG-523] - StrBuilder - Performance improvement by doubling the size of the String in ensureCapacity
+ * [LANG-575] - Compare, Equals and HashCode builders - use ArrayUtils to avoid creating a temporary List
+ * [LANG-467] - EqualsBuilder - removing the special handling of BigDecimal (LANG-393) to use compareTo
+ * [LANG-574] - HashCodeBuilder - Performance improvement: check for isArray to short-circuit the 9 instanceof checks
+ * [LANG-520] - HashCodeBuilder - Changing the hashCode() method to return toHashCode()
+ * [LANG-459] - HashCodeBuilder - reflectionHashCode() can generate incorrect hashcodes
+ * [LANG-586] - HashCodeBuilder and ToStringStyle - use of ThreadLocal causes memory leaks in container environments
+ * [LANG-487] - ToStringBuilder - make default style thread-safe
+ * [LANG-472] - RandomUtils - nextLong() always produces even numbers
+ * [LANG-592] - RandomUtils - RandomUtils tests are failing frequently
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-2.6.txt b/src/site/resources/release-notes/RELEASE-NOTES-2.6.txt
new file mode 100644
index 000000000..3f4674830
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-2.6.txt
@@ -0,0 +1,74 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 2.6
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 2.6 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any Java environment.
+
+
+COMPATIBILITY WITH 2.5
+======================
+Lang 2.6 is binary compatible release with Lang 2.5, containing bug fixes and small enhancements.
+
+Lang 2.6 requires a minimum of JDK 1.3.
+
+
+IMPROVEMENTS IN 2.6
+===================
+
+ * [LANG-633] - BooleanUtils: use same optimization in toBooleanObject(String) as in toBoolean(String)
+ * [LANG-599] - ClassUtils: allow Dots as Inner Class Separators in getClass()
+ * [LANG-594] - DateUtils: equal and compare functions up to most significant field
+ * [LANG-632] - DateUtils: provide a Date to Calendar convenience method
+ * [LANG-576] - ObjectUtils: add clone methods to ObjectUtils
+ * [LANG-667] - ObjectUtils: add a Null-safe compare() method
+ * [LANG-670] - ObjectUtils: add notEqual() method
+ * [LANG-302] - StrBuilder: implement clone() method
+ * [LANG-640] - StringUtils: add a normalizeSpace() method
+ * [LANG-614] - StringUtils: add endsWithAny() method
+ * [LANG-655] - StringUtils: add defaultIfBlank() method
+ * [LANG-596] - StrSubstitutor: add a replace(String, Properties) variant
+ * [LANG-482] - StrSubstitutor: support substitution in variable names
+ * [LANG-669] - Use StrBuilder instead of StringBuffer to improve performance where sync. is not an issue
+
+BUG FIXES IN 2.6
+================
+
+ * [LANG-629] - CharSet: make the underlying set synchronized
+ * [LANG-635] - CompareToBuilder: fix passing along compareTransients to the reflectionCompare method
+ * [LANG-636] - ExtendedMessageFormat doesn't override equals(Object)
+ * [LANG-645] - FastDateFormat: fix to properly include the locale when formatting a Date
+ * [LANG-638] - NumberUtils: createNumber() throws a StringIndexOutOfBoundsException when argument containing "e" and "E" is passed in
+ * [LANG-607] - StringUtils methods do not handle Unicode 2.0+ supplementary characters correctly
+ * [LANG-624] - SystemUtils: getJavaVersionAsFloat throws StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+ * [BEANUTILS-381] - MemberUtils: getMatchingAccessibleMethod does not correctly handle inheritance and method overloading
+
+OTHER CHANGES IN 2.6
+====================
+
+ * [LANG-600] - Javadoc is incorrect for lastIndexOf() method
+ * [LANG-628] - Javadoc for HashCodeBuilder.append(boolean) does not match implementation
+ * [LANG-643] - Javadoc StringUtils.left() claims to throw an exception on negative length, but doesn't
+ * [LANG-370] - Javadoc - document thread safety
+ * [LANG-623] - Test for StringUtils replaceChars() icelandic characters
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.0.1.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.0.1.txt
new file mode 100644
index 000000000..0aa1ae561
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.0.1.txt
@@ -0,0 +1,57 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 3.0.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.0.1 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+CHANGES IN 3.0.1
+================
+
+ [LANG-686] Improve exception message when StringUtils.replaceEachRepeatedly detects recursion
+ [LANG-717] Specify source encoding for Ant build
+ [LANG-721] Complement ArrayUtils.addAll() variants with by-index and by-value removal methods
+ [LANG-726] Add Range<T> Range<T>.intersectionWith(Range<T>)
+ [LANG-723] Add mode and median Comparable... methods to ObjectUtils
+ [LANG-722] Add BooleanUtils.and + or varargs methods
+ [LANG-730] EnumSet -> bit vector
+ [LANG-735] Deprecate CharUtils.toCharacterObject(char) in favor of java.lang.Character.valueOf(char)
+ [LANG-737] Missing method getRawMessage for ContextedException and ContextedRuntimeException
+
+BUG FIXES IN 3.0.1
+==================
+
+ [LANG-626] SerializationUtils.clone: Fallback to context classloader if class not found in current classloader
+ [LANG-727] ToStringBuilderTest.testReflectionHierarchyArrayList fails with IBM JDK 6
+ [LANG-720] StringEscapeUtils.escapeXml(input) wrong when input contains characters in Supplementary Planes
+ [LANG-708] StringEscapeUtils.escapeEcmaScript from lang3 cuts off long unicode string
+ [LANG-734] The CHAR_ARRAY cache in CharUtils duplicates the cache in java.lang.Character
+ [LANG-738] Use internal Java's Number caches instead creating new objects
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.0.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.0.txt
new file mode 100644
index 000000000..b6b0e13b0
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.0.txt
@@ -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.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 3.0
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.0 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the latest advice on upgrading, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+ADDITIONS IN 3.0
+================
+
+ [LANG-276] MutableBigDecimal and MutableBigInteger.
+ [LANG-285] Wish : method unaccent.
+ [LANG-358] ObjectUtils.coalesce.
+ [LANG-386] LeftOf/RightOfNumber in Range convenience methods necessary.
+ [LANG-435] Add ClassUtils.isAssignable() variants with autoboxing.
+ [LANG-444] StringUtils.emptyToNull.
+ [LANG-482] Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+ [LANG-482] StrSubstitutor now supports substitution in variable names.
+ [LANG-496] A generic implementation of the Lazy initialization pattern.
+ [LANG-497] Addition of ContextedException and ContextedRuntimeException.
+ [LANG-498] Add StringEscapeUtils.escapeText() methods.
+ [LANG-499] Add support for the handling of ExecutionExceptions.
+ [LANG-501] Add support for background initialization.
+ [LANG-529] Add a concurrent package.
+ [LANG-533] Validate: support for validating blank strings.
+ [LANG-537] Add ArrayUtils.toArray to create generic arrays.
+ [LANG-545] Add ability to create a Future for a constant.
+ [LANG-546] Add methods to Validate to check whether the index is valid for the array/list/string.
+ [LANG-553] Add TypeUtils class to provide utility code for working with generic types.
+ [LANG-559] Added isAssignableFrom and isInstanceOf validation methods.
+ [LANG-559] Added validState validation method.
+ [LANG-560] New TimedSemaphore class.
+ [LANG-582] Provide an implementation of the ThreadFactory interface.
+ [LANG-588] Create a basic Pair<L, R> class.
+ [LANG-594] DateUtils equal & compare functions up to most significant field.
+ [LANG-601] Add Builder Interface / Update Builders to Implement It.
+ [LANG-609] Support lazy initialization using atomic variables
+ [LANG-610] Extend exception handling in ConcurrentUtils to runtime exceptions.
+ [LANG-614] StringUtils.endsWithAny method
+ [LANG-640] Add normalizeSpace to StringUtils
+ [LANG-644] Provide documentation about the new concurrent package
+ [LANG-649] BooleanUtils.toBooleanObject to support single character input
+ [LANG-651] Add AnnotationUtils
+ [LANG-653] Provide a very basic ConcurrentInitializer implementation
+ [LANG-655] Add StringUtils.defaultIfBlank()
+ [LANG-667] Add a Null-safe compare() method to ObjectUtils
+ [LANG-676] Documented potential NPE if auto-boxing occurs for some BooleanUtils methods
+ [LANG-678] Add support for ConcurrentMap.putIfAbsent()
+ [LANG-692] Add hashCodeMulti varargs method
+ [LANG-697] Add FormattableUtils class
+ [LANG-684] Levenshtein Distance Within a Given Threshold
+
+REMOVALS IN 3.0
+===============
+
+ [LANG-438] Remove @deprecateds.
+ [LANG-492] Remove code handled now by the JDK.
+ [LANG-493] Remove code that does not hold enough value to remain.
+ [LANG-590] Remove JDK 1.2/1.3 bug handling in StringUtils.indexOf(String, String, int).
+ [LANG-673] WordUtils.abbreviate() removed
+ [LANG-691] Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS IN 3.0
+===================
+
+ [LANG-290] EnumUtils for JDK 5.0.
+ [LANG-336] Finally start using generics.
+ [LANG-355] StrBuilder should implement CharSequence and Appendable.
+ [LANG-396] Investigate for vararg usages.
+ [LANG-424] Improve Javadoc for StringUtils class.
+ [LANG-458] Refactor Validate.java to eliminate code redundancy.
+ [LANG-479] Document where in SVN trunk is.
+ [LANG-504] bring ArrayUtils.isEmpty to the generics world.
+ [LANG-505] Rewrite StringEscapeUtils.
+ [LANG-507] StringEscapeUtils.unescapeJava should support \u+ notation.
+ [LANG-510] Convert StringUtils API to take CharSequence.
+ [LANG-513] Better EnumUtils.
+ [LANG-528] Mutable classes should implement an appropriately typed Mutable interface.
+ [LANG-539] Compile commons.lang for CDC 1.1/Foundation 1.1.
+ [LANG-540] Make NumericEntityEscaper immutable.
+ [LANG-541] Replace StringBuffer with StringBuilder.
+ [LANG-548] Use Iterable on API instead of Collection.
+ [LANG-551] Replace Range classes with generic version.
+ [LANG-562] Change Maven groupId.
+ [LANG-563] Change Java package name.
+ [LANG-570] Do the test cases really still require main() and suite() methods?.
+ [LANG-579] Add new Validate methods.
+ [LANG-599] ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+ [LANG-605] DefaultExceptionContext overwrites values in recursive situations.
+ [LANG-668] Change ObjectUtils min() & max() functions to use varargs rather than just two parameters
+ [LANG-681] Push down WordUtils to "text" sub-package.
+ [LANG-711] Add includeantruntime=false to javac targets to quell warnings in ant 1.8.1 and better (and modest performance gain).
+ [LANG-713] Increase test coverage of FieldUtils read methods and tweak javadoc.
+ [LANG-718] build.xml Java 1.5+ updates.
+
+BUG FIXES IN 3.0
+================
+
+ [LANG-11] Depend on JDK 1.5+.
+ [LANG-302] StrBuilder does not implement clone().
+ [LANG-339] StringEscapeUtils.escapeHtml() escapes multibyte characters like Chinese, Japanese, etc.
+ [LANG-369] ExceptionUtils not thread-safe.
+ [LANG-418] Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+ [LANG-428] StringUtils.isAlpha, isAlphanumeric and isNumeric now return false for ""
+ [LANG-439] StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+ [LANG-448] Lower Ascii Characters don't get encoded by Entities.java.
+ [LANG-468] JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+ [LANG-474] Fixes for thread safety.
+ [LANG-478] StopWatch does not resist to system time changes.
+ [LANG-480] StringEscapeUtils.escapeHtml incorrectly converts unicode characters above U+00FFFF into 2 characters.
+ [LANG-481] Possible race-conditions in hashCode of the range classes.
+ [LANG-564] Improve StrLookup API documentation.
+ [LANG-568] @SuppressWarnings("unchecked") is used too generally.
+ [LANG-571] ArrayUtils.add(T[] array, T element) can create unexpected ClassCastException.
+ [LANG-585] exception.DefaultExceptionContext.getFormattedExceptionMessage catches Throwable.
+ [LANG-596] StrSubstitutor should also handle the default properties of a java.util.Properties class
+ [LANG-600] Javadoc is incorrect for public static int lastIndexOf(String str, String searchStr).
+ [LANG-602] ContextedRuntimeException no longer an 'unchecked' exception.
+ [LANG-606] EqualsBuilder causes StackOverflowException.
+ [LANG-608] Some StringUtils methods should take an int character instead of char to use String API features.
+ [LANG-617] StringEscapeUtils.escapeXML() can't process UTF-16 supplementary characters
+ [LANG-624] SystemUtils.getJavaVersionAsFloat throws StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+ [LANG-629] Charset may not be threadsafe, because the HashSet is not synch.
+ [LANG-638] NumberUtils createNumber throws a StringIndexOutOfBoundsException when argument containing "e" and "E" is passed in
+ [LANG-643] Javadoc StringUtils.left() claims to throw on negative len, but doesn't
+ [LANG-645] FastDateFormat.format() outputs incorrect week of year because locale isn't respected
+ [LANG-646] StringEscapeUtils.unescapeJava doesn't handle octal escapes and Unicode with extra u
+ [LANG-656] Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+ [LANG-658] Some entities like &Ouml; are not matched properly against its ISO8859-1 representation
+ [LANG-659] EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212 ISOtech
+ [LANG-66] StringEscaper.escapeXml() escapes characters > 0x7f.
+ [LANG-662] org.apache.commons.lang3.math.Fraction does not reduce (Integer.MIN_VALUE, 2^k)
+ [LANG-663] org.apache.commons.lang3.math.Fraction does not always succeed in multiplyBy and divideBy
+ [LANG-664] NumberUtils.isNumber(String) is not right when the String is "1.1L"
+ [LANG-672] Doc bug in DateUtils#ceiling
+ [LANG-677] DateUtils.isSameLocalTime compares using 12-hour clock and not 24-hour
+ [LANG-685] EqualsBuilder synchronizes on HashCodeBuilder.
+ [LANG-703] StringUtils.join throws NPE when toString returns null for one of objects in collection
+ [LANG-710] StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+ [LANG-714] StringUtils doc/comment spelling fixes.
+ [LANG-715] CharSetUtils.squeeze() speedup.
+ [LANG-716] swapCase and *capitalize speedups.
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.1.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.1.txt
new file mode 100644
index 000000000..c20d3cc96
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.1.txt
@@ -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.
+
+=============================================================================
+
+ Commons Lang Package
+ Version 3.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.1 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+CHANGES IN 3.1
+================
+
+ [LANG-760] Add API StringUtils.toString(byte[] input, String charsetName)
+ [LANG-756] Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and isPrimitiveOrWrapper(Class<?>)
+ [LANG-758] Add an example with whitespace in StringUtils.defaultIfEmpty
+ [LANG-752] Fix createLong() so it behaves like createInteger()
+ [LANG-751] Include the actual type in the Validate.isInstance and isAssignableFrom exception messages
+ [LANG-748] Deprecating chomp(String, String)
+ [LANG-736] CharUtils static final array CHAR_STRING is not needed to compute CHAR_STRING_ARRAY
+ [LANG-695] SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES IN 3.1
+==================
+
+ [LANG-749] Incorrect Bundle-SymbolicName in Manifest
+ [LANG-746] NumberUtils does not handle upper-case hex: 0X and -0X
+ [LANG-744] StringUtils throws java.security.AccessControlException on Google App Engine
+ [LANG-741] Ant build has wrong component.name
+ [LANG-698] Document that the Mutable numbers don't work as expected with String.format
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.10.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.10.txt
new file mode 100644
index 000000000..c77df7fa2
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.10.txt
@@ -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.
+
+
+ Apache Commons Lang
+ Version 3.10
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.10 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.9 and onwards now targets Java 8, making use of features that arrived with Java 8.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 8, supports Java 9, 10, 11.
+
+Changes in this version include:
+
+New features:
+o LANG-1457: Add ExceptionUtils.throwableOfType(Throwable, Class) and friends.
+o LANG-1458: Add EMPTY_ARRAY constants to classes in org.apache.commons.lang3.tuple.
+o LANG-1461: Add null-safe StringUtils APIs to wrap String#getBytes([Charset|String]).
+o LANG-1467: Add zero arg constructor for org.apache.commons.lang3.NotImplementedException.
+o LANG-1470: Add ArrayUtils.addFirst() methods.
+o LANG-1479: Add Range.fit(T) to fit a value into a range.
+o LANG-1477: Added Functions.as*, and tests thereof, as suggested by Peter Verhas
+o LANG-1485: Add getters for lhs and rhs objects in DiffResult #451. Thanks to nicolasbd.
+o LANG-1486: Generify builder classes Diffable, DiffBuilder, and DiffResult #452. Thanks to Gary Gregory.
+o LANG-1487: Add ClassLoaderUtils with toString() implementations #453. Thanks to Gary Gregory.
+o LANG-1489: Add null-safe APIs as StringUtils.toRootLowerCase(String) and StringUtils.toRootUpperCase(String) #456. Thanks to Gary Gregory.
+o LANG-1494: Add org.apache.commons.lang3.time.Calendars. Thanks to Gary Gregory.
+o LANG-1495: Add EnumUtils getEnum() methods with default values #475. Thanks to Cheong Voon Leong.
+o LANG-1177: Added indexesOf methods and simplified removeAllOccurences #471. Thanks to Liel Fridman.
+o LANG-1498: Add support of lambda value evaluation for defaulting methods #416. Thanks to Lysergid, Gary Gregory.
+o LANG-1503: Add factory methods to Pair classes with Map.Entry input. #454. Thanks to XenoAmess, Gary Gregory.
+o LANG-1505: Add StopWatch convenience APIs to format times and create a simple instance. Thanks to Gary Gregory.
+o LANG-1506: Allow a StopWatch to carry an optional message. Thanks to Gary Gregory.
+o LANG-1507: Add ComparableUtils #398. Thanks to Sam Kruglov, Mark Dacek, Marc Magon, Pascal Schumacher, Rob Tompkins, Bruno P. Kinoshita, Amey Jadiye, Gary Gregory.
+o LANG-1508: Add org.apache.commons.lang3.SystemUtils.getUserName(). Thanks to Gary Gregory.
+o LANG-1509: Add ObjectToStringComparator. #483. Thanks to Gary Gregory.
+o LANG-1510: Add org.apache.commons.lang3.arch.Processor.Arch.getLabel(). Thanks to Gary Gregory.
+o LANG-1512: Add IS_JAVA_14 and IS_JAVA_15 to org.apache.commons.lang3.SystemUtils. Thanks to Gary Gregory.
+o LANG-1513: ObjectUtils: Get first non-null supplier value. Thanks to Bernhard Bonigl, Gary Gregory.
+o Added the Streams class, and Functions.stream() as an accessor thereof.
+
+Fixed Bugs:
+o LANG-1514: Make test more stable by wrapping assertions in hashset. Thanks to contextshuffling.
+o LANG-1450: Generate Javadoc jar on build.
+o LANG-1460: Trivial: year of release for 3.9 says 2018, should be 2019 Thanks to Larry West.
+o LANG-1476: Use synchronize on a set created with Collections.synchronizedSet before iterating Thanks to emopers.
+o LANG-1475: StringUtils.unwrap incorrect throw StringIndexOutOfBoundsException. Thanks to stzx.
+o LANG-1406: StringIndexOutOfBoundsException in StringUtils.replaceIgnoreCase #423. Thanks to geratorres.
+o LANG-1453: StringUtils.removeIgnoreCase("?a", "a") throws IndexOutOfBoundsException #423. Thanks to geratorres.
+o LANG-1426: Corrected usage examples in Javadocs #458. Thanks to Brower, Mikko Maunu, Suraj Gautam.
+o LANG-1463: StringUtils abbreviate returns String of length greater than maxWidth #477. Thanks to bbeckercscc, Gary Gregory.
+o LANG-1500: Test may fail due to a different order of fields returned by reflection api #480. Thanks to contextshuffling.
+o LANG-1501: Sort fields in ReflectionToStringBuilder for deterministic order #481. Thanks to contextshuffling.
+o LANG-1433: MethodUtils will throw a NPE if invokeMethod() is called for a var-args method #407. Thanks to Christian Franzen.
+o LANG-1518: MethodUtils.getAnnotation() with searchSupers = true does not work if super is generic #494. Thanks to Michele Preti, Bruno P. Kinoshita, Gary Gregory.
+
+Changes:
+o LANG-1437: Remove redundant if statements in join methods #411. Thanks to Andrei Troie.
+o commons.japicmp.version 0.13.1 -> 0.14.1.
+o junit-jupiter 5.5.0 -> 5.5.1.
+o junit-jupiter 5.5.1 -> 5.5.2.
+o Improve Javadoc based on the discussion of the GitHub PR #459. Thanks to Jonathan Leitschuh, Bruno P. Kinoshita, Rob Tompkins, Gary Gregory.
+o maven-checkstyle-plugin 3.0.0 -> 3.1.0.
+o LANG-696: Update documentation related to the issue LANG-696 #449. Thanks to Peter Verhas.
+o AnnotationUtils little cleanup #467. Thanks to Peter Verhas.
+o Update test dependency: org.easymock:easymock 4.0.2 -> 4.1. Thanks to Gary Gregory.
+o Update test dependency: org.hamcrest:hamcrest 2.1 -> 2.2. Thanks to Gary Gregory.
+o Update test dependency: org.junit-pioneer:junit-pioneer 0.3.0 -> 0.4.2. Thanks to Gary Gregory.
+o Update build dependency: com.puppycrawl.tools:checkstyle 8.18 -> 8.27. Thanks to Gary Gregory.
+o Update POM parent: org.apache.commons:commons-parent 48 -> 50. Thanks to Gary Gregory.
+o BooleanUtils Javadoc #469. Thanks to Peter Verhas.
+o Functions Javadoc #466. Thanks to Peter Verhas.
+o org.easymock:easymock 4.1 -> 4.2. Thanks to Gary Gregory.
+o org.junit-pioneer:junit-pioneer 0.4.2 -> 0.5.4. Thanks to Gary Gregory.
+o org.junit.jupiter:junit-jupiter 5.5.2 -> 5.6.0. Thanks to Gary Gregory.
+o Use Javadoc {@code} instead of pre tags. #490. Thanks to Peter Verhas.
+o ExceptionUtilsTest to 100% #486. Thanks to Peter Verhas.
+o Reuse own code in Functions.java #493. Thanks to Peter Verhas.
+o LANG-1523: Avoid unnecessary allocation in StringUtils.wrapIfMissing. #496. Thanks to Edgar Asatryan, Bruno P. Kinoshita, Gary Gregory.
+o LANG-1525: Internally use Validate.notNull(foo, ...) instead of Validate.isTrue(foo != null, ...). Thanks to Edgar Asatryan, Bruno P. Kinoshita, Gary Gregory.
+o LANG-1526: Add 1 and 0 in toBooleanObject(final String str) #502. Thanks to Dominik Schramm.
+o LANG-1527: Remove a redundant argument check in NumberUtils #504. Thanks to Pengyu Nie.
+o LANG-1529: Deprecate org.apache.commons.lang3.ArrayUtils.removeAllOccurences(*) for org.apache.commons.lang3.ArrayUtils.removeAllOccurrences(*). Thanks to Gary Gregory, BillCindy, Bruno P. Kinoshita.
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+Download page: https://commons.apache.org/proper/commons-lang/download_lang.cgi
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.2.1.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.2.1.txt
new file mode 100644
index 000000000..bb554a629
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.2.1.txt
@@ -0,0 +1,417 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.2.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.2.1 version of
+Apache Commons Lang. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.2.1
+at least requires Java 6.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12-hour clock and not
+ 24-hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.2.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.2.txt
new file mode 100644
index 000000000..f686b0852
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.2.txt
@@ -0,0 +1,405 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.2
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.2 version of
+Apache Commons Lang. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.2
+at least requires Java 6.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12-hour clock and not
+ 24-hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.3.1.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.3.1.txt
new file mode 100644
index 000000000..0a42081ff
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.3.1.txt
@@ -0,0 +1,496 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.3.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.3.1 version of
+Apache Commons Lang. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.3.1
+at least requires Java 6.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Götz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernández.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12-hour clock and not
+ 24-hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.3.2.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.3.2.txt
new file mode 100644
index 000000000..fb498b2ef
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.3.2.txt
@@ -0,0 +1,509 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.3.2
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.3.2 version of
+Apache Commons Lang. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.3.2
+at least requires Java 6.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Götz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernández.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12-hour clock and not
+ 24-hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.3.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.3.txt
new file mode 100644
index 000000000..77a28fb19
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.3.txt
@@ -0,0 +1,482 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.3
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.3 version of
+Apache Commons Lang. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.3
+at least requires Java 6.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Götz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernández.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12-hour clock and not
+ 24-hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.4.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.4.txt
new file mode 100644
index 000000000..afbcc4a1d
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.4.txt
@@ -0,0 +1,642 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.4
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.4 version of
+Apache Commons Lang. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.4
+at least requires Java 6.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+
+COMPATIBILITY
+=============
+
+Commons Lang 3.4 is fully binary compatible to the last release and can
+therefore be used as a drop in replacement for 3.3.2. Note that the value of
+org.apache.commons.lang3.time.DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN
+has changed, which may affect clients using the constant. Furthermore the
+constant is used internally in
+o DurationFormatUtils.formatDurationISO(long)
+o DurationFormatUtils.formatPeriodISO(long, long)
+
+For more information see https://issues.apache.org/jira/browse/LANG-1000.
+
+NEW FEATURES
+==============
+
+o LANG-821: Support OS X versions in SystemUtils. Thanks to Timo Kockert.
+o LANG-1103: Add SystemUtils.IS_JAVA_1_9
+o LANG-1093: Add ClassUtils.getAbbreviatedName(). Thanks to Fabian Lange.
+o LANG-1082: Add option to disable the "objectsTriviallyEqual" test in
+ DiffBuilder. Thanks to Jonathan Baker.
+o LANG-1015: Add JsonToStringStyle implementation to ToStringStyle. Thanks to
+ Thiago Andrade.
+o LANG-1080: Add NoClassNameToStringStyle implementation of ToStringStyle.
+ Thanks to Innokenty Shuvalov.
+o LANG-883: Add StringUtils.containsAny(CharSequence, CharSequence...) method.
+ Thanks to Daniel Stewart.
+o LANG-1052: Multiline recursive to string style. Thanks to Jan Matèrne.
+o LANG-536: Add isSorted() to ArrayUtils. Thanks to James Sawle.
+o LANG-1033: Add StringUtils.countMatches(CharSequence, char)
+o LANG-1021: Provide methods to retrieve all fields/methods annotated with a
+ specific type. Thanks to Alexander Müller.
+o LANG-1016: NumberUtils#isParsable method(s). Thanks to
+ Juan Pablo Santos Rodríguez.
+o LANG-999: Add fuzzy String matching logic to StringUtils. Thanks to
+ Ben Ripkens.
+o LANG-994: Add zero copy read method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-993: Add zero copy write method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-1044: Add method MethodUtils.invokeExactMethod(Object, String)
+o LANG-1045: Add method MethodUtils.invokeMethod(Object, String)
+
+FIXED BUGS
+============
+
+o LANG-794: SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect. Thanks to
+ Timo Kockert.
+o LANG-1104: Parse test fails for TimeZone America/Sao_Paulo
+o LANG-948: Exception while using ExtendedMessageFormat and escaping braces.
+ Thanks to Andrey Khobnya.
+o LANG-1092: Wrong formatting of time zones with daylight saving time in
+ FastDatePrinter
+o LANG-1090: FastDateParser does not set error indication in ParsePosition
+o LANG-1089: FastDateParser does not handle excess hours as per
+ SimpleDateFormat
+o LANG-1061: FastDateParser error - timezones not handled correctly. Thanks to
+ dmeneses.
+o LANG-1087: NumberUtils#createNumber() returns positive BigDecimal when
+ negative Float is expected. Thanks to Renat Zhilkibaev.
+o LANG-1081: DiffBuilder.append(String, Object left, Object right) does not do
+ a left.equals(right) check. Thanks to Jonathan Baker.
+o LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently.
+ Thanks to Jonathan Baker.
+o LANG-1083: Add (T) casts to get unit tests to pass in old JDK. Thanks to
+ Jonathan Baker.
+o LANG-1073: Read wrong component type of array in add in ArrayUtils.
+ Thanks to haiyang li.
+o LANG-1077: StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils.
+ Thanks to haiyang li.
+o LANG-1072: Duplicated "0x" check in createBigInteger in NumberUtils. Thanks
+ to haiyang li.
+o LANG-1064: StringUtils.abbreviate description doesn't agree with the
+ examples. Thanks to B.J. Herbison.
+o LANG-1041: Fix MethodUtilsTest so it does not depend on JDK method ordering.
+ Thanks to Alexandre Bartel.
+o LANG-1000: ParseException when trying to parse UTC dates with Z as zone
+ designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
+o LANG-1035: Javadoc for EqualsBuilder.reflectionEquals() is unclear
+o LANG-1001: ISO 8601 misspelled throughout the Javadocs. Thanks to
+ Michael Osipov.
+o LANG-1088: FastDateParser should be case-insensitive
+o LANG-995: Fix bug with stripping spaces on last line in WordUtils.wrap().
+ Thanks to Andrey Khobnya.
+
+CHANGES
+=========
+
+o LANG-1102: Make logic for comparing OS versions in SystemUtils smarter
+o LANG-1091: Shutdown thread pools in test cases. Thanks to Fabian Lange.
+o LANG-1101: FastDateParser and FastDatePrinter support 'X' format
+o LANG-1100: Avoid memory allocation when using date formatting to StringBuffer.
+ Thanks to mbracher.
+o LANG-935: Possible performance improvement on string escape functions.
+ Thanks to Fabian Lange, Thomas Neidhart.
+o LANG-1098: Avoid String allocation in StrBuilder.append(CharSequence). Thanks
+ to Mikhail Mazurskiy, Fabian Lange.
+o LANG-1098: Update maven-checkstyle-plugin to 2.14. Thanks to Micha? Kordas.
+o LANG-1097: Update org.easymock:easymock to 3.3.1. Thanks to Micha? Kordas.
+o LANG-1096: Update maven-pmd-plugin to 3.4. Thanks to Micha? Kordas.
+o LANG-1095: Update maven-antrun-plugin to 1.8. Thanks to Micha? Kordas.
+o LANG-877: Performance improvements for StringEscapeUtils. Thanks to
+ Fabian Lange.
+o LANG-1071: Fix wrong examples in Javadoc of
+ StringUtils.replaceEachRepeatedly(...),
+ StringUtils.replaceEach(...) Thanks to Arno Noordover.
+o LANG-827: CompareToBuilder's doc doesn't specify precedence of fields it
+ uses in performing comparisons
+o LANG-1020: Improve performance of normalize space. Thanks to Libor Ondrusek.
+o LANG-1027: org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should
+ return true by default
+o LANG-1026: Bring static method references in StringUtils to consistent style.
+ Thanks to Alex Yursha.
+o LANG-1017: Use non-ASCII digits in Javadoc examples for
+ StringUtils.isNumeric. Thanks to Christoph Schneegans.
+o LANG-1008: Change min/max methods in NumberUtils/IEEE754rUtils from array
+ input parameters to varargs. Thanks to Thiago Andrade.
+o LANG-1006: Add wrap (with String or char) to StringUtils. Thanks to
+ Thiago Andrade.
+o LANG-1005: Extend DurationFormatUtils#formatDurationISO default pattern to
+ match #formatDurationHMS. Thanks to Michael Osipov.
+o LANG-1007: Fixing NumberUtils JAVADoc comments for max methods. Thanks to
+ Thiago Andrade.
+o LANG-731: Better Javadoc for BitField class
+o LANG-1004: DurationFormatUtils#formatDurationHMS implementation does not
+ correspond to Javadoc and vice versa. Thanks to Michael Osipov.
+o LANG-1003: DurationFormatUtils are not able to handle negative
+ durations/periods
+o LANG-998: Javadoc is not clear on preferred pattern to instantiate
+ FastDateParser / FastDatePrinter
+
+ Release Notes for version 3.3.2
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Götz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernández.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12-hour clock and not
+ 24-hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.5.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.5.txt
new file mode 100644
index 000000000..37add8890
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.5.txt
@@ -0,0 +1,940 @@
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Release Notes for version 3.5
+
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o Added Java 9 detection to org.apache.commons.lang3.SystemUtils.
+o Support for shifting and swapping elements in
+ org.apache.commons.lang3.ArrayUtils.
+o New methods for generating random strings from different character classes
+ including alphabetic, alphanumeric and ASCII added to
+ org.apache.commons.lang3.RandomStringUtils.
+o Numerous extensions to org.apache.commons.lang3.StringUtils including
+ null safe compare variants, more remove and replace variants, rotation and
+ truncation.
+o Added org.apache.commons.lang3.ThreadUtils - a utility class to work with
+ instances of java.lang.Thread and java.lang.ThreadGroup.
+o Added annotations @EqualsExclude, @HashCodeExclude and @ToStringExclude to
+ mark fields which should be ignored by the reflective builders in the
+ org.apache.commons.lang3.builder package.
+o Support for various modify and retrieve value use cases added to the classes
+ in org.apache.commons.lang3.mutable.
+
+COMPATIBILITY
+=============
+
+Apache Commons Lang 3.5 is binary compatible with the 3.4 release. Users
+should not experience any problems when upgrading from 3.4 to 3.5.
+
+There has been an addition to the org.apache.commons.lang3.time.DatePrinter
+interface:
+
+o Added method 'public boolean parse(java.lang.String, java.text.ParsePosition,
+ java.util.Calendar)'
+o Added method 'public java.lang.Appendable format(long, java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Date,
+ java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Calendar,
+ java.lang.Appendable)'
+
+For this reason 3.5 is not strictly source compatible to 3.4. Since the
+DatePrinter interface is not meant to be implemented by clients, this
+change it not considered to cause any problems.
+
+JAVA 9 SUPPORT
+==============
+
+Java 9 introduces a new version-string scheme. Details of this new scheme are
+documented in JEP-223 (https://openjdk.org/jeps/223). In order to support
+JEP-223 two classes had to be changed:
+
+o org.apache.commons.lang3.JavaVersion
+ deprecated enum constant JAVA_1_9
+ introduced enum constant JAVA_9
+
+o org.apache.commons.lang3.SystemUtils
+ deprecated constant IS_JAVA_1_9
+ introduced constant IS_JAVA_9
+
+For more information see LANG-1197
+(https://issues.apache.org/jira/browse/LANG-1197). All other APIs are expected
+to work with Java 9.
+
+BUILDING ON JAVA 9
+==================
+
+Java 8 introduced the Unicode Consortium's Common Locale Data Repository as
+alternative source for locale data. Java 9 will use the CLDR provider as
+default provider for locale data (see https://openjdk.org/jeps/252). This
+causes a number of locale-sensitive test in Commons Lang to fail. In order
+to build Commons Lang 3.5 on Java 9, the locale provider has to be set to
+'JRE':
+
+ mvn -Djava.locale.providers=JRE clean install
+
+We are currently investigating ways to support building on Java 9 without
+further configuration. For more information see:
+https://issues.apache.org/jira/browse/LANG-1265
+
+
+NEW FEATURES
+==============
+
+o LANG-1275: Added a tryAcquire() method to TimedSemaphore.
+o LANG-1255: Add DateUtils.toCalendar(Date, TimeZone). Thanks to Kaiyuan Wang.
+o LANG-1023: Add WordUtils.wrap overload with customizable breakable character.
+ Thanks to Marko Bekhta.
+o LANG-787: Add method removeIgnoreCase(String, String) to StringUtils. Thanks
+ to Gokul Nanthakumar C.
+o LANG-1224: Extend RandomStringUtils with methods that generate strings
+ between a min and max length. Thanks to Caleb Cushing.
+o LANG-1257: Add APIs StringUtils.wrapIfMissing(String, char|String). Thanks to
+ Gary Gregory.
+o LANG-1253: Add RandomUtils#nextBoolean() method. Thanks to adilek.
+o LANG-1085: Add a circuit breaker implementation. Thanks to Oliver Heger and
+ Bruno P. Kinoshita.
+o LANG-1013: Add StringUtils.truncate(). Thanks to Thiago Andrade.
+o LANG-1195: Enhance MethodUtils to allow invocation of private methods. Thanks
+ to Derek C. Ashmore.
+o LANG-1189: Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/
+ decrementAndGet/addAndGet in Mutable* classes. Thanks to
+ Haiyang Li and Matthew Bartenschlag.
+o LANG-1225: Add RandomStringUtils#randomGraph and #randomPrint which match
+ corresponding regular expression class. Thanks to Caleb Cushing.
+o LANG-1223: Add StopWatch#getTime(TimeUnit). Thanks to Nick Manley.
+o LANG-781: Add methods to ObjectUtils class to check for null elements in the
+ array. Thanks to Krzysztof Wolny.
+o LANG-1228: Prefer Throwable.getCause() in ExceptionUtils.getCause().
+ Thanks to Brad Hess.
+o LANG-1233: DiffBuilder add method to allow appending from a DiffResult.
+ Thanks to Nick Manley.
+o LANG-1168: Add SystemUtils.IS_OS_WINDOWS_10 property.
+ Thanks to Pascal Schumacher.
+o LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and
+ MethodUtils. Thanks to Jim Lloyd and Joe Ferner.
+o LANG-1134: Add methods to check numbers against NaN and infinite to
+ Validate. Thanks to Alan Smithee.
+o LANG-1220: Add tests for missed branches in DateUtils.
+ Thanks to Casey Scarborough.
+o LANG-1146: z/OS identification in SystemUtils.
+ Thanks to Gabor Liptak.
+o LANG-1192: FastDateFormat support of the week-year component (uppercase 'Y').
+ Thanks to Dominik Stadler.
+o LANG-1169: Add StringUtils methods to compare a string to multiple strings.
+ Thanks to Rafal Glowinski, Robert Parr and Arman Sharif.
+o LANG-1185: Add remove by regular expression methods in StringUtils.
+o LANG-1139: Add replace by regular expression methods in StringUtils.
+o LANG-1171: Add compare methods in StringUtils.
+o LANG-1174: Add sugar to RandomUtils. Thanks to Punkratz312.
+o LANG-1154: FastDateFormat APIs that use a StringBuilder. Thanks to
+ Gary Gregory.
+o LANG-1149: Ability to throw checked exceptions without declaring them. Thanks
+ to Gregory Zak.
+o LANG-1153: Implement ParsePosition api for FastDateParser.
+o LANG-1137: Add check for duplicate event listener in EventListenerSupport.
+ Thanks to Matthew Aguirre.
+o LANG-1135: Add method containsAllWords to WordUtils. Thanks to
+ Eduardo Martins.
+o LANG-1132: ReflectionToStringBuilder doesn't throw IllegalArgumentException
+ when the constructor's object param is null. Thanks to Jack Tan.
+o LANG-701: StringUtils join with var args. Thanks to James Sawle.
+o LANG-1105: Add ThreadUtils - A utility class which provides helper methods
+ related to java.lang.Thread Issue: LANG-1105. Thanks to
+ Hendrik Saly.
+o LANG-1031: Add annotations to exclude fields from ReflectionEqualsBuilder,
+ ReflectionToStringBuilder and ReflectionHashCodeBuilder. Thanks
+ to Felipe Adorno.
+o LANG-1127: Use JUnit rules to set and reset the default Locale and TimeZone.
+o LANG-1119: Add rotate(string, int) method to StringUtils. Thanks to
+ Loic Guibert.
+o LANG-1099: Add swap and shift operations for arrays to ArrayUtils. Thanks to
+ Adrian Ber.
+o LANG-1050: Change nullToEmpty methods to generics. Thanks to James Sawle.
+o LANG-1074: Add a method to ArrayUtils for removing all occurrences of a given
+ element Issue: LANG-1074. Thanks to Haiyang Li.
+
+FIXED BUGS
+============
+
+o LANG-1261: ArrayUtils.contains returns false for instances of subtypes.
+o LANG-1252: Rename NumberUtils.isNumber, isCreatable to better reflect
+ createNumber. Also, accommodated for "+" symbol as prefix in
+ isCreatable and isNumber. Thanks to Rob Tompkins.
+o LANG-1230: Remove unnecessary synchronization from registry lookup in
+ EqualsBuilder and HashCodeBuilder. Thanks to Philippe Marschall.
+o LANG-1214: Handle "void" in ClassUtils.getClass(). Thanks to Henry Tung.
+o LANG-1250: SerializationUtils#deserialize has unnecessary code and a comment
+ for that. Thanks to Glease Wang.
+o LANG-1190: TypeUtils.isAssignable throws NullPointerException when fromType
+ has type variables and toType generic superclass specifies type
+ variable. Thanks to Pascal Schumacher.
+o LANG-1226: StringUtils#normalizeSpace does not trim the string anymore.
+ Thanks to Pascal Schumacher.
+o LANG-1251: SerializationUtils.ClassLoaderAwareObjectInputStream should use
+ static initializer to initialize primitiveTypes map. Thanks to
+ Takuya Ueshin.
+o LANG-1248: FastDatePrinter Memory allocation regression. Thanks to
+ Benoit Wiart.
+o LANG-1018: Fix precision loss on NumberUtils.createNumber(String). Thanks to
+ Nick Manley.
+o LANG-1199: Fix implementation of StringUtils.getJaroWinklerDistance(). Thanks
+ to M. Steiger.
+o LANG-1244: Fix dead links in StringUtils.getLevenshteinDistance() javadoc.
+ Thanks to jjbankert.
+o LANG-1242: "\u2284":"?" mapping missing from
+ EntityArrays#HTML40_EXTENDED_ESCAPE. Thanks to Neal Stewart.
+o LANG-901: StringUtils#startsWithAny/endsWithAny is case-sensitive -
+ documented as case-insensitive. Thanks to Matthew Bartenschlag.
+o LANG-1232: DiffBuilder: Add null check on fieldName when appending Object or
+ Object[]. Thanks to Nick Manley.
+o LANG-1178: ArrayUtils.removeAll(Object array, int... indices) should do the
+ clone, not its callers. Thanks to Henri Yandell.
+o LANG-1120: StringUtils.stripAccents should remove accents from "Ł" and "ł".
+ Thanks to kaching88.
+o LANG-1205: NumberUtils.createNumber() behaves inconsistently with
+ NumberUtils.isNumber(). Thanks to pbrose.
+o LANG-1222: Fix for incorrect comment on StringUtils.containsIgnoreCase
+ method. Thanks to Adam J.
+o LANG-1221: Fix typo on appendIfMissing javadoc. Thanks to Pierre Templier.
+o LANG-1202: parseDateStrictly doesn't pass specified locale. Thanks to
+ Markus Jelsma.
+o LANG-1219: FastDateFormat doesn't respect summer daylight in some localized
+ strings. Thanks to Jarek.
+o LANG-1175: Remove Ant-based build.
+o LANG-1194: Limit max heap memory for consistent Travis CI build.
+o LANG-1186: Fix NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to NickManley.
+o LANG-1193: ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1
+ (correct answer should be 0); revert fix for LANG-1077. Thanks to
+ Qin Li.
+o LANG-1002: Several predefined ISO FastDateFormats in DateFormatUtils are
+ incorrect. Thanks to Michael Osipov.
+o LANG-1152: StringIndexOutOfBoundsException or field over-write for large year
+ fields in FastDateParser. Thanks to Pas Filip.
+o LANG-1141: StrLookup.systemPropertiesLookup() no longer reacts on changes on
+ system properties.
+o LANG-1147: EnumUtils *BitVector issue with more than 32 values Enum. Thanks
+ to Loic Guibert.
+o LANG-1059: Capitalize javadoc is incorrect. Thanks to Colin Casey.
+o LANG-1122: Inconsistent behavior of swap for malformed inputs. Thanks to
+ Adrian Ber.
+o LANG-1130: Fix critical issues reported by SonarQube.
+o LANG-1131: StrBuilder.equals(StrBuilder) doesn't check for null inputs.
+o LANG-1128: JsonToStringStyle doesn't handle chars and objects correctly.
+ Thanks to Jack Tan.
+o LANG-1126: DateFormatUtilsTest.testSMTP depends on the default Locale.
+o LANG-1123: Unit test FastDatePrinterTimeZonesTest needs a timezone set.
+ Thanks to Christian P. Momon.
+o LANG-916: DateFormatUtils.format does not correctly change Calendar
+ TimeZone in certain situations. Thanks to Christian P. Momon.
+o LANG-1116: DateUtilsTest.testLang530 fails for some timezones. Thanks to
+ Aaron Sheldon.
+o LANG-1114: TypeUtils.ParameterizedType#equals doesn't work with wildcard
+ types. Thanks to Andy Coates.
+o LANG-1118: StringUtils.repeat('z', -1) throws NegativeArraySizeException.
+ Thanks to Loic Guibert.
+o LANG-1111: Fix FindBugs warnings in DurationFormatUtils.
+o LANG-1162: StringUtils#equals fails with Index OOBE on non-Strings with
+ identical leading prefix..
+o LANG-1163: There are no tests for CharSequenceUtils.regionMatches.
+o LANG-1200: Fix Javadoc of StringUtils.ordinalIndexOf. Thanks to BarkZhang.
+o LANG-1191: Incorrect Javadoc
+ StringUtils.containsAny(CharSequence, CharSequence...). Thanks to
+ qed, Brent Worden and Gary Gregory.
+
+CHANGES
+=========
+o LANG-1197: Prepare Java 9 detection.
+o LANG-1262: CompareToBuilder.append(Object, Object, Comparator) method is too
+ big to be inlined. Thanks to Ruslan Cheremin.
+o LANG-1259: Javadoc for ArrayUtils.isNotEmpty() is slightly misleading. Thanks
+ to Dominik Stadler.
+o LANG-1247: FastDatePrinter generates extra Date objects. Thanks to
+ Benoit Wiart.
+o LANG-1229: HashCodeBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1243: Simplify ArrayUtils removeElements by using new decrementAndGet()
+ method.
+o LANG-1240: Optimize BitField constructor implementation. Thanks to zhanhb.
+o LANG-1206: Improve CharSetUtils.squeeze() performance. Thanks to
+ Mohammed Alfallaj.
+o LANG-1176: Improve ArrayUtils removeElements time complexity to O(n). Thanks
+ to Jeffery Yuan.
+o LANG-1234: getLevenshteinDistance with a threshold: optimize implementation
+ if the strings lengths differ more than the threshold. Thanks to
+ Jonatan Jönsson.
+o LANG-1151: Performance improvements for NumberUtils.isParsable. Thanks to
+ Juan Pablo Santos Rodríguez.
+o LANG-1218: EqualsBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1210: StringUtils#startsWithAny has error in Javadoc. Thanks to
+ Matthias Niehoff.
+o LANG-1208: StrSubstitutor can preserve escapes. Thanks to Samuel Karp.
+o LANG-1182: Clarify Javadoc of StringUtils.containsAny(). Thanks to
+ Larry West and Pascal Schumacher.
+o LANG-1183: Making replacePattern/removePattern methods null safe in
+ StringUtils.
+o LANG-1057: Replace StringBuilder with String concatenation for better
+ optimization. Thanks to Otávio Santana.
+o LANG-1075: Deprecate SystemUtils.FILE_SEPARATOR and
+ SystemUtils.PATH_SEPARATOR.
+o LANG-979: TypeUtils.parameterizeWithOwner - wrong format descriptor for
+ "invalid number of type parameters". Thanks to Bruno P. Kinoshita.
+o LANG-1112: MultilineRecursiveToStringStyle largely unusable due to being
+ package-private.
+o LANG-1058: StringUtils.uncapitalize performance improvement. Thanks to
+ Leo Wang.
+o LANG-1069: CharSet.getInstance documentation does not clearly explain how
+ to include negation character in set. Thanks to Arno Noordover.
+o LANG-1107: Fix parsing edge cases in FastDateParser.
+o LANG-1273: Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils. Thanks
+ to Jake Wang.
+
+=============================================================================
+
+ Release Notes for version 3.4
+
+
+COMPATIBILITY
+=============
+
+Commons Lang 3.4 is fully binary compatible to the last release and can
+therefore be used as a drop in replacement for 3.3.2. Note that the value of
+org.apache.commons.lang3.time.DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN
+has changed, which may affect clients using the constant. Furthermore the
+constant is used internally in
+o DurationFormatUtils.formatDurationISO(long)
+o DurationFormatUtils.formatPeriodISO(long, long)
+
+For more information see https://issues.apache.org/jira/browse/LANG-1000.
+
+NEW FEATURES
+==============
+
+o LANG-821: Support OS X versions in SystemUtils. Thanks to Timo Kockert.
+o LANG-1103: Add SystemUtils.IS_JAVA_1_9
+o LANG-1093: Add ClassUtils.getAbbreviatedName(). Thanks to Fabian Lange.
+o LANG-1082: Add option to disable the "objectsTriviallyEqual" test in
+ DiffBuilder. Thanks to Jonathan Baker.
+o LANG-1015: Add JsonToStringStyle implementation to ToStringStyle. Thanks to
+ Thiago Andrade.
+o LANG-1080: Add NoClassNameToStringStyle implementation of ToStringStyle.
+ Thanks to Innokenty Shuvalov.
+o LANG-883: Add StringUtils.containsAny(CharSequence, CharSequence...) method.
+ Thanks to Daniel Stewart.
+o LANG-1052: Multiline recursive to string style. Thanks to Jan Matèrne.
+o LANG-536: Add isSorted() to ArrayUtils. Thanks to James Sawle.
+o LANG-1033: Add StringUtils.countMatches(CharSequence, char)
+o LANG-1021: Provide methods to retrieve all fields/methods annotated with a
+ specific type. Thanks to Alexander Müller.
+o LANG-1016: NumberUtils#isParsable method(s). Thanks to
+ Juan Pablo Santos Rodríguez.
+o LANG-999: Add fuzzy String matching logic to StringUtils. Thanks to
+ Ben Ripkens.
+o LANG-994: Add zero copy read method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-993: Add zero copy write method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-1044: Add method MethodUtils.invokeExactMethod(Object, String)
+o LANG-1045: Add method MethodUtils.invokeMethod(Object, String)
+
+FIXED BUGS
+============
+
+o LANG-794: SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect. Thanks to
+ Timo Kockert.
+o LANG-1104: Parse test fails for TimeZone America/Sao_Paulo
+o LANG-948: Exception while using ExtendedMessageFormat and escaping braces.
+ Thanks to Andrey Khobnya.
+o LANG-1092: Wrong formatting of time zones with daylight saving time in
+ FastDatePrinter
+o LANG-1090: FastDateParser does not set error indication in ParsePosition
+o LANG-1089: FastDateParser does not handle excess hours as per
+ SimpleDateFormat
+o LANG-1061: FastDateParser error - timezones not handled correctly. Thanks to
+ dmeneses.
+o LANG-1087: NumberUtils#createNumber() returns positive BigDecimal when
+ negative Float is expected. Thanks to Renat Zhilkibaev.
+o LANG-1081: DiffBuilder.append(String, Object left, Object right) does not do
+ a left.equals(right) check. Thanks to Jonathan Baker.
+o LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently.
+ Thanks to Jonathan Baker.
+o LANG-1083: Add (T) casts to get unit tests to pass in old JDK. Thanks to
+ Jonathan Baker.
+o LANG-1073: Read wrong component type of array in add in ArrayUtils.
+ Thanks to haiyang li.
+o LANG-1077: StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils.
+ Thanks to haiyang li.
+o LANG-1072: Duplicated "0x" check in createBigInteger in NumberUtils. Thanks
+ to haiyang li.
+o LANG-1064: StringUtils.abbreviate description doesn't agree with the
+ examples. Thanks to B.J. Herbison.
+o LANG-1041: Fix MethodUtilsTest so it does not depend on JDK method ordering.
+ Thanks to Alexandre Bartel.
+o LANG-1000: ParseException when trying to parse UTC dates with Z as zone
+ designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
+o LANG-1035: Javadoc for EqualsBuilder.reflectionEquals() is unclear
+o LANG-1001: ISO 8601 misspelled throughout the Javadocs. Thanks to
+ Michael Osipov.
+o LANG-1088: FastDateParser should be case-insensitive
+o LANG-995: Fix bug with stripping spaces on last line in WordUtils.wrap().
+ Thanks to Andrey Khobnya.
+
+CHANGES
+=========
+
+o LANG-1102: Make logic for comparing OS versions in SystemUtils smarter
+o LANG-1091: Shutdown thread pools in test cases. Thanks to Fabian Lange.
+o LANG-1101: FastDateParser and FastDatePrinter support 'X' format
+o LANG-1100: Avoid memory allocation when using date formatting to StringBuffer.
+ Thanks to mbracher.
+o LANG-935: Possible performance improvement on string escape functions.
+ Thanks to Fabian Lange, Thomas Neidhart.
+o LANG-1098: Avoid String allocation in StrBuilder.append(CharSequence). Thanks
+ to Mikhail Mazurskiy, Fabian Lange.
+o LANG-1098: Update maven-checkstyle-plugin to 2.14. Thanks to Micha? Kordas.
+o LANG-1097: Update org.easymock:easymock to 3.3.1. Thanks to Micha? Kordas.
+o LANG-1096: Update maven-pmd-plugin to 3.4. Thanks to Micha? Kordas.
+o LANG-1095: Update maven-antrun-plugin to 1.8. Thanks to Micha? Kordas.
+o LANG-877: Performance improvements for StringEscapeUtils. Thanks to
+ Fabian Lange.
+o LANG-1071: Fix wrong examples in Javadoc of
+ StringUtils.replaceEachRepeatedly(...),
+ StringUtils.replaceEach(...) Thanks to Arno Noordover.
+o LANG-827: CompareToBuilder's doc doesn't specify precedence of fields it
+ uses in performing comparisons
+o LANG-1020: Improve performance of normalize space. Thanks to Libor Ondrusek.
+o LANG-1027: org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should
+ return true by default
+o LANG-1026: Bring static method references in StringUtils to consistent style.
+ Thanks to Alex Yursha.
+o LANG-1017: Use non-ASCII digits in Javadoc examples for
+ StringUtils.isNumeric. Thanks to Christoph Schneegans.
+o LANG-1008: Change min/max methods in NumberUtils/IEEE754rUtils from array
+ input parameters to varargs. Thanks to Thiago Andrade.
+o LANG-1006: Add wrap (with String or char) to StringUtils. Thanks to
+ Thiago Andrade.
+o LANG-1005: Extend DurationFormatUtils#formatDurationISO default pattern to
+ match #formatDurationHMS. Thanks to Michael Osipov.
+o LANG-1007: Fixing NumberUtils JAVADoc comments for max methods. Thanks to
+ Thiago Andrade.
+o LANG-731: Better Javadoc for BitField class
+o LANG-1004: DurationFormatUtils#formatDurationHMS implementation does not
+ correspond to Javadoc and vice versa. Thanks to Michael Osipov.
+o LANG-1003: DurationFormatUtils are not able to handle negative
+ durations/periods
+o LANG-998: Javadoc is not clear on preferred pattern to instantiate
+ FastDateParser / FastDatePrinter
+
+=============================================================================
+
+ Release Notes for version 3.3.2
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+=============================================================================
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+=============================================================================
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Götz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernández.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+=============================================================================
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+=============================================================================
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+=============================================================================
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+=============================================================================
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12-hour clock and not
+ 24-hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.6.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.6.txt
new file mode 100644
index 000000000..78478c4a6
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.6.txt
@@ -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.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.6
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.6 version of
+Apache Commons Lang as well as a history all changes in the Commons Lang 3.x
+release line. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.6 at
+least requires Java 7.0. Note that this has changed from Commons Lang 3.5, which
+only required Java 1.6.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o The class org.apache.commons.lang3.concurrent.Memoizer is an implementation
+ of the Memoizer pattern as shown in
+ Goetz, Brian et al. (2006) - Java Concurrency in Practice, p. 108.
+o The class org.apache.commons.lang3.ArchUtils has been added. ArchUtils is
+ a utility class for the "os.arch" system property.
+
+DEPRECATIONS
+============
+
+The Apache Commons Community has recently set up the Commons Text component
+as a home for algorithms working on strings. For this reason most of the string
+focused functionality in Commons Lang has been deprecated and moved to
+Commons Text. This includes:
+
+o All classes in the org.apache.commons.lang3.text and the
+ org.apache.commons.lang3.text.translate packages
+o org.apache.commons.lang3.StringEscapeUtils
+o org.apache.commons.lang3.RandomStringUtils
+o The methods org.apache.commons.lang3.StringUtils.getJaroWinklerDistance and
+ org.apache.commons.lang3.StringUtils.getLevenshteinDistance
+
+For more information see the Commons Text website:
+
+ https://commons.apache.org/text
+
+The class org.apache.commons.lang3.CharEncoding has been deprecated in favor of
+java.nio.charset.StandardCharsets.
+
+The following methods have been deprecated in
+org.apache.commons.lang3.ArrayUtils in favor of the corresponding insert
+methods. Note that the handling for null inputs differs between add and insert.
+
+o add(boolean[], int, boolean) -> insert(int, boolean[], boolean...)
+o add(byte[], int, boolean) -> insert(int, byte[], byte...)
+o add(char[], int, boolean) -> insert(int, char[], char...)
+o add(double[], int, boolean) -> insert(int, double[], double...)
+o add(float[], int, boolean) -> insert(int, float[], float...)
+o add(int[], int, boolean) -> insert(int, int[], int...)
+o add(long[], int, boolean) -> insert(int, long[], long...)
+o add(short[], int, boolean) -> insert(int, short[], short...)
+o add(T[], int, boolean) -> insert(int, T[], T...)
+
+
+COMPATIBILITY WITH JAVA 9
+==================
+
+The MANIFEST.MF now contains an additional entry:
+
+ Automatic-Module-Name: org.apache.commons.lang3
+
+This should make it possible to use Commons Lang 3.6 as a module in the Java 9
+module system. For more information see the corresponding issue and the
+referenced mailing list discussions:
+
+ https://issues.apache.org/jira/browse/LANG-1338
+
+The build problems present in the 3.5 release have been resolved. Building
+Commons Lang 3.6 should work out of the box with the latest Java 9 EA build.
+Please report any Java 9 related issues at:
+
+ https://issues.apache.org/jira/browse/LANG
+
+NEW FEATURES
+============
+
+o LANG-1336: Add NUL Byte To CharUtils. Thanks to Beluga Behr.
+o LANG-1304: Add method in StringUtils to determine if string contains both
+ mixed cased characters. Thanks to Andy Klimczak.
+o LANG-1325: Increase test coverage of ToStringBuilder class to 100%.
+ Thanks to Arshad Basha.
+o LANG-1307: Add a method in StringUtils to extract only digits out of input
+ string. Thanks to Arshad Basha.
+o LANG-1256: Add JMH maven dependencies. Thanks to C0rWin.
+o LANG-1167: Add null filter to ReflectionToStringBuilder.
+ Thanks to Mark Dacek.
+o LANG-1299: Add method for converting string to an array of code points.
+o LANG-660: Add methods to insert arrays into arrays at an index.
+o LANG-1034: Add support for recursive comparison to
+ EqualsBuilder#reflectionEquals. Thanks to Yathos UG.
+o LANG-1067: Add a reflection-based variant of DiffBuilder.
+o LANG-740: Implementation of a Memoizer. Thanks to James Sawle.
+o LANG-1258: Add ArrayUtils#toStringArray method.
+ Thanks to IG, Grzegorz Rożniecki.
+o LANG-1160: StringUtils#abbreviate should support 'custom ellipses' parameter.
+o LANG-1293: Add StringUtils#isAllEmpty and #isAllBlank methods.
+ Thanks to Pierre Templier, Martin Tarjanyi.
+o LANG-1313: Add ArchUtils - An utility class for the "os.arch" system property.
+ Thanks to Tomschi.
+o LANG-1272: Add shuffle methods to ArrayUtils.
+o LANG-1317: Add MethodUtils#findAnnotation and extend
+ MethodUtils#getMethodsWithAnnotation for non-public, super-class
+ and interface methods. Thanks to Yasser Zamani.
+o LANG-1331: Add ImmutablePair.nullPair().
+o LANG-1332: Add ImmutableTriple.nullTriple().
+
+FIXED BUGS
+==========
+
+o LANG-1337: Fix test failures in IBM JDK 8 for ToStringBuilderTest.
+o LANG-1319: MultilineRecursiveToStringStyle StackOverflowError when object is
+ an array.
+o LANG-1320: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code followed by variant.
+o LANG-1300: Clarify or improve behavior of int-based indexOf methods in
+ StringUtils. Thanks to Mark Dacek.
+o LANG-1286: RandomStringUtils random method can overflow and return characters
+ outside of specified range.
+o LANG-1292: WordUtils.wrap throws StringIndexOutOfBoundsException.
+o LANG-1287: RandomStringUtils#random can enter infinite loop if end parameter
+ is to small. Thanks to Ivan Morozov.
+o LANG-1285: NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to Francesco Chicchiriccò.
+o LANG-1281: Javadoc of StringUtils.ordinalIndexOf is contradictory.
+ Thanks to Andreas Lundblad.
+o LANG-1188: StringUtils#join(T...): warning: [unchecked] Possible heap
+ pollution from parameterized vararg type T.
+o LANG-1144: Multiple calls of
+ org.apache.commons.lang3.concurrent.LazyInitializer.initialize()
+ are possible. Thanks to Waldemar Maier, Gary Gregory.
+o LANG-1276: StrBuilder#replaceAll ArrayIndexOutOfBoundsException.
+ Thanks to Andy Klimczak.
+o LANG-1278: BooleanUtils javadoc issues. Thanks to Duke Yin.
+o LANG-1070: ArrayUtils#add confusing example in javadoc.
+ Thanks to Paul Pogonyshev.
+o LANG-1271: StringUtils#isAnyEmpty and #isAnyBlank should return false for an
+ empty array. Thanks to Pierre Templier.
+o LANG-1155: Add StringUtils#unwrap. Thanks to Saif Asif, Thiago Andrade.
+o LANG-1311: TypeUtils.toString() doesn't handle primitive and Object arrays
+ correctly. Thanks to Aaron Digulla.
+o LANG-1312: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code.
+o LANG-1265: Build failures when building with Java 9 EA.
+o LANG-1314: javadoc creation broken with Java 8. Thanks to Allon Murienik.
+o LANG-1310: MethodUtils.invokeMethod throws ArrayStoreException if using
+ varargs arguments and smaller types than the method defines.
+ Thanks to Don Jeba.
+
+CHANGES
+=======
+
+o LANG-1338: Add Automatic-Module-Name MANIFEST entry for Java 9
+ compatibility.
+o LANG-1334: Deprecate CharEncoding in favour of
+ java.nio.charset.StandardCharsets.
+o LANG-1110: Implement HashSetvBitSetTest using JMH.
+ Thanks to Bruno P. Kinoshita.
+o LANG-1290: Increase test coverage of org.apache.commons.lang3.ArrayUtils.
+ Thanks to Andrii Abramov.
+o LANG-1274: StrSubstitutor should state its thread safety.
+o LANG-1277: StringUtils#getLevenshteinDistance reduce memory consumption.
+ Thanks to yufcuy.
+o LANG-1279: Update Java requirement from Java 6 to 7.
+o LANG-1143: StringUtils should use toXxxxCase(int) rather than
+ toXxxxCase(char). Thanks to sebb.
+o LANG-1297: Add SystemUtils.getHostName() API.
+o LANG-1301: Moving apache-rat-plugin configuration into pluginManagement.
+ Thanks to Karl Heinz Marbaise.
+o LANG-1316: Deprecate classes/methods moved to commons-text.
+
+=============================================================================
+
+ Release Notes for version 3.5
+
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o Added Java 9 detection to org.apache.commons.lang3.SystemUtils.
+o Support for shifting and swapping elements in
+ org.apache.commons.lang3.ArrayUtils.
+o New methods for generating random strings from different character classes
+ including alphabetic, alpha-numeric and ASCII added to
+ org.apache.commons.lang3.RandomStringUtils.
+o Numerous extensions to org.apache.commons.lang3.StringUtils including
+ null safe compare variants, more remove and replace variants, rotation and
+ truncation.
+o Added org.apache.commons.lang3.ThreadUtils - a utility class to work with
+ instances of java.lang.Thread and java.lang.ThreadGroup.
+o Added annotations @EqualsExclude, @HashCodeExclude and @ToStringExclude to
+ mark fields which should be ignored by the reflective builders in the
+ org.apache.commons.lang3.builder package.
+o Support for various modify and retrieve value use cases added to the classes
+ in org.apache.commons.lang3.mutable.
+
+COMPATIBILITY
+=============
+
+Apache Commons Lang 3.5 is binary compatible with the 3.4 release. Users
+should not experience any problems when upgrading from 3.4 to 3.5.
+
+There has been an addition to the org.apache.commons.lang3.time.DatePrinter
+interface:
+
+o Added method 'public boolean parse(java.lang.String, java.text.ParsePosition,
+ java.util.Calendar)'
+o Added method 'public java.lang.Appendable format(long, java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Date,
+ java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Calendar,
+ java.lang.Appendable)'
+
+For this reason 3.5 is not strictly source compatible to 3.4. Since the
+DatePrinter interface is not meant to be implemented by clients, this
+change it not considered to cause any problems.
+
+JAVA 9 SUPPORT
+==============
+
+Java 9 introduces a new version-string scheme. Details of this new scheme are
+documented in JEP-223 (https://openjdk.org/jeps/223). In order to support
+JEP-223 two classes had to be changed:
+
+o org.apache.commons.lang3.JavaVersion
+ deprecated enum constant JAVA_1_9
+ introduced enum constant JAVA_9
+
+o org.apache.commons.lang3.SystemUtils
+ deprecated constant IS_JAVA_1_9
+ introduced constant IS_JAVA_9
+
+For more information see LANG-1197
+(https://issues.apache.org/jira/browse/LANG-1197). All other APIs are expected
+to work with Java 9.
+
+BUILDING ON JAVA 9
+==================
+
+Java 8 introduced the Unicode Consortium's Common Locale Data Repository as
+alternative source for locale data. Java 9 will use the CLDR provider as
+default provider for locale data (see https://openjdk.org/jeps/252). This
+causes an number of locale-sensitive test in Commons Lang to fail. In order
+to build Commons Lang 3.5 on Java 9, the locale provider has to be set to
+'JRE':
+
+ mvn -Djava.locale.providers=JRE clean install
+
+We are currently investigating ways to support building on Java 9 without
+further configuration. For more information see:
+https://issues.apache.org/jira/browse/LANG-1265
+
+
+NEW FEATURES
+==============
+
+o LANG-1275: Added a tryAcquire() method to TimedSemaphore.
+o LANG-1255: Add DateUtils.toCalendar(Date, TimeZone). Thanks to Kaiyuan Wang.
+o LANG-1023: Add WordUtils.wrap overload with customizable breakable character.
+ Thanks to Marko Bekhta.
+o LANG-787: Add method removeIgnoreCase(String, String) to StringUtils. Thanks
+ to Gokul Nanthakumar C.
+o LANG-1224: Extend RandomStringUtils with methods that generate strings
+ between a min and max length. Thanks to Caleb Cushing.
+o LANG-1257: Add APIs StringUtils.wrapIfMissing(String, char|String). Thanks to
+ Gary Gregory.
+o LANG-1253: Add RandomUtils#nextBoolean() method. Thanks to adilek.
+o LANG-1085: Add a circuit breaker implementation. Thanks to Oliver Heger and
+ Bruno P. Kinoshita.
+o LANG-1013: Add StringUtils.truncate(). Thanks to Thiago Andrade.
+o LANG-1195: Enhance MethodUtils to allow invocation of private methods. Thanks
+ to Derek C. Ashmore.
+o LANG-1189: Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/
+ decrementAndGet/addAndGet in Mutable* classes. Thanks to
+ Haiyang Li and Matthew Bartenschlag.
+o LANG-1225: Add RandomStringUtils#randomGraph and #randomPrint which match
+ corresponding regular expression class. Thanks to Caleb Cushing.
+o LANG-1223: Add StopWatch#getTime(TimeUnit). Thanks to Nick Manley.
+o LANG-781: Add methods to ObjectUtils class to check for null elements in the
+ array. Thanks to Krzysztof Wolny.
+o LANG-1228: Prefer Throwable.getCause() in ExceptionUtils.getCause().
+ Thanks to Brad Hess.
+o LANG-1233: DiffBuilder add method to allow appending from a DiffResult.
+ Thanks to Nick Manley.
+o LANG-1168: Add SystemUtils.IS_OS_WINDOWS_10 property.
+ Thanks to Pascal Schumacher.
+o LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and
+ MethodUtils. Thanks to Jim Lloyd and Joe Ferner.
+o LANG-1134: Add methods to check numbers against NaN and infinite to
+ Validate. Thanks to Alan Smithee.
+o LANG-1220: Add tests for missed branches in DateUtils.
+ Thanks to Casey Scarborough.
+o LANG-1146: z/OS identification in SystemUtils.
+ Thanks to Gabor Liptak.
+o LANG-1192: FastDateFormat support of the week-year component (uppercase 'Y').
+ Thanks to Dominik Stadler.
+o LANG-1169: Add StringUtils methods to compare a string to multiple strings.
+ Thanks to Rafal Glowinski, Robert Parr and Arman Sharif.
+o LANG-1185: Add remove by regular expression methods in StringUtils.
+o LANG-1139: Add replace by regular expression methods in StringUtils.
+o LANG-1171: Add compare methods in StringUtils.
+o LANG-1174: Add sugar to RandomUtils. Thanks to Punkratz312.
+o LANG-1154: FastDateFormat APIs that use a StringBuilder. Thanks to
+ Gary Gregory.
+o LANG-1149: Ability to throw checked exceptions without declaring them. Thanks
+ to Gregory Zak.
+o LANG-1153: Implement ParsePosition api for FastDateParser.
+o LANG-1137: Add check for duplicate event listener in EventListenerSupport.
+ Thanks to Matthew Aguirre.
+o LANG-1135: Add method containsAllWords to WordUtils. Thanks to
+ Eduardo Martins.
+o LANG-1132: ReflectionToStringBuilder doesn't throw IllegalArgumentException
+ when the constructor's object param is null. Thanks to Jack Tan.
+o LANG-701: StringUtils join with var args. Thanks to James Sawle.
+o LANG-1105: Add ThreadUtils - A utility class which provides helper methods
+ related to java.lang.Thread Issue: LANG-1105. Thanks to
+ Hendrik Saly.
+o LANG-1031: Add annotations to exclude fields from ReflectionEqualsBuilder,
+ ReflectionToStringBuilder and ReflectionHashCodeBuilder. Thanks
+ to Felipe Adorno.
+o LANG-1127: Use JUnit rules to set and reset the default Locale and TimeZone.
+o LANG-1119: Add rotate(string, int) method to StringUtils. Thanks to
+ Loic Guibert.
+o LANG-1099: Add swap and shift operations for arrays to ArrayUtils. Thanks to
+ Adrian Ber.
+o LANG-1050: Change nullToEmpty methods to generics. Thanks to James Sawle.
+o LANG-1074: Add a method to ArrayUtils for removing all occurrences of a given
+ element Issue: LANG-1074. Thanks to Haiyang Li.
+
+FIXED BUGS
+============
+
+o LANG-1261: ArrayUtils.contains returns false for instances of subtypes.
+o LANG-1252: Rename NumberUtils.isNumber, isCreatable to better reflect
+ createNumber. Also, accommodated for "+" symbol as prefix in
+ isCreatable and isNumber. Thanks to Rob Tompkins.
+o LANG-1230: Remove unnecessary synchronization from registry lookup in
+ EqualsBuilder and HashCodeBuilder. Thanks to Philippe Marschall.
+o LANG-1214: Handle "void" in ClassUtils.getClass(). Thanks to Henry Tung.
+o LANG-1250: SerializationUtils#deserialize has unnecessary code and a comment
+ for that. Thanks to Glease Wang.
+o LANG-1190: TypeUtils.isAssignable throws NullPointerException when fromType
+ has type variables and toType generic superclass specifies type
+ variable. Thanks to Pascal Schumacher.
+o LANG-1226: StringUtils#normalizeSpace does not trim the string anymore.
+ Thanks to Pascal Schumacher.
+o LANG-1251: SerializationUtils.ClassLoaderAwareObjectInputStream should use
+ static initializer to initialize primitiveTypes map. Thanks to
+ Takuya Ueshin.
+o LANG-1248: FastDatePrinter Memory allocation regression. Thanks to
+ Benoit Wiart.
+o LANG-1018: Fix precision loss on NumberUtils.createNumber(String). Thanks to
+ Nick Manley.
+o LANG-1199: Fix implementation of StringUtils.getJaroWinklerDistance(). Thanks
+ to M. Steiger.
+o LANG-1244: Fix dead links in StringUtils.getLevenshteinDistance() javadoc.
+ Thanks to jjbankert.
+o LANG-1242: "\u2284":"?" mapping missing from
+ EntityArrays#HTML40_EXTENDED_ESCAPE. Thanks to Neal Stewart.
+o LANG-901: StringUtils#startsWithAny/endsWithAny is case sensitive -
+ documented as case insensitive. Thanks to Matthew Bartenschlag.
+o LANG-1232: DiffBuilder: Add null check on fieldName when appending Object or
+ Object[]. Thanks to Nick Manley.
+o LANG-1178: ArrayUtils.removeAll(Object array, int... indices) should do the
+ clone, not its callers. Thanks to Henri Yandell.
+o LANG-1120: StringUtils.stripAccents should remove accents from "Ł" and "ł".
+ Thanks to kaching88.
+o LANG-1205: NumberUtils.createNumber() behaves inconsistently with
+ NumberUtils.isNumber(). Thanks to pbrose.
+o LANG-1222: Fix for incorrect comment on StringUtils.containsIgnoreCase
+ method. Thanks to Adam J.
+o LANG-1221: Fix typo on appendIfMissing javadoc. Thanks to Pierre Templier.
+o LANG-1202: parseDateStrictly doesn't pass specified locale. Thanks to
+ Markus Jelsma.
+o LANG-1219: FastDateFormat doesn't respect summer daylight in some localized
+ strings. Thanks to Jarek.
+o LANG-1175: Remove Ant-based build.
+o LANG-1194: Limit max heap memory for consistent Travis CI build.
+o LANG-1186: Fix NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to NickManley.
+o LANG-1193: ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1
+ (correct answer should be 0); revert fix for LANG-1077. Thanks to
+ Qin Li.
+o LANG-1002: Several predefined ISO FastDateFormats in DateFormatUtils are
+ incorrect. Thanks to Michael Osipov.
+o LANG-1152: StringIndexOutOfBoundsException or field over-write for large year
+ fields in FastDateParser. Thanks to Pas Filip.
+o LANG-1141: StrLookup.systemPropertiesLookup() no longer reacts on changes on
+ system properties.
+o LANG-1147: EnumUtils *BitVector issue with more than 32 values Enum. Thanks
+ to Loic Guibert.
+o LANG-1059: Capitalize javadoc is incorrect. Thanks to Colin Casey.
+o LANG-1122: Inconsistent behavior of swap for malformed inputs. Thanks to
+ Adrian Ber.
+o LANG-1130: Fix critical issues reported by SonarQube.
+o LANG-1131: StrBuilder.equals(StrBuilder) doesn't check for null inputs.
+o LANG-1128: JsonToStringStyle doesn't handle chars and objects correctly.
+ Thanks to Jack Tan.
+o LANG-1126: DateFormatUtilsTest.testSMTP depends on the default Locale.
+o LANG-1123: Unit test FastDatePrinterTimeZonesTest needs a timezone set.
+ Thanks to Christian P. Momon.
+o LANG-916: DateFormatUtils.format does not correctly change Calendar
+ TimeZone in certain situations. Thanks to Christian P. Momon.
+o LANG-1116: DateUtilsTest.testLang530 fails for some timezones. Thanks to
+ Aaron Sheldon.
+o LANG-1114: TypeUtils.ParameterizedType#equals doesn't work with wildcard
+ types. Thanks to Andy Coates.
+o LANG-1118: StringUtils.repeat('z', -1) throws NegativeArraySizeException.
+ Thanks to Loic Guibert.
+o LANG-1111: Fix FindBugs warnings in DurationFormatUtils.
+o LANG-1162: StringUtils#equals fails with Index OOBE on non-Strings with
+ identical leading prefix..
+o LANG-1163: There are no tests for CharSequenceUtils.regionMatches.
+o LANG-1200: Fix Javadoc of StringUtils.ordinalIndexOf. Thanks to BarkZhang.
+o LANG-1191: Incorrect Javadoc
+ StringUtils.containsAny(CharSequence, CharSequence...). Thanks to
+ qed, Brent Worden and Gary Gregory.
+
+CHANGES
+=========
+o LANG-1197: Prepare Java 9 detection.
+o LANG-1262: CompareToBuilder.append(Object, Object, Comparator) method is too
+ big to be inlined. Thanks to Ruslan Cheremin.
+o LANG-1259: Javadoc for ArrayUtils.isNotEmpty() is slightly misleading. Thanks
+ to Dominik Stadler.
+o LANG-1247: FastDatePrinter generates extra Date objects. Thanks to
+ Benoit Wiart.
+o LANG-1229: HashCodeBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1243: Simplify ArrayUtils removeElements by using new decrementAndGet()
+ method.
+o LANG-1240: Optimize BitField constructor implementation. Thanks to zhanhb.
+o LANG-1206: Improve CharSetUtils.squeeze() performance. Thanks to
+ Mohammed Alfallaj.
+o LANG-1176: Improve ArrayUtils removeElements time complexity to O(n). Thanks
+ to Jeffery Yuan.
+o LANG-1234: getLevenshteinDistance with a threshold: optimize implementation
+ if the strings lengths differ more than the threshold. Thanks to
+ Jonatan Jönsson.
+o LANG-1151: Performance improvements for NumberUtils.isParsable. Thanks to
+ Juan Pablo Santos Rodríguez.
+o LANG-1218: EqualsBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1210: StringUtils#startsWithAny has error in Javadoc. Thanks to
+ Matthias Niehoff.
+o LANG-1208: StrSubstitutor can preserve escapes. Thanks to Samuel Karp.
+o LANG-1182: Clarify Javadoc of StringUtils.containsAny(). Thanks to
+ Larry West and Pascal Schumacher.
+o LANG-1183: Making replacePattern/removePattern methods null safe in
+ StringUtils.
+o LANG-1057: Replace StringBuilder with String concatenation for better
+ optimization. Thanks to Otávio Santana.
+o LANG-1075: Deprecate SystemUtils.FILE_SEPARATOR and
+ SystemUtils.PATH_SEPARATOR.
+o LANG-979: TypeUtils.parameterizeWithOwner - wrong format descriptor for
+ "invalid number of type parameters". Thanks to Bruno P. Kinoshita.
+o LANG-1112: MultilineRecursiveToStringStyle largely unusable due to being
+ package-private.
+o LANG-1058: StringUtils.uncapitalize performance improvement. Thanks to
+ Leo Wang.
+o LANG-1069: CharSet.getInstance documentation does not clearly explain how
+ to include negation character in set. Thanks to Arno Noordover.
+o LANG-1107: Fix parsing edge cases in FastDateParser.
+o LANG-1273: Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils. Thanks
+ to Jake Wang.
+
+=============================================================================
+
+ Release Notes for version 3.4
+
+
+COMPATIBILITY
+=============
+
+Commons Lang 3.4 is fully binary compatible to the last release and can
+therefore be used as a drop in replacement for 3.3.2. Note that the value of
+org.apache.commons.lang3.time.DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN
+has changed, which may affect clients using the constant. Furthermore the
+constant is used internally in
+o DurationFormatUtils.formatDurationISO(long)
+o DurationFormatUtils.formatPeriodISO(long, long)
+
+For more information see https://issues.apache.org/jira/browse/LANG-1000.
+
+NEW FEATURES
+==============
+
+o LANG-821: Support OS X versions in SystemUtils. Thanks to Timo Kockert.
+o LANG-1103: Add SystemUtils.IS_JAVA_1_9
+o LANG-1093: Add ClassUtils.getAbbreviatedName(). Thanks to Fabian Lange.
+o LANG-1082: Add option to disable the "objectsTriviallyEqual" test in
+ DiffBuilder. Thanks to Jonathan Baker.
+o LANG-1015: Add JsonToStringStyle implementation to ToStringStyle. Thanks to
+ Thiago Andrade.
+o LANG-1080: Add NoClassNameToStringStyle implementation of ToStringStyle.
+ Thanks to Innokenty Shuvalov.
+o LANG-883: Add StringUtils.containsAny(CharSequence, CharSequence...) method.
+ Thanks to Daniel Stewart.
+o LANG-1052: Multiline recursive to string style. Thanks to Jan Matèrne.
+o LANG-536: Add isSorted() to ArrayUtils. Thanks to James Sawle.
+o LANG-1033: Add StringUtils.countMatches(CharSequence, char)
+o LANG-1021: Provide methods to retrieve all fields/methods annotated with a
+ specific type. Thanks to Alexander Müller.
+o LANG-1016: NumberUtils#isParsable method(s). Thanks to
+ Juan Pablo Santos Rodríguez.
+o LANG-999: Add fuzzy String matching logic to StringUtils. Thanks to
+ Ben Ripkens.
+o LANG-994: Add zero copy read method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-993: Add zero copy write method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-1044: Add method MethodUtils.invokeExactMethod(Object, String)
+o LANG-1045: Add method MethodUtils.invokeMethod(Object, String)
+
+FIXED BUGS
+============
+
+o LANG-794: SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect. Thanks to
+ Timo Kockert.
+o LANG-1104: Parse test fails for TimeZone America/Sao_Paulo
+o LANG-948: Exception while using ExtendedMessageFormat and escaping braces.
+ Thanks to Andrey Khobnya.
+o LANG-1092: Wrong formatting of time zones with daylight saving time in
+ FastDatePrinter
+o LANG-1090: FastDateParser does not set error indication in ParsePosition
+o LANG-1089: FastDateParser does not handle excess hours as per
+ SimpleDateFormat
+o LANG-1061: FastDateParser error - timezones not handled correctly. Thanks to
+ dmeneses.
+o LANG-1087: NumberUtils#createNumber() returns positive BigDecimal when
+ negative Float is expected. Thanks to Renat Zhilkibaev.
+o LANG-1081: DiffBuilder.append(String, Object left, Object right) does not do
+ a left.equals(right) check. Thanks to Jonathan Baker.
+o LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently.
+ Thanks to Jonathan Baker.
+o LANG-1083: Add (T) casts to get unit tests to pass in old JDK. Thanks to
+ Jonathan Baker.
+o LANG-1073: Read wrong component type of array in add in ArrayUtils.
+ Thanks to haiyang li.
+o LANG-1077: StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils.
+ Thanks to haiyang li.
+o LANG-1072: Duplicated "0x" check in createBigInteger in NumberUtils. Thanks
+ to haiyang li.
+o LANG-1064: StringUtils.abbreviate description doesn't agree with the
+ examples. Thanks to B.J. Herbison.
+o LANG-1041: Fix MethodUtilsTest so it does not depend on JDK method ordering.
+ Thanks to Alexandre Bartel.
+o LANG-1000: ParseException when trying to parse UTC dates with Z as zone
+ designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
+o LANG-1035: Javadoc for EqualsBuilder.reflectionEquals() is unclear
+o LANG-1001: ISO 8601 misspelled throughout the Javadocs. Thanks to
+ Michael Osipov.
+o LANG-1088: FastDateParser should be case insensitive
+o LANG-995: Fix bug with stripping spaces on last line in WordUtils.wrap().
+ Thanks to Andrey Khobnya.
+
+CHANGES
+=========
+
+o LANG-1102: Make logic for comparing OS versions in SystemUtils smarter
+o LANG-1091: Shutdown thread pools in test cases. Thanks to Fabian Lange.
+o LANG-1101: FastDateParser and FastDatePrinter support 'X' format
+o LANG-1100: Avoid memory allocation when using date formatting to StringBuffer.
+ Thanks to mbracher.
+o LANG-935: Possible performance improvement on string escape functions.
+ Thanks to Fabian Lange, Thomas Neidhart.
+o LANG-1098: Avoid String allocation in StrBuilder.append(CharSequence). Thanks
+ to Mikhail Mazurskiy, Fabian Lange.
+o LANG-1098: Update maven-checkstyle-plugin to 2.14. Thanks to Micha? Kordas.
+o LANG-1097: Update org.easymock:easymock to 3.3.1. Thanks to Micha? Kordas.
+o LANG-1096: Update maven-pmd-plugin to 3.4. Thanks to Micha? Kordas.
+o LANG-1095: Update maven-antrun-plugin to 1.8. Thanks to Micha? Kordas.
+o LANG-877: Performance improvements for StringEscapeUtils. Thanks to
+ Fabian Lange.
+o LANG-1071: Fix wrong examples in Javadoc of
+ StringUtils.replaceEachRepeatedly(...),
+ StringUtils.replaceEach(...) Thanks to Arno Noordover.
+o LANG-827: CompareToBuilder's doc doesn't specify precedence of fields it
+ uses in performing comparisons
+o LANG-1020: Improve performance of normalize space. Thanks to Libor Ondrusek.
+o LANG-1027: org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should
+ return true by default
+o LANG-1026: Bring static method references in StringUtils to consistent style.
+ Thanks to Alex Yursha.
+o LANG-1017: Use non-ASCII digits in Javadoc examples for
+ StringUtils.isNumeric. Thanks to Christoph Schneegans.
+o LANG-1008: Change min/max methods in NumberUtils/IEEE754rUtils from array
+ input parameters to varargs. Thanks to Thiago Andrade.
+o LANG-1006: Add wrap (with String or char) to StringUtils. Thanks to
+ Thiago Andrade.
+o LANG-1005: Extend DurationFormatUtils#formatDurationISO default pattern to
+ match #formatDurationHMS. Thanks to Michael Osipov.
+o LANG-1007: Fixing NumberUtils JAVADoc comments for max methods. Thanks to
+ Thiago Andrade.
+o LANG-731: Better Javadoc for BitField class
+o LANG-1004: DurationFormatUtils#formatDurationHMS implementation does not
+ correspond to Javadoc and vice versa. Thanks to Michael Osipov.
+o LANG-1003: DurationFormatUtils are not able to handle negative
+ durations/periods
+o LANG-998: Javadoc is not clear on preferred pattern to instantiate
+ FastDateParser / FastDatePrinter
+
+=============================================================================
+
+ Release Notes for version 3.3.2
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+=============================================================================
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+=============================================================================
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Götz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernández.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+=============================================================================
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+=============================================================================
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+=============================================================================
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+=============================================================================
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12 hour clock and not
+ 24 hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.7.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.7.txt
new file mode 100644
index 000000000..d71f1e804
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.7.txt
@@ -0,0 +1,1176 @@
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+ Apache Commons Lang
+ Version 3.7
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.7 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1355: TimeZone.getTimeZone() in FastDateParser causes resource contention (PR #296.) Thanks to Chas Honton.
+o LANG-1360: Add methods to ObjectUtils to get various forms of class names in a null-safe manner Thanks to Gary Gregory.
+
+Fixed Bugs:
+o LANG-1362: Fix tests DateUtilsTest for Java 9 with en_GB locale Thanks to Stephen Colebourne.
+o LANG-1365: Fix NullPointerException in isJavaVersionAtLeast on Java 10, add SystemUtils.IS_JAVA_10, add JavaVersion.JAVA_10 Thanks to Gary Gregory.
+o LANG-1348: StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf Thanks to mbusso.
+o LANG-1350: ConstructorUtils.invokeConstructor(Class, Object...) regression Thanks to Brett Kail.
+o LANG-1349: EqualsBuilder#isRegistered: swappedPair construction bug Thanks to Naman Nigam.
+o LANG-1357: org.apache.commons.lang3.time.FastDateParser should use toUpperCase(Locale) Thanks to BruceKuiLiu.
+
+Changes:
+o LANG-1358: Improve StringUtils#replace throughput Thanks to Stephane Landelle.
+o LANG-1346: Remove deprecation from RandomStringUtils
+o LANG-1361: ExceptionUtils.getThrowableList() is using deprecated ExceptionUtils.getCause() Thanks to Ana.
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.6
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.6 version of
+Apache Commons Lang as well as a history all changes in the Commons Lang 3.x
+release line. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.6 at
+least requires Java 7.0. Note that this has changed from Commons Lang 3.5, which
+only required Java 1.6.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o The class org.apache.commons.lang3.concurrent.Memoizer is an implementation
+ of the Memoizer pattern as shown in
+ Goetz, Brian et al. (2006) - Java Concurrency in Practice, p. 108.
+o The class org.apache.commons.lang3.ArchUtils has been added. ArchUtils is
+ a utility class for the "os.arch" system property.
+
+DEPRECATIONS
+============
+
+The Apache Commons Community has recently set up the Commons Text component
+as a home for algorithms working on strings. For this reason most of the string
+focused functionality in Commons Lang has been deprecated and moved to
+Commons Text. This includes:
+
+o All classes in the org.apache.commons.lang3.text and the
+ org.apache.commons.lang3.text.translate packages
+o org.apache.commons.lang3.StringEscapeUtils
+o org.apache.commons.lang3.RandomStringUtils
+o The methods org.apache.commons.lang3.StringUtils.getJaroWinklerDistance and
+ org.apache.commons.lang3.StringUtils.getLevenshteinDistance
+
+For more information see the Commons Text website:
+
+ https://commons.apache.org/text
+
+The class org.apache.commons.lang3.CharEncoding has been deprecated in favor of
+java.nio.charset.StandardCharsets.
+
+The following methods have been deprecated in
+org.apache.commons.lang3.ArrayUtils in favor of the corresponding insert
+methods. Note that the handling for null inputs differs between add and insert.
+
+o add(boolean[], int, boolean) -> insert(int, boolean[], boolean...)
+o add(byte[], int, boolean) -> insert(int, byte[], byte...)
+o add(char[], int, boolean) -> insert(int, char[], char...)
+o add(double[], int, boolean) -> insert(int, double[], double...)
+o add(float[], int, boolean) -> insert(int, float[], float...)
+o add(int[], int, boolean) -> insert(int, int[], int...)
+o add(long[], int, boolean) -> insert(int, long[], long...)
+o add(short[], int, boolean) -> insert(int, short[], short...)
+o add(T[], int, boolean) -> insert(int, T[], T...)
+
+
+COMPATIBILITY WITH JAVA 9
+==================
+
+The MANIFEST.MF now contains an additional entry:
+
+ Automatic-Module-Name: org.apache.commons.lang3
+
+This should make it possible to use Commons Lang 3.6 as a module in the Java 9
+module system. For more information see the corresponding issue and the
+referenced mailing list discussions:
+
+ https://issues.apache.org/jira/browse/LANG-1338
+
+The build problems present in the 3.5 release have been resolved. Building
+Commons Lang 3.6 should work out of the box with the latest Java 9 EA build.
+Please report any Java 9 related issues at:
+
+ https://issues.apache.org/jira/browse/LANG
+
+NEW FEATURES
+============
+
+o LANG-1336: Add NUL Byte To CharUtils. Thanks to Beluga Behr.
+o LANG-1304: Add method in StringUtils to determine if string contains both
+ mixed cased characters. Thanks to Andy Klimczak.
+o LANG-1325: Increase test coverage of ToStringBuilder class to 100%.
+ Thanks to Arshad Basha.
+o LANG-1307: Add a method in StringUtils to extract only digits out of input
+ string. Thanks to Arshad Basha.
+o LANG-1256: Add JMH maven dependencies. Thanks to C0rWin.
+o LANG-1167: Add null filter to ReflectionToStringBuilder.
+ Thanks to Mark Dacek.
+o LANG-1299: Add method for converting string to an array of code points.
+o LANG-660: Add methods to insert arrays into arrays at an index.
+o LANG-1034: Add support for recursive comparison to
+ EqualsBuilder#reflectionEquals. Thanks to Yathos UG.
+o LANG-1067: Add a reflection-based variant of DiffBuilder.
+o LANG-740: Implementation of a Memoizer. Thanks to James Sawle.
+o LANG-1258: Add ArrayUtils#toStringArray method.
+ Thanks to IG, Grzegorz Rożniecki.
+o LANG-1160: StringUtils#abbreviate should support 'custom ellipses' parameter.
+o LANG-1293: Add StringUtils#isAllEmpty and #isAllBlank methods.
+ Thanks to Pierre Templier, Martin Tarjanyi.
+o LANG-1313: Add ArchUtils - An utility class for the "os.arch" system property.
+ Thanks to Tomschi.
+o LANG-1272: Add shuffle methods to ArrayUtils.
+o LANG-1317: Add MethodUtils#findAnnotation and extend
+ MethodUtils#getMethodsWithAnnotation for non-public, super-class
+ and interface methods. Thanks to Yasser Zamani.
+o LANG-1331: Add ImmutablePair.nullPair().
+o LANG-1332: Add ImmutableTriple.nullTriple().
+
+FIXED BUGS
+==========
+
+o LANG-1337: Fix test failures in IBM JDK 8 for ToStringBuilderTest.
+o LANG-1319: MultilineRecursiveToStringStyle StackOverflowError when object is
+ an array.
+o LANG-1320: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code followed by variant.
+o LANG-1300: Clarify or improve behavior of int-based indexOf methods in
+ StringUtils. Thanks to Mark Dacek.
+o LANG-1286: RandomStringUtils random method can overflow and return characters
+ outside of specified range.
+o LANG-1292: WordUtils.wrap throws StringIndexOutOfBoundsException.
+o LANG-1287: RandomStringUtils#random can enter infinite loop if end parameter
+ is to small. Thanks to Ivan Morozov.
+o LANG-1285: NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to Francesco Chicchiriccò.
+o LANG-1281: Javadoc of StringUtils.ordinalIndexOf is contradictory.
+ Thanks to Andreas Lundblad.
+o LANG-1188: StringUtils#join(T...): warning: [unchecked] Possible heap
+ pollution from parameterized vararg type T.
+o LANG-1144: Multiple calls of
+ org.apache.commons.lang3.concurrent.LazyInitializer.initialize()
+ are possible. Thanks to Waldemar Maier, Gary Gregory.
+o LANG-1276: StrBuilder#replaceAll ArrayIndexOutOfBoundsException.
+ Thanks to Andy Klimczak.
+o LANG-1278: BooleanUtils javadoc issues. Thanks to Duke Yin.
+o LANG-1070: ArrayUtils#add confusing example in javadoc.
+ Thanks to Paul Pogonyshev.
+o LANG-1271: StringUtils#isAnyEmpty and #isAnyBlank should return false for an
+ empty array. Thanks to Pierre Templier.
+o LANG-1155: Add StringUtils#unwrap. Thanks to Saif Asif, Thiago Andrade.
+o LANG-1311: TypeUtils.toString() doesn't handle primitive and Object arrays
+ correctly. Thanks to Aaron Digulla.
+o LANG-1312: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code.
+o LANG-1265: Build failures when building with Java 9 EA.
+o LANG-1314: javadoc creation broken with Java 8. Thanks to Allon Murienik.
+o LANG-1310: MethodUtils.invokeMethod throws ArrayStoreException if using
+ varargs arguments and smaller types than the method defines.
+ Thanks to Don Jeba.
+
+CHANGES
+=======
+
+o LANG-1338: Add Automatic-Module-Name MANIFEST entry for Java 9
+ compatibility.
+o LANG-1334: Deprecate CharEncoding in favour of
+ java.nio.charset.StandardCharsets.
+o LANG-1110: Implement HashSetvBitSetTest using JMH.
+ Thanks to Bruno P. Kinoshita.
+o LANG-1290: Increase test coverage of org.apache.commons.lang3.ArrayUtils.
+ Thanks to Andrii Abramov.
+o LANG-1274: StrSubstitutor should state its thread safety.
+o LANG-1277: StringUtils#getLevenshteinDistance reduce memory consumption.
+ Thanks to yufcuy.
+o LANG-1279: Update Java requirement from Java 6 to 7.
+o LANG-1143: StringUtils should use toXxxxCase(int) rather than
+ toXxxxCase(char). Thanks to sebb.
+o LANG-1297: Add SystemUtils.getHostName() API.
+o LANG-1301: Moving apache-rat-plugin configuration into pluginManagement.
+ Thanks to Karl Heinz Marbaise.
+o LANG-1316: Deprecate classes/methods moved to commons-text.
+
+=============================================================================
+
+ Release Notes for version 3.5
+
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o Added Java 9 detection to org.apache.commons.lang3.SystemUtils.
+o Support for shifting and swapping elements in
+ org.apache.commons.lang3.ArrayUtils.
+o New methods for generating random strings from different character classes
+ including alphabetic, alpha-numeric and ASCII added to
+ org.apache.commons.lang3.RandomStringUtils.
+o Numerous extensions to org.apache.commons.lang3.StringUtils including
+ null safe compare variants, more remove and replace variants, rotation and
+ truncation.
+o Added org.apache.commons.lang3.ThreadUtils - a utility class to work with
+ instances of java.lang.Thread and java.lang.ThreadGroup.
+o Added annotations @EqualsExclude, @HashCodeExclude and @ToStringExclude to
+ mark fields which should be ignored by the reflective builders in the
+ org.apache.commons.lang3.builder package.
+o Support for various modify and retrieve value use cases added to the classes
+ in org.apache.commons.lang3.mutable.
+
+COMPATIBILITY
+=============
+
+Apache Commons Lang 3.5 is binary compatible with the 3.4 release. Users
+should not experience any problems when upgrading from 3.4 to 3.5.
+
+There has been an addition to the org.apache.commons.lang3.time.DatePrinter
+interface:
+
+o Added method 'public boolean parse(java.lang.String, java.text.ParsePosition,
+ java.util.Calendar)'
+o Added method 'public java.lang.Appendable format(long, java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Date,
+ java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Calendar,
+ java.lang.Appendable)'
+
+For this reason 3.5 is not strictly source compatible to 3.4. Since the
+DatePrinter interface is not meant to be implemented by clients, this
+change it not considered to cause any problems.
+
+JAVA 9 SUPPORT
+==============
+
+Java 9 introduces a new version-string scheme. Details of this new scheme are
+documented in JEP-223 (https://openjdk.org/jeps/223). In order to support
+JEP-223 two classes had to be changed:
+
+o org.apache.commons.lang3.JavaVersion
+ deprecated enum constant JAVA_1_9
+ introduced enum constant JAVA_9
+
+o org.apache.commons.lang3.SystemUtils
+ deprecated constant IS_JAVA_1_9
+ introduced constant IS_JAVA_9
+
+For more information see LANG-1197
+(https://issues.apache.org/jira/browse/LANG-1197). All other APIs are expected
+to work with Java 9.
+
+BUILDING ON JAVA 9
+==================
+
+Java 8 introduced the Unicode Consortium's Common Locale Data Repository as
+alternative source for locale data. Java 9 will use the CLDR provider as
+default provider for locale data (see https://openjdk.org/jeps/252). This
+causes an number of locale-sensitive test in Commons Lang to fail. In order
+to build Commons Lang 3.5 on Java 9, the locale provider has to be set to
+'JRE':
+
+ mvn -Djava.locale.providers=JRE clean install
+
+We are currently investigating ways to support building on Java 9 without
+further configuration. For more information see:
+https://issues.apache.org/jira/browse/LANG-1265
+
+
+NEW FEATURES
+==============
+
+o LANG-1275: Added a tryAcquire() method to TimedSemaphore.
+o LANG-1255: Add DateUtils.toCalendar(Date, TimeZone). Thanks to Kaiyuan Wang.
+o LANG-1023: Add WordUtils.wrap overload with customizable breakable character.
+ Thanks to Marko Bekhta.
+o LANG-787: Add method removeIgnoreCase(String, String) to StringUtils. Thanks
+ to Gokul Nanthakumar C.
+o LANG-1224: Extend RandomStringUtils with methods that generate strings
+ between a min and max length. Thanks to Caleb Cushing.
+o LANG-1257: Add APIs StringUtils.wrapIfMissing(String, char|String). Thanks to
+ Gary Gregory.
+o LANG-1253: Add RandomUtils#nextBoolean() method. Thanks to adilek.
+o LANG-1085: Add a circuit breaker implementation. Thanks to Oliver Heger and
+ Bruno P. Kinoshita.
+o LANG-1013: Add StringUtils.truncate(). Thanks to Thiago Andrade.
+o LANG-1195: Enhance MethodUtils to allow invocation of private methods. Thanks
+ to Derek C. Ashmore.
+o LANG-1189: Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/
+ decrementAndGet/addAndGet in Mutable* classes. Thanks to
+ Haiyang Li and Matthew Bartenschlag.
+o LANG-1225: Add RandomStringUtils#randomGraph and #randomPrint which match
+ corresponding regular expression class. Thanks to Caleb Cushing.
+o LANG-1223: Add StopWatch#getTime(TimeUnit). Thanks to Nick Manley.
+o LANG-781: Add methods to ObjectUtils class to check for null elements in the
+ array. Thanks to Krzysztof Wolny.
+o LANG-1228: Prefer Throwable.getCause() in ExceptionUtils.getCause().
+ Thanks to Brad Hess.
+o LANG-1233: DiffBuilder add method to allow appending from a DiffResult.
+ Thanks to Nick Manley.
+o LANG-1168: Add SystemUtils.IS_OS_WINDOWS_10 property.
+ Thanks to Pascal Schumacher.
+o LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and
+ MethodUtils. Thanks to Jim Lloyd and Joe Ferner.
+o LANG-1134: Add methods to check numbers against NaN and infinite to
+ Validate. Thanks to Alan Smithee.
+o LANG-1220: Add tests for missed branches in DateUtils.
+ Thanks to Casey Scarborough.
+o LANG-1146: z/OS identification in SystemUtils.
+ Thanks to Gabor Liptak.
+o LANG-1192: FastDateFormat support of the week-year component (uppercase 'Y').
+ Thanks to Dominik Stadler.
+o LANG-1169: Add StringUtils methods to compare a string to multiple strings.
+ Thanks to Rafal Glowinski, Robert Parr and Arman Sharif.
+o LANG-1185: Add remove by regular expression methods in StringUtils.
+o LANG-1139: Add replace by regular expression methods in StringUtils.
+o LANG-1171: Add compare methods in StringUtils.
+o LANG-1174: Add sugar to RandomUtils. Thanks to Punkratz312.
+o LANG-1154: FastDateFormat APIs that use a StringBuilder. Thanks to
+ Gary Gregory.
+o LANG-1149: Ability to throw checked exceptions without declaring them. Thanks
+ to Gregory Zak.
+o LANG-1153: Implement ParsePosition api for FastDateParser.
+o LANG-1137: Add check for duplicate event listener in EventListenerSupport.
+ Thanks to Matthew Aguirre.
+o LANG-1135: Add method containsAllWords to WordUtils. Thanks to
+ Eduardo Martins.
+o LANG-1132: ReflectionToStringBuilder doesn't throw IllegalArgumentException
+ when the constructor's object param is null. Thanks to Jack Tan.
+o LANG-701: StringUtils join with var args. Thanks to James Sawle.
+o LANG-1105: Add ThreadUtils - A utility class which provides helper methods
+ related to java.lang.Thread Issue: LANG-1105. Thanks to
+ Hendrik Saly.
+o LANG-1031: Add annotations to exclude fields from ReflectionEqualsBuilder,
+ ReflectionToStringBuilder and ReflectionHashCodeBuilder. Thanks
+ to Felipe Adorno.
+o LANG-1127: Use JUnit rules to set and reset the default Locale and TimeZone.
+o LANG-1119: Add rotate(string, int) method to StringUtils. Thanks to
+ Loic Guibert.
+o LANG-1099: Add swap and shift operations for arrays to ArrayUtils. Thanks to
+ Adrian Ber.
+o LANG-1050: Change nullToEmpty methods to generics. Thanks to James Sawle.
+o LANG-1074: Add a method to ArrayUtils for removing all occurrences of a given
+ element Issue: LANG-1074. Thanks to Haiyang Li.
+
+FIXED BUGS
+============
+
+o LANG-1261: ArrayUtils.contains returns false for instances of subtypes.
+o LANG-1252: Rename NumberUtils.isNumber, isCreatable to better reflect
+ createNumber. Also, accommodated for "+" symbol as prefix in
+ isCreatable and isNumber. Thanks to Rob Tompkins.
+o LANG-1230: Remove unnecessary synchronization from registry lookup in
+ EqualsBuilder and HashCodeBuilder. Thanks to Philippe Marschall.
+o LANG-1214: Handle "void" in ClassUtils.getClass(). Thanks to Henry Tung.
+o LANG-1250: SerializationUtils#deserialize has unnecessary code and a comment
+ for that. Thanks to Glease Wang.
+o LANG-1190: TypeUtils.isAssignable throws NullPointerException when fromType
+ has type variables and toType generic superclass specifies type
+ variable. Thanks to Pascal Schumacher.
+o LANG-1226: StringUtils#normalizeSpace does not trim the string anymore.
+ Thanks to Pascal Schumacher.
+o LANG-1251: SerializationUtils.ClassLoaderAwareObjectInputStream should use
+ static initializer to initialize primitiveTypes map. Thanks to
+ Takuya Ueshin.
+o LANG-1248: FastDatePrinter Memory allocation regression. Thanks to
+ Benoit Wiart.
+o LANG-1018: Fix precision loss on NumberUtils.createNumber(String). Thanks to
+ Nick Manley.
+o LANG-1199: Fix implementation of StringUtils.getJaroWinklerDistance(). Thanks
+ to M. Steiger.
+o LANG-1244: Fix dead links in StringUtils.getLevenshteinDistance() javadoc.
+ Thanks to jjbankert.
+o LANG-1242: "\u2284":"?" mapping missing from
+ EntityArrays#HTML40_EXTENDED_ESCAPE. Thanks to Neal Stewart.
+o LANG-901: StringUtils#startsWithAny/endsWithAny is case sensitive -
+ documented as case insensitive. Thanks to Matthew Bartenschlag.
+o LANG-1232: DiffBuilder: Add null check on fieldName when appending Object or
+ Object[]. Thanks to Nick Manley.
+o LANG-1178: ArrayUtils.removeAll(Object array, int... indices) should do the
+ clone, not its callers. Thanks to Henri Yandell.
+o LANG-1120: StringUtils.stripAccents should remove accents from "Ł" and "ł".
+ Thanks to kaching88.
+o LANG-1205: NumberUtils.createNumber() behaves inconsistently with
+ NumberUtils.isNumber(). Thanks to pbrose.
+o LANG-1222: Fix for incorrect comment on StringUtils.containsIgnoreCase
+ method. Thanks to Adam J.
+o LANG-1221: Fix typo on appendIfMissing javadoc. Thanks to Pierre Templier.
+o LANG-1202: parseDateStrictly doesn't pass specified locale. Thanks to
+ Markus Jelsma.
+o LANG-1219: FastDateFormat doesn't respect summer daylight in some localized
+ strings. Thanks to Jarek.
+o LANG-1175: Remove Ant-based build.
+o LANG-1194: Limit max heap memory for consistent Travis CI build.
+o LANG-1186: Fix NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to NickManley.
+o LANG-1193: ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1
+ (correct answer should be 0); revert fix for LANG-1077. Thanks to
+ Qin Li.
+o LANG-1002: Several predefined ISO FastDateFormats in DateFormatUtils are
+ incorrect. Thanks to Michael Osipov.
+o LANG-1152: StringIndexOutOfBoundsException or field over-write for large year
+ fields in FastDateParser. Thanks to Pas Filip.
+o LANG-1141: StrLookup.systemPropertiesLookup() no longer reacts on changes on
+ system properties.
+o LANG-1147: EnumUtils *BitVector issue with more than 32 values Enum. Thanks
+ to Loic Guibert.
+o LANG-1059: Capitalize javadoc is incorrect. Thanks to Colin Casey.
+o LANG-1122: Inconsistent behavior of swap for malformed inputs. Thanks to
+ Adrian Ber.
+o LANG-1130: Fix critical issues reported by SonarQube.
+o LANG-1131: StrBuilder.equals(StrBuilder) doesn't check for null inputs.
+o LANG-1128: JsonToStringStyle doesn't handle chars and objects correctly.
+ Thanks to Jack Tan.
+o LANG-1126: DateFormatUtilsTest.testSMTP depends on the default Locale.
+o LANG-1123: Unit test FastDatePrinterTimeZonesTest needs a timezone set.
+ Thanks to Christian P. Momon.
+o LANG-916: DateFormatUtils.format does not correctly change Calendar
+ TimeZone in certain situations. Thanks to Christian P. Momon.
+o LANG-1116: DateUtilsTest.testLang530 fails for some timezones. Thanks to
+ Aaron Sheldon.
+o LANG-1114: TypeUtils.ParameterizedType#equals doesn't work with wildcard
+ types. Thanks to Andy Coates.
+o LANG-1118: StringUtils.repeat('z', -1) throws NegativeArraySizeException.
+ Thanks to Loic Guibert.
+o LANG-1111: Fix FindBugs warnings in DurationFormatUtils.
+o LANG-1162: StringUtils#equals fails with Index OOBE on non-Strings with
+ identical leading prefix..
+o LANG-1163: There are no tests for CharSequenceUtils.regionMatches.
+o LANG-1200: Fix Javadoc of StringUtils.ordinalIndexOf. Thanks to BarkZhang.
+o LANG-1191: Incorrect Javadoc
+ StringUtils.containsAny(CharSequence, CharSequence...). Thanks to
+ qed, Brent Worden and Gary Gregory.
+
+CHANGES
+=========
+o LANG-1197: Prepare Java 9 detection.
+o LANG-1262: CompareToBuilder.append(Object, Object, Comparator) method is too
+ big to be inlined. Thanks to Ruslan Cheremin.
+o LANG-1259: Javadoc for ArrayUtils.isNotEmpty() is slightly misleading. Thanks
+ to Dominik Stadler.
+o LANG-1247: FastDatePrinter generates extra Date objects. Thanks to
+ Benoit Wiart.
+o LANG-1229: HashCodeBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1243: Simplify ArrayUtils removeElements by using new decrementAndGet()
+ method.
+o LANG-1240: Optimize BitField constructor implementation. Thanks to zhanhb.
+o LANG-1206: Improve CharSetUtils.squeeze() performance. Thanks to
+ Mohammed Alfallaj.
+o LANG-1176: Improve ArrayUtils removeElements time complexity to O(n). Thanks
+ to Jeffery Yuan.
+o LANG-1234: getLevenshteinDistance with a threshold: optimize implementation
+ if the strings lengths differ more than the threshold. Thanks to
+ Jonatan Jönsson.
+o LANG-1151: Performance improvements for NumberUtils.isParsable. Thanks to
+ Juan Pablo Santos Rodríguez.
+o LANG-1218: EqualsBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1210: StringUtils#startsWithAny has error in Javadoc. Thanks to
+ Matthias Niehoff.
+o LANG-1208: StrSubstitutor can preserve escapes. Thanks to Samuel Karp.
+o LANG-1182: Clarify Javadoc of StringUtils.containsAny(). Thanks to
+ Larry West and Pascal Schumacher.
+o LANG-1183: Making replacePattern/removePattern methods null safe in
+ StringUtils.
+o LANG-1057: Replace StringBuilder with String concatenation for better
+ optimization. Thanks to Otávio Santana.
+o LANG-1075: Deprecate SystemUtils.FILE_SEPARATOR and
+ SystemUtils.PATH_SEPARATOR.
+o LANG-979: TypeUtils.parameterizeWithOwner - wrong format descriptor for
+ "invalid number of type parameters". Thanks to Bruno P. Kinoshita.
+o LANG-1112: MultilineRecursiveToStringStyle largely unusable due to being
+ package-private.
+o LANG-1058: StringUtils.uncapitalize performance improvement. Thanks to
+ Leo Wang.
+o LANG-1069: CharSet.getInstance documentation does not clearly explain how
+ to include negation character in set. Thanks to Arno Noordover.
+o LANG-1107: Fix parsing edge cases in FastDateParser.
+o LANG-1273: Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils. Thanks
+ to Jake Wang.
+
+=============================================================================
+
+ Release Notes for version 3.4
+
+
+COMPATIBILITY
+=============
+
+Commons Lang 3.4 is fully binary compatible to the last release and can
+therefore be used as a drop in replacement for 3.3.2. Note that the value of
+org.apache.commons.lang3.time.DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN
+has changed, which may affect clients using the constant. Furthermore the
+constant is used internally in
+o DurationFormatUtils.formatDurationISO(long)
+o DurationFormatUtils.formatPeriodISO(long, long)
+
+For more information see https://issues.apache.org/jira/browse/LANG-1000.
+
+NEW FEATURES
+==============
+
+o LANG-821: Support OS X versions in SystemUtils. Thanks to Timo Kockert.
+o LANG-1103: Add SystemUtils.IS_JAVA_1_9
+o LANG-1093: Add ClassUtils.getAbbreviatedName(). Thanks to Fabian Lange.
+o LANG-1082: Add option to disable the "objectsTriviallyEqual" test in
+ DiffBuilder. Thanks to Jonathan Baker.
+o LANG-1015: Add JsonToStringStyle implementation to ToStringStyle. Thanks to
+ Thiago Andrade.
+o LANG-1080: Add NoClassNameToStringStyle implementation of ToStringStyle.
+ Thanks to Innokenty Shuvalov.
+o LANG-883: Add StringUtils.containsAny(CharSequence, CharSequence...) method.
+ Thanks to Daniel Stewart.
+o LANG-1052: Multiline recursive to string style. Thanks to Jan Matèrne.
+o LANG-536: Add isSorted() to ArrayUtils. Thanks to James Sawle.
+o LANG-1033: Add StringUtils.countMatches(CharSequence, char)
+o LANG-1021: Provide methods to retrieve all fields/methods annotated with a
+ specific type. Thanks to Alexander Müller.
+o LANG-1016: NumberUtils#isParsable method(s). Thanks to
+ Juan Pablo Santos Rodríguez.
+o LANG-999: Add fuzzy String matching logic to StringUtils. Thanks to
+ Ben Ripkens.
+o LANG-994: Add zero copy read method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-993: Add zero copy write method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-1044: Add method MethodUtils.invokeExactMethod(Object, String)
+o LANG-1045: Add method MethodUtils.invokeMethod(Object, String)
+
+FIXED BUGS
+============
+
+o LANG-794: SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect. Thanks to
+ Timo Kockert.
+o LANG-1104: Parse test fails for TimeZone America/Sao_Paulo
+o LANG-948: Exception while using ExtendedMessageFormat and escaping braces.
+ Thanks to Andrey Khobnya.
+o LANG-1092: Wrong formatting of time zones with daylight saving time in
+ FastDatePrinter
+o LANG-1090: FastDateParser does not set error indication in ParsePosition
+o LANG-1089: FastDateParser does not handle excess hours as per
+ SimpleDateFormat
+o LANG-1061: FastDateParser error - timezones not handled correctly. Thanks to
+ dmeneses.
+o LANG-1087: NumberUtils#createNumber() returns positive BigDecimal when
+ negative Float is expected. Thanks to Renat Zhilkibaev.
+o LANG-1081: DiffBuilder.append(String, Object left, Object right) does not do
+ a left.equals(right) check. Thanks to Jonathan Baker.
+o LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently.
+ Thanks to Jonathan Baker.
+o LANG-1083: Add (T) casts to get unit tests to pass in old JDK. Thanks to
+ Jonathan Baker.
+o LANG-1073: Read wrong component type of array in add in ArrayUtils.
+ Thanks to haiyang li.
+o LANG-1077: StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils.
+ Thanks to haiyang li.
+o LANG-1072: Duplicated "0x" check in createBigInteger in NumberUtils. Thanks
+ to haiyang li.
+o LANG-1064: StringUtils.abbreviate description doesn't agree with the
+ examples. Thanks to B.J. Herbison.
+o LANG-1041: Fix MethodUtilsTest so it does not depend on JDK method ordering.
+ Thanks to Alexandre Bartel.
+o LANG-1000: ParseException when trying to parse UTC dates with Z as zone
+ designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
+o LANG-1035: Javadoc for EqualsBuilder.reflectionEquals() is unclear
+o LANG-1001: ISO 8601 misspelled throughout the Javadocs. Thanks to
+ Michael Osipov.
+o LANG-1088: FastDateParser should be case insensitive
+o LANG-995: Fix bug with stripping spaces on last line in WordUtils.wrap().
+ Thanks to Andrey Khobnya.
+
+CHANGES
+=========
+
+o LANG-1102: Make logic for comparing OS versions in SystemUtils smarter
+o LANG-1091: Shutdown thread pools in test cases. Thanks to Fabian Lange.
+o LANG-1101: FastDateParser and FastDatePrinter support 'X' format
+o LANG-1100: Avoid memory allocation when using date formatting to StringBuffer.
+ Thanks to mbracher.
+o LANG-935: Possible performance improvement on string escape functions.
+ Thanks to Fabian Lange, Thomas Neidhart.
+o LANG-1098: Avoid String allocation in StrBuilder.append(CharSequence). Thanks
+ to Mikhail Mazurskiy, Fabian Lange.
+o LANG-1098: Update maven-checkstyle-plugin to 2.14. Thanks to Micha? Kordas.
+o LANG-1097: Update org.easymock:easymock to 3.3.1. Thanks to Micha? Kordas.
+o LANG-1096: Update maven-pmd-plugin to 3.4. Thanks to Micha? Kordas.
+o LANG-1095: Update maven-antrun-plugin to 1.8. Thanks to Micha? Kordas.
+o LANG-877: Performance improvements for StringEscapeUtils. Thanks to
+ Fabian Lange.
+o LANG-1071: Fix wrong examples in Javadoc of
+ StringUtils.replaceEachRepeatedly(...),
+ StringUtils.replaceEach(...) Thanks to Arno Noordover.
+o LANG-827: CompareToBuilder's doc doesn't specify precedence of fields it
+ uses in performing comparisons
+o LANG-1020: Improve performance of normalize space. Thanks to Libor Ondrusek.
+o LANG-1027: org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should
+ return true by default
+o LANG-1026: Bring static method references in StringUtils to consistent style.
+ Thanks to Alex Yursha.
+o LANG-1017: Use non-ASCII digits in Javadoc examples for
+ StringUtils.isNumeric. Thanks to Christoph Schneegans.
+o LANG-1008: Change min/max methods in NumberUtils/IEEE754rUtils from array
+ input parameters to varargs. Thanks to Thiago Andrade.
+o LANG-1006: Add wrap (with String or char) to StringUtils. Thanks to
+ Thiago Andrade.
+o LANG-1005: Extend DurationFormatUtils#formatDurationISO default pattern to
+ match #formatDurationHMS. Thanks to Michael Osipov.
+o LANG-1007: Fixing NumberUtils JAVADoc comments for max methods. Thanks to
+ Thiago Andrade.
+o LANG-731: Better Javadoc for BitField class
+o LANG-1004: DurationFormatUtils#formatDurationHMS implementation does not
+ correspond to Javadoc and vice versa. Thanks to Michael Osipov.
+o LANG-1003: DurationFormatUtils are not able to handle negative
+ durations/periods
+o LANG-998: Javadoc is not clear on preferred pattern to instantiate
+ FastDateParser / FastDatePrinter
+
+=============================================================================
+
+ Release Notes for version 3.3.2
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+=============================================================================
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+=============================================================================
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Götz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernández.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+=============================================================================
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+=============================================================================
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skyttä.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+=============================================================================
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+=============================================================================
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12 hour clock and not
+ 24 hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.8.1.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.8.1.txt
new file mode 100644
index 000000000..c11adfcf3
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.8.1.txt
@@ -0,0 +1,1270 @@
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.8.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.8.1 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 7.0, making use of features that arrived with Java 7.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+This release is a bugfix for Restoring Bundle-SymbolicName in the MANIFEST.mf file.
+
+Changes in this version include:
+
+
+Fixed Bugs:
+o LANG-1419: Restore BundleSymbolicName for OSGi
+
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.8
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.8 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1352: EnumUtils.getEnumIgnoreCase and isValidEnumIgnoreCase methods added Thanks to Ruslan Sibgatullin.
+o LANG-1372: Add ToStringSummary annotation Thanks to Srgio Ozaki.
+o LANG-1356: Add bypass option for classes to recursive and reflective EqualsBuilder Thanks to Yathos UG.
+o LANG-1391: Improve Javadoc for StringUtils.isAnyEmpty(null) Thanks to Sauro Matulli, Oleg Chubaryov.
+o LANG-1393: Add API SystemUtils.String getEnvironmentVariable(final String name, final String defaultValue) Thanks to Gary Gregory.
+o LANG-1394: org.apache.commons.lang3.SystemUtils should not write to System.err. Thanks to Sebb, Gary Gregory.
+o LANG-1238: Add RegexUtils class instead of overloading methods in StringUtils that take a regex to take precompiled Pattern. Thanks to Christopher Cordeiro, Gary Gregory, Bruno P. Kinoshita, Oleg Chubaryov.
+o LANG-1390: StringUtils.join() with support for List<?> with configurable start/end indices. Thanks to Jochen Schalanda.
+o LANG-1392: Methods for getting first non empty or non blank value Thanks to Jeff Nelson.
+o LANG-1408: Rounding utilities for converting to BigDecimal
+
+Fixed Bugs:
+o LANG-1380: FastDateParser too strict on abbreviated short month symbols Thanks to Markus Jelsma.
+o LANG-1396: JsonToStringStyle does not escape string names
+o LANG-1395: JsonToStringStyle does not escape double quote in a string value Thanks to Jim Gan.
+o LANG-1384: New Java version ("11") must be handled Thanks to Ian Young.
+o LANG-1364: ExceptionUtils#getRootCause(Throwable t) should return t if no lower level cause exists Thanks to Zheng Xie.
+o LANG-1060: NumberUtils.isNumber assumes number starting with Zero Thanks to Piotr Kosmala.
+o LANG-1375: defaultString(final String str) in StringUtils to reuse defaultString(final String str, final String defaultStr) Thanks to Jerry Zhao.
+o LANG-1374: Parsing Json Array failed Thanks to Jaswanth Bala.
+o LANG-1371: Fix TypeUtils#parameterize to work correctly with narrower-typed array Thanks to Dmitry Ovchinnikov.
+o LANG-1370: Fix EventCountCircuitBreaker increment batch Thanks to Andre Dieb.
+o LANG-1385: NumberUtils.createNumber() throws StringIndexOutOfBoundsException instead of NumberFormatException Thanks to Rohan Padhye.
+o LANG-1397: WordUtils.wrap throws StringIndexOutOfBoundsException when wrapLength is Integer.MAX_VALUE. Thanks to Takanobu Asanuma.
+o LANG-1401: Typo in JavaDoc for lastIndexOf Thanks to Roman Golyshev, Alex Mamedov.
+
+Changes:
+o LANG-1367: ObjectUtils.identityToString(Object) and friends should allocate builders and buffers with a size Thanks to Gary Gregory.
+o LANG-1405: Remove checks for java versions below the minimum supported one Thanks to Lars Grefer.
+o LANG-1402: Null/index safe get methods for ArrayUtils Thanks to Mark Dacek.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.7
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.7 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1355: TimeZone.getTimeZone() in FastDateParser causes resource contention (PR #296.) Thanks to Chas Honton.
+o LANG-1360: Add methods to ObjectUtils to get various forms of class names in a null-safe manner Thanks to Gary Gregory.
+
+Fixed Bugs:
+o LANG-1362: Fix tests DateUtilsTest for Java 9 with en_GB locale Thanks to Stephen Colebourne.
+o LANG-1365: Fix NullPointerException in isJavaVersionAtLeast on Java 10, add SystemUtils.IS_JAVA_10, add JavaVersion.JAVA_10 Thanks to Gary Gregory.
+o LANG-1348: StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf Thanks to mbusso.
+o LANG-1350: ConstructorUtils.invokeConstructor(Class, Object...) regression Thanks to Brett Kail.
+o LANG-1349: EqualsBuilder#isRegistered: swappedPair construction bug Thanks to Naman Nigam.
+o LANG-1357: org.apache.commons.lang3.time.FastDateParser should use toUpperCase(Locale) Thanks to BruceKuiLiu.
+
+Changes:
+o LANG-1358: Improve StringUtils#replace throughput Thanks to Stephane Landelle.
+o LANG-1346: Remove deprecation from RandomStringUtils
+o LANG-1361: ExceptionUtils.getThrowableList() is using deprecated ExceptionUtils.getCause() Thanks to Ana.
+
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.6
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.6 version of
+Apache Commons Lang as well as a history all changes in the Commons Lang 3.x
+release line. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.6 at
+least requires Java 7.0. Note that this has changed from Commons Lang 3.5, which
+only required Java 1.6.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o The class org.apache.commons.lang3.concurrent.Memoizer is an implementation
+ of the Memoizer pattern as shown in
+ Goetz, Brian et al. (2006) - Java Concurrency in Practice, p. 108.
+o The class org.apache.commons.lang3.ArchUtils has been added. ArchUtils is
+ a utility class for the "os.arch" system property.
+
+DEPRECATIONS
+============
+
+The Apache Commons Community has recently set up the Commons Text component
+as a home for algorithms working on strings. For this reason most of the string
+focused functionality in Commons Lang has been deprecated and moved to
+Commons Text. This includes:
+
+o All classes in the org.apache.commons.lang3.text and the
+ org.apache.commons.lang3.text.translate packages
+o org.apache.commons.lang3.StringEscapeUtils
+o org.apache.commons.lang3.RandomStringUtils
+o The methods org.apache.commons.lang3.StringUtils.getJaroWinklerDistance and
+ org.apache.commons.lang3.StringUtils.getLevenshteinDistance
+
+For more information see the Commons Text website:
+
+ https://commons.apache.org/text
+
+The class org.apache.commons.lang3.CharEncoding has been deprecated in favor of
+java.nio.charset.StandardCharsets.
+
+The following methods have been deprecated in
+org.apache.commons.lang3.ArrayUtils in favor of the corresponding insert
+methods. Note that the handling for null inputs differs between add and insert.
+
+o add(boolean[], int, boolean) -> insert(int, boolean[], boolean...)
+o add(byte[], int, boolean) -> insert(int, byte[], byte...)
+o add(char[], int, boolean) -> insert(int, char[], char...)
+o add(double[], int, boolean) -> insert(int, double[], double...)
+o add(float[], int, boolean) -> insert(int, float[], float...)
+o add(int[], int, boolean) -> insert(int, int[], int...)
+o add(long[], int, boolean) -> insert(int, long[], long...)
+o add(short[], int, boolean) -> insert(int, short[], short...)
+o add(T[], int, boolean) -> insert(int, T[], T...)
+
+
+COMPATIBILITY WITH JAVA 9
+==================
+
+The MANIFEST.MF now contains an additional entry:
+
+ Automatic-Module-Name: org.apache.commons.lang3
+
+This should make it possible to use Commons Lang 3.6 as a module in the Java 9
+module system. For more information see the corresponding issue and the
+referenced mailing list discussions:
+
+ https://issues.apache.org/jira/browse/LANG-1338
+
+The build problems present in the 3.5 release have been resolved. Building
+Commons Lang 3.6 should work out of the box with the latest Java 9 EA build.
+Please report any Java 9 related issues at:
+
+ https://issues.apache.org/jira/browse/LANG
+
+NEW FEATURES
+============
+
+o LANG-1336: Add NUL Byte To CharUtils. Thanks to Beluga Behr.
+o LANG-1304: Add method in StringUtils to determine if string contains both
+ mixed cased characters. Thanks to Andy Klimczak.
+o LANG-1325: Increase test coverage of ToStringBuilder class to 100%.
+ Thanks to Arshad Basha.
+o LANG-1307: Add a method in StringUtils to extract only digits out of input
+ string. Thanks to Arshad Basha.
+o LANG-1256: Add JMH maven dependencies. Thanks to C0rWin.
+o LANG-1167: Add null filter to ReflectionToStringBuilder.
+ Thanks to Mark Dacek.
+o LANG-1299: Add method for converting string to an array of code points.
+o LANG-660: Add methods to insert arrays into arrays at an index.
+o LANG-1034: Add support for recursive comparison to
+ EqualsBuilder#reflectionEquals. Thanks to Yathos UG.
+o LANG-1067: Add a reflection-based variant of DiffBuilder.
+o LANG-740: Implementation of a Memoizer. Thanks to James Sawle.
+o LANG-1258: Add ArrayUtils#toStringArray method.
+ Thanks to IG, Grzegorz Ro?niecki.
+o LANG-1160: StringUtils#abbreviate should support 'custom ellipses' parameter.
+o LANG-1293: Add StringUtils#isAllEmpty and #isAllBlank methods.
+ Thanks to Pierre Templier, Martin Tarjanyi.
+o LANG-1313: Add ArchUtils - An utility class for the "os.arch" system property.
+ Thanks to Tomschi.
+o LANG-1272: Add shuffle methods to ArrayUtils.
+o LANG-1317: Add MethodUtils#findAnnotation and extend
+ MethodUtils#getMethodsWithAnnotation for non-public, super-class
+ and interface methods. Thanks to Yasser Zamani.
+o LANG-1331: Add ImmutablePair.nullPair().
+o LANG-1332: Add ImmutableTriple.nullTriple().
+
+FIXED BUGS
+==========
+
+o LANG-1337: Fix test failures in IBM JDK 8 for ToStringBuilderTest.
+o LANG-1319: MultilineRecursiveToStringStyle StackOverflowError when object is
+ an array.
+o LANG-1320: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code followed by variant.
+o LANG-1300: Clarify or improve behavior of int-based indexOf methods in
+ StringUtils. Thanks to Mark Dacek.
+o LANG-1286: RandomStringUtils random method can overflow and return characters
+ outside of specified range.
+o LANG-1292: WordUtils.wrap throws StringIndexOutOfBoundsException.
+o LANG-1287: RandomStringUtils#random can enter infinite loop if end parameter
+ is to small. Thanks to Ivan Morozov.
+o LANG-1285: NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to Francesco Chicchiricc.
+o LANG-1281: Javadoc of StringUtils.ordinalIndexOf is contradictory.
+ Thanks to Andreas Lundblad.
+o LANG-1188: StringUtils#join(T...): warning: [unchecked] Possible heap
+ pollution from parameterized vararg type T.
+o LANG-1144: Multiple calls of
+ org.apache.commons.lang3.concurrent.LazyInitializer.initialize()
+ are possible. Thanks to Waldemar Maier, Gary Gregory.
+o LANG-1276: StrBuilder#replaceAll ArrayIndexOutOfBoundsException.
+ Thanks to Andy Klimczak.
+o LANG-1278: BooleanUtils javadoc issues. Thanks to Duke Yin.
+o LANG-1070: ArrayUtils#add confusing example in javadoc.
+ Thanks to Paul Pogonyshev.
+o LANG-1271: StringUtils#isAnyEmpty and #isAnyBlank should return false for an
+ empty array. Thanks to Pierre Templier.
+o LANG-1155: Add StringUtils#unwrap. Thanks to Saif Asif, Thiago Andrade.
+o LANG-1311: TypeUtils.toString() doesn't handle primitive and Object arrays
+ correctly. Thanks to Aaron Digulla.
+o LANG-1312: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code.
+o LANG-1265: Build failures when building with Java 9 EA.
+o LANG-1314: javadoc creation broken with Java 8. Thanks to Allon Murienik.
+o LANG-1310: MethodUtils.invokeMethod throws ArrayStoreException if using
+ varargs arguments and smaller types than the method defines.
+ Thanks to Don Jeba.
+
+CHANGES
+=======
+
+o LANG-1338: Add Automatic-Module-Name MANIFEST entry for Java 9
+ compatibility.
+o LANG-1334: Deprecate CharEncoding in favour of
+ java.nio.charset.StandardCharsets.
+o LANG-1110: Implement HashSetvBitSetTest using JMH.
+ Thanks to Bruno P. Kinoshita.
+o LANG-1290: Increase test coverage of org.apache.commons.lang3.ArrayUtils.
+ Thanks to Andrii Abramov.
+o LANG-1274: StrSubstitutor should state its thread safety.
+o LANG-1277: StringUtils#getLevenshteinDistance reduce memory consumption.
+ Thanks to yufcuy.
+o LANG-1279: Update Java requirement from Java 6 to 7.
+o LANG-1143: StringUtils should use toXxxxCase(int) rather than
+ toXxxxCase(char). Thanks to sebb.
+o LANG-1297: Add SystemUtils.getHostName() API.
+o LANG-1301: Moving apache-rat-plugin configuration into pluginManagement.
+ Thanks to Karl Heinz Marbaise.
+o LANG-1316: Deprecate classes/methods moved to commons-text.
+
+=============================================================================
+
+ Release Notes for version 3.5
+
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o Added Java 9 detection to org.apache.commons.lang3.SystemUtils.
+o Support for shifting and swapping elements in
+ org.apache.commons.lang3.ArrayUtils.
+o New methods for generating random strings from different character classes
+ including alphabetic, alpha-numeric and ASCII added to
+ org.apache.commons.lang3.RandomStringUtils.
+o Numerous extensions to org.apache.commons.lang3.StringUtils including
+ null safe compare variants, more remove and replace variants, rotation and
+ truncation.
+o Added org.apache.commons.lang3.ThreadUtils - a utility class to work with
+ instances of java.lang.Thread and java.lang.ThreadGroup.
+o Added annotations @EqualsExclude, @HashCodeExclude and @ToStringExclude to
+ mark fields which should be ignored by the reflective builders in the
+ org.apache.commons.lang3.builder package.
+o Support for various modify and retrieve value use cases added to the classes
+ in org.apache.commons.lang3.mutable.
+
+COMPATIBILITY
+=============
+
+Apache Commons Lang 3.5 is binary compatible with the 3.4 release. Users
+should not experience any problems when upgrading from 3.4 to 3.5.
+
+There has been an addition to the org.apache.commons.lang3.time.DatePrinter
+interface:
+
+o Added method 'public boolean parse(java.lang.String, java.text.ParsePosition,
+ java.util.Calendar)'
+o Added method 'public java.lang.Appendable format(long, java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Date,
+ java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Calendar,
+ java.lang.Appendable)'
+
+For this reason 3.5 is not strictly source compatible to 3.4. Since the
+DatePrinter interface is not meant to be implemented by clients, this
+change it not considered to cause any problems.
+
+JAVA 9 SUPPORT
+==============
+
+Java 9 introduces a new version-string scheme. Details of this new scheme are
+documented in JEP-223 (https://openjdk.org/jeps/223). In order to support
+JEP-223 two classes had to be changed:
+
+o org.apache.commons.lang3.JavaVersion
+ deprecated enum constant JAVA_1_9
+ introduced enum constant JAVA_9
+
+o org.apache.commons.lang3.SystemUtils
+ deprecated constant IS_JAVA_1_9
+ introduced constant IS_JAVA_9
+
+For more information see LANG-1197
+(https://issues.apache.org/jira/browse/LANG-1197). All other APIs are expected
+to work with Java 9.
+
+BUILDING ON JAVA 9
+==================
+
+Java 8 introduced the Unicode Consortium's Common Locale Data Repository as
+alternative source for locale data. Java 9 will use the CLDR provider as
+default provider for locale data (see https://openjdk.org/jeps/252). This
+causes an number of locale-sensitive test in Commons Lang to fail. In order
+to build Commons Lang 3.5 on Java 9, the locale provider has to be set to
+'JRE':
+
+ mvn -Djava.locale.providers=JRE clean install
+
+We are currently investigating ways to support building on Java 9 without
+further configuration. For more information see:
+https://issues.apache.org/jira/browse/LANG-1265
+
+
+NEW FEATURES
+==============
+
+o LANG-1275: Added a tryAcquire() method to TimedSemaphore.
+o LANG-1255: Add DateUtils.toCalendar(Date, TimeZone). Thanks to Kaiyuan Wang.
+o LANG-1023: Add WordUtils.wrap overload with customizable breakable character.
+ Thanks to Marko Bekhta.
+o LANG-787: Add method removeIgnoreCase(String, String) to StringUtils. Thanks
+ to Gokul Nanthakumar C.
+o LANG-1224: Extend RandomStringUtils with methods that generate strings
+ between a min and max length. Thanks to Caleb Cushing.
+o LANG-1257: Add APIs StringUtils.wrapIfMissing(String, char|String). Thanks to
+ Gary Gregory.
+o LANG-1253: Add RandomUtils#nextBoolean() method. Thanks to adilek.
+o LANG-1085: Add a circuit breaker implementation. Thanks to Oliver Heger and
+ Bruno P. Kinoshita.
+o LANG-1013: Add StringUtils.truncate(). Thanks to Thiago Andrade.
+o LANG-1195: Enhance MethodUtils to allow invocation of private methods. Thanks
+ to Derek C. Ashmore.
+o LANG-1189: Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/
+ decrementAndGet/addAndGet in Mutable* classes. Thanks to
+ Haiyang Li and Matthew Bartenschlag.
+o LANG-1225: Add RandomStringUtils#randomGraph and #randomPrint which match
+ corresponding regular expression class. Thanks to Caleb Cushing.
+o LANG-1223: Add StopWatch#getTime(TimeUnit). Thanks to Nick Manley.
+o LANG-781: Add methods to ObjectUtils class to check for null elements in the
+ array. Thanks to Krzysztof Wolny.
+o LANG-1228: Prefer Throwable.getCause() in ExceptionUtils.getCause().
+ Thanks to Brad Hess.
+o LANG-1233: DiffBuilder add method to allow appending from a DiffResult.
+ Thanks to Nick Manley.
+o LANG-1168: Add SystemUtils.IS_OS_WINDOWS_10 property.
+ Thanks to Pascal Schumacher.
+o LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and
+ MethodUtils. Thanks to Jim Lloyd and Joe Ferner.
+o LANG-1134: Add methods to check numbers against NaN and infinite to
+ Validate. Thanks to Alan Smithee.
+o LANG-1220: Add tests for missed branches in DateUtils.
+ Thanks to Casey Scarborough.
+o LANG-1146: z/OS identification in SystemUtils.
+ Thanks to Gabor Liptak.
+o LANG-1192: FastDateFormat support of the week-year component (uppercase 'Y').
+ Thanks to Dominik Stadler.
+o LANG-1169: Add StringUtils methods to compare a string to multiple strings.
+ Thanks to Rafal Glowinski, Robert Parr and Arman Sharif.
+o LANG-1185: Add remove by regular expression methods in StringUtils.
+o LANG-1139: Add replace by regular expression methods in StringUtils.
+o LANG-1171: Add compare methods in StringUtils.
+o LANG-1174: Add sugar to RandomUtils. Thanks to Punkratz312.
+o LANG-1154: FastDateFormat APIs that use a StringBuilder. Thanks to
+ Gary Gregory.
+o LANG-1149: Ability to throw checked exceptions without declaring them. Thanks
+ to Gregory Zak.
+o LANG-1153: Implement ParsePosition api for FastDateParser.
+o LANG-1137: Add check for duplicate event listener in EventListenerSupport.
+ Thanks to Matthew Aguirre.
+o LANG-1135: Add method containsAllWords to WordUtils. Thanks to
+ Eduardo Martins.
+o LANG-1132: ReflectionToStringBuilder doesn't throw IllegalArgumentException
+ when the constructor's object param is null. Thanks to Jack Tan.
+o LANG-701: StringUtils join with var args. Thanks to James Sawle.
+o LANG-1105: Add ThreadUtils - A utility class which provides helper methods
+ related to java.lang.Thread Issue: LANG-1105. Thanks to
+ Hendrik Saly.
+o LANG-1031: Add annotations to exclude fields from ReflectionEqualsBuilder,
+ ReflectionToStringBuilder and ReflectionHashCodeBuilder. Thanks
+ to Felipe Adorno.
+o LANG-1127: Use JUnit rules to set and reset the default Locale and TimeZone.
+o LANG-1119: Add rotate(string, int) method to StringUtils. Thanks to
+ Loic Guibert.
+o LANG-1099: Add swap and shift operations for arrays to ArrayUtils. Thanks to
+ Adrian Ber.
+o LANG-1050: Change nullToEmpty methods to generics. Thanks to James Sawle.
+o LANG-1074: Add a method to ArrayUtils for removing all occurrences of a given
+ element Issue: LANG-1074. Thanks to Haiyang Li.
+
+FIXED BUGS
+============
+
+o LANG-1261: ArrayUtils.contains returns false for instances of subtypes.
+o LANG-1252: Rename NumberUtils.isNumber, isCreatable to better reflect
+ createNumber. Also, accommodated for "+" symbol as prefix in
+ isCreatable and isNumber. Thanks to Rob Tompkins.
+o LANG-1230: Remove unnecessary synchronization from registry lookup in
+ EqualsBuilder and HashCodeBuilder. Thanks to Philippe Marschall.
+o LANG-1214: Handle "void" in ClassUtils.getClass(). Thanks to Henry Tung.
+o LANG-1250: SerializationUtils#deserialize has unnecessary code and a comment
+ for that. Thanks to Glease Wang.
+o LANG-1190: TypeUtils.isAssignable throws NullPointerException when fromType
+ has type variables and toType generic superclass specifies type
+ variable. Thanks to Pascal Schumacher.
+o LANG-1226: StringUtils#normalizeSpace does not trim the string anymore.
+ Thanks to Pascal Schumacher.
+o LANG-1251: SerializationUtils.ClassLoaderAwareObjectInputStream should use
+ static initializer to initialize primitiveTypes map. Thanks to
+ Takuya Ueshin.
+o LANG-1248: FastDatePrinter Memory allocation regression. Thanks to
+ Benoit Wiart.
+o LANG-1018: Fix precision loss on NumberUtils.createNumber(String). Thanks to
+ Nick Manley.
+o LANG-1199: Fix implementation of StringUtils.getJaroWinklerDistance(). Thanks
+ to M. Steiger.
+o LANG-1244: Fix dead links in StringUtils.getLevenshteinDistance() javadoc.
+ Thanks to jjbankert.
+o LANG-1242: "\u2284":"?" mapping missing from
+ EntityArrays#HTML40_EXTENDED_ESCAPE. Thanks to Neal Stewart.
+o LANG-901: StringUtils#startsWithAny/endsWithAny is case sensitive -
+ documented as case insensitive. Thanks to Matthew Bartenschlag.
+o LANG-1232: DiffBuilder: Add null check on fieldName when appending Object or
+ Object[]. Thanks to Nick Manley.
+o LANG-1178: ArrayUtils.removeAll(Object array, int... indices) should do the
+ clone, not its callers. Thanks to Henri Yandell.
+o LANG-1120: StringUtils.stripAccents should remove accents from "?" and "?".
+ Thanks to kaching88.
+o LANG-1205: NumberUtils.createNumber() behaves inconsistently with
+ NumberUtils.isNumber(). Thanks to pbrose.
+o LANG-1222: Fix for incorrect comment on StringUtils.containsIgnoreCase
+ method. Thanks to Adam J.
+o LANG-1221: Fix typo on appendIfMissing javadoc. Thanks to Pierre Templier.
+o LANG-1202: parseDateStrictly doesn't pass specified locale. Thanks to
+ Markus Jelsma.
+o LANG-1219: FastDateFormat doesn't respect summer daylight in some localized
+ strings. Thanks to Jarek.
+o LANG-1175: Remove Ant-based build.
+o LANG-1194: Limit max heap memory for consistent Travis CI build.
+o LANG-1186: Fix NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to NickManley.
+o LANG-1193: ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1
+ (correct answer should be 0); revert fix for LANG-1077. Thanks to
+ Qin Li.
+o LANG-1002: Several predefined ISO FastDateFormats in DateFormatUtils are
+ incorrect. Thanks to Michael Osipov.
+o LANG-1152: StringIndexOutOfBoundsException or field over-write for large year
+ fields in FastDateParser. Thanks to Pas Filip.
+o LANG-1141: StrLookup.systemPropertiesLookup() no longer reacts on changes on
+ system properties.
+o LANG-1147: EnumUtils *BitVector issue with more than 32 values Enum. Thanks
+ to Loic Guibert.
+o LANG-1059: Capitalize javadoc is incorrect. Thanks to Colin Casey.
+o LANG-1122: Inconsistent behavior of swap for malformed inputs. Thanks to
+ Adrian Ber.
+o LANG-1130: Fix critical issues reported by SonarQube.
+o LANG-1131: StrBuilder.equals(StrBuilder) doesn't check for null inputs.
+o LANG-1128: JsonToStringStyle doesn't handle chars and objects correctly.
+ Thanks to Jack Tan.
+o LANG-1126: DateFormatUtilsTest.testSMTP depends on the default Locale.
+o LANG-1123: Unit test FastDatePrinterTimeZonesTest needs a timezone set.
+ Thanks to Christian P. Momon.
+o LANG-916: DateFormatUtils.format does not correctly change Calendar
+ TimeZone in certain situations. Thanks to Christian P. Momon.
+o LANG-1116: DateUtilsTest.testLang530 fails for some timezones. Thanks to
+ Aaron Sheldon.
+o LANG-1114: TypeUtils.ParameterizedType#equals doesn't work with wildcard
+ types. Thanks to Andy Coates.
+o LANG-1118: StringUtils.repeat('z', -1) throws NegativeArraySizeException.
+ Thanks to Loic Guibert.
+o LANG-1111: Fix FindBugs warnings in DurationFormatUtils.
+o LANG-1162: StringUtils#equals fails with Index OOBE on non-Strings with
+ identical leading prefix..
+o LANG-1163: There are no tests for CharSequenceUtils.regionMatches.
+o LANG-1200: Fix Javadoc of StringUtils.ordinalIndexOf. Thanks to BarkZhang.
+o LANG-1191: Incorrect Javadoc
+ StringUtils.containsAny(CharSequence, CharSequence...). Thanks to
+ qed, Brent Worden and Gary Gregory.
+
+CHANGES
+=========
+o LANG-1197: Prepare Java 9 detection.
+o LANG-1262: CompareToBuilder.append(Object, Object, Comparator) method is too
+ big to be inlined. Thanks to Ruslan Cheremin.
+o LANG-1259: Javadoc for ArrayUtils.isNotEmpty() is slightly misleading. Thanks
+ to Dominik Stadler.
+o LANG-1247: FastDatePrinter generates extra Date objects. Thanks to
+ Benoit Wiart.
+o LANG-1229: HashCodeBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1243: Simplify ArrayUtils removeElements by using new decrementAndGet()
+ method.
+o LANG-1240: Optimize BitField constructor implementation. Thanks to zhanhb.
+o LANG-1206: Improve CharSetUtils.squeeze() performance. Thanks to
+ Mohammed Alfallaj.
+o LANG-1176: Improve ArrayUtils removeElements time complexity to O(n). Thanks
+ to Jeffery Yuan.
+o LANG-1234: getLevenshteinDistance with a threshold: optimize implementation
+ if the strings lengths differ more than the threshold. Thanks to
+ Jonatan Jnsson.
+o LANG-1151: Performance improvements for NumberUtils.isParsable. Thanks to
+ Juan Pablo Santos Rodrguez.
+o LANG-1218: EqualsBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1210: StringUtils#startsWithAny has error in Javadoc. Thanks to
+ Matthias Niehoff.
+o LANG-1208: StrSubstitutor can preserve escapes. Thanks to Samuel Karp.
+o LANG-1182: Clarify Javadoc of StringUtils.containsAny(). Thanks to
+ Larry West and Pascal Schumacher.
+o LANG-1183: Making replacePattern/removePattern methods null safe in
+ StringUtils.
+o LANG-1057: Replace StringBuilder with String concatenation for better
+ optimization. Thanks to Otvio Santana.
+o LANG-1075: Deprecate SystemUtils.FILE_SEPARATOR and
+ SystemUtils.PATH_SEPARATOR.
+o LANG-979: TypeUtils.parameterizeWithOwner - wrong format descriptor for
+ "invalid number of type parameters". Thanks to Bruno P. Kinoshita.
+o LANG-1112: MultilineRecursiveToStringStyle largely unusable due to being
+ package-private.
+o LANG-1058: StringUtils.uncapitalize performance improvement. Thanks to
+ Leo Wang.
+o LANG-1069: CharSet.getInstance documentation does not clearly explain how
+ to include negation character in set. Thanks to Arno Noordover.
+o LANG-1107: Fix parsing edge cases in FastDateParser.
+o LANG-1273: Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils. Thanks
+ to Jake Wang.
+
+=============================================================================
+
+ Release Notes for version 3.4
+
+
+COMPATIBILITY
+=============
+
+Commons Lang 3.4 is fully binary compatible to the last release and can
+therefore be used as a drop in replacement for 3.3.2. Note that the value of
+org.apache.commons.lang3.time.DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN
+has changed, which may affect clients using the constant. Furthermore the
+constant is used internally in
+o DurationFormatUtils.formatDurationISO(long)
+o DurationFormatUtils.formatPeriodISO(long, long)
+
+For more information see https://issues.apache.org/jira/browse/LANG-1000.
+
+NEW FEATURES
+==============
+
+o LANG-821: Support OS X versions in SystemUtils. Thanks to Timo Kockert.
+o LANG-1103: Add SystemUtils.IS_JAVA_1_9
+o LANG-1093: Add ClassUtils.getAbbreviatedName(). Thanks to Fabian Lange.
+o LANG-1082: Add option to disable the "objectsTriviallyEqual" test in
+ DiffBuilder. Thanks to Jonathan Baker.
+o LANG-1015: Add JsonToStringStyle implementation to ToStringStyle. Thanks to
+ Thiago Andrade.
+o LANG-1080: Add NoClassNameToStringStyle implementation of ToStringStyle.
+ Thanks to Innokenty Shuvalov.
+o LANG-883: Add StringUtils.containsAny(CharSequence, CharSequence...) method.
+ Thanks to Daniel Stewart.
+o LANG-1052: Multiline recursive to string style. Thanks to Jan Matrne.
+o LANG-536: Add isSorted() to ArrayUtils. Thanks to James Sawle.
+o LANG-1033: Add StringUtils.countMatches(CharSequence, char)
+o LANG-1021: Provide methods to retrieve all fields/methods annotated with a
+ specific type. Thanks to Alexander Mller.
+o LANG-1016: NumberUtils#isParsable method(s). Thanks to
+ Juan Pablo Santos Rodrguez.
+o LANG-999: Add fuzzy String matching logic to StringUtils. Thanks to
+ Ben Ripkens.
+o LANG-994: Add zero copy read method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-993: Add zero copy write method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-1044: Add method MethodUtils.invokeExactMethod(Object, String)
+o LANG-1045: Add method MethodUtils.invokeMethod(Object, String)
+
+FIXED BUGS
+============
+
+o LANG-794: SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect. Thanks to
+ Timo Kockert.
+o LANG-1104: Parse test fails for TimeZone America/Sao_Paulo
+o LANG-948: Exception while using ExtendedMessageFormat and escaping braces.
+ Thanks to Andrey Khobnya.
+o LANG-1092: Wrong formatting of time zones with daylight saving time in
+ FastDatePrinter
+o LANG-1090: FastDateParser does not set error indication in ParsePosition
+o LANG-1089: FastDateParser does not handle excess hours as per
+ SimpleDateFormat
+o LANG-1061: FastDateParser error - timezones not handled correctly. Thanks to
+ dmeneses.
+o LANG-1087: NumberUtils#createNumber() returns positive BigDecimal when
+ negative Float is expected. Thanks to Renat Zhilkibaev.
+o LANG-1081: DiffBuilder.append(String, Object left, Object right) does not do
+ a left.equals(right) check. Thanks to Jonathan Baker.
+o LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently.
+ Thanks to Jonathan Baker.
+o LANG-1083: Add (T) casts to get unit tests to pass in old JDK. Thanks to
+ Jonathan Baker.
+o LANG-1073: Read wrong component type of array in add in ArrayUtils.
+ Thanks to haiyang li.
+o LANG-1077: StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils.
+ Thanks to haiyang li.
+o LANG-1072: Duplicated "0x" check in createBigInteger in NumberUtils. Thanks
+ to haiyang li.
+o LANG-1064: StringUtils.abbreviate description doesn't agree with the
+ examples. Thanks to B.J. Herbison.
+o LANG-1041: Fix MethodUtilsTest so it does not depend on JDK method ordering.
+ Thanks to Alexandre Bartel.
+o LANG-1000: ParseException when trying to parse UTC dates with Z as zone
+ designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
+o LANG-1035: Javadoc for EqualsBuilder.reflectionEquals() is unclear
+o LANG-1001: ISO 8601 misspelled throughout the Javadocs. Thanks to
+ Michael Osipov.
+o LANG-1088: FastDateParser should be case insensitive
+o LANG-995: Fix bug with stripping spaces on last line in WordUtils.wrap().
+ Thanks to Andrey Khobnya.
+
+CHANGES
+=========
+
+o LANG-1102: Make logic for comparing OS versions in SystemUtils smarter
+o LANG-1091: Shutdown thread pools in test cases. Thanks to Fabian Lange.
+o LANG-1101: FastDateParser and FastDatePrinter support 'X' format
+o LANG-1100: Avoid memory allocation when using date formatting to StringBuffer.
+ Thanks to mbracher.
+o LANG-935: Possible performance improvement on string escape functions.
+ Thanks to Fabian Lange, Thomas Neidhart.
+o LANG-1098: Avoid String allocation in StrBuilder.append(CharSequence). Thanks
+ to Mikhail Mazurskiy, Fabian Lange.
+o LANG-1098: Update maven-checkstyle-plugin to 2.14. Thanks to Micha? Kordas.
+o LANG-1097: Update org.easymock:easymock to 3.3.1. Thanks to Micha? Kordas.
+o LANG-1096: Update maven-pmd-plugin to 3.4. Thanks to Micha? Kordas.
+o LANG-1095: Update maven-antrun-plugin to 1.8. Thanks to Micha? Kordas.
+o LANG-877: Performance improvements for StringEscapeUtils. Thanks to
+ Fabian Lange.
+o LANG-1071: Fix wrong examples in Javadoc of
+ StringUtils.replaceEachRepeatedly(...),
+ StringUtils.replaceEach(...) Thanks to Arno Noordover.
+o LANG-827: CompareToBuilder's doc doesn't specify precedence of fields it
+ uses in performing comparisons
+o LANG-1020: Improve performance of normalize space. Thanks to Libor Ondrusek.
+o LANG-1027: org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should
+ return true by default
+o LANG-1026: Bring static method references in StringUtils to consistent style.
+ Thanks to Alex Yursha.
+o LANG-1017: Use non-ASCII digits in Javadoc examples for
+ StringUtils.isNumeric. Thanks to Christoph Schneegans.
+o LANG-1008: Change min/max methods in NumberUtils/IEEE754rUtils from array
+ input parameters to varargs. Thanks to Thiago Andrade.
+o LANG-1006: Add wrap (with String or char) to StringUtils. Thanks to
+ Thiago Andrade.
+o LANG-1005: Extend DurationFormatUtils#formatDurationISO default pattern to
+ match #formatDurationHMS. Thanks to Michael Osipov.
+o LANG-1007: Fixing NumberUtils JAVADoc comments for max methods. Thanks to
+ Thiago Andrade.
+o LANG-731: Better Javadoc for BitField class
+o LANG-1004: DurationFormatUtils#formatDurationHMS implementation does not
+ correspond to Javadoc and vice versa. Thanks to Michael Osipov.
+o LANG-1003: DurationFormatUtils are not able to handle negative
+ durations/periods
+o LANG-998: Javadoc is not clear on preferred pattern to instantiate
+ FastDateParser / FastDatePrinter
+
+=============================================================================
+
+ Release Notes for version 3.3.2
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+=============================================================================
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+=============================================================================
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Gtz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernndez.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+=============================================================================
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+=============================================================================
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skytt.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+=============================================================================
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+=============================================================================
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12 hour clock and not
+ 24 hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.8.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.8.txt
new file mode 100644
index 000000000..b760d8308
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.8.txt
@@ -0,0 +1,1237 @@
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.8
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.8 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 7.0, making use of features that arrived with Java 7.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1352: EnumUtils.getEnumIgnoreCase and isValidEnumIgnoreCase methods added Thanks to Ruslan Sibgatullin.
+o LANG-1372: Add ToStringSummary annotation Thanks to Srgio Ozaki.
+o LANG-1356: Add bypass option for classes to recursive and reflective EqualsBuilder Thanks to Yathos UG.
+o LANG-1391: Improve Javadoc for StringUtils.isAnyEmpty(null) Thanks to Sauro Matulli, Oleg Chubaryov.
+o LANG-1393: Add API SystemUtils.String getEnvironmentVariable(final String name, final String defaultValue) Thanks to Gary Gregory.
+o LANG-1394: org.apache.commons.lang3.SystemUtils should not write to System.err. Thanks to Sebb, Gary Gregory.
+o LANG-1238: Add RegexUtils class instead of overloading methods in StringUtils that take a regex to take precompiled Pattern. Thanks to Christopher Cordeiro, Gary Gregory, Bruno P. Kinoshita, Oleg Chubaryov.
+o LANG-1390: StringUtils.join() with support for List<?> with configurable start/end indices. Thanks to Jochen Schalanda.
+o LANG-1392: Methods for getting first non empty or non blank value Thanks to Jeff Nelson.
+o LANG-1408: Rounding utilities for converting to BigDecimal
+
+Fixed Bugs:
+o LANG-1380: FastDateParser too strict on abbreviated short month symbols Thanks to Markus Jelsma.
+o LANG-1396: JsonToStringStyle does not escape string names
+o LANG-1395: JsonToStringStyle does not escape double quote in a string value Thanks to Jim Gan.
+o LANG-1384: New Java version ("11") must be handled Thanks to Ian Young.
+o LANG-1364: ExceptionUtils#getRootCause(Throwable t) should return t if no lower level cause exists Thanks to Zheng Xie.
+o LANG-1060: NumberUtils.isNumber assumes number starting with Zero Thanks to Piotr Kosmala.
+o LANG-1375: defaultString(final String str) in StringUtils to reuse defaultString(final String str, final String defaultStr) Thanks to Jerry Zhao.
+o LANG-1374: Parsing Json Array failed Thanks to Jaswanth Bala.
+o LANG-1371: Fix TypeUtils#parameterize to work correctly with narrower-typed array Thanks to Dmitry Ovchinnikov.
+o LANG-1370: Fix EventCountCircuitBreaker increment batch Thanks to Andre Dieb.
+o LANG-1385: NumberUtils.createNumber() throws StringIndexOutOfBoundsException instead of NumberFormatException Thanks to Rohan Padhye.
+o LANG-1397: WordUtils.wrap throws StringIndexOutOfBoundsException when wrapLength is Integer.MAX_VALUE. Thanks to Takanobu Asanuma.
+o LANG-1401: Typo in JavaDoc for lastIndexOf Thanks to Roman Golyshev, Alex Mamedov.
+
+Changes:
+o LANG-1367: ObjectUtils.identityToString(Object) and friends should allocate builders and buffers with a size Thanks to Gary Gregory.
+o LANG-1405: Remove checks for java versions below the minimum supported one Thanks to Lars Grefer.
+o LANG-1402: Null/index safe get methods for ArrayUtils Thanks to Mark Dacek.
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.7
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.7 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1355: TimeZone.getTimeZone() in FastDateParser causes resource contention (PR #296.) Thanks to Chas Honton.
+o LANG-1360: Add methods to ObjectUtils to get various forms of class names in a null-safe manner Thanks to Gary Gregory.
+
+Fixed Bugs:
+o LANG-1362: Fix tests DateUtilsTest for Java 9 with en_GB locale Thanks to Stephen Colebourne.
+o LANG-1365: Fix NullPointerException in isJavaVersionAtLeast on Java 10, add SystemUtils.IS_JAVA_10, add JavaVersion.JAVA_10 Thanks to Gary Gregory.
+o LANG-1348: StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf Thanks to mbusso.
+o LANG-1350: ConstructorUtils.invokeConstructor(Class, Object...) regression Thanks to Brett Kail.
+o LANG-1349: EqualsBuilder#isRegistered: swappedPair construction bug Thanks to Naman Nigam.
+o LANG-1357: org.apache.commons.lang3.time.FastDateParser should use toUpperCase(Locale) Thanks to BruceKuiLiu.
+
+Changes:
+o LANG-1358: Improve StringUtils#replace throughput Thanks to Stephane Landelle.
+o LANG-1346: Remove deprecation from RandomStringUtils
+o LANG-1361: ExceptionUtils.getThrowableList() is using deprecated ExceptionUtils.getCause() Thanks to Ana.
+
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.6
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.6 version of
+Apache Commons Lang as well as a history all changes in the Commons Lang 3.x
+release line. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.6 at
+least requires Java 7.0. Note that this has changed from Commons Lang 3.5, which
+only required Java 1.6.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o The class org.apache.commons.lang3.concurrent.Memoizer is an implementation
+ of the Memoizer pattern as shown in
+ Goetz, Brian et al. (2006) - Java Concurrency in Practice, p. 108.
+o The class org.apache.commons.lang3.ArchUtils has been added. ArchUtils is
+ a utility class for the "os.arch" system property.
+
+DEPRECATIONS
+============
+
+The Apache Commons Community has recently set up the Commons Text component
+as a home for algorithms working on strings. For this reason most of the string
+focused functionality in Commons Lang has been deprecated and moved to
+Commons Text. This includes:
+
+o All classes in the org.apache.commons.lang3.text and the
+ org.apache.commons.lang3.text.translate packages
+o org.apache.commons.lang3.StringEscapeUtils
+o org.apache.commons.lang3.RandomStringUtils
+o The methods org.apache.commons.lang3.StringUtils.getJaroWinklerDistance and
+ org.apache.commons.lang3.StringUtils.getLevenshteinDistance
+
+For more information see the Commons Text website:
+
+ https://commons.apache.org/text
+
+The class org.apache.commons.lang3.CharEncoding has been deprecated in favor of
+java.nio.charset.StandardCharsets.
+
+The following methods have been deprecated in
+org.apache.commons.lang3.ArrayUtils in favor of the corresponding insert
+methods. Note that the handling for null inputs differs between add and insert.
+
+o add(boolean[], int, boolean) -> insert(int, boolean[], boolean...)
+o add(byte[], int, boolean) -> insert(int, byte[], byte...)
+o add(char[], int, boolean) -> insert(int, char[], char...)
+o add(double[], int, boolean) -> insert(int, double[], double...)
+o add(float[], int, boolean) -> insert(int, float[], float...)
+o add(int[], int, boolean) -> insert(int, int[], int...)
+o add(long[], int, boolean) -> insert(int, long[], long...)
+o add(short[], int, boolean) -> insert(int, short[], short...)
+o add(T[], int, boolean) -> insert(int, T[], T...)
+
+
+COMPATIBILITY WITH JAVA 9
+==================
+
+The MANIFEST.MF now contains an additional entry:
+
+ Automatic-Module-Name: org.apache.commons.lang3
+
+This should make it possible to use Commons Lang 3.6 as a module in the Java 9
+module system. For more information see the corresponding issue and the
+referenced mailing list discussions:
+
+ https://issues.apache.org/jira/browse/LANG-1338
+
+The build problems present in the 3.5 release have been resolved. Building
+Commons Lang 3.6 should work out of the box with the latest Java 9 EA build.
+Please report any Java 9 related issues at:
+
+ https://issues.apache.org/jira/browse/LANG
+
+NEW FEATURES
+============
+
+o LANG-1336: Add NUL Byte To CharUtils. Thanks to Beluga Behr.
+o LANG-1304: Add method in StringUtils to determine if string contains both
+ mixed cased characters. Thanks to Andy Klimczak.
+o LANG-1325: Increase test coverage of ToStringBuilder class to 100%.
+ Thanks to Arshad Basha.
+o LANG-1307: Add a method in StringUtils to extract only digits out of input
+ string. Thanks to Arshad Basha.
+o LANG-1256: Add JMH maven dependencies. Thanks to C0rWin.
+o LANG-1167: Add null filter to ReflectionToStringBuilder.
+ Thanks to Mark Dacek.
+o LANG-1299: Add method for converting string to an array of code points.
+o LANG-660: Add methods to insert arrays into arrays at an index.
+o LANG-1034: Add support for recursive comparison to
+ EqualsBuilder#reflectionEquals. Thanks to Yathos UG.
+o LANG-1067: Add a reflection-based variant of DiffBuilder.
+o LANG-740: Implementation of a Memoizer. Thanks to James Sawle.
+o LANG-1258: Add ArrayUtils#toStringArray method.
+ Thanks to IG, Grzegorz Ro?niecki.
+o LANG-1160: StringUtils#abbreviate should support 'custom ellipses' parameter.
+o LANG-1293: Add StringUtils#isAllEmpty and #isAllBlank methods.
+ Thanks to Pierre Templier, Martin Tarjanyi.
+o LANG-1313: Add ArchUtils - An utility class for the "os.arch" system property.
+ Thanks to Tomschi.
+o LANG-1272: Add shuffle methods to ArrayUtils.
+o LANG-1317: Add MethodUtils#findAnnotation and extend
+ MethodUtils#getMethodsWithAnnotation for non-public, super-class
+ and interface methods. Thanks to Yasser Zamani.
+o LANG-1331: Add ImmutablePair.nullPair().
+o LANG-1332: Add ImmutableTriple.nullTriple().
+
+FIXED BUGS
+==========
+
+o LANG-1337: Fix test failures in IBM JDK 8 for ToStringBuilderTest.
+o LANG-1319: MultilineRecursiveToStringStyle StackOverflowError when object is
+ an array.
+o LANG-1320: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code followed by variant.
+o LANG-1300: Clarify or improve behavior of int-based indexOf methods in
+ StringUtils. Thanks to Mark Dacek.
+o LANG-1286: RandomStringUtils random method can overflow and return characters
+ outside of specified range.
+o LANG-1292: WordUtils.wrap throws StringIndexOutOfBoundsException.
+o LANG-1287: RandomStringUtils#random can enter infinite loop if end parameter
+ is to small. Thanks to Ivan Morozov.
+o LANG-1285: NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to Francesco Chicchiricc.
+o LANG-1281: Javadoc of StringUtils.ordinalIndexOf is contradictory.
+ Thanks to Andreas Lundblad.
+o LANG-1188: StringUtils#join(T...): warning: [unchecked] Possible heap
+ pollution from parameterized vararg type T.
+o LANG-1144: Multiple calls of
+ org.apache.commons.lang3.concurrent.LazyInitializer.initialize()
+ are possible. Thanks to Waldemar Maier, Gary Gregory.
+o LANG-1276: StrBuilder#replaceAll ArrayIndexOutOfBoundsException.
+ Thanks to Andy Klimczak.
+o LANG-1278: BooleanUtils javadoc issues. Thanks to Duke Yin.
+o LANG-1070: ArrayUtils#add confusing example in javadoc.
+ Thanks to Paul Pogonyshev.
+o LANG-1271: StringUtils#isAnyEmpty and #isAnyBlank should return false for an
+ empty array. Thanks to Pierre Templier.
+o LANG-1155: Add StringUtils#unwrap. Thanks to Saif Asif, Thiago Andrade.
+o LANG-1311: TypeUtils.toString() doesn't handle primitive and Object arrays
+ correctly. Thanks to Aaron Digulla.
+o LANG-1312: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code.
+o LANG-1265: Build failures when building with Java 9 EA.
+o LANG-1314: javadoc creation broken with Java 8. Thanks to Allon Murienik.
+o LANG-1310: MethodUtils.invokeMethod throws ArrayStoreException if using
+ varargs arguments and smaller types than the method defines.
+ Thanks to Don Jeba.
+
+CHANGES
+=======
+
+o LANG-1338: Add Automatic-Module-Name MANIFEST entry for Java 9
+ compatibility.
+o LANG-1334: Deprecate CharEncoding in favour of
+ java.nio.charset.StandardCharsets.
+o LANG-1110: Implement HashSetvBitSetTest using JMH.
+ Thanks to Bruno P. Kinoshita.
+o LANG-1290: Increase test coverage of org.apache.commons.lang3.ArrayUtils.
+ Thanks to Andrii Abramov.
+o LANG-1274: StrSubstitutor should state its thread safety.
+o LANG-1277: StringUtils#getLevenshteinDistance reduce memory consumption.
+ Thanks to yufcuy.
+o LANG-1279: Update Java requirement from Java 6 to 7.
+o LANG-1143: StringUtils should use toXxxxCase(int) rather than
+ toXxxxCase(char). Thanks to sebb.
+o LANG-1297: Add SystemUtils.getHostName() API.
+o LANG-1301: Moving apache-rat-plugin configuration into pluginManagement.
+ Thanks to Karl Heinz Marbaise.
+o LANG-1316: Deprecate classes/methods moved to commons-text.
+
+=============================================================================
+
+ Release Notes for version 3.5
+
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o Added Java 9 detection to org.apache.commons.lang3.SystemUtils.
+o Support for shifting and swapping elements in
+ org.apache.commons.lang3.ArrayUtils.
+o New methods for generating random strings from different character classes
+ including alphabetic, alpha-numeric and ASCII added to
+ org.apache.commons.lang3.RandomStringUtils.
+o Numerous extensions to org.apache.commons.lang3.StringUtils including
+ null safe compare variants, more remove and replace variants, rotation and
+ truncation.
+o Added org.apache.commons.lang3.ThreadUtils - a utility class to work with
+ instances of java.lang.Thread and java.lang.ThreadGroup.
+o Added annotations @EqualsExclude, @HashCodeExclude and @ToStringExclude to
+ mark fields which should be ignored by the reflective builders in the
+ org.apache.commons.lang3.builder package.
+o Support for various modify and retrieve value use cases added to the classes
+ in org.apache.commons.lang3.mutable.
+
+COMPATIBILITY
+=============
+
+Apache Commons Lang 3.5 is binary compatible with the 3.4 release. Users
+should not experience any problems when upgrading from 3.4 to 3.5.
+
+There has been an addition to the org.apache.commons.lang3.time.DatePrinter
+interface:
+
+o Added method 'public boolean parse(java.lang.String, java.text.ParsePosition,
+ java.util.Calendar)'
+o Added method 'public java.lang.Appendable format(long, java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Date,
+ java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Calendar,
+ java.lang.Appendable)'
+
+For this reason 3.5 is not strictly source compatible to 3.4. Since the
+DatePrinter interface is not meant to be implemented by clients, this
+change it not considered to cause any problems.
+
+JAVA 9 SUPPORT
+==============
+
+Java 9 introduces a new version-string scheme. Details of this new scheme are
+documented in JEP-223 (https://openjdk.org/jeps/223). In order to support
+JEP-223 two classes had to be changed:
+
+o org.apache.commons.lang3.JavaVersion
+ deprecated enum constant JAVA_1_9
+ introduced enum constant JAVA_9
+
+o org.apache.commons.lang3.SystemUtils
+ deprecated constant IS_JAVA_1_9
+ introduced constant IS_JAVA_9
+
+For more information see LANG-1197
+(https://issues.apache.org/jira/browse/LANG-1197). All other APIs are expected
+to work with Java 9.
+
+BUILDING ON JAVA 9
+==================
+
+Java 8 introduced the Unicode Consortium's Common Locale Data Repository as
+alternative source for locale data. Java 9 will use the CLDR provider as
+default provider for locale data (see https://openjdk.org/jeps/252). This
+causes an number of locale-sensitive test in Commons Lang to fail. In order
+to build Commons Lang 3.5 on Java 9, the locale provider has to be set to
+'JRE':
+
+ mvn -Djava.locale.providers=JRE clean install
+
+We are currently investigating ways to support building on Java 9 without
+further configuration. For more information see:
+https://issues.apache.org/jira/browse/LANG-1265
+
+
+NEW FEATURES
+==============
+
+o LANG-1275: Added a tryAcquire() method to TimedSemaphore.
+o LANG-1255: Add DateUtils.toCalendar(Date, TimeZone). Thanks to Kaiyuan Wang.
+o LANG-1023: Add WordUtils.wrap overload with customizable breakable character.
+ Thanks to Marko Bekhta.
+o LANG-787: Add method removeIgnoreCase(String, String) to StringUtils. Thanks
+ to Gokul Nanthakumar C.
+o LANG-1224: Extend RandomStringUtils with methods that generate strings
+ between a min and max length. Thanks to Caleb Cushing.
+o LANG-1257: Add APIs StringUtils.wrapIfMissing(String, char|String). Thanks to
+ Gary Gregory.
+o LANG-1253: Add RandomUtils#nextBoolean() method. Thanks to adilek.
+o LANG-1085: Add a circuit breaker implementation. Thanks to Oliver Heger and
+ Bruno P. Kinoshita.
+o LANG-1013: Add StringUtils.truncate(). Thanks to Thiago Andrade.
+o LANG-1195: Enhance MethodUtils to allow invocation of private methods. Thanks
+ to Derek C. Ashmore.
+o LANG-1189: Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/
+ decrementAndGet/addAndGet in Mutable* classes. Thanks to
+ Haiyang Li and Matthew Bartenschlag.
+o LANG-1225: Add RandomStringUtils#randomGraph and #randomPrint which match
+ corresponding regular expression class. Thanks to Caleb Cushing.
+o LANG-1223: Add StopWatch#getTime(TimeUnit). Thanks to Nick Manley.
+o LANG-781: Add methods to ObjectUtils class to check for null elements in the
+ array. Thanks to Krzysztof Wolny.
+o LANG-1228: Prefer Throwable.getCause() in ExceptionUtils.getCause().
+ Thanks to Brad Hess.
+o LANG-1233: DiffBuilder add method to allow appending from a DiffResult.
+ Thanks to Nick Manley.
+o LANG-1168: Add SystemUtils.IS_OS_WINDOWS_10 property.
+ Thanks to Pascal Schumacher.
+o LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and
+ MethodUtils. Thanks to Jim Lloyd and Joe Ferner.
+o LANG-1134: Add methods to check numbers against NaN and infinite to
+ Validate. Thanks to Alan Smithee.
+o LANG-1220: Add tests for missed branches in DateUtils.
+ Thanks to Casey Scarborough.
+o LANG-1146: z/OS identification in SystemUtils.
+ Thanks to Gabor Liptak.
+o LANG-1192: FastDateFormat support of the week-year component (uppercase 'Y').
+ Thanks to Dominik Stadler.
+o LANG-1169: Add StringUtils methods to compare a string to multiple strings.
+ Thanks to Rafal Glowinski, Robert Parr and Arman Sharif.
+o LANG-1185: Add remove by regular expression methods in StringUtils.
+o LANG-1139: Add replace by regular expression methods in StringUtils.
+o LANG-1171: Add compare methods in StringUtils.
+o LANG-1174: Add sugar to RandomUtils. Thanks to Punkratz312.
+o LANG-1154: FastDateFormat APIs that use a StringBuilder. Thanks to
+ Gary Gregory.
+o LANG-1149: Ability to throw checked exceptions without declaring them. Thanks
+ to Gregory Zak.
+o LANG-1153: Implement ParsePosition api for FastDateParser.
+o LANG-1137: Add check for duplicate event listener in EventListenerSupport.
+ Thanks to Matthew Aguirre.
+o LANG-1135: Add method containsAllWords to WordUtils. Thanks to
+ Eduardo Martins.
+o LANG-1132: ReflectionToStringBuilder doesn't throw IllegalArgumentException
+ when the constructor's object param is null. Thanks to Jack Tan.
+o LANG-701: StringUtils join with var args. Thanks to James Sawle.
+o LANG-1105: Add ThreadUtils - A utility class which provides helper methods
+ related to java.lang.Thread Issue: LANG-1105. Thanks to
+ Hendrik Saly.
+o LANG-1031: Add annotations to exclude fields from ReflectionEqualsBuilder,
+ ReflectionToStringBuilder and ReflectionHashCodeBuilder. Thanks
+ to Felipe Adorno.
+o LANG-1127: Use JUnit rules to set and reset the default Locale and TimeZone.
+o LANG-1119: Add rotate(string, int) method to StringUtils. Thanks to
+ Loic Guibert.
+o LANG-1099: Add swap and shift operations for arrays to ArrayUtils. Thanks to
+ Adrian Ber.
+o LANG-1050: Change nullToEmpty methods to generics. Thanks to James Sawle.
+o LANG-1074: Add a method to ArrayUtils for removing all occurrences of a given
+ element Issue: LANG-1074. Thanks to Haiyang Li.
+
+FIXED BUGS
+============
+
+o LANG-1261: ArrayUtils.contains returns false for instances of subtypes.
+o LANG-1252: Rename NumberUtils.isNumber, isCreatable to better reflect
+ createNumber. Also, accommodated for "+" symbol as prefix in
+ isCreatable and isNumber. Thanks to Rob Tompkins.
+o LANG-1230: Remove unnecessary synchronization from registry lookup in
+ EqualsBuilder and HashCodeBuilder. Thanks to Philippe Marschall.
+o LANG-1214: Handle "void" in ClassUtils.getClass(). Thanks to Henry Tung.
+o LANG-1250: SerializationUtils#deserialize has unnecessary code and a comment
+ for that. Thanks to Glease Wang.
+o LANG-1190: TypeUtils.isAssignable throws NullPointerException when fromType
+ has type variables and toType generic superclass specifies type
+ variable. Thanks to Pascal Schumacher.
+o LANG-1226: StringUtils#normalizeSpace does not trim the string anymore.
+ Thanks to Pascal Schumacher.
+o LANG-1251: SerializationUtils.ClassLoaderAwareObjectInputStream should use
+ static initializer to initialize primitiveTypes map. Thanks to
+ Takuya Ueshin.
+o LANG-1248: FastDatePrinter Memory allocation regression. Thanks to
+ Benoit Wiart.
+o LANG-1018: Fix precision loss on NumberUtils.createNumber(String). Thanks to
+ Nick Manley.
+o LANG-1199: Fix implementation of StringUtils.getJaroWinklerDistance(). Thanks
+ to M. Steiger.
+o LANG-1244: Fix dead links in StringUtils.getLevenshteinDistance() javadoc.
+ Thanks to jjbankert.
+o LANG-1242: "\u2284":"?" mapping missing from
+ EntityArrays#HTML40_EXTENDED_ESCAPE. Thanks to Neal Stewart.
+o LANG-901: StringUtils#startsWithAny/endsWithAny is case sensitive -
+ documented as case insensitive. Thanks to Matthew Bartenschlag.
+o LANG-1232: DiffBuilder: Add null check on fieldName when appending Object or
+ Object[]. Thanks to Nick Manley.
+o LANG-1178: ArrayUtils.removeAll(Object array, int... indices) should do the
+ clone, not its callers. Thanks to Henri Yandell.
+o LANG-1120: StringUtils.stripAccents should remove accents from "?" and "?".
+ Thanks to kaching88.
+o LANG-1205: NumberUtils.createNumber() behaves inconsistently with
+ NumberUtils.isNumber(). Thanks to pbrose.
+o LANG-1222: Fix for incorrect comment on StringUtils.containsIgnoreCase
+ method. Thanks to Adam J.
+o LANG-1221: Fix typo on appendIfMissing javadoc. Thanks to Pierre Templier.
+o LANG-1202: parseDateStrictly doesn't pass specified locale. Thanks to
+ Markus Jelsma.
+o LANG-1219: FastDateFormat doesn't respect summer daylight in some localized
+ strings. Thanks to Jarek.
+o LANG-1175: Remove Ant-based build.
+o LANG-1194: Limit max heap memory for consistent Travis CI build.
+o LANG-1186: Fix NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to NickManley.
+o LANG-1193: ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1
+ (correct answer should be 0); revert fix for LANG-1077. Thanks to
+ Qin Li.
+o LANG-1002: Several predefined ISO FastDateFormats in DateFormatUtils are
+ incorrect. Thanks to Michael Osipov.
+o LANG-1152: StringIndexOutOfBoundsException or field over-write for large year
+ fields in FastDateParser. Thanks to Pas Filip.
+o LANG-1141: StrLookup.systemPropertiesLookup() no longer reacts on changes on
+ system properties.
+o LANG-1147: EnumUtils *BitVector issue with more than 32 values Enum. Thanks
+ to Loic Guibert.
+o LANG-1059: Capitalize javadoc is incorrect. Thanks to Colin Casey.
+o LANG-1122: Inconsistent behavior of swap for malformed inputs. Thanks to
+ Adrian Ber.
+o LANG-1130: Fix critical issues reported by SonarQube.
+o LANG-1131: StrBuilder.equals(StrBuilder) doesn't check for null inputs.
+o LANG-1128: JsonToStringStyle doesn't handle chars and objects correctly.
+ Thanks to Jack Tan.
+o LANG-1126: DateFormatUtilsTest.testSMTP depends on the default Locale.
+o LANG-1123: Unit test FastDatePrinterTimeZonesTest needs a timezone set.
+ Thanks to Christian P. Momon.
+o LANG-916: DateFormatUtils.format does not correctly change Calendar
+ TimeZone in certain situations. Thanks to Christian P. Momon.
+o LANG-1116: DateUtilsTest.testLang530 fails for some timezones. Thanks to
+ Aaron Sheldon.
+o LANG-1114: TypeUtils.ParameterizedType#equals doesn't work with wildcard
+ types. Thanks to Andy Coates.
+o LANG-1118: StringUtils.repeat('z', -1) throws NegativeArraySizeException.
+ Thanks to Loic Guibert.
+o LANG-1111: Fix FindBugs warnings in DurationFormatUtils.
+o LANG-1162: StringUtils#equals fails with Index OOBE on non-Strings with
+ identical leading prefix..
+o LANG-1163: There are no tests for CharSequenceUtils.regionMatches.
+o LANG-1200: Fix Javadoc of StringUtils.ordinalIndexOf. Thanks to BarkZhang.
+o LANG-1191: Incorrect Javadoc
+ StringUtils.containsAny(CharSequence, CharSequence...). Thanks to
+ qed, Brent Worden and Gary Gregory.
+
+CHANGES
+=========
+o LANG-1197: Prepare Java 9 detection.
+o LANG-1262: CompareToBuilder.append(Object, Object, Comparator) method is too
+ big to be inlined. Thanks to Ruslan Cheremin.
+o LANG-1259: Javadoc for ArrayUtils.isNotEmpty() is slightly misleading. Thanks
+ to Dominik Stadler.
+o LANG-1247: FastDatePrinter generates extra Date objects. Thanks to
+ Benoit Wiart.
+o LANG-1229: HashCodeBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1243: Simplify ArrayUtils removeElements by using new decrementAndGet()
+ method.
+o LANG-1240: Optimize BitField constructor implementation. Thanks to zhanhb.
+o LANG-1206: Improve CharSetUtils.squeeze() performance. Thanks to
+ Mohammed Alfallaj.
+o LANG-1176: Improve ArrayUtils removeElements time complexity to O(n). Thanks
+ to Jeffery Yuan.
+o LANG-1234: getLevenshteinDistance with a threshold: optimize implementation
+ if the strings lengths differ more than the threshold. Thanks to
+ Jonatan Jnsson.
+o LANG-1151: Performance improvements for NumberUtils.isParsable. Thanks to
+ Juan Pablo Santos Rodrguez.
+o LANG-1218: EqualsBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1210: StringUtils#startsWithAny has error in Javadoc. Thanks to
+ Matthias Niehoff.
+o LANG-1208: StrSubstitutor can preserve escapes. Thanks to Samuel Karp.
+o LANG-1182: Clarify Javadoc of StringUtils.containsAny(). Thanks to
+ Larry West and Pascal Schumacher.
+o LANG-1183: Making replacePattern/removePattern methods null safe in
+ StringUtils.
+o LANG-1057: Replace StringBuilder with String concatenation for better
+ optimization. Thanks to Otvio Santana.
+o LANG-1075: Deprecate SystemUtils.FILE_SEPARATOR and
+ SystemUtils.PATH_SEPARATOR.
+o LANG-979: TypeUtils.parameterizeWithOwner - wrong format descriptor for
+ "invalid number of type parameters". Thanks to Bruno P. Kinoshita.
+o LANG-1112: MultilineRecursiveToStringStyle largely unusable due to being
+ package-private.
+o LANG-1058: StringUtils.uncapitalize performance improvement. Thanks to
+ Leo Wang.
+o LANG-1069: CharSet.getInstance documentation does not clearly explain how
+ to include negation character in set. Thanks to Arno Noordover.
+o LANG-1107: Fix parsing edge cases in FastDateParser.
+o LANG-1273: Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils. Thanks
+ to Jake Wang.
+
+=============================================================================
+
+ Release Notes for version 3.4
+
+
+COMPATIBILITY
+=============
+
+Commons Lang 3.4 is fully binary compatible to the last release and can
+therefore be used as a drop in replacement for 3.3.2. Note that the value of
+org.apache.commons.lang3.time.DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN
+has changed, which may affect clients using the constant. Furthermore the
+constant is used internally in
+o DurationFormatUtils.formatDurationISO(long)
+o DurationFormatUtils.formatPeriodISO(long, long)
+
+For more information see https://issues.apache.org/jira/browse/LANG-1000.
+
+NEW FEATURES
+==============
+
+o LANG-821: Support OS X versions in SystemUtils. Thanks to Timo Kockert.
+o LANG-1103: Add SystemUtils.IS_JAVA_1_9
+o LANG-1093: Add ClassUtils.getAbbreviatedName(). Thanks to Fabian Lange.
+o LANG-1082: Add option to disable the "objectsTriviallyEqual" test in
+ DiffBuilder. Thanks to Jonathan Baker.
+o LANG-1015: Add JsonToStringStyle implementation to ToStringStyle. Thanks to
+ Thiago Andrade.
+o LANG-1080: Add NoClassNameToStringStyle implementation of ToStringStyle.
+ Thanks to Innokenty Shuvalov.
+o LANG-883: Add StringUtils.containsAny(CharSequence, CharSequence...) method.
+ Thanks to Daniel Stewart.
+o LANG-1052: Multiline recursive to string style. Thanks to Jan Matrne.
+o LANG-536: Add isSorted() to ArrayUtils. Thanks to James Sawle.
+o LANG-1033: Add StringUtils.countMatches(CharSequence, char)
+o LANG-1021: Provide methods to retrieve all fields/methods annotated with a
+ specific type. Thanks to Alexander Mller.
+o LANG-1016: NumberUtils#isParsable method(s). Thanks to
+ Juan Pablo Santos Rodrguez.
+o LANG-999: Add fuzzy String matching logic to StringUtils. Thanks to
+ Ben Ripkens.
+o LANG-994: Add zero copy read method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-993: Add zero copy write method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-1044: Add method MethodUtils.invokeExactMethod(Object, String)
+o LANG-1045: Add method MethodUtils.invokeMethod(Object, String)
+
+FIXED BUGS
+============
+
+o LANG-794: SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect. Thanks to
+ Timo Kockert.
+o LANG-1104: Parse test fails for TimeZone America/Sao_Paulo
+o LANG-948: Exception while using ExtendedMessageFormat and escaping braces.
+ Thanks to Andrey Khobnya.
+o LANG-1092: Wrong formatting of time zones with daylight saving time in
+ FastDatePrinter
+o LANG-1090: FastDateParser does not set error indication in ParsePosition
+o LANG-1089: FastDateParser does not handle excess hours as per
+ SimpleDateFormat
+o LANG-1061: FastDateParser error - timezones not handled correctly. Thanks to
+ dmeneses.
+o LANG-1087: NumberUtils#createNumber() returns positive BigDecimal when
+ negative Float is expected. Thanks to Renat Zhilkibaev.
+o LANG-1081: DiffBuilder.append(String, Object left, Object right) does not do
+ a left.equals(right) check. Thanks to Jonathan Baker.
+o LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently.
+ Thanks to Jonathan Baker.
+o LANG-1083: Add (T) casts to get unit tests to pass in old JDK. Thanks to
+ Jonathan Baker.
+o LANG-1073: Read wrong component type of array in add in ArrayUtils.
+ Thanks to haiyang li.
+o LANG-1077: StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils.
+ Thanks to haiyang li.
+o LANG-1072: Duplicated "0x" check in createBigInteger in NumberUtils. Thanks
+ to haiyang li.
+o LANG-1064: StringUtils.abbreviate description doesn't agree with the
+ examples. Thanks to B.J. Herbison.
+o LANG-1041: Fix MethodUtilsTest so it does not depend on JDK method ordering.
+ Thanks to Alexandre Bartel.
+o LANG-1000: ParseException when trying to parse UTC dates with Z as zone
+ designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
+o LANG-1035: Javadoc for EqualsBuilder.reflectionEquals() is unclear
+o LANG-1001: ISO 8601 misspelled throughout the Javadocs. Thanks to
+ Michael Osipov.
+o LANG-1088: FastDateParser should be case insensitive
+o LANG-995: Fix bug with stripping spaces on last line in WordUtils.wrap().
+ Thanks to Andrey Khobnya.
+
+CHANGES
+=========
+
+o LANG-1102: Make logic for comparing OS versions in SystemUtils smarter
+o LANG-1091: Shutdown thread pools in test cases. Thanks to Fabian Lange.
+o LANG-1101: FastDateParser and FastDatePrinter support 'X' format
+o LANG-1100: Avoid memory allocation when using date formatting to StringBuffer.
+ Thanks to mbracher.
+o LANG-935: Possible performance improvement on string escape functions.
+ Thanks to Fabian Lange, Thomas Neidhart.
+o LANG-1098: Avoid String allocation in StrBuilder.append(CharSequence). Thanks
+ to Mikhail Mazurskiy, Fabian Lange.
+o LANG-1098: Update maven-checkstyle-plugin to 2.14. Thanks to Micha? Kordas.
+o LANG-1097: Update org.easymock:easymock to 3.3.1. Thanks to Micha? Kordas.
+o LANG-1096: Update maven-pmd-plugin to 3.4. Thanks to Micha? Kordas.
+o LANG-1095: Update maven-antrun-plugin to 1.8. Thanks to Micha? Kordas.
+o LANG-877: Performance improvements for StringEscapeUtils. Thanks to
+ Fabian Lange.
+o LANG-1071: Fix wrong examples in Javadoc of
+ StringUtils.replaceEachRepeatedly(...),
+ StringUtils.replaceEach(...) Thanks to Arno Noordover.
+o LANG-827: CompareToBuilder's doc doesn't specify precedence of fields it
+ uses in performing comparisons
+o LANG-1020: Improve performance of normalize space. Thanks to Libor Ondrusek.
+o LANG-1027: org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should
+ return true by default
+o LANG-1026: Bring static method references in StringUtils to consistent style.
+ Thanks to Alex Yursha.
+o LANG-1017: Use non-ASCII digits in Javadoc examples for
+ StringUtils.isNumeric. Thanks to Christoph Schneegans.
+o LANG-1008: Change min/max methods in NumberUtils/IEEE754rUtils from array
+ input parameters to varargs. Thanks to Thiago Andrade.
+o LANG-1006: Add wrap (with String or char) to StringUtils. Thanks to
+ Thiago Andrade.
+o LANG-1005: Extend DurationFormatUtils#formatDurationISO default pattern to
+ match #formatDurationHMS. Thanks to Michael Osipov.
+o LANG-1007: Fixing NumberUtils JAVADoc comments for max methods. Thanks to
+ Thiago Andrade.
+o LANG-731: Better Javadoc for BitField class
+o LANG-1004: DurationFormatUtils#formatDurationHMS implementation does not
+ correspond to Javadoc and vice versa. Thanks to Michael Osipov.
+o LANG-1003: DurationFormatUtils are not able to handle negative
+ durations/periods
+o LANG-998: Javadoc is not clear on preferred pattern to instantiate
+ FastDateParser / FastDatePrinter
+
+=============================================================================
+
+ Release Notes for version 3.3.2
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+=============================================================================
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+=============================================================================
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Gtz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernndez.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+=============================================================================
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+=============================================================================
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skytt.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+=============================================================================
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+=============================================================================
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12 hour clock and not
+ 24 hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/resources/release-notes/RELEASE-NOTES-3.9.txt b/src/site/resources/release-notes/RELEASE-NOTES-3.9.txt
new file mode 100644
index 000000000..613847d80
--- /dev/null
+++ b/src/site/resources/release-notes/RELEASE-NOTES-3.9.txt
@@ -0,0 +1,1293 @@
+ Apache Commons Lang
+ Version 3.9
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.9 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.9 and onwards now targets Java 8, making use of features that arrived with Java 8.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 8, supports Java 9, 10, 11
+
+Changes in this version include:
+
+New features:
+o LANG-1442: Javadoc pointing to Commons RNG.
+o Adding the Functions class.
+o LANG-1411: Add isEmpty method to ObjectUtils Thanks to Alexander Tsvetkov.
+o LANG-1422: Add null-safe StringUtils.valueOf(char[]) to delegate to String.valueOf(char[])
+o LANG-1427: Add API org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost(JavaVersion)
+
+
+Changes:
+o LANG-1416: Add more SystemUtils.IS_JAVA_XX variants.
+o LANG-1416: Update to JUnit 5
+o LANG-1417: Add @FunctionalInterface to ThreadPredicate and ThreadGroupPredicate
+o LANG-1415: Update Java Language requirement to 1.8
+o LANG-1436: Consolidate the StringUtils equals and equalsIgnoreCase Javadoc and implementation
+o (doc) Fix javadoc for 'startIndex' parameter of StringUtils.join() methods. GitHub PR #412. Thanks to Andrei Troie aft90.
+
+
+Historical list of changes: https://commons.apache.org/proper/commons-lang/changes-report.html
+
+For complete information on Apache Commons Lang, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Lang website:
+
+https://commons.apache.org/proper/commons-lang/
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.8.1
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.8.1 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 7.0, making use of features that arrived with Java 7.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+This release is a bugfix for Restoring Bundle-SymbolicName in the MANIFEST.mf file.
+
+Changes in this version include:
+
+
+Fixed Bugs:
+o LANG-1419: Restore BundleSymbolicName for OSGi
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.8
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.8 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 7.0, making use of features that arrived with Java 7.0.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1352: EnumUtils.getEnumIgnoreCase and isValidEnumIgnoreCase methods added Thanks to Ruslan Sibgatullin.
+o LANG-1372: Add ToStringSummary annotation Thanks to Srgio Ozaki.
+o LANG-1356: Add bypass option for classes to recursive and reflective EqualsBuilder Thanks to Yathos UG.
+o LANG-1391: Improve Javadoc for StringUtils.isAnyEmpty(null) Thanks to Sauro Matulli, Oleg Chubaryov.
+o LANG-1393: Add API SystemUtils.String getEnvironmentVariable(final String name, final String defaultValue) Thanks to Gary Gregory.
+o LANG-1394: org.apache.commons.lang3.SystemUtils should not write to System.err. Thanks to Sebb, Gary Gregory.
+o LANG-1238: Add RegexUtils class instead of overloading methods in StringUtils that take a regex to take precompiled Pattern. Thanks to Christopher Cordeiro, Gary Gregory, Bruno P. Kinoshita, Oleg Chubaryov.
+o LANG-1390: StringUtils.join() with support for List<?> with configurable start/end indices. Thanks to Jochen Schalanda.
+o LANG-1392: Methods for getting first non empty or non blank value Thanks to Jeff Nelson.
+o LANG-1408: Rounding utilities for converting to BigDecimal
+
+Fixed Bugs:
+o LANG-1380: FastDateParser too strict on abbreviated short month symbols Thanks to Markus Jelsma.
+o LANG-1396: JsonToStringStyle does not escape string names
+o LANG-1395: JsonToStringStyle does not escape double quote in a string value Thanks to Jim Gan.
+o LANG-1384: New Java version ("11") must be handled Thanks to Ian Young.
+o LANG-1364: ExceptionUtils#getRootCause(Throwable t) should return t if no lower level cause exists Thanks to Zheng Xie.
+o LANG-1060: NumberUtils.isNumber assumes number starting with Zero Thanks to Piotr Kosmala.
+o LANG-1375: defaultString(final String str) in StringUtils to reuse defaultString(final String str, final String defaultStr) Thanks to Jerry Zhao.
+o LANG-1374: Parsing Json Array failed Thanks to Jaswanth Bala.
+o LANG-1371: Fix TypeUtils#parameterize to work correctly with narrower-typed array Thanks to Dmitry Ovchinnikov.
+o LANG-1370: Fix EventCountCircuitBreaker increment batch Thanks to Andre Dieb.
+o LANG-1385: NumberUtils.createNumber() throws StringIndexOutOfBoundsException instead of NumberFormatException Thanks to Rohan Padhye.
+o LANG-1397: WordUtils.wrap throws StringIndexOutOfBoundsException when wrapLength is Integer.MAX_VALUE. Thanks to Takanobu Asanuma.
+o LANG-1401: Typo in JavaDoc for lastIndexOf Thanks to Roman Golyshev, Alex Mamedov.
+
+Changes:
+o LANG-1367: ObjectUtils.identityToString(Object) and friends should allocate builders and buffers with a size Thanks to Gary Gregory.
+o LANG-1405: Remove checks for java versions below the minimum supported one Thanks to Lars Grefer.
+o LANG-1402: Null/index safe get methods for ArrayUtils Thanks to Mark Dacek.
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.7
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.7 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+Apache Commons Lang, a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang.
+
+New features and bug fixes. Requires Java 7, supports Java 8, 9, 10.
+
+Changes in this version include:
+
+New features:
+o LANG-1355: TimeZone.getTimeZone() in FastDateParser causes resource contention (PR #296.) Thanks to Chas Honton.
+o LANG-1360: Add methods to ObjectUtils to get various forms of class names in a null-safe manner Thanks to Gary Gregory.
+
+Fixed Bugs:
+o LANG-1362: Fix tests DateUtilsTest for Java 9 with en_GB locale Thanks to Stephen Colebourne.
+o LANG-1365: Fix NullPointerException in isJavaVersionAtLeast on Java 10, add SystemUtils.IS_JAVA_10, add JavaVersion.JAVA_10 Thanks to Gary Gregory.
+o LANG-1348: StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf Thanks to mbusso.
+o LANG-1350: ConstructorUtils.invokeConstructor(Class, Object...) regression Thanks to Brett Kail.
+o LANG-1349: EqualsBuilder#isRegistered: swappedPair construction bug Thanks to Naman Nigam.
+o LANG-1357: org.apache.commons.lang3.time.FastDateParser should use toUpperCase(Locale) Thanks to BruceKuiLiu.
+
+Changes:
+o LANG-1358: Improve StringUtils#replace throughput Thanks to Stephane Landelle.
+o LANG-1346: Remove deprecation from RandomStringUtils
+o LANG-1361: ExceptionUtils.getThrowableList() is using deprecated ExceptionUtils.getCause() Thanks to Ana.
+
+
+=============================================================================
+
+ Apache Commons Lang
+ Version 3.6
+ Release Notes
+
+
+INTRODUCTION:
+
+This document contains the release notes for the 3.6 version of
+Apache Commons Lang as well as a history all changes in the Commons Lang 3.x
+release line. Commons Lang is a set of utility functions and reusable
+components that should be of use in any Java environment. Commons Lang 3.6 at
+least requires Java 7.0. Note that this has changed from Commons Lang 3.5, which
+only required Java 1.6.
+
+For the advice on upgrading from 2.x to 3.x, see the following page:
+
+ https://commons.apache.org/lang/article3_0.html
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o The class org.apache.commons.lang3.concurrent.Memoizer is an implementation
+ of the Memoizer pattern as shown in
+ Goetz, Brian et al. (2006) - Java Concurrency in Practice, p. 108.
+o The class org.apache.commons.lang3.ArchUtils has been added. ArchUtils is
+ a utility class for the "os.arch" system property.
+
+DEPRECATIONS
+============
+
+The Apache Commons Community has recently set up the Commons Text component
+as a home for algorithms working on strings. For this reason most of the string
+focused functionality in Commons Lang has been deprecated and moved to
+Commons Text. This includes:
+
+o All classes in the org.apache.commons.lang3.text and the
+ org.apache.commons.lang3.text.translate packages
+o org.apache.commons.lang3.StringEscapeUtils
+o org.apache.commons.lang3.RandomStringUtils
+o The methods org.apache.commons.lang3.StringUtils.getJaroWinklerDistance and
+ org.apache.commons.lang3.StringUtils.getLevenshteinDistance
+
+For more information see the Commons Text website:
+
+ https://commons.apache.org/text
+
+The class org.apache.commons.lang3.CharEncoding has been deprecated in favor of
+java.nio.charset.StandardCharsets.
+
+The following methods have been deprecated in
+org.apache.commons.lang3.ArrayUtils in favor of the corresponding insert
+methods. Note that the handling for null inputs differs between add and insert.
+
+o add(boolean[], int, boolean) -> insert(int, boolean[], boolean...)
+o add(byte[], int, boolean) -> insert(int, byte[], byte...)
+o add(char[], int, boolean) -> insert(int, char[], char...)
+o add(double[], int, boolean) -> insert(int, double[], double...)
+o add(float[], int, boolean) -> insert(int, float[], float...)
+o add(int[], int, boolean) -> insert(int, int[], int...)
+o add(long[], int, boolean) -> insert(int, long[], long...)
+o add(short[], int, boolean) -> insert(int, short[], short...)
+o add(T[], int, boolean) -> insert(int, T[], T...)
+
+
+COMPATIBILITY WITH JAVA 9
+==================
+
+The MANIFEST.MF now contains an additional entry:
+
+ Automatic-Module-Name: org.apache.commons.lang3
+
+This should make it possible to use Commons Lang 3.6 as a module in the Java 9
+module system. For more information see the corresponding issue and the
+referenced mailing list discussions:
+
+ https://issues.apache.org/jira/browse/LANG-1338
+
+The build problems present in the 3.5 release have been resolved. Building
+Commons Lang 3.6 should work out of the box with the latest Java 9 EA build.
+Please report any Java 9 related issues at:
+
+ https://issues.apache.org/jira/browse/LANG
+
+NEW FEATURES
+============
+
+o LANG-1336: Add NUL Byte To CharUtils. Thanks to Beluga Behr.
+o LANG-1304: Add method in StringUtils to determine if string contains both
+ mixed cased characters. Thanks to Andy Klimczak.
+o LANG-1325: Increase test coverage of ToStringBuilder class to 100%.
+ Thanks to Arshad Basha.
+o LANG-1307: Add a method in StringUtils to extract only digits out of input
+ string. Thanks to Arshad Basha.
+o LANG-1256: Add JMH maven dependencies. Thanks to C0rWin.
+o LANG-1167: Add null filter to ReflectionToStringBuilder.
+ Thanks to Mark Dacek.
+o LANG-1299: Add method for converting string to an array of code points.
+o LANG-660: Add methods to insert arrays into arrays at an index.
+o LANG-1034: Add support for recursive comparison to
+ EqualsBuilder#reflectionEquals. Thanks to Yathos UG.
+o LANG-1067: Add a reflection-based variant of DiffBuilder.
+o LANG-740: Implementation of a Memoizer. Thanks to James Sawle.
+o LANG-1258: Add ArrayUtils#toStringArray method.
+ Thanks to IG, Grzegorz Ro?niecki.
+o LANG-1160: StringUtils#abbreviate should support 'custom ellipses' parameter.
+o LANG-1293: Add StringUtils#isAllEmpty and #isAllBlank methods.
+ Thanks to Pierre Templier, Martin Tarjanyi.
+o LANG-1313: Add ArchUtils - An utility class for the "os.arch" system property.
+ Thanks to Tomschi.
+o LANG-1272: Add shuffle methods to ArrayUtils.
+o LANG-1317: Add MethodUtils#findAnnotation and extend
+ MethodUtils#getMethodsWithAnnotation for non-public, super-class
+ and interface methods. Thanks to Yasser Zamani.
+o LANG-1331: Add ImmutablePair.nullPair().
+o LANG-1332: Add ImmutableTriple.nullTriple().
+
+FIXED BUGS
+==========
+
+o LANG-1337: Fix test failures in IBM JDK 8 for ToStringBuilderTest.
+o LANG-1319: MultilineRecursiveToStringStyle StackOverflowError when object is
+ an array.
+o LANG-1320: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code followed by variant.
+o LANG-1300: Clarify or improve behavior of int-based indexOf methods in
+ StringUtils. Thanks to Mark Dacek.
+o LANG-1286: RandomStringUtils random method can overflow and return characters
+ outside of specified range.
+o LANG-1292: WordUtils.wrap throws StringIndexOutOfBoundsException.
+o LANG-1287: RandomStringUtils#random can enter infinite loop if end parameter
+ is to small. Thanks to Ivan Morozov.
+o LANG-1285: NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to Francesco Chicchiricc.
+o LANG-1281: Javadoc of StringUtils.ordinalIndexOf is contradictory.
+ Thanks to Andreas Lundblad.
+o LANG-1188: StringUtils#join(T...): warning: [unchecked] Possible heap
+ pollution from parameterized vararg type T.
+o LANG-1144: Multiple calls of
+ org.apache.commons.lang3.concurrent.LazyInitializer.initialize()
+ are possible. Thanks to Waldemar Maier, Gary Gregory.
+o LANG-1276: StrBuilder#replaceAll ArrayIndexOutOfBoundsException.
+ Thanks to Andy Klimczak.
+o LANG-1278: BooleanUtils javadoc issues. Thanks to Duke Yin.
+o LANG-1070: ArrayUtils#add confusing example in javadoc.
+ Thanks to Paul Pogonyshev.
+o LANG-1271: StringUtils#isAnyEmpty and #isAnyBlank should return false for an
+ empty array. Thanks to Pierre Templier.
+o LANG-1155: Add StringUtils#unwrap. Thanks to Saif Asif, Thiago Andrade.
+o LANG-1311: TypeUtils.toString() doesn't handle primitive and Object arrays
+ correctly. Thanks to Aaron Digulla.
+o LANG-1312: LocaleUtils#toLocale does not support language followed by UN M.49
+ numeric-3 area code.
+o LANG-1265: Build failures when building with Java 9 EA.
+o LANG-1314: javadoc creation broken with Java 8. Thanks to Allon Murienik.
+o LANG-1310: MethodUtils.invokeMethod throws ArrayStoreException if using
+ varargs arguments and smaller types than the method defines.
+ Thanks to Don Jeba.
+
+CHANGES
+=======
+
+o LANG-1338: Add Automatic-Module-Name MANIFEST entry for Java 9
+ compatibility.
+o LANG-1334: Deprecate CharEncoding in favour of
+ java.nio.charset.StandardCharsets.
+o LANG-1110: Implement HashSetvBitSetTest using JMH.
+ Thanks to Bruno P. Kinoshita.
+o LANG-1290: Increase test coverage of org.apache.commons.lang3.ArrayUtils.
+ Thanks to Andrii Abramov.
+o LANG-1274: StrSubstitutor should state its thread safety.
+o LANG-1277: StringUtils#getLevenshteinDistance reduce memory consumption.
+ Thanks to yufcuy.
+o LANG-1279: Update Java requirement from Java 6 to 7.
+o LANG-1143: StringUtils should use toXxxxCase(int) rather than
+ toXxxxCase(char). Thanks to sebb.
+o LANG-1297: Add SystemUtils.getHostName() API.
+o LANG-1301: Moving apache-rat-plugin configuration into pluginManagement.
+ Thanks to Karl Heinz Marbaise.
+o LANG-1316: Deprecate classes/methods moved to commons-text.
+
+=============================================================================
+
+ Release Notes for version 3.5
+
+
+HIGHLIGHTS
+==========
+
+Some of the highlights in this release include:
+
+o Added Java 9 detection to org.apache.commons.lang3.SystemUtils.
+o Support for shifting and swapping elements in
+ org.apache.commons.lang3.ArrayUtils.
+o New methods for generating random strings from different character classes
+ including alphabetic, alpha-numeric and ASCII added to
+ org.apache.commons.lang3.RandomStringUtils.
+o Numerous extensions to org.apache.commons.lang3.StringUtils including
+ null safe compare variants, more remove and replace variants, rotation and
+ truncation.
+o Added org.apache.commons.lang3.ThreadUtils - a utility class to work with
+ instances of java.lang.Thread and java.lang.ThreadGroup.
+o Added annotations @EqualsExclude, @HashCodeExclude and @ToStringExclude to
+ mark fields which should be ignored by the reflective builders in the
+ org.apache.commons.lang3.builder package.
+o Support for various modify and retrieve value use cases added to the classes
+ in org.apache.commons.lang3.mutable.
+
+COMPATIBILITY
+=============
+
+Apache Commons Lang 3.5 is binary compatible with the 3.4 release. Users
+should not experience any problems when upgrading from 3.4 to 3.5.
+
+There has been an addition to the org.apache.commons.lang3.time.DatePrinter
+interface:
+
+o Added method 'public boolean parse(java.lang.String, java.text.ParsePosition,
+ java.util.Calendar)'
+o Added method 'public java.lang.Appendable format(long, java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Date,
+ java.lang.Appendable)'
+o Added method 'public java.lang.Appendable format(java.util.Calendar,
+ java.lang.Appendable)'
+
+For this reason 3.5 is not strictly source compatible to 3.4. Since the
+DatePrinter interface is not meant to be implemented by clients, this
+change it not considered to cause any problems.
+
+JAVA 9 SUPPORT
+==============
+
+Java 9 introduces a new version-string scheme. Details of this new scheme are
+documented in JEP-223 (https://openjdk.org/jeps/223). In order to support
+JEP-223 two classes had to be changed:
+
+o org.apache.commons.lang3.JavaVersion
+ deprecated enum constant JAVA_1_9
+ introduced enum constant JAVA_9
+
+o org.apache.commons.lang3.SystemUtils
+ deprecated constant IS_JAVA_1_9
+ introduced constant IS_JAVA_9
+
+For more information see LANG-1197
+(https://issues.apache.org/jira/browse/LANG-1197). All other APIs are expected
+to work with Java 9.
+
+BUILDING ON JAVA 9
+==================
+
+Java 8 introduced the Unicode Consortium's Common Locale Data Repository as
+alternative source for locale data. Java 9 will use the CLDR provider as
+default provider for locale data (see https://openjdk.org/jeps/252). This
+causes an number of locale-sensitive test in Commons Lang to fail. In order
+to build Commons Lang 3.5 on Java 9, the locale provider has to be set to
+'JRE':
+
+ mvn -Djava.locale.providers=JRE clean install
+
+We are currently investigating ways to support building on Java 9 without
+further configuration. For more information see:
+https://issues.apache.org/jira/browse/LANG-1265
+
+
+NEW FEATURES
+==============
+
+o LANG-1275: Added a tryAcquire() method to TimedSemaphore.
+o LANG-1255: Add DateUtils.toCalendar(Date, TimeZone). Thanks to Kaiyuan Wang.
+o LANG-1023: Add WordUtils.wrap overload with customizable breakable character.
+ Thanks to Marko Bekhta.
+o LANG-787: Add method removeIgnoreCase(String, String) to StringUtils. Thanks
+ to Gokul Nanthakumar C.
+o LANG-1224: Extend RandomStringUtils with methods that generate strings
+ between a min and max length. Thanks to Caleb Cushing.
+o LANG-1257: Add APIs StringUtils.wrapIfMissing(String, char|String). Thanks to
+ Gary Gregory.
+o LANG-1253: Add RandomUtils#nextBoolean() method. Thanks to adilek.
+o LANG-1085: Add a circuit breaker implementation. Thanks to Oliver Heger and
+ Bruno P. Kinoshita.
+o LANG-1013: Add StringUtils.truncate(). Thanks to Thiago Andrade.
+o LANG-1195: Enhance MethodUtils to allow invocation of private methods. Thanks
+ to Derek C. Ashmore.
+o LANG-1189: Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/
+ decrementAndGet/addAndGet in Mutable* classes. Thanks to
+ Haiyang Li and Matthew Bartenschlag.
+o LANG-1225: Add RandomStringUtils#randomGraph and #randomPrint which match
+ corresponding regular expression class. Thanks to Caleb Cushing.
+o LANG-1223: Add StopWatch#getTime(TimeUnit). Thanks to Nick Manley.
+o LANG-781: Add methods to ObjectUtils class to check for null elements in the
+ array. Thanks to Krzysztof Wolny.
+o LANG-1228: Prefer Throwable.getCause() in ExceptionUtils.getCause().
+ Thanks to Brad Hess.
+o LANG-1233: DiffBuilder add method to allow appending from a DiffResult.
+ Thanks to Nick Manley.
+o LANG-1168: Add SystemUtils.IS_OS_WINDOWS_10 property.
+ Thanks to Pascal Schumacher.
+o LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and
+ MethodUtils. Thanks to Jim Lloyd and Joe Ferner.
+o LANG-1134: Add methods to check numbers against NaN and infinite to
+ Validate. Thanks to Alan Smithee.
+o LANG-1220: Add tests for missed branches in DateUtils.
+ Thanks to Casey Scarborough.
+o LANG-1146: z/OS identification in SystemUtils.
+ Thanks to Gabor Liptak.
+o LANG-1192: FastDateFormat support of the week-year component (uppercase 'Y').
+ Thanks to Dominik Stadler.
+o LANG-1169: Add StringUtils methods to compare a string to multiple strings.
+ Thanks to Rafal Glowinski, Robert Parr and Arman Sharif.
+o LANG-1185: Add remove by regular expression methods in StringUtils.
+o LANG-1139: Add replace by regular expression methods in StringUtils.
+o LANG-1171: Add compare methods in StringUtils.
+o LANG-1174: Add sugar to RandomUtils. Thanks to Punkratz312.
+o LANG-1154: FastDateFormat APIs that use a StringBuilder. Thanks to
+ Gary Gregory.
+o LANG-1149: Ability to throw checked exceptions without declaring them. Thanks
+ to Gregory Zak.
+o LANG-1153: Implement ParsePosition api for FastDateParser.
+o LANG-1137: Add check for duplicate event listener in EventListenerSupport.
+ Thanks to Matthew Aguirre.
+o LANG-1135: Add method containsAllWords to WordUtils. Thanks to
+ Eduardo Martins.
+o LANG-1132: ReflectionToStringBuilder doesn't throw IllegalArgumentException
+ when the constructor's object param is null. Thanks to Jack Tan.
+o LANG-701: StringUtils join with var args. Thanks to James Sawle.
+o LANG-1105: Add ThreadUtils - A utility class which provides helper methods
+ related to java.lang.Thread Issue: LANG-1105. Thanks to
+ Hendrik Saly.
+o LANG-1031: Add annotations to exclude fields from ReflectionEqualsBuilder,
+ ReflectionToStringBuilder and ReflectionHashCodeBuilder. Thanks
+ to Felipe Adorno.
+o LANG-1127: Use JUnit rules to set and reset the default Locale and TimeZone.
+o LANG-1119: Add rotate(string, int) method to StringUtils. Thanks to
+ Loic Guibert.
+o LANG-1099: Add swap and shift operations for arrays to ArrayUtils. Thanks to
+ Adrian Ber.
+o LANG-1050: Change nullToEmpty methods to generics. Thanks to James Sawle.
+o LANG-1074: Add a method to ArrayUtils for removing all occurrences of a given
+ element Issue: LANG-1074. Thanks to Haiyang Li.
+
+FIXED BUGS
+============
+
+o LANG-1261: ArrayUtils.contains returns false for instances of subtypes.
+o LANG-1252: Rename NumberUtils.isNumber, isCreatable to better reflect
+ createNumber. Also, accommodated for "+" symbol as prefix in
+ isCreatable and isNumber. Thanks to Rob Tompkins.
+o LANG-1230: Remove unnecessary synchronization from registry lookup in
+ EqualsBuilder and HashCodeBuilder. Thanks to Philippe Marschall.
+o LANG-1214: Handle "void" in ClassUtils.getClass(). Thanks to Henry Tung.
+o LANG-1250: SerializationUtils#deserialize has unnecessary code and a comment
+ for that. Thanks to Glease Wang.
+o LANG-1190: TypeUtils.isAssignable throws NullPointerException when fromType
+ has type variables and toType generic superclass specifies type
+ variable. Thanks to Pascal Schumacher.
+o LANG-1226: StringUtils#normalizeSpace does not trim the string anymore.
+ Thanks to Pascal Schumacher.
+o LANG-1251: SerializationUtils.ClassLoaderAwareObjectInputStream should use
+ static initializer to initialize primitiveTypes map. Thanks to
+ Takuya Ueshin.
+o LANG-1248: FastDatePrinter Memory allocation regression. Thanks to
+ Benoit Wiart.
+o LANG-1018: Fix precision loss on NumberUtils.createNumber(String). Thanks to
+ Nick Manley.
+o LANG-1199: Fix implementation of StringUtils.getJaroWinklerDistance(). Thanks
+ to M. Steiger.
+o LANG-1244: Fix dead links in StringUtils.getLevenshteinDistance() javadoc.
+ Thanks to jjbankert.
+o LANG-1242: "\u2284":"?" mapping missing from
+ EntityArrays#HTML40_EXTENDED_ESCAPE. Thanks to Neal Stewart.
+o LANG-901: StringUtils#startsWithAny/endsWithAny is case sensitive -
+ documented as case insensitive. Thanks to Matthew Bartenschlag.
+o LANG-1232: DiffBuilder: Add null check on fieldName when appending Object or
+ Object[]. Thanks to Nick Manley.
+o LANG-1178: ArrayUtils.removeAll(Object array, int... indices) should do the
+ clone, not its callers. Thanks to Henri Yandell.
+o LANG-1120: StringUtils.stripAccents should remove accents from "?" and "?".
+ Thanks to kaching88.
+o LANG-1205: NumberUtils.createNumber() behaves inconsistently with
+ NumberUtils.isNumber(). Thanks to pbrose.
+o LANG-1222: Fix for incorrect comment on StringUtils.containsIgnoreCase
+ method. Thanks to Adam J.
+o LANG-1221: Fix typo on appendIfMissing javadoc. Thanks to Pierre Templier.
+o LANG-1202: parseDateStrictly doesn't pass specified locale. Thanks to
+ Markus Jelsma.
+o LANG-1219: FastDateFormat doesn't respect summer daylight in some localized
+ strings. Thanks to Jarek.
+o LANG-1175: Remove Ant-based build.
+o LANG-1194: Limit max heap memory for consistent Travis CI build.
+o LANG-1186: Fix NullPointerException in FastDateParser$TimeZoneStrategy.
+ Thanks to NickManley.
+o LANG-1193: ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1
+ (correct answer should be 0); revert fix for LANG-1077. Thanks to
+ Qin Li.
+o LANG-1002: Several predefined ISO FastDateFormats in DateFormatUtils are
+ incorrect. Thanks to Michael Osipov.
+o LANG-1152: StringIndexOutOfBoundsException or field over-write for large year
+ fields in FastDateParser. Thanks to Pas Filip.
+o LANG-1141: StrLookup.systemPropertiesLookup() no longer reacts on changes on
+ system properties.
+o LANG-1147: EnumUtils *BitVector issue with more than 32 values Enum. Thanks
+ to Loic Guibert.
+o LANG-1059: Capitalize javadoc is incorrect. Thanks to Colin Casey.
+o LANG-1122: Inconsistent behavior of swap for malformed inputs. Thanks to
+ Adrian Ber.
+o LANG-1130: Fix critical issues reported by SonarQube.
+o LANG-1131: StrBuilder.equals(StrBuilder) doesn't check for null inputs.
+o LANG-1128: JsonToStringStyle doesn't handle chars and objects correctly.
+ Thanks to Jack Tan.
+o LANG-1126: DateFormatUtilsTest.testSMTP depends on the default Locale.
+o LANG-1123: Unit test FastDatePrinterTimeZonesTest needs a timezone set.
+ Thanks to Christian P. Momon.
+o LANG-916: DateFormatUtils.format does not correctly change Calendar
+ TimeZone in certain situations. Thanks to Christian P. Momon.
+o LANG-1116: DateUtilsTest.testLang530 fails for some timezones. Thanks to
+ Aaron Sheldon.
+o LANG-1114: TypeUtils.ParameterizedType#equals doesn't work with wildcard
+ types. Thanks to Andy Coates.
+o LANG-1118: StringUtils.repeat('z', -1) throws NegativeArraySizeException.
+ Thanks to Loic Guibert.
+o LANG-1111: Fix FindBugs warnings in DurationFormatUtils.
+o LANG-1162: StringUtils#equals fails with Index OOBE on non-Strings with
+ identical leading prefix..
+o LANG-1163: There are no tests for CharSequenceUtils.regionMatches.
+o LANG-1200: Fix Javadoc of StringUtils.ordinalIndexOf. Thanks to BarkZhang.
+o LANG-1191: Incorrect Javadoc
+ StringUtils.containsAny(CharSequence, CharSequence...). Thanks to
+ qed, Brent Worden and Gary Gregory.
+
+CHANGES
+=========
+o LANG-1197: Prepare Java 9 detection.
+o LANG-1262: CompareToBuilder.append(Object, Object, Comparator) method is too
+ big to be inlined. Thanks to Ruslan Cheremin.
+o LANG-1259: Javadoc for ArrayUtils.isNotEmpty() is slightly misleading. Thanks
+ to Dominik Stadler.
+o LANG-1247: FastDatePrinter generates extra Date objects. Thanks to
+ Benoit Wiart.
+o LANG-1229: HashCodeBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1243: Simplify ArrayUtils removeElements by using new decrementAndGet()
+ method.
+o LANG-1240: Optimize BitField constructor implementation. Thanks to zhanhb.
+o LANG-1206: Improve CharSetUtils.squeeze() performance. Thanks to
+ Mohammed Alfallaj.
+o LANG-1176: Improve ArrayUtils removeElements time complexity to O(n). Thanks
+ to Jeffery Yuan.
+o LANG-1234: getLevenshteinDistance with a threshold: optimize implementation
+ if the strings lengths differ more than the threshold. Thanks to
+ Jonatan Jnsson.
+o LANG-1151: Performance improvements for NumberUtils.isParsable. Thanks to
+ Juan Pablo Santos Rodrguez.
+o LANG-1218: EqualsBuilder.append(Object,Object) is too big to be inlined,
+ which prevents whole builder to be scalarized. Thanks to
+ Ruslan Cheremin.
+o LANG-1210: StringUtils#startsWithAny has error in Javadoc. Thanks to
+ Matthias Niehoff.
+o LANG-1208: StrSubstitutor can preserve escapes. Thanks to Samuel Karp.
+o LANG-1182: Clarify Javadoc of StringUtils.containsAny(). Thanks to
+ Larry West and Pascal Schumacher.
+o LANG-1183: Making replacePattern/removePattern methods null safe in
+ StringUtils.
+o LANG-1057: Replace StringBuilder with String concatenation for better
+ optimization. Thanks to Otvio Santana.
+o LANG-1075: Deprecate SystemUtils.FILE_SEPARATOR and
+ SystemUtils.PATH_SEPARATOR.
+o LANG-979: TypeUtils.parameterizeWithOwner - wrong format descriptor for
+ "invalid number of type parameters". Thanks to Bruno P. Kinoshita.
+o LANG-1112: MultilineRecursiveToStringStyle largely unusable due to being
+ package-private.
+o LANG-1058: StringUtils.uncapitalize performance improvement. Thanks to
+ Leo Wang.
+o LANG-1069: CharSet.getInstance documentation does not clearly explain how
+ to include negation character in set. Thanks to Arno Noordover.
+o LANG-1107: Fix parsing edge cases in FastDateParser.
+o LANG-1273: Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils. Thanks
+ to Jake Wang.
+
+=============================================================================
+
+ Release Notes for version 3.4
+
+
+COMPATIBILITY
+=============
+
+Commons Lang 3.4 is fully binary compatible to the last release and can
+therefore be used as a drop in replacement for 3.3.2. Note that the value of
+org.apache.commons.lang3.time.DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN
+has changed, which may affect clients using the constant. Furthermore the
+constant is used internally in
+o DurationFormatUtils.formatDurationISO(long)
+o DurationFormatUtils.formatPeriodISO(long, long)
+
+For more information see https://issues.apache.org/jira/browse/LANG-1000.
+
+NEW FEATURES
+==============
+
+o LANG-821: Support OS X versions in SystemUtils. Thanks to Timo Kockert.
+o LANG-1103: Add SystemUtils.IS_JAVA_1_9
+o LANG-1093: Add ClassUtils.getAbbreviatedName(). Thanks to Fabian Lange.
+o LANG-1082: Add option to disable the "objectsTriviallyEqual" test in
+ DiffBuilder. Thanks to Jonathan Baker.
+o LANG-1015: Add JsonToStringStyle implementation to ToStringStyle. Thanks to
+ Thiago Andrade.
+o LANG-1080: Add NoClassNameToStringStyle implementation of ToStringStyle.
+ Thanks to Innokenty Shuvalov.
+o LANG-883: Add StringUtils.containsAny(CharSequence, CharSequence...) method.
+ Thanks to Daniel Stewart.
+o LANG-1052: Multiline recursive to string style. Thanks to Jan Matrne.
+o LANG-536: Add isSorted() to ArrayUtils. Thanks to James Sawle.
+o LANG-1033: Add StringUtils.countMatches(CharSequence, char)
+o LANG-1021: Provide methods to retrieve all fields/methods annotated with a
+ specific type. Thanks to Alexander Mller.
+o LANG-1016: NumberUtils#isParsable method(s). Thanks to
+ Juan Pablo Santos Rodrguez.
+o LANG-999: Add fuzzy String matching logic to StringUtils. Thanks to
+ Ben Ripkens.
+o LANG-994: Add zero copy read method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-993: Add zero copy write method to StrBuilder. Thanks to
+ Mikhail Mazursky.
+o LANG-1044: Add method MethodUtils.invokeExactMethod(Object, String)
+o LANG-1045: Add method MethodUtils.invokeMethod(Object, String)
+
+FIXED BUGS
+============
+
+o LANG-794: SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect. Thanks to
+ Timo Kockert.
+o LANG-1104: Parse test fails for TimeZone America/Sao_Paulo
+o LANG-948: Exception while using ExtendedMessageFormat and escaping braces.
+ Thanks to Andrey Khobnya.
+o LANG-1092: Wrong formatting of time zones with daylight saving time in
+ FastDatePrinter
+o LANG-1090: FastDateParser does not set error indication in ParsePosition
+o LANG-1089: FastDateParser does not handle excess hours as per
+ SimpleDateFormat
+o LANG-1061: FastDateParser error - timezones not handled correctly. Thanks to
+ dmeneses.
+o LANG-1087: NumberUtils#createNumber() returns positive BigDecimal when
+ negative Float is expected. Thanks to Renat Zhilkibaev.
+o LANG-1081: DiffBuilder.append(String, Object left, Object right) does not do
+ a left.equals(right) check. Thanks to Jonathan Baker.
+o LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently.
+ Thanks to Jonathan Baker.
+o LANG-1083: Add (T) casts to get unit tests to pass in old JDK. Thanks to
+ Jonathan Baker.
+o LANG-1073: Read wrong component type of array in add in ArrayUtils.
+ Thanks to haiyang li.
+o LANG-1077: StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils.
+ Thanks to haiyang li.
+o LANG-1072: Duplicated "0x" check in createBigInteger in NumberUtils. Thanks
+ to haiyang li.
+o LANG-1064: StringUtils.abbreviate description doesn't agree with the
+ examples. Thanks to B.J. Herbison.
+o LANG-1041: Fix MethodUtilsTest so it does not depend on JDK method ordering.
+ Thanks to Alexandre Bartel.
+o LANG-1000: ParseException when trying to parse UTC dates with Z as zone
+ designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
+o LANG-1035: Javadoc for EqualsBuilder.reflectionEquals() is unclear
+o LANG-1001: ISO 8601 misspelled throughout the Javadocs. Thanks to
+ Michael Osipov.
+o LANG-1088: FastDateParser should be case insensitive
+o LANG-995: Fix bug with stripping spaces on last line in WordUtils.wrap().
+ Thanks to Andrey Khobnya.
+
+CHANGES
+=========
+
+o LANG-1102: Make logic for comparing OS versions in SystemUtils smarter
+o LANG-1091: Shutdown thread pools in test cases. Thanks to Fabian Lange.
+o LANG-1101: FastDateParser and FastDatePrinter support 'X' format
+o LANG-1100: Avoid memory allocation when using date formatting to StringBuffer.
+ Thanks to mbracher.
+o LANG-935: Possible performance improvement on string escape functions.
+ Thanks to Fabian Lange, Thomas Neidhart.
+o LANG-1098: Avoid String allocation in StrBuilder.append(CharSequence). Thanks
+ to Mikhail Mazurskiy, Fabian Lange.
+o LANG-1098: Update maven-checkstyle-plugin to 2.14. Thanks to Micha? Kordas.
+o LANG-1097: Update org.easymock:easymock to 3.3.1. Thanks to Micha? Kordas.
+o LANG-1096: Update maven-pmd-plugin to 3.4. Thanks to Micha? Kordas.
+o LANG-1095: Update maven-antrun-plugin to 1.8. Thanks to Micha? Kordas.
+o LANG-877: Performance improvements for StringEscapeUtils. Thanks to
+ Fabian Lange.
+o LANG-1071: Fix wrong examples in Javadoc of
+ StringUtils.replaceEachRepeatedly(...),
+ StringUtils.replaceEach(...) Thanks to Arno Noordover.
+o LANG-827: CompareToBuilder's doc doesn't specify precedence of fields it
+ uses in performing comparisons
+o LANG-1020: Improve performance of normalize space. Thanks to Libor Ondrusek.
+o LANG-1027: org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should
+ return true by default
+o LANG-1026: Bring static method references in StringUtils to consistent style.
+ Thanks to Alex Yursha.
+o LANG-1017: Use non-ASCII digits in Javadoc examples for
+ StringUtils.isNumeric. Thanks to Christoph Schneegans.
+o LANG-1008: Change min/max methods in NumberUtils/IEEE754rUtils from array
+ input parameters to varargs. Thanks to Thiago Andrade.
+o LANG-1006: Add wrap (with String or char) to StringUtils. Thanks to
+ Thiago Andrade.
+o LANG-1005: Extend DurationFormatUtils#formatDurationISO default pattern to
+ match #formatDurationHMS. Thanks to Michael Osipov.
+o LANG-1007: Fixing NumberUtils JAVADoc comments for max methods. Thanks to
+ Thiago Andrade.
+o LANG-731: Better Javadoc for BitField class
+o LANG-1004: DurationFormatUtils#formatDurationHMS implementation does not
+ correspond to Javadoc and vice versa. Thanks to Michael Osipov.
+o LANG-1003: DurationFormatUtils are not able to handle negative
+ durations/periods
+o LANG-998: Javadoc is not clear on preferred pattern to instantiate
+ FastDateParser / FastDatePrinter
+
+=============================================================================
+
+ Release Notes for version 3.3.2
+
+NEW FEATURES
+==============
+
+o LANG-989: Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8
+
+FIXED BUGS
+============
+
+o LANG-992: NumberUtils#isNumber() returns false for "0.0", "0.4790", et al
+
+=============================================================================
+
+ Release Notes for version 3.3.1
+
+FIXED BUGS
+============
+
+o LANG-987: DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong
+ days
+o LANG-983: DurationFormatUtils does not describe format string fully
+o LANG-981: DurationFormatUtils#lexx does not detect unmatched quote char
+o LANG-984: DurationFormatUtils does not handle large durations correctly
+o LANG-982: DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field
+ size should be 4 digits
+o LANG-978: Failing tests with Java 8 b128
+
+=============================================================================
+
+ Release Notes for version 3.3
+
+NEW FEATURES
+==============
+
+o LANG-955: Add methods for removing all invalid characters according to
+ XML 1.0 and XML 1.1 in an input string to StringEscapeUtils.
+ Thanks to Adam Hooper.
+o LANG-970: Add APIs MutableBoolean setTrue() and setFalse()
+o LANG-962: Add SerializationUtils.roundtrip(T extends Serializable) to
+ serialize then deserialize
+o LANG-637: There should be a DifferenceBuilder with a
+ ReflectionDifferenceBuilder implementation
+o LANG-944: Add the Jaro-Winkler string distance algorithm to StringUtils.
+ Thanks to Rekha Joshi.
+o LANG-417: New class ClassPathUtils with methods for turning FQN into
+ resource path
+o LANG-834: Validate: add inclusiveBetween and exclusiveBetween overloads
+ for primitive types
+o LANG-900: New RandomUtils class. Thanks to Duncan Jones.
+o LANG-966: Add IBM OS/400 detection
+
+FIXED BUGS
+============
+
+o LANG-621: ReflectionToStringBuilder.toString does not debug 3rd party object
+ fields within 3rd party object. Thanks to Philip Hodges,
+ Thomas Neidhart.
+o LANG-977: NumericEntityEscaper incorrectly encodes supplementary characters.
+ Thanks to Chris Karcher.
+o LANG-973: Make some private fields final
+o LANG-971: NumberUtils#isNumber(String) fails to reject invalid Octal numbers
+o LANG-972: NumberUtils#isNumber does not allow for hex 0XABCD
+o LANG-969: StringUtils.toEncodedString(byte[], Charset) needlessly throws
+ UnsupportedEncodingException. Thanks to Matt Bishop.
+o LANG-946: ConstantInitializerTest fails when building with IBM JDK 7
+o LANG-954: uncaught PatternSyntaxException in FastDateFormat on Android.
+ Thanks to Michael Keppler.
+o LANG-936: StringUtils.getLevenshteinDistance with too big of a threshold
+ returns wrong result. Thanks to Yaniv Kunda, Eli Lindsey.
+o LANG-943: Test DurationFormatUtilsTest.testEdgeDuration fails in
+ JDK 1.6, 1.7 and 1.8, BRST time zone
+o LANG-613: ConstructorUtils.getAccessibleConstructor() Does Not Check the
+ Accessibility of Enclosing Classes
+o LANG-951: Fragments are wrong by 1 day when using fragment YEAR or MONTH.
+ Thanks to Sebastian Gtz.
+o LANG-950: FastDateParser does not handle two digit year parsing like
+ SimpleDateFormat
+o LANG-949: FastDateParserTest.testParses does not test FastDateParser
+o LANG-915: Wrong locale handling in LocaleUtils.toLocale().
+ Thanks to Sergio Fernndez.
+
+CHANGES
+=========
+
+o LANG-961: org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field)
+ does not clean up after itself
+o LANG-958: FastDateParser javadoc incorrectly states that SimpleDateFormat
+ is used internally
+o LANG-956: Improve Javadoc of WordUtils.wrap methods
+o LANG-939: Move Documentation from user guide to package-info files
+o LANG-953: Convert package.html files to package-info.java files
+o LANG-940: Fix deprecation warnings
+o LANG-819: EnumUtils.generateBitVector needs a "? extends"
+
+=============================================================================
+
+ Release Notes for version 3.2.1
+
+BUG FIXES
+===========
+
+o LANG-937: Fix missing Hamcrest dependency in Ant Build
+o LANG-941: Test failure in LocaleUtilsTest when building with JDK 8
+o LANG-942: Test failure in FastDateParserTest and FastDateFormat_ParserTest
+ when building with JDK8. Thanks to Bruno P. Kinoshita,
+ Henri Yandell.
+o LANG-938: Build fails with test failures when building with JDK 8
+
+=============================================================================
+
+ Release Notes for version 3.2
+
+COMPATIBILITY WITH 3.1
+========================
+
+This release introduces backwards incompatible changes in
+org.apache.commons.lang3.time.FastDateFormat:
+o Method 'protected java.util.List parsePattern()' has been removed
+o Method 'protected java.lang.String parseToken(java.lang.String, int[])' has
+ been removed
+o Method 'protected org.apache.commons.lang3.time.FastDateFormat$NumberRule
+ selectNumberRule(int, int)' has been removed
+
+These changes were the result of [LANG-462]. It is assumed that this change
+will not break clients as Charles Honton pointed out on 25/Jan/12:
+"
+ 1. Methods "FastDateFormat$NumberRule selectNumberRule(int, int)" and
+ "List<Rule> parsePattern()" couldn't have been overridden because
+ NumberRule and Rule were private to FastDateFormat.
+ 2. Due to the factory pattern used, it's unlikely other two methods would have
+ been overridden.
+ 3. The four methods are highly implementation specific. I consider it a
+ mistake that the methods were exposed.
+"
+For more information see https://issues.apache.org/jira/browse/LANG-462.
+
+NEW FEATURES
+==============
+
+o LANG-934: Add removeFinalModifier to FieldUtils
+o LANG-863: Method returns number of inheritance hops between parent and
+ subclass. Thanks to Daneel S. Yaitskov.
+o LANG-774: Added isStarted, isSuspended and isStopped to StopWatch.
+ Thanks to Erhan Bagdemir.
+o LANG-848: Added StringUtils.isBlank/isEmpty CharSequence... methods.
+ Thanks to Alexander Muthmann.
+o LANG-926: Added ArrayUtils.reverse(array, from, to) methods.
+o LANG-795: StringUtils.toString(byte[], String) deprecated in favour of a new
+ StringUtils.toString(byte[], CharSet). Thanks to Aaron Digulla.
+o LANG-893: StrSubstitutor now supports default values for variables.
+ Thanks to Woonsan Ko.
+o LANG-913: Adding .gitignore to commons-lang. Thanks to Allon Mureinik.
+o LANG-837: Add ObjectUtils.toIdentityString methods that support
+ StringBuilder, StrBuilder, and Appendable.
+o LANG-886: Added CharSetUtils.containsAny(String, String).
+o LANG-797: Added escape/unescapeJson to StringEscapeUtils.
+o LANG-875: Added appendIfMissing and prependIfMissing methods to StringUtils.
+o LANG-870: Add StringUtils.LF and StringUtils.CR values.
+o LANG-873: Add FieldUtils getAllFields() to return all the fields defined in
+ the given class and super classes.
+o LANG-835: StrBuilder should support StringBuilder as an input parameter.
+o LANG-857: StringIndexOutOfBoundsException in CharSequenceTranslator.
+o LANG-856: Code refactoring in NumberUtils.
+o LANG-855: NumberUtils#createBigInteger does not allow for hex and octal
+ numbers.
+o LANG-854: NumberUtils#createNumber - does not allow for hex numbers to be
+ larger than Long.
+o LANG-853: StringUtils join APIs for primitives.
+o LANG-841: Add StringUtils API to call String.replaceAll in DOTALL a.k.a.
+ single-line mode.
+o LANG-825: Create StrBuilder APIs similar to
+ String.format(String, Object...).
+o LANG-675: Add Triple class (ternary version of Pair).
+o LANG-462: FastDateFormat supports parse methods.
+
+BUG FIXES
+===========
+
+o LANG-932: Spelling fixes. Thanks to Ville Skytt.
+o LANG-929: OctalUnescaper tried to parse all of \279.
+o LANG-928: OctalUnescaper had bugs when parsing octals starting with a zero.
+o LANG-905: EqualsBuilder returned true when comparing arrays, even when the
+ elements are different.
+o LANG-917: Fixed exception when combining custom and choice format in
+ ExtendedMessageFormat. Thanks to Arne Burmeister.
+o LANG-902: RandomStringUtils.random javadoc was incorrectly promising letters
+ and numbers would, as opposed to may, appear Issue:. Thanks to
+ Andrzej Winnicki.
+o LANG-921: BooleanUtils.xor(boolean...) produces wrong results.
+o LANG-896: BooleanUtils.toBoolean(String str) javadoc is not updated. Thanks
+ to Mark Bryan Yu.
+o LANG-879: LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese"
+ of JDK7.
+o LANG-836: StrSubstitutor does not support StringBuilder or CharSequence.
+ Thanks to Arnaud Brunet.
+o LANG-693: Method createNumber from NumberUtils doesn't work for floating
+ point numbers other than Float Issue: LANG-693. Thanks to
+ Calvin Echols.
+o LANG-887: FastDateFormat does not use the locale specific cache correctly.
+o LANG-754: ClassUtils.getShortName(String) will now only do a reverse lookup
+ for array types.
+o LANG-881: NumberUtils.createNumber() Javadoc says it does not work for octal
+ numbers.
+o LANG-865: LocaleUtils.toLocale does not parse strings starting with an
+ underscore.
+o LANG-858: StringEscapeUtils.escapeJava() and escapeEcmaScript() do not
+ output the escaped surrogate pairs that are Java parsable.
+o LANG-849: FastDateFormat and FastDatePrinter generates Date objects
+ wastefully.
+o LANG-845: Spelling fixes.
+o LANG-844: Fix examples contained in javadoc of StringUtils.center methods.
+o LANG-832: FastDateParser does not handle unterminated quotes correctly.
+o LANG-831: FastDateParser does not handle white-space properly.
+o LANG-830: FastDateParser could use \Q \E to quote regexes.
+o LANG-828: FastDateParser does not handle non-Gregorian calendars properly.
+o LANG-826: FastDateParser does not handle non-ASCII digits correctly.
+o LANG-822: NumberUtils#createNumber - bad behavior for leading "--".
+o LANG-818: FastDateFormat's "z" pattern does not respect timezone of Calendar
+ instances passed to format().
+o LANG-817: Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8.
+o LANG-813: StringUtils.equalsIgnoreCase doesn't check string reference
+ equality.
+o LANG-810: StringUtils.join() endIndex, bugged for loop.
+o LANG-807: RandomStringUtils throws confusing IAE when end <= start.
+o LANG-805: RandomStringUtils.random(count, 0, 0, false, false, universe,
+ random) always throws java.lang.ArrayIndexOutOfBoundsException.
+o LANG-802: LocaleUtils - unnecessary recursive call in SyncAvoid class.
+o LANG-800: Javadoc bug in DateUtils#ceiling for Calendar and Object versions.
+o LANG-788: SerializationUtils throws ClassNotFoundException when cloning
+ primitive classes.
+o LANG-786: StringUtils equals() relies on undefined behavior.
+o LANG-783: Documentation bug: StringUtils.split.
+o LANG-777: jar contains velocity template of release notes.
+o LANG-776: TypeUtilsTest contains incorrect type assignability assertion.
+o LANG-775: TypeUtils.getTypeArguments() misses type arguments for
+ partially-assigned classes.
+o LANG-773: ImmutablePair doc contains nonsense text.
+o LANG-772: ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text.
+o LANG-765: EventListenerSupport.ProxyInvocationHandler no longer defines
+ serialVersionUID.
+o LANG-764: StrBuilder is now serializable.
+o LANG-761: Fix Javadoc Ant warnings.
+o LANG-747: NumberUtils does not handle Long Hex numbers.
+o LANG-743: Javadoc bug in static inner class DateIterator.
+
+CHANGES
+=========
+
+o LANG-931: Misleading Javadoc comment in StrBuilderReader class. Thanks
+ to Christoph Schneegans.
+o LANG-910: StringUtils.normalizeSpace now handles non-breaking spaces
+ (Unicode 00A0). Thanks to Timur Yarosh.
+o LANG-804: Redundant check for zero in HashCodeBuilder ctor. Thanks to
+ Allon Mureinik.
+o LANG-884: Simplify FastDateFormat; eliminate boxing.
+o LANG-882: LookupTranslator now works with implementations of CharSequence
+ other than String.
+o LANG-846: Provide CharSequenceUtils.regionMatches with a proper green
+ implementation instead of inefficiently converting to Strings.
+o LANG-839: ArrayUtils removeElements methods use unnecessary HashSet.
+o LANG-838: ArrayUtils removeElements methods clone temporary index arrays
+ unnecessarily.
+o LANG-799: DateUtils#parseDate uses default locale; add Locale support.
+o LANG-798: Use generics in SerializationUtils.
+
+CHANGES WITHOUT TICKET
+========================
+
+o Fixed URLs in javadoc to point to new oracle.com pages
+
+=============================================================================
+
+ Release Notes for version 3.1
+
+NEW FEATURES
+==============
+
+o LANG-801: Add Conversion utility to convert between data types on byte level
+o LANG-760: Add API StringUtils.toString(byte[] input, String charsetName)
+o LANG-756: Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and
+ isPrimitiveOrWrapper(Class<?>)
+o LANG-695: SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
+
+BUG FIXES
+===========
+
+o LANG-749: Incorrect Bundle-SymbolicName in Manifest
+o LANG-746: NumberUtils does not handle upper-case hex: 0X and -0X
+o LANG-744: StringUtils throws java.security.AccessControlException on Google
+ App Engine
+o LANG-741: Ant build has wrong component.name
+o LANG-698: Document that the Mutable numbers don't work as expected with
+ String.format
+
+CHANGES
+=========
+
+o LANG-758: Add an example with whitespace in StringUtils.defaultIfEmpty
+o LANG-752: Fix createLong() so it behaves like createInteger()
+o LANG-751: Include the actual type in the Validate.isInstance and
+ isAssignableFrom exception messages
+o LANG-748: Deprecating chomp(String, String)
+o LANG-736: CharUtils static final array CHAR_STRING is not needed to compute
+ CHAR_STRING_ARRAY
+
+=============================================================================
+
+ Release Notes for version 3.0
+
+ADDITIONS
+===========
+
+o LANG-276: MutableBigDecimal and MutableBigInteger.
+o LANG-285: Wish : method unaccent.
+o LANG-358: ObjectUtils.coalesce.
+o LANG-386: LeftOf/RightOfNumber in Range convenience methods necessary.
+o LANG-435: Add ClassUtils.isAssignable() variants with autoboxing.
+o LANG-444: StringUtils.emptyToNull.
+o LANG-482: Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+o LANG-482: StrSubstitutor now supports substitution in variable names.
+o LANG-496: A generic implementation of the Lazy initialization pattern.
+o LANG-497: Addition of ContextedException and ContextedRuntimeException.
+o LANG-498: Add StringEscapeUtils.escapeText() methods.
+o LANG-499: Add support for the handling of ExecutionExceptions.
+o LANG-501: Add support for background initialization.
+o LANG-529: Add a concurrent package.
+o LANG-533: Validate: support for validating blank strings.
+o LANG-537: Add ArrayUtils.toArray to create generic arrays.
+o LANG-545: Add ability to create a Future for a constant.
+o LANG-546: Add methods to Validate to check whether the index is valid for
+ the array/list/string.
+o LANG-553: Add TypeUtils class to provide utility code for working with generic
+ types.
+o LANG-559: Added isAssignableFrom and isInstanceOf validation methods.
+o LANG-559: Added validState validation method.
+o LANG-560: New TimedSemaphore class.
+o LANG-582: Provide an implementation of the ThreadFactory interface.
+o LANG-588: Create a basic Pair<L, R> class.
+o LANG-594: DateUtils equal & compare functions up to most significant field.
+o LANG-601: Add Builder Interface / Update Builders to Implement It.
+o LANG-609: Support lazy initialization using atomic variables
+o LANG-610: Extend exception handling in ConcurrentUtils to runtime exceptions.
+o LANG-614: StringUtils.endsWithAny method
+o LANG-640: Add normalizeSpace to StringUtils
+o LANG-644: Provide documentation about the new concurrent package
+o LANG-649: BooleanUtils.toBooleanObject to support single character input
+o LANG-651: Add AnnotationUtils
+o LANG-653: Provide a very basic ConcurrentInitializer implementation
+o LANG-655: Add StringUtils.defaultIfBlank()
+o LANG-667: Add a Null-safe compare() method to ObjectUtils
+o LANG-676: Documented potential NPE if auto-boxing occurs for some BooleanUtils
+ methods
+o LANG-678: Add support for ConcurrentMap.putIfAbsent()
+o LANG-692: Add hashCodeMulti varargs method
+o LANG-697: Add FormattableUtils class
+o LANG-684: Levenshtein Distance Within a Given Threshold
+
+REMOVALS
+==========
+
+o LANG-438: Remove @deprecateds.
+o LANG-492: Remove code handled now by the JDK.
+o LANG-493: Remove code that does not hold enough value to remain.
+o LANG-590: Remove JDK 1.2/1.3 bug handling in
+ StringUtils.indexOf(String, String, int).
+o LANG-673: WordUtils.abbreviate() removed
+o LANG-691: Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS
+==============
+
+o LANG-290: EnumUtils for JDK 5.0.
+o LANG-336: Finally start using generics.
+o LANG-355: StrBuilder should implement CharSequence and Appendable.
+o LANG-396: Investigate for vararg usages.
+o LANG-424: Improve Javadoc for StringUtils class.
+o LANG-458: Refactor Validate.java to eliminate code redundancy.
+o LANG-479: Document where in SVN trunk is.
+o LANG-504: bring ArrayUtils.isEmpty to the generics world.
+o LANG-505: Rewrite StringEscapeUtils.
+o LANG-507: StringEscapeUtils.unescapeJava should support \u+ notation.
+o LANG-510: Convert StringUtils API to take CharSequence.
+o LANG-513: Better EnumUtils.
+o LANG-528: Mutable classes should implement an appropriately typed Mutable
+ interface.
+o LANG-539: Compile commons.lang for CDC 1.1/Foundation 1.1.
+o LANG-540: Make NumericEntityEscaper immutable.
+o LANG-541: Replace StringBuffer with StringBuilder.
+o LANG-548: Use Iterable on API instead of Collection.
+o LANG-551: Replace Range classes with generic version.
+o LANG-562: Change Maven groupId.
+o LANG-563: Change Java package name.
+o LANG-570: Do the test cases really still require main() and suite() methods?
+o LANG-579: Add new Validate methods.
+o LANG-599: ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+o LANG-605: DefaultExceptionContext overwrites values in recursive situations.
+o LANG-668: Change ObjectUtils min() & max() functions to use varargs rather
+ than just two parameters
+o LANG-681: Push down WordUtils to "text" sub-package.
+o LANG-711: Add includeantruntime=false to javac targets to quell warnings in
+ ant 1.8.1 and better (and modest performance gain).
+o LANG-713: Increase test coverage of FieldUtils read methods and tweak
+ javadoc.
+o LANG-718: build.xml Java 1.5+ updates.
+
+BUG FIXES
+===========
+
+o LANG-11: Depend on JDK 1.5+.
+o LANG-302: StrBuilder does not implement clone().
+o LANG-339: StringEscapeUtils.escapeHtml() escapes multibyte characters like
+ Chinese, Japanese, etc.
+o LANG-369: ExceptionUtils not thread-safe.
+o LANG-418: Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+o LANG-428: StringUtils.isAlpha, isAlphanumeric and isNumeric now return false
+ for ""
+o LANG-439: StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+o LANG-448: Lower Ascii Characters don't get encoded by Entities.java.
+o LANG-468: JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+o LANG-474: Fixes for thread safety.
+o LANG-478: StopWatch does not resist to system time changes.
+o LANG-480: StringEscapeUtils.escapeHtml incorrectly converts unicode
+ characters above U+00FFFF into 2 characters.
+o LANG-481: Possible race-conditions in hashCode of the range classes.
+o LANG-564: Improve StrLookup API documentation.
+o LANG-568: @SuppressWarnings("unchecked") is used too generally.
+o LANG-571: ArrayUtils.add(T[: array, T element) can create unexpected
+ ClassCastException.
+o LANG-585: exception.DefaultExceptionContext.getFormattedExceptionMessage
+ catches Throwable.
+o LANG-596: StrSubstitutor should also handle the default properties of a
+ java.util.Properties class
+o LANG-600: Javadoc is incorrect for public static int
+ lastIndexOf(String str, String searchStr).
+o LANG-602: ContextedRuntimeException no longer an 'unchecked' exception.
+o LANG-606: EqualsBuilder causes StackOverflowException.
+o LANG-608: Some StringUtils methods should take an int character instead of
+ char to use String API features.
+o LANG-617: StringEscapeUtils.escapeXML() can't process UTF-16 supplementary
+ characters
+o LANG-624: SystemUtils.getJavaVersionAsFloat throws
+ StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+o LANG-629: Charset may not be threadsafe, because the HashSet is not synch.
+o LANG-638: NumberUtils createNumber throws a StringIndexOutOfBoundsException
+ when argument containing "e" and "E" is passed in
+o LANG-643: Javadoc StringUtils.left() claims to throw on negative len, but
+ doesn't
+o LANG-645: FastDateFormat.format() outputs incorrect week of year because
+ locale isn't respected
+o LANG-646: StringEscapeUtils.unescapeJava doesn't handle octal escapes and
+ Unicode with extra u
+o LANG-656: Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+o LANG-658: Some entities like &Ouml; are not matched properly against its
+ ISO8859-1 representation
+o LANG-659: EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212
+ ISOtech
+o LANG-66: StringEscaper.escapeXml() escapes characters > 0x7f.
+o LANG-662: org.apache.commons.lang3.math.Fraction does not reduce
+ (Integer.MIN_VALUE, 2^k)
+o LANG-663: org.apache.commons.lang3.math.Fraction does not always succeed in
+ multiplyBy and divideBy
+o LANG-664: NumberUtils.isNumber(String) is not right when the String is
+ "1.1L"
+o LANG-672: Doc bug in DateUtils#ceiling
+o LANG-677: DateUtils.isSameLocalTime compares using 12 hour clock and not
+ 24 hour
+o LANG-685: EqualsBuilder synchronizes on HashCodeBuilder.
+o LANG-703: StringUtils.join throws NPE when toString returns null for one of
+ objects in collection
+o LANG-710: StringIndexOutOfBoundsException when calling unescapeHtml4("&#03")
+o LANG-714: StringUtils doc/comment spelling fixes.
+o LANG-715: CharSetUtils.squeeze() speedup.
+o LANG-716: swapCase and *capitalize speedups.
+
+
+Historical list of changes: https://commons.apache.org/lang/changes-report.html
+
+For complete information on Commons Lang, including instructions on how to
+submit bug reports, patches, or suggestions for improvement, see the
+Apache Commons Lang website:
+
+https://commons.apache.org/lang/
+
+Have fun!
+-Apache Commons Lang team
+
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 000000000..a08e5f0ed
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project name="Lang">
+ <bannerRight>
+ <name>Commons Lang</name>
+ <src>/images/logo.png</src>
+ <href>/index.html</href>
+ </bannerRight>
+
+ <body>
+ <menu name="Lang">
+ <item name="Overview" href="/index.html" />
+ <item name="Download" href="/download_lang.cgi" />
+ <item name="Users guide" href="/userguide.html" />
+ <item name="Release History" href="/changes-report.html" />
+ <item name="Javadoc" href="/apidocs/index.html" />
+ <item name="Javadoc Archive" href="https://javadoc.io/doc/org.apache.commons/commons-lang3" />
+ </menu>
+
+ <menu name="Development">
+ <item name="Building" href="/building.html" />
+ <item name="Mailing Lists" href="/mail-lists.html" />
+ <item name="Issue Tracking" href="/issue-tracking.html" />
+ <item name="Proposal" href="/proposal.html" />
+ <item name="Developer guide" href="/developerguide.html" />
+ <item name="Source Repository" href="/scm.html" />
+ </menu>
+
+ </body>
+
+</project>
diff --git a/src/site/xdoc/article2_4.xml b/src/site/xdoc/article2_4.xml
new file mode 100644
index 000000000..996ef6bd6
--- /dev/null
+++ b/src/site/xdoc/article2_4.xml
@@ -0,0 +1,195 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>What's new in Commons Lang 2.4?</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="What's new in Commons Lang 2.4?">
+<p>Commons Lang 2.4 is out, and the obvious question is: <em>"So what? What's changed?"</em>.</p>
+<p>This article aims to briefly cover the changes and save you from having to dig through each JIRA
+issue to see what went on in the year of development between Lang 2.3 and 2.4.</p>
+<section name="Deprecations">
+<p>First, let us start with a couple of deprecations. As you can see in the release notes, we chose
+to deprecate the <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/ObjectUtils.html#appendIdentityToString(java.lang.StringBuffer,%20java.lang.Object)"><code>ObjectUtils.appendIdentityToString(StringBuffer, Object)</code></a> method as its
+null handling did not match its design (see <a href="https://issues.apache.org/jira/browse/LANG-360">LANG-360</a>
+for more details. Instead users should use <code>ObjectUtils.identityToString(StringBuffer, Object)</code>.</p>
+
+<p>We also deprecated <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/time/DateUtils.html#add(java.util.Date,%20int,%20int)"><code>DateUtils.add(java.util.Date, int, int)</code></a>. It should have been <code>private</code>
+from the beginning; please let us know if you actually use it.</p>
+</section>
+<section name="The build">
+<p>Before we move on, a quick note on the build: we built 2.4 using Maven 2 and Java 1.4. We also tested that the Ant build passed the tests
+successfully under Java 1.3, and that the classes compiled under Java 1.2. As it's been so long, we stopped building a Java 1.1-compatible jar. <strong>Most importantly</strong>, it <em>should</em> be a drop in replacement for Lang 2.3, but we recommend testing first, of course. Also, for those of you who work within an OSGi framework, the jar should be ready for OSGi. Now... time to move on.
+</p>
+</section>
+<section name="New classes">
+<p>Three new classes were added, so let's cover those next.</p>
+<p>Firstly, we added an <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/math/IEEE754rUtils.html"><code>IEEE754rUtils</code></a>
+class to the <code>org.apache.commons.lang.math</code> package.
+This candidate for ugly name of the month was needed to add <a href="https://en.wikipedia.org/wiki/IEEE_754r#min_and_max">IEEE-754r</a>
+semantics for some of the <code>NumberUtils</code> methods. The relevant part of that
+IEEE specification in this case is the NaN handling for <code>min</code> and <code>max</code> methods, and
+you can read more about it in <a href="https://issues.apache.org/jira/browse/LANG-381">LANG-381</a>.
+</p>
+<p>Second and third on our newcomers list are the <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/text/ExtendedMessageFormat.html"><code>ExtendedMessageFormat</code></a> class and its peer
+<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/text/FormatFactory.html"><code>FormatFactory</code></a>
+interface, both found in the <code>org.apache.commons.lang.text</code> package.</p>
+<p>Together they allow you to take the <code>java.text.MessageFormat</code> class further and insert your own formatting elements.</p>
+<p>
+By way of an example, imagine that we have a need for custom formatting of an employee identification
+number or EIN. Perhaps, simplistically, our EIN is composed of a two-character department code
+followed by a four-digit number, and that it is customary within our organization to render the EIN
+with a hyphen following the department identifier. Here we'll represent the EIN as a simple
+String (of course in real life we would likely create a class composed of department and number).
+We can create a custom <code>Format</code> class:
+<pre><code>
+public class EINFormat extends Format {
+ private char[] idMask;
+
+ public EINFormat() {
+ }
+ public EINFormat(char maskChar) {
+ idMask = new char[4];
+ Arrays.fill(idMask, maskChar);
+ }
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ String ein = (String) obj; //assume or assert length &gt;= 2
+ if (idMask == null) {
+ return new StringBuffer(ein).insert(2, '-').toString();
+ }
+ return new StringBuffer(ein.substring(0, 2)).append('-').append(idMask).toString();
+ }
+ public Object parseObject(String source, ParsePosition pos) {
+ int idx = pos.getIndex();
+ int endIdx = idx + 7;
+ if (source == null || source.length() &lt; endIdx) {
+ pos.setErrorIndex(idx);
+ return null;
+ }
+ if (source.charAt(idx + 2) != '-') {
+ pos.setErrorIndex(idx);
+ return null;
+ }
+ pos.setIndex(endIdx);
+ return source.substring(idx, endIdx).deleteCharAt(2);
+ }
+}
+</code></pre>
+Our custom EIN format is made available for <code>MessageFormat</code>-style processing by a
+<code>FormatFactory</code> implementation:
+<pre><code>
+public class EINFormatFactory implements FormatFactory {
+ public static final String EIN_FORMAT = "ein";
+ public Format getFormat(String name, String arguments, Locale locale) {
+ if (EIN_FORMAT.equals(name)) {
+ if (arguments == null || "".equals(arguments)) {
+ return new EINFormat();
+ }
+ return new EINFormat(arguments.charAt(0));
+ }
+ return null;
+ }
+}
+</code></pre>
+
+Now you simply provide a <code>java.util.Map&lt;String, FormatFactory&gt;</code> registry (keyed
+by format type) to <code>ExtendedMessageFormat</code>:
+<pre><code>
+new ExtendedMessageFormat("EIN: {0,ein}", Collections.singletonMap(EINFormatFactory.EIN_FORMAT, new EINFormatFactory()));
+</code></pre>
+As expected, this will render a String EIN "AA9999" as: <code>"EIN: AA-9999"</code>.
+<br /> <br />
+If we wanted to trigger the EIN masking code, we could trigger that in the format pattern:
+<pre><code>
+new ExtendedMessageFormat("EIN: {0,ein,#}", Collections.singletonMap(EINFormatFactory.EIN_FORMAT, new EINFormatFactory()));
+</code></pre>
+This should render "AA9999" as: <code>"EIN: AA-####"</code>.
+<br /> <br />
+You can also use <code>ExtendedMessageFormat</code> to override any or all of the built-in
+formats supported by <code>java.text.MessageFormat</code>. Finally, note that because
+<code>ExtendedMessageFormat</code> extends <code>MessageFormat</code> it should work in most
+cases as a <em>true</em> drop-in replacement.
+</p>
+</section>
+<section name="New methods">
+<p>There were 58 new methods added to existing Commons Lang classes. Going through each one, one at a time would be dull,
+and fortunately there are some nice groupings that we can discuss instead:</p>
+<p>CharSet <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/CharSet.html#getInstance(java.lang.String[])">getInstance(String[])</a> adds an additional builder method by which you can build a CharSet from multiple sets of characters at the same time. If you weren't aware of the CharSet class, it holds a set of characters created by a simple pattern language allowing constructs such as <code>"a-z"</code> and <code>"^a"</code> (everything but 'a'). It's most used by the CharSetUtils class, and came out of CharSetUtils.translate, a simple variant of the UNIX tr command.</p>
+<p>ClassUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/ClassUtils.html">canonical name</a> methods are akin to the non '<code>Canonical</code>' methods, except they work with the more human readable <code>int[]</code> type names rather than the JVM versions of <code>[I</code>. This makes them useful for parsing input from developer's configuration files. </p>
+<p>ClassUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/ClassUtils.html#toClass(java.lang.Object[])">toClass(String[])</a> is very easy to explain - it calls <code>toClass</code> on each <code>Object</code> in the array and returns an array of <code>Class</code> objects.</p>
+<p>ClassUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/ClassUtils.html#wrappersToPrimitives(java.lang.Class[])">wrapper-&gt;primitive</a> conversions are the reflection of the pre-existing <code>primitiveToWrapper</code> methods. Again easy to explain, they turn an array of <code>Integer</code> into an array of <code>int[]</code>.</p>
+<p>ObjectUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/ObjectUtils.html#identityToString(java.lang.StringBuffer,%20java.lang.Object)">identityToString(StringBuffer, Object)</a> is the StringBuffer variant of the pre-existing <code>identityToString</code> method. In case you've not met that before, it produces the toString that would have been produced by an Object if it hadn't been overridden.</p>
+<p>StringEscapeUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringEscapeUtils.html#escapeCsv(java.lang.String)">CSV methods</a> are a new addition to our range of simple parser/printers. These, quite as expected, parse and unparse CSV text as per <a href="https://datatracker.ietf.org/doc/html/rfc4180">RFC-4180</a>.</p>
+<p>StringUtils has a host of new methods, as always, and we'll leave these for later.</p>
+<p>WordUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/WordUtils.html#abbreviate(java.lang.String,%20int,%20int,%20java.lang.String)">abbreviate</a> finds the first space after the lower limit and abbreviates the text.</p>
+<p>math.<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/math/IntRange.html#toArray()">IntRange</a>/<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/math/LongRange.html#toArray()">LongRange.toArray</a> turn the range into an array of primitive <code>int</code>/<code>long</code>s contained in the range.</p>
+<p>text.StrMatch.<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/text/StrMatcher.html#isMatch(char[],%20int)">isMatch(char[], int)</a> is a helper method for checking whether there was a match with the StrMatcher objects.</p>
+<p>time.DateFormatUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/time/DateFormatUtils.html">format(Calendar, ...)</a> provide Calendar variants for the pre-existing format methods. If these are new to you, they are helper methods to formatting a date.</p>
+<p>time.DateUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/time/DateUtils.html">getFragment*</a> methods are used to splice the time element out of Date. If you have <code>2008/12/13 14:57</code>, then these could, for example, pull out the 13.</p>
+<p>time.DateUtils <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/time/DateUtils.html">setXxx methods</a> round off our walk through the methods - the setXxx variant of the existing addXxx helper methods.</p>
+</section>
+
+<section name="StringUtils methods">
+<p>The <code>StringUtils</code> class is a little large, isn't it? Sorry, but it's gotten bigger.
+</p>
+<ul>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#containsOnly(java.lang.String,%20char[])">boolean containsAny(String, char[])</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#containsOnly(java.lang.String,%20java.lang.String)">boolean containsAny(String, String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#endsWith(java.lang.String,%20java.lang.String)">boolean endsWith(String, String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#endsWithIgnoreCase(java.lang.String,%20java.lang.String)">boolean endsWithIgnoreCase(String, String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#getCommonPrefix(java.lang.String[])">String getCommonPrefix(String[])</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#indexOfDifference(java.lang.String[])">int indexOfDifference(String[])</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#length(java.lang.String)">int length(String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#removeEndIgnoreCase(java.lang.String,%20java.lang.String)">String removeEndIgnoreCase(String, String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#removeStartIgnoreCase(java.lang.String,%20java.lang.String)">String removeStartIgnoreCase(String, String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#replaceEach(java.lang.String,%20java.lang.String[],%20java.lang.String[])">String replaceEach(String, String[], String[])</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#replaceEachRepeatedly(java.lang.String,%20java.lang.String[],%20java.lang.String[])">String replaceEachRepeatedly(String, String[], String[])</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#splitByCharacterType(java.lang.String)">String[] splitByCharacterType(String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#splitByCharacterTypeCamelCase(java.lang.String)">String[] splitByCharacterTypeCamelCase(String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#splitByWholeSeparatorPreserveAllTokens(java.lang.String,%20java.lang.String)">String[] splitByWholeSeparatorPreserveAllTokens(String, String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#splitByWholeSeparatorPreserveAllTokens(java.lang.String,%20java.lang.String,%20int)">String[] splitByWholeSeparatorPreserveAllTokens(String, String, int)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#startsWith(java.lang.String,%20java.lang.String)">boolean startsWith(String, String)</a></li>
+ <li><a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.4/org/apache/commons/lang/StringUtils.html#startsWithIgnoreCase(java.lang.String,%20java.lang.String)">boolean startsWithIgnoreCase(String, String)</a></li>
+</ul>
+
+<p>Hopefully they are in many cases self-describing. Rather than spend a lot of time describing them, we'll let you read the Javadoc of the ones that interest you.</p>
+
+</section>
+
+<section name="What's fixed in Lang 2.4?">
+<p>In addition to new things, there are the bugfixes. As you can tell from the release notes, there are a good few - 24 in fact according to JIRA. Here are some of the interesting ones: </p>
+<ul>
+<li><a href="https://issues.apache.org/jira/browse/LANG-393">LANG-393</a> - We fixed EqualsBuilder so that it understands that BigDecimals are equal even when they think they're not. It seems very likely that usually you will want "29.0" and "29.00" to be equal, even if BigDecimal disagrees. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-380">LANG-380</a> - Chances are you'll know if you met this one. Fraction.reduce has an infinite loop if the numerator is 0. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-369">LANG-369</a>, <a href="https://issues.apache.org/jira/browse/LANG-367">LANG-367</a>, <a href="https://issues.apache.org/jira/browse/LANG-334">LANG-334</a> - Threading bugs - we improved how things work in concurrency situations for ExceptionUtils, FastDateFormat and Enum. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-346">LANG-346</a> - DateUtils.round was getting things wrong for minutes and seconds. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-328">LANG-328</a> - LocaleUtils.toLocale was broken if there was no country code defined. </li>
+</ul>
+</section>
+
+<section name="So long, farewell...">
+<p>Hopefully that was all of interest. Don't forget to download <a href="https://commons.apache.org/lang/download_lang.cgi">Lang 2.4</a>, or, for the Maven repository users, upgrade your &lt;version&gt; tag to 2.4. Please feel free to raise any questions you might have on the <a href="mail-lists.html">mailing lists</a>, and report bugs or enhancements in the <a href="issue-tracking.html">issue tracker</a>.</p>
+</section>
+
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/article2_5.xml b/src/site/xdoc/article2_5.xml
new file mode 100644
index 000000000..be45444f7
--- /dev/null
+++ b/src/site/xdoc/article2_5.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>What's new in Commons Lang 2.5?</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="What's new in Commons Lang 2.5?">
+<p>Commons Lang 2.5 is out, and the obvious question is: <em>"So what? What's changed?"</em>.</p>
+<p>This article aims to briefly cover the changes and save you from having to dig through each JIRA
+issue to see what went on in the two years of development between Lang 2.4 and 2.5.</p>
+<p>Two years?!? Yes, it's true. The reason is that 2.5 represents the backwards compatible changes in the
+nearly complete Java-5 focused Lang 3.0. </p>
+<section name="Deprecations">
+<p>There were no new deprecations in 2.5. </p>
+</section>
+<section name="The build">
+<p>2.5 was built using Sun's 1.6.0_17 JVM, but targets Java 1.3. </p>
+</section>
+<section name="New classes">
+<p>A new org.apache.commons.lang.reflect package was added, accumulating common high-level uses of the java.lang.reflect APIs. The
+classes, hopefully self-evident in nature, were pulled together from the existing BeanUtils and the unreleased Reflect components.
+The classes are: </p>
+<ul>
+<li>ConstructorUtils - primarily creating new instances of classes</li>
+<li>FieldUtils - primarily reading and writing to Object/Class fields</li>
+<li>MethodUtils - primarily methods to make invoking methods simpler</li>
+</ul>
+<p>You can read more about the classes in their
+<a href="LANG_2_5/src/main/java/org/apache/commons/lang/reflect//MemberUtils.java">javadoc</a>. </p>
+</section>
+<section name="New fields">
+<p>With both Java 7 and Windows 7 becoming a reality,
+<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/org/apache/commons/lang/SystemUtils.html">SystemUtils</a> was updated to
+provide boolean fields for both versions. </p>
+</section>
+<section name="New methods">
+<p>There were 66 new methods added to existing Commons Lang classes. </p>
+<p>The <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/org/apache/commons/lang/ArrayUtils.html">ArrayUtils</a> class
+received two new types of methods. Firstly, a boolean isNotEmpty(array) set of methods, identifying whether the particular
+array is null or an empty sized array. This makes it the inverse of the existing isEmpty(array) methods. Secondly, an array
+nullToEmpty(array) set of methods that converts null or empty arrays to a singleton empty array already available from the
+ArrayUtils class. Non-null/empty arrays are left untouched. </p>
+
+<p>The constructor for the
+<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/org/apache/commons/lang/CharRange.html">CharRange</a> class is somewhat
+confusing. It takes a boolean parameter that when set to true means the CharRange is negated. To make code easier to read, the
+following static helper methods were added: </p>
+<ul>
+<li>public org.apache.commons.lang.CharRange is(char)</li>
+<li>public org.apache.commons.lang.CharRange isIn(char, char)</li>
+<li>public org.apache.commons.lang.CharRange isNot(char)</li>
+<li>public org.apache.commons.lang.CharRange isNotIn(char, char)</li>
+</ul>
+<p>An iterator() method was also added to provide another way of walking the range. </p>
+
+<p>The <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/org/apache/commons/lang/builder/EqualsBuilder.html">EqualsBuilder</a>
+obtained a new reset() method to allow for reuse, while the
+<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/org/apache/commons/lang/builder/HashCodeBuilder.html">HashCodeBuilder</a>
+received a hashCode() method that returns the built hash code instead of the natural hash code of the builder object itself. It
+doesn't really matter what the builder chooses to use as a hash code and this stops accidental use of the hashCode() instead of
+toHashCode() method from causing lots of pain. </p>
+
+<p>Helper isFalse(), isTrue() and toBoolean() methods were added to
+<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/org/apache/commons/lang/mutable/MutableBoolean.html">MutableBoolean</a>,
+while the other mutable classes received String argument constructors. </p>
+
+<p>Lastly, the <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/org/apache/commons/lang/time/DateUtils.html">DateUtils</a>
+class received a new ceiling set of methods to truncate upwards, and a parseDateStrictly method to parse a Date with the
+supplied DateFormat classes leniency set to false. </p>
+
+</section>
+
+<section name="StringUtils methods">
+<p>As with 2.4, the
+<a href="https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/org/apache/commons/lang/StringUtils.html">StringUtils</a> class has
+grown and we cover its new methods in its own section. </p>
+<ul>
+<li>abbreviateMiddle(String, String, int);String - This method turns aRatherLongNameSuchAsAFileName into 'aRatherLo...AFileName'.
+This is often desirable when you want to restrict the length of a name, but you can afford to have quite long names. </li>
+<li>indexOfIgnoreCase(String, String);int - An indexOf method that ignores the case of what it's matching. Matching lastIndexOfIgnoreCase and 'start at index' variants were also added. </li>
+<li>lastOrdinalIndexOf(String, String, int);int - A matching variant for the already existing ordinalIndexOf method - they
+support finding the Nth indexOf instead of the first time the search term is found. </li>
+<li>isAllLowerCase(String);boolean - Is the String all lower case. </li>
+<li>isAllUpperCase(String);boolean - Is the String all upper case. </li>
+<li>lowerCase(String, Locale);String - Null protected toLowerCase methods for the platform independent inclined. </li>
+<li>upperCase(String, Locale);String - Null protected toUpperCase methods for the platform independent inclined. </li>
+<li>repeat(String, String, int);String - Repeat option that includes an optional separator. </li>
+<li>startsWithAny(String, String[]);boolean - Does the specified String start with any of the supplied values. </li>
+</ul>
+</section>
+
+<section name="What's fixed in Lang 2.5?">
+<p>Per the <a href="upgradeto2_5.html">release notes</a> there are 32 bugs fixed in Lang 2.5. Some highlights are: </p>
+<ul>
+<li><a href="https://issues.apache.org/jira/browse/LANG-477">LANG-477</a> - fixing an OutOfMemoryError in ExtendedMessageFormat. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-76">LANG-76</a> - EnumUtils.getEnum() doesn't work well in 1.5. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-204">LANG-204</a> and
+<a href="https://issues.apache.org/jira/browse/LANG-506">LANG-506</a> - Multithreading improvements to the package private Entities
+class, used behind the scenes by StringEscapeUtils. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-511">LANG-511</a> - Improve performance by deferring LocaleUtils initialization. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-523">LANG-523</a> - Two orders of magnitude performance improvement in StrBuilder. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-467">LANG-467</a> - Reverted the change to EqualsBuilder in Lang 2.4 to
+specially handle BigDecimal. While useful, it put things out of sync with HashCodeBuilder. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-586">LANG-586</a> - Use of a ThreadLocal in HashCodeBuilder and
+ToStringStyle meant that containers could end up with memory leaks. This was rewritten to avoid this. </li>
+<li><a href="https://issues.apache.org/jira/browse/LANG-472">LANG-472</a> - RandomUtils.nextLong() was returning only even numbers. Fans of Java-based roulette wheels can breathe a sigh of relief. </li>
+</ul>
+</section>
+
+<section name="So long, farewell...">
+<p>Hopefully that was all of interest. Don't forget to download <a href="https://commons.apache.org/lang/download_lang.cgi">Lang 2.5</a>, or, for the Maven repository users, upgrade your &lt;version&gt; tag to 2.5. Please feel free to raise any questions you might have on the <a href="mail-lists.html">mailing lists</a>, and report bugs or enhancements in the <a href="issue-tracking.html">issue tracker</a>.</p>
+</section>
+
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/article3_0.xml b/src/site/xdoc/article3_0.xml
new file mode 100644
index 000000000..ff5976d08
--- /dev/null
+++ b/src/site/xdoc/article3_0.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>What's new in Commons Lang 3.0?</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="What's new in Commons Lang 3.0?">
+<p>Commons Lang 3.0 is out, and the obvious question is: <em>"So what? What's changed?"</em>.</p>
+<section name="The big story">
+<p>Lang is now Java 5 based. We've generified the API, moved certain APIs to support <code>varargs</code> and thrown out any features
+that are now supported by Java itself. We've removed the deprecated parts of the API and have also removed some features that
+were deemed weak or unnecessary. All of this means that Lang 3.0 is not backwards compatible. </p>
+<p>To that end we have changed the package name, allowing Lang 3.0 to sit side-by-side with your previous version of Lang without any bad side effects. The new package name is the exciting and original <code>org.apache.commons.lang3</code>. This also forces you to recompile your code, making sure the compiler can let you know if a backwards incompatibility affects you. </p>
+<p>As you'd expect, there are also new features, enhancements and bugs fixed. </p>
+</section>
+<!--
+<section name="The build">
+<p>We built 3.0 using Maven 2.2.1 and Java 1.5. <strong>Needs confirmation before release of actual build details</strong></p>
+</section>
+-->
+<section name="Migrating from 2.x">
+<h3>Java code</h3>
+<p>Despite the label of backwards incompatibility, in the vast majority of cases the simple addition of a <code>'3'</code> to an import statement will suffice for your migration. </p><br/>
+<p>Change: <code>import org.apache.commons.lang</code> -&gt; <code>import org.apache.commons.lang3</code></p>
+<h3>Maven</h3>
+<p><code>groupId</code>: <code>commons-lang</code> -&gt; <code>org.apache.commons</code></p>
+<p><code>artifactId</code>: <code>commons-lang</code> -&gt; <code>commons-lang3</code></p>
+</section>
+
+<section name="What's gone?">
+<h3>Enum package</h3>
+<p>Java 5 provided enums out of the box, therefore we dispensed with both the deprecated enum package,
+and the enums package. Instead you should migrate over to the standard Java enum. An EnumUtils class has been born
+from the ashes of the old code, focused on providing additional functionality to the standard Java enum API. </p>
+<h3>NestedExceptions</h3>
+<p>In Java 1.4, the notion that all Throwables could be linked to a cause was introduced. In Lang we
+had provided a NestedException framework to support the same feature, and now that we're jumping from Java 1.3 to
+Java 5 we are remove this feature. The deprecation section below covers one part of ExceptionUtils that remains until
+we are on Java 6, where the last remaining parts of the JDK appear to have embraced the new cause API. </p>
+<h3>JVMRandom</h3>
+<p>This class was introduced in Lang 2.0 to support a Random object built around the system seed. This
+proved to be both an uncommon use case and one with bugs and so was dropped. </p>
+<h3>StringEscapeUtils.escapeSql</h3>
+<p>This was a misleading method, only handling the simplest of possible SQL cases. As SQL is not Lang's focus, it didn't make sense to maintain this method. </p>
+<h3>*Exceptions removed</h3>
+<p>Various Exception classes were removed - the lesson in defining more semantically relevant exception classes is that you can keep on coming up with more and more new classes. Simpler to focus on using the main JDK classes. </p>
+<h3>math.*Range</h3>
+<p>The various Range classes in the <code>math</code> package were removed in favour of a new generic Range class. </p>
+<h3>Previous Deprecations</h3>
+<p>All deprecated fields/methods/classes - with a new major version, all of the previously deprecated parts of the API could finally go away. </p>
+<p>If you feel that something was unfairly taken away, please feel free to contact the list. In many cases the possibility exists to reintroduce code. </p>
+</section>
+<section name="Deprecations">
+<p>The lone deprecation in 3.0 is that of the notion of 'cause method names' in ExceptionUtils. In Java 5.0 it is still just about
+needed to handle some JDK classes that have not been migrated to the getCause API. In Java 6.0 things appear to be resolved and
+we will remove the related methods in Lang 4.0. </p>
+</section>
+<section name="New packages">
+<p>Two new packages have shown up. org.apache.commons.lang3.concurrent, which unsurprisingly provides support classes for
+multithreaded programming, and org.apache.commons.lang3.text.translate, which provides a pluggable API for text transformation. </p>
+
+<h3>concurrent.*</h3>
+<p>Java 1.5 adds a great bunch of functionality related to multithreaded programming
+below the <code>java.util.concurrent</code> package. Commons Lang 3.0 provides
+some additional classes in this area which are intended to further simplify the
+development of concurrent applications.</p>
+<p>The classes located in the <code>concurrent</code> package can be roughly
+divided into the following categories:
+<ul>
+<li>Utility classes</li>
+<li>Initializer classes</li>
+</ul>
+</p>
+<p>Classes of the former category provide some basic functionality a developer
+typically has to implement manually again and again. Examples are a configurable
+<code>ThreadFactory</code> implementation or utility methods for the handling of
+<code>ExecutionException</code>s thrown by Java's executor service framework.</p>
+<p>Initializer classes deal with the creation of objects in a multithreaded
+environment. There are several variants of initializer implementations serving
+different purposes. For instance, there are a couple of concrete initializers
+supporting lazy initialization of objects in a safe way. Another example is
+<code>BackgroundInitializer</code> which allows pushing the creation of an
+expensive object to a background thread while the application can continue with
+the execution of other tasks. Here is an example of the usage of <code>BackgroundInitializer</code>
+which creates an <code>EntityManagerFactory</code> object:</p>
+<pre>
+ public class DBInitializer extends BackgroundInitialize&lt;EntityManagerFactory&gt; {
+ protected EntityManagerFactory initialize() {
+ return Persistence.createEntityManagerFactory(&quot;mypersistenceunit&quot;);
+ }
+ }
+</pre>
+<p>An application creates an instance of the <code>DBInitializer</code> class
+and calls its <code>start()</code> method. When it later needs access to the
+<code>EntityManagerFactory</code> created by the initializer it calls the
+<code>get()</code> method; <code>get()</code> returns the object produced by the
+initializer if it is already available or blocks if necessary until initialization
+is complete. Alternatively a convenience method of the <code>ConcurrentUtils</code>
+class can be used to obtain the object from the initializer which hides the
+checked exception declared by <code>get()</code>:</p>
+<pre>
+ DBInitializer init = new DBInitializer();
+ init.start();
+
+ // now do some other stuff
+
+ EntityManagerFactory factory = ConcurrentUtils.initializeUnchecked(init);
+</pre>
+<p>Comprehensive documentation about the <code>concurrent</code> package is
+available in the <a href="userguide.html#lang.concurrent.">user guide</a>.</p>
+<h3>text.translate.*</h3>
+<p>A common complaint with StringEscapeUtils was that its escapeXml and escapeHtml methods should not be escaping non-ASCII characters. We agreed and made the change while creating a modular approach to let users define their own escaping constructs. </p>
+<p>The simplest way to show this is to look at the code that implements escapeXml:</p>
+<pre>
+ return ESCAPE_XML.translate(input);
+</pre>
+<p>Very simple. Maybe a bit too very simple, let's look a bit deeper. </p>
+<pre>
+ public static final CharSequenceTranslator ESCAPE_XML =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
+ new LookupTranslator(EntityArrays.APOS_ESCAPE())
+ );
+</pre>
+<p>Here we see that <code>ESCAPE_XML</code> is a '<code>CharSequenceTranslator</code>', which in turn is made up of two lookup translators based on the basic XML escapes and another to escape apostrophes. This shows one way to combine translators. Another can be shown by looking at the example to achieve the old XML escaping functionality (escaping non-ASCII): </p>
+<pre>
+ StringEscapeUtils.ESCAPE_XML.with( NumericEntityEscaper.between(0x7f, Integer.MAX_VALUE) );
+</pre>
+<p>That takes the standard Commons Lang provided escape functionality, and adds on another translation layer. Another JIRA requested option was to also escape non-printable ASCII, this is now achievable with a modification of the above: </p>
+<pre>
+ StringEscapeUtils.ESCAPE_XML.with(
+ new AggregateTranslator(
+ NumericEntityEscaper.between(0, 31),
+ NumericEntityEscaper.between(0x80, Integer.MAX_VALUE)
+ )
+ )
+</pre>
+<p>You can also implement your own translators (be they for escaping, unescaping or some aspect of your own). See the <code>CharSequenceTranslator</code> and its <code>CodePointTranslator</code> helper subclass for details - primarily a case of implementing the translate(CharSequence, int, Writer);int method. </p>
+</section>
+<section name="New classes + methods">
+<p>There are many new classes and methods in Lang 3.0 - the most complete way to see the changes is via this <a href="lang2-lang3-clirr-report.html">Lang2 to Lang3 Clirr report</a>. </p>
+<p>Here is a summary of the new classes: </p>
+<ul>
+<li><code>AnnotationUtils</code></li>
+<li><code>CharSequenceUtils</code></li>
+<li><code>EnumUtils</code></li>
+<li><code>JavaVersion</code> - used in SystemUtils</li>
+<li><code>Pair, ImmutablePair and MutablePair</code></li>
+<li><code>Range</code> - replaces the old math.*Range classes</li>
+<li><code>builder.Builder</code></li>
+<li><code>exception.ContextedException</code></li>
+<li><code>exception.CloneFailedException</code></li>
+<li><code>reflect.ConstructorUtils</code></li>
+<li><code>reflect.FieldUtils</code></li>
+<li><code>reflect.MethodUtils</code></li>
+<li><code>reflect.TypeUtils</code></li>
+<li><code>text.WordUtils</code></li>
+</ul>
+</section>
+
+<section name="Bugfixes?">
+<p>See the <a href="changes-report.html#3.0">3.0 changes report</a> for the list of fixed bugs and other enhancements. </p>
+</section>
+
+<section name="Other Notable Changes">
+<ul>
+<li>StringUtils.isAlpha, isNumeric and isAlphanumeric now all return false when passed an empty String. Previously they returned true. </li>
+<li>SystemUtils.isJavaVersionAtLeast now relies on the <code>java.specification.version</code> and not the <code>java.version</code> System property. </li>
+<li>StringEscapeUtils.escapeXml and escapeHtml no longer escape high value Unicode characters by default. The text.translate package is available to recreate the old behavior. </li>
+<li>Validate utility methods have been changed and genericized to return the
+validated argument where possible, to allow for inline use. </li>
+<li>Validate utility methods handle validity violations arising from
+<code>null</code> values by throwing <code>NullPointerException</code>s.
+This better aligns with standard JDK behavior (lang <em>is</em> intended to
+complement <code>java.lang</code>, after all). Users upgrading from v2.x may
+need to adjust to this change. See <code>Validate#isTrue()</code> for a
+general-purpose mechanism to raise an <code>IllegalArgumentException</code>.</li>
+</ul>
+</section>
+
+<!--
+<section name="What next???"> TODO: Add Beta info.
+<p>Hopefully that was all of interest. Don't forget to download <a href="https://commons.apache.org/lang/download_lang.cgi">Lang 3.0</a>, or, for the Maven repository users, upgrade your &lt;version&gt; tag to 3.0 and your groupId to org.apache.commons. Please feel free to raise any questions you might have on the <a href="mail-lists.html">mailing lists</a>, and report bugs or enhancements in the <a href="issue-tracking.html">issue tracker</a>.</p>
+</section>
+-->
+
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/building.xml b/src/site/xdoc/building.xml
new file mode 100644
index 000000000..ee1c52a30
--- /dev/null
+++ b/src/site/xdoc/building.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>Building</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+<!-- ================================================== -->
+<section name="Overview">
+<p>
+ Commons Lang uses <a href="https://maven.apache.org">Maven</a>.
+</p>
+<p>
+ You may also be interested in the upgrade notes:
+</p>
+<ul>
+ <li>Upgrade from 2.6 to 3.0 - <a href="upgradeto3_0.html">Lang 3.0 Release Notes</a></li>
+ <li>Upgrade from 2.5 to 2.6 - <a href="upgradeto2_6.html">Lang 2.6 Release Notes</a></li>
+ <li>Upgrade from 2.4 to 2.5 - <a href="upgradeto2_5.html">Lang 2.5 Release Notes</a></li>
+ <li>Upgrade from 2.3 to 2.4 - <a href="upgradeto2_4.html">Lang 2.4 Release Notes</a></li>
+ <li>Upgrade from 2.2 to 2.3 - <a href="upgradeto2_3.html">Lang 2.3 Release Notes</a></li>
+ <li>Upgrade from 2.1 to 2.2 - <a href="upgradeto2_2.html">Lang 2.2 Release Notes</a></li>
+ <li>Upgrade from 2.0 to 2.1 - <a href="upgradeto2_1.html">Lang 2.1 Release Notes</a></li>
+ <li>Upgrade from 1.0 to 2.0 - <a href="upgradeto2_0.html">Lang 2.0 Release Notes</a></li>
+</ul>
+</section>
+<!-- ================================================== -->
+<section name="Maven Goals">
+ <p>
+ To build a jar file, change into the root directory of Lang and run "mvn package".
+ The result will be in the "target" subdirectory.
+ </p>
+ <p>
+ To build the full website, run "mvn site".
+ The result will be in "target/site".
+ You must be online to successfully complete this target.
+ </p>
+ <p>
+ Further details can be found in the
+ <a href="https://commons.apache.org/building.html">commons build instructions</a>.
+ </p>
+</section>
+<!-- ================================================== -->
+<section name="Ant Goals">
+ <p>
+ To build a jar file, change into the root directory of Lang and run "ant jar".
+ The result will be in the "target" subdirectory.
+ </p>
+ <p>
+ To build the Javadocs, run "ant javadoc".
+ The result will be in "target/docs/api".
+ </p>
+</section>
+<!-- ================================================== -->
+</body>
+</document>
diff --git a/src/site/xdoc/developerguide.xml b/src/site/xdoc/developerguide.xml
new file mode 100644
index 000000000..3cfa9b938
--- /dev/null
+++ b/src/site/xdoc/developerguide.xml
@@ -0,0 +1,150 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+<properties>
+<title>Developer guide for Commons "Lang"</title>
+</properties>
+<body>
+
+
+<section name='Developer guide for Commons "Lang"'>
+
+<h1>The Commons <em>Lang</em> Package</h1>
+<h2>Developers Guide</h2>
+<br />
+<a href="#Introduction">[Introduction]</a>
+<a href="#PackageStructure">[Package Structure]</a>
+<a href="#UtilityClasses">[Utility Classes]</a>
+<a href="#Javadoc">[Javadoc]</a>
+<a href="#Building">[Building]</a>
+<br /><br />
+
+<a name="Introduction"></a>
+<h3>1. INTRODUCTION</h3>
+
+<p>The <em>Lang</em> package contains a set of Java classes that extend
+the basic JDK classes. This developer guide seeks to set
+out rules for the naming of classes and methods within the package. The purpose
+of this, as with all naming standards, is to improve the coherency and
+consistency of the whole API.</p>
+
+<p>The philosophy of the naming standards is to follow those of the JDK
+if possible.</p>
+
+
+
+<a name="PackageStructure"></a>
+<h3>2. PACKAGE STRUCTURE</h3>
+
+<p>The main package for Lang is <code>org.apache.commons.lang3</code>. Subpackages should
+be created for each group of related items. </p>
+
+<p>Each package should have a <code>package.html</code> file for javadoc. This should
+describe the use of the package and its scope.</p>
+
+
+
+<a name="UtilityClasses"></a>
+<h3>3. UTILITY CLASSES</h3>
+
+<p>Utility classes provide additional functionality around a class or interface.
+Examples include StringUtils and SerializationUtils.</p>
+
+<p>Each class shall follow the naming pattern XxxUtils where Xxx relates to the
+class or interface that the utility services. Variations on a theme (<code>Integer</code>
+as opposed to <code>Number</code>) should be dealt with in one Utils class where possible.
+Each Utils class shall:</p>
+
+<ul>
+<li>be a single, static method based, class</li>
+<li>have a name consisting of the interface name plus 'Utils'</li>
+<li>deal with one class or interface and its variations (subclasses)</li>
+<li>provide methods that perform useful utility functions</li>
+<li>the class will not be final</li>
+<li>for null parameters, rather than throwing an Exception, consider performing a Null patterned concept, such as returning 0 or ""</li>
+</ul>
+
+<p>A utility class can act as a factory for specific implementations of a class or
+interface. In such cases the implementations should be non-public, static, inner classes
+of the utility class. However, if warranted due to maintenance or other reasons, these
+decorator classes may be moved to top-level classes in a subpackage. The
+naming of such a subpackage should be discussed and agreed upon on the
+developers mailing list.</p>
+
+<p>If different overloaded variants of a method are desired, with the same method signature, it should not be indicated via a boolean argument, but via a more focused method name. Rather than replace(boolean repeat), replace and replaceAll, or replaceOnce and replace. </p>
+
+
+<a name="Javadoc"></a>
+<h3>4. JAVADOC</h3>
+
+<p>The Sun javadoc guidelines are the starting point for Lang. These points are
+an extension to make it easier for users reading the generated
+docs and developers with javadoc-popup capabilities from within their IDE.</p>
+
+<h4>General</h4>
+<p>References to other objects, interfaces or methods use the @link-tag the
+first time it is referenced in a class or interface. On the following
+references always enclose it inside &lt;code&gt;&lt;/code&gt;.</p>
+
+<p>References to <code>null</code>, <code>this</code>, <code>long</code>,
+<code>int</code>, <code>short</code>, <code>char</code>, <code>byte</code>,
+<code>double</code>, <code>float</code> and <code>boolean</code> should be enclosed
+in &lt;code&gt;&lt;/code&gt;.</p>
+
+<h4>Classes/Interfaces/Methods</h4>
+<p>Use a short description of what the class/interface/method is used for,
+enclose with &lt;p&gt;&lt;/p&gt;.</p>
+
+<p>A longer description about what the class/interface/method is used for
+and if it is needed how it is done. If it is necessary include
+description of the parameters, what they are used for and how. Enclose
+with &lt;p&gt;&lt;/p&gt; where it is needed, try to divide into smaller parts (not
+to small!) to enhance readability of the generated Javadoc.</p>
+
+<p>If an example is needed enclose it with &lt;pre&gt;&lt;/pre&gt;.
+It should be supported with an explanation within a normal paragraph.</p>
+
+<h4>Exception throwing</h4>
+<p>When throwing an exception to indicate a bad argument, always try to throw
+IllegalArgumentException, even if the argument was null. Do not throw
+NullPointerException. (Obviously, you should document what the code actually does!)</p>
+
+<h4>Deprecations</h4>
+<p>When deprecating a method or class include a clear reference to when the method will be deleted.
+This should be of the form 'Method will be removed in Commons Lang 3.0.'. </p>
+
+<h4>Language used in code/comments</h4>
+<p>It has been decided to casually standardize on US-English.
+To avoid misplaced jeers of 'americanisation', the people making this decision largely write in non-US-English.
+However, it's not something to get worked up about. Lots of spelling differences will creep in all over.</p>
+
+<a name="Building"></a>
+<h3>5.BUILDING</h3>
+<h4>Building a Release</h4>
+<p>
+The currently targeted version of Java is 1.6.
+</p>
+<p>
+To build Lang:
+<table>
+<tr><th></th><th>Tested JAR</th><th>Distribution</th><th>Site</th></tr>
+<tr><td>Maven 2.x</td><td><code>mvn package</code></td><td>mvn assembly:assembly</td><td>mvn site</td></tr>
+</table>
+</p>
+</section>
+</body>
+</document>
diff --git a/src/site/xdoc/download_lang.xml b/src/site/xdoc/download_lang.xml
new file mode 100644
index 000000000..003c0684a
--- /dev/null
+++ b/src/site/xdoc/download_lang.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |**** ****|
+ |**** THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN ****|
+ |**** DO NOT EDIT DIRECTLY ****|
+ |**** ****|
+ +======================================================================+
+ | TEMPLATE FILE: download-page-template.xml |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ | |
+ | 1) Re-generate using: mvn commons-build:download-page |
+ | |
+ | 2) Set the following properties in the component's pom: |
+ | - commons.componentid (required, alphabetic, lower case) |
+ | - commons.release.version (required) |
+ | - commons.release.name (required) |
+ | - commons.binary.suffix (optional) |
+ | (defaults to "-bin", set to "" for pre-maven2 releases) |
+ | - commons.release.desc (optional) |
+ | - commons.release.subdir (optional) |
+ | - commons.release.hash (optional, lowercase, default sha512) |
+ | |
+ | - commons.release.[234].version (conditional) |
+ | - commons.release.[234].name (conditional) |
+ | - commons.release.[234].binary.suffix (optional) |
+ | - commons.release.[234].desc (optional) |
+ | - commons.release.[234].subdir (optional) |
+ | - commons.release.[234].hash (optional, lowercase, [sha512])|
+ | |
+ | 3) Example Properties |
+ | (commons.release.name inherited by parent: |
+ | ${project.artifactId}-${commons.release.version} |
+ | |
+ | <properties> |
+ | <commons.componentid>math</commons.componentid> |
+ | <commons.release.version>1.2</commons.release.version> |
+ | </properties> |
+ | |
+ +======================================================================+
+-->
+<document>
+ <properties>
+ <title>Download Apache Commons Lang</title>
+ <author email="dev@commons.apache.org">Apache Commons Documentation Team</author>
+ </properties>
+ <body>
+ <section name="Download Apache Commons Lang">
+ <subsection name="Using a Mirror">
+ <p>
+ We recommend you use a mirror to download our release
+ builds, but you <strong>must</strong> <a href="https://www.apache.org/info/verification.html">verify the integrity</a> of
+ the downloaded files using signatures downloaded from our main
+ distribution directories. Recent releases (48 hours) may not yet
+ be available from all the mirrors.
+ </p>
+
+ <p>
+ You are currently using <b>[preferred]</b>. If you
+ encounter a problem with this mirror, please select another
+ mirror. If all mirrors are failing, there are <i>backup</i>
+ mirrors (at the end of the mirrors list) that should be
+ available.
+ <br></br>
+ [if-any logo]<a href="[link]"><img align="right" src="[logo]" border="0"></img></a>[end]
+ </p>
+
+ <form action="[location]" method="get" id="SelectMirror">
+ <p>
+ Other mirrors:
+ <select name="Preferred">
+ [if-any http]
+ [for http]<option value="[http]">[http]</option>[end]
+ [end]
+ [if-any ftp]
+ [for ftp]<option value="[ftp]">[ftp]</option>[end]
+ [end]
+ [if-any backup]
+ [for backup]<option value="[backup]">[backup] (backup)</option>[end]
+ [end]
+ </select>
+ <input type="submit" value="Change"></input>
+ </p>
+ </form>
+
+ <p>
+ It is essential that you
+ <a href="https://www.apache.org/info/verification.html">verify the integrity</a>
+ of downloaded files, preferably using the <code>PGP</code> signature (<code>*.asc</code> files);
+ failing that using the <code>SHA512</code> hash (<code>*.sha512</code> checksum files).
+ </p>
+ <p>
+ The <a href="https://www.apache.org/dist/commons/KEYS">KEYS</a>
+ file contains the public PGP keys used by Apache Commons developers
+ to sign releases.
+ </p>
+ </subsection>
+ </section>
+ <section name="Apache Commons Lang 3.12.0 (Java 8+)">
+ <subsection name="Binaries">
+ <table>
+ <tr>
+ <td><a href="[preferred]/commons/lang/binaries/commons-lang3-3.12.0-bin.tar.gz">commons-lang3-3.12.0-bin.tar.gz</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/binaries/commons-lang3-3.12.0-bin.tar.gz.sha512">sha512</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/binaries/commons-lang3-3.12.0-bin.tar.gz.asc">pgp</a></td>
+ </tr>
+ <tr>
+ <td><a href="[preferred]/commons/lang/binaries/commons-lang3-3.12.0-bin.zip">commons-lang3-3.12.0-bin.zip</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/binaries/commons-lang3-3.12.0-bin.zip.sha512">sha512</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/binaries/commons-lang3-3.12.0-bin.zip.asc">pgp</a></td>
+ </tr>
+ </table>
+ </subsection>
+ <subsection name="Source">
+ <table>
+ <tr>
+ <td><a href="[preferred]/commons/lang/source/commons-lang3-3.12.0-src.tar.gz">commons-lang3-3.12.0-src.tar.gz</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/source/commons-lang3-3.12.0-src.tar.gz.sha512">sha512</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/source/commons-lang3-3.12.0-src.tar.gz.asc">pgp</a></td>
+ </tr>
+ <tr>
+ <td><a href="[preferred]/commons/lang/source/commons-lang3-3.12.0-src.zip">commons-lang3-3.12.0-src.zip</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/source/commons-lang3-3.12.0-src.zip.sha512">sha512</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/source/commons-lang3-3.12.0-src.zip.asc">pgp</a></td>
+ </tr>
+ </table>
+ </subsection>
+ </section>
+ <section name="Apache Commons Lang 2.6 (Requires Java 1.2 or later)">
+ <subsection name="Binaries">
+ <table>
+ <tr>
+ <td><a href="[preferred]/commons/lang/binaries/commons-lang-2.6-bin.tar.gz">commons-lang-2.6-bin.tar.gz</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/binaries/commons-lang-2.6-bin.tar.gz.sha512">sha512</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/binaries/commons-lang-2.6-bin.tar.gz.asc">pgp</a></td>
+ </tr>
+ <tr>
+ <td><a href="[preferred]/commons/lang/binaries/commons-lang-2.6-bin.zip">commons-lang-2.6-bin.zip</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/binaries/commons-lang-2.6-bin.zip.sha512">sha512</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/binaries/commons-lang-2.6-bin.zip.asc">pgp</a></td>
+ </tr>
+ </table>
+ </subsection>
+ <subsection name="Source">
+ <table>
+ <tr>
+ <td><a href="[preferred]/commons/lang/source/commons-lang-2.6-src.tar.gz">commons-lang-2.6-src.tar.gz</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/source/commons-lang-2.6-src.tar.gz.sha512">sha512</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/source/commons-lang-2.6-src.tar.gz.asc">pgp</a></td>
+ </tr>
+ <tr>
+ <td><a href="[preferred]/commons/lang/source/commons-lang-2.6-src.zip">commons-lang-2.6-src.zip</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/source/commons-lang-2.6-src.zip.sha512">sha512</a></td>
+ <td><a href="https://www.apache.org/dist/commons/lang/source/commons-lang-2.6-src.zip.asc">pgp</a></td>
+ </tr>
+ </table>
+ </subsection>
+ </section>
+ <section name="Archives">
+ <p>
+ Older releases can be obtained from the archives.
+ </p>
+ <ul>
+ <li class="download"><a href="[preferred]/commons/lang/">browse download area</a></li>
+ <li><a href="https://archive.apache.org/dist/commons/lang/">archives...</a></li>
+ </ul>
+ </section>
+ </body>
+</document>
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
new file mode 100644
index 000000000..d80deb05a
--- /dev/null
+++ b/src/site/xdoc/index.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>Home</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+<!-- ================================================== -->
+<section name="Commons Lang">
+
+<p>
+The standard Java libraries fail to provide enough methods for
+manipulation of its core classes. Apache Commons Lang provides
+these extra methods.
+</p>
+
+<p>
+Apache Commons Lang provides a host of helper utilities for the java.lang API, notably
+String manipulation methods, basic numerical methods, object reflection, concurrency, creation and serialization
+and System properties. Additionally it contains basic enhancements to java.util.Date and a series of utilities dedicated to help with
+building methods, such as hashCode, toString and equals.
+</p>
+<p>
+Note that Commons Lang 3.0 (and subsequent versions) use a different package (<em>org.apache.commons.lang3</em>) than the previous versions (<em>org.apache.commons.lang</em>),
+allowing Commons Lang 3 to be used at the same time as Commons Lang 2.
+</p>
+</section>
+<!-- ================================================== -->
+<section name="Documentation">
+<p>
+The package descriptions in the <a href="javadocs/api-release/index.html">Javadoc</a> give an overview of the available features
+and various <a href="project-reports.html">project reports</a> are provided.
+</p>
+<p>
+The Javadoc API documents are available online:
+</p>
+<ul>
+<li>The <a href="javadocs/api-release/index.html">current release</a> [Java 8 and up]</li>
+<li>The <a href="javadocs/api-2.6/index.html">legacy release 2.6</a> [Java 1.2 and up]</li>
+<li>Older releases - see the <a href="changes-report.html">Release History</a> page</li>
+</ul>
+<p>
+The <a href="scm.html">git repository</a> can be
+<a href="https://gitbox.apache.org/repos/asf?p=commons-lang.git">browsed</a>, or you can browse/contribute via <a href="https://github.com/apache/commons-lang">GitHub</a>.
+</p>
+</section>
+<!-- ================================================== -->
+<section name="Release Information">
+<p>Read about the latest release:</p>
+<ul>
+<li>Pull it using a build tool like Maven using a <a href="dependency-info.html">dependency management reference</a>.</li>
+<li>Download the latest release from a <a href="https://commons.apache.org/lang/download_lang.cgi">mirror</a>.</li>
+<li>Read the <a href="changes-report.html">change report</a>.</li>
+<li>Examine the <a href="article3_0.html">2.x to 3.0 upgrade notes</a>.</li>
+<li>Compare major versions via the <a href="lang2-lang3-clirr-report.html">Lang2 to Lang3 Clirr report</a>.</li>
+</ul>
+
+<p>
+For information on previous releases see the <a href="changes-report.html">Release History</a>, and to download previous releases see the <a href="https://archive.apache.org/dist/commons/lang/">Commons Lang Archive</a>.
+</p>
+</section>
+<!-- ================================================== -->
+<section name="Getting Involved">
+<p>
+The <a href="mail-lists.html">commons developer mailing list</a> is the main channel of communication for contributors. Please remember that the lists are shared between all commons components, so prefix your email by [lang]. </p>
+<p>You can also visit the #apache-commons IRC channel on irc.freenode.net or peruse <a href="issue-tracking.html">JIRA</a>. Specific links of interest for JIRA are:</p>
+<ul>
+<li>Ideas looking for code: <a href="https://issues.apache.org/jira/issues/?jql=project%20%3D%20LANG%20AND%20resolution%20%3D%20Unresolved%20AND%20fixVersion%20%3D%20%22Patch%20Needed%22%20ORDER%20BY%20priority%20DESC">Patch Needed</a></li>
+<li>Issues with patches, looking for reviews: <a href="https://issues.apache.org/jira/issues/?jql=fixVersion%20%3D%20%22Review%20Patch%22%20AND%20project%20%3D%20LANG%20AND%20resolution%20%3D%20Unresolved%20ORDER%20BY%20priority%20DESC">Review Patch</a></li>
+</ul>
+<p>Alternatively you can go through the <em>Needs Work</em> tags in the <a href="taglist.html">TagList report</a>.</p>
+<p>If you'd like to offer up pull requests via GitHub rather than applying patches to JIRA, we have a <a href="https://github.com/apache/commons-lang/">GitHub mirror</a>. </p>
+</section>
+<!-- ================================================== -->
+<section name="Support">
+<p>
+The <a href="mail-lists.html">commons mailing lists</a> act as the main support forum.
+The user list is suitable for most library usage queries.
+The dev list is intended for the development discussion.
+Please remember that the lists are shared between all commons components,
+so prefix your email by [lang].
+</p>
+<p>
+Bug reports and enhancements are also welcomed via the <a href="issue-tracking.html">JIRA</a> issue tracker.
+Please read the instructions carefully.
+</p>
+</section>
+<!-- ================================================== -->
+</body>
+</document>
diff --git a/src/site/xdoc/issue-tracking.xml b/src/site/xdoc/issue-tracking.xml
new file mode 100644
index 000000000..748603569
--- /dev/null
+++ b/src/site/xdoc/issue-tracking.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |**** ****|
+ |**** THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN ****|
+ |**** DO NOT EDIT DIRECTLY ****|
+ |**** ****|
+ +======================================================================+
+ | TEMPLATE FILE: issue-tracking-template.xml |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ | |
+ | 1) Re-generate using: mvn commons-build:jira-page |
+ | |
+ | 2) Set the following properties in the component's pom: |
+ | - commons.jira.id (required, alphabetic, upper case) |
+ | - commons.jira.pid (required, numeric) |
+ | |
+ | 3) Example Properties |
+ | |
+ | <properties> |
+ | <commons.jira.id>MATH</commons.jira.id> |
+ | <commons.jira.pid>12310485</commons.jira.pid> |
+ | </properties> |
+ | |
+ +======================================================================+
+-->
+<document>
+ <properties>
+ <title>Apache Commons Lang Issue tracking</title>
+ <author email="dev@commons.apache.org">Apache Commons Documentation Team</author>
+ </properties>
+ <body>
+
+ <section name="Apache Commons Lang Issue tracking">
+ <p>
+ Apache Commons Lang uses <a href="https://issues.apache.org/jira/">ASF JIRA</a> for tracking issues.
+ See the <a href="https://issues.apache.org/jira/browse/LANG">Apache Commons Lang JIRA project page</a>.
+ </p>
+
+ <p>
+ To use JIRA you may need to <a href="https://issues.apache.org/jira/secure/Signup!default.jspa">create an account</a>
+ (if you have previously created/updated Commons issues using Bugzilla an account will have been automatically
+ created and you can use the <a href="https://issues.apache.org/jira/secure/ForgotLoginDetails.jspa">Forgot Password</a>
+ page to get a new password).
+ </p>
+
+ <p>
+ If you would like to report a bug, or raise an enhancement request with
+ Apache Commons Lang please do the following:
+ <ol>
+ <li><a href="https://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&amp;pid=12310481&amp;sorter/field=issuekey&amp;sorter/order=DESC&amp;status=1&amp;status=3&amp;status=4">Search existing open bugs</a>.
+ If you find your issue listed then please add a comment with your details.</li>
+ <li><a href="mail-lists.html">Search the mailing list archive(s)</a>.
+ You may find your issue or idea has already been discussed.</li>
+ <li>Decide if your issue is a bug or an enhancement.</li>
+ <li>Submit either a <a href="https://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310481&amp;issuetype=1&amp;priority=4&amp;assignee=-1">bug report</a>
+ or <a href="https://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310481&amp;issuetype=4&amp;priority=4&amp;assignee=-1">enhancement request</a>.</li>
+ </ol>
+ </p>
+
+ <p>
+ Please also remember these points:
+ <ul>
+ <li>the more information you provide, the better we can help you</li>
+ <li>test cases are vital, particularly for any proposed enhancements</li>
+ <li>the developers of Apache Commons Lang are all unpaid volunteers</li>
+ </ul>
+ </p>
+
+ <p>
+ For more information on subversion and creating patches see the
+ <a href="https://www.apache.org/dev/contributors.html">Apache Contributors Guide</a>.
+ </p>
+
+ <p>
+ You may also find these links useful:
+ <ul>
+ <li><a href="https://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&amp;pid=12310481&amp;sorter/field=issuekey&amp;sorter/order=DESC&amp;status=1&amp;status=3&amp;status=4">All Open Apache Commons Lang bugs</a></li>
+ <li><a href="https://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&amp;pid=12310481&amp;sorter/field=issuekey&amp;sorter/order=DESC&amp;status=5&amp;status=6">All Resolved Apache Commons Lang bugs</a></li>
+ <li><a href="https://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&amp;pid=12310481&amp;sorter/field=issuekey&amp;sorter/order=DESC">All Apache Commons Lang bugs</a></li>
+ </ul>
+ </p>
+ </section>
+ </body>
+</document>
diff --git a/src/site/xdoc/mail-lists.xml b/src/site/xdoc/mail-lists.xml
new file mode 100644
index 000000000..380401253
--- /dev/null
+++ b/src/site/xdoc/mail-lists.xml
@@ -0,0 +1,205 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |**** ****|
+ |**** THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN ****|
+ |**** DO NOT EDIT DIRECTLY ****|
+ |**** ****|
+ +======================================================================+
+ | TEMPLATE FILE: mail-lists-template.xml |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ | |
+ | 1) Re-generate using: mvn commons-build:mail-page |
+ | |
+ | 2) Set the following properties in the component's pom: |
+ | - commons.componentid (required, alphabetic, lower case) |
+ | |
+ | 3) Example Properties |
+ | |
+ | <properties> |
+ | <commons.componentid>math</commons.componentid> |
+ | </properties> |
+ | |
+ +======================================================================+
+-->
+<document>
+ <properties>
+ <title>Apache Commons Lang Mailing Lists</title>
+ <author email="dev@commons.apache.org">Apache Commons Documentation Team</author>
+ </properties>
+ <body>
+
+ <section name="Overview">
+ <p>
+ <a href="index.html">Apache Commons Lang</a> shares mailing lists with all the other
+ <a href="https://commons.apache.org/components.html">Commons Components</a>.
+ To make it easier for people to only read messages related to components they are interested in,
+ the convention in Commons is to prefix the subject line of messages with the component's name,
+ for example:
+ <ul>
+ <li>[lang] Problem with the ...</li>
+ </ul>
+ </p>
+ <p>
+ Questions related to the usage of Apache Commons Lang should be posted to the
+ <a href="https://mail-archives.apache.org/mod_mbox/commons-user/">User List</a>.
+ <br />
+ The <a href="https://mail-archives.apache.org/mod_mbox/commons-dev/">Developer List</a>
+ is for questions and discussion related to the development of Apache Commons Lang.
+ <br />
+ Please do not cross-post; developers are also subscribed to the user list.
+ <br />
+ You must be subscribed to post to the mailing lists. Follow the Subscribe links below
+ to subscribe.
+ </p>
+ <p>
+ <strong>Note:</strong> please don't send patches or attachments to any of the mailing lists.
+ Patches are best handled via the <a href="issue-tracking.html">Issue Tracking</a> system.
+ Otherwise, please upload the file to a public server and include the URL in the mail.
+ </p>
+ </section>
+
+ <section name="Apache Commons Lang Mailing Lists">
+ <p>
+ <strong>Please prefix the subject line of any messages for <a href="index.html">Apache Commons Lang</a>
+ with <i>[lang]</i></strong> - <i>thanks!</i>
+ <br />
+ <br />
+ </p>
+
+ <table>
+ <tr>
+ <th>Name</th>
+ <th>Subscribe</th>
+ <th>Unsubscribe</th>
+ <th>Post</th>
+ <th>Archive</th>
+ <th>Other Archives</th>
+ </tr>
+
+
+ <tr>
+ <td>
+ <strong>Commons User List</strong>
+ <br /><br />
+ Questions on using Apache Commons Lang.
+ <br /><br />
+ </td>
+ <td><a href="mailto:user-subscribe@commons.apache.org">Subscribe</a></td>
+ <td><a href="mailto:user-unsubscribe@commons.apache.org">Unsubscribe</a></td>
+ <td><a href="mailto:user@commons.apache.org?subject=[lang]">Post</a></td>
+ <td><a href="https://mail-archives.apache.org/mod_mbox/commons-user/">mail-archives.apache.org</a></td>
+ <td><a href="https://markmail.org/list/org.apache.commons.users/">markmail.org</a><br />
+ <a href="https://www.mail-archive.com/user@commons.apache.org/">www.mail-archive.com</a><br />
+ <a href="https://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td>
+ <strong>Commons Developer List</strong>
+ <br /><br />
+ Discussion of development of Apache Commons Lang.
+ <br /><br />
+ </td>
+ <td><a href="mailto:dev-subscribe@commons.apache.org">Subscribe</a></td>
+ <td><a href="mailto:dev-unsubscribe@commons.apache.org">Unsubscribe</a></td>
+ <td><a href="mailto:dev@commons.apache.org?subject=[lang]">Post</a></td>
+ <td><a href="https://mail-archives.apache.org/mod_mbox/commons-dev/">mail-archives.apache.org</a></td>
+ <td><a href="https://markmail.org/list/org.apache.commons.dev/">markmail.org</a><br />
+ <a href="https://www.mail-archive.com/dev@commons.apache.org/">www.mail-archive.com</a><br />
+ <a href="https://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td>
+ <strong>Commons Issues List</strong>
+ <br /><br />
+ Only for e-mails automatically generated by the <a href="issue-tracking.html">issue tracking</a> system.
+ <br /><br />
+ </td>
+ <td><a href="mailto:issues-subscribe@commons.apache.org">Subscribe</a></td>
+ <td><a href="mailto:issues-unsubscribe@commons.apache.org">Unsubscribe</a></td>
+ <td><i>read only</i></td>
+ <td><a href="https://mail-archives.apache.org/mod_mbox/commons-issues/">mail-archives.apache.org</a></td>
+ <td><a href="https://markmail.org/list/org.apache.commons.issues/">markmail.org</a><br />
+ <a href="https://www.mail-archive.com/issues@commons.apache.org/">www.mail-archive.com</a>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td>
+ <strong>Commons Commits List</strong>
+ <br /><br />
+ Only for e-mails automatically generated by the <a href="scm.html">source control</a> system.
+ <br /><br />
+ </td>
+ <td><a href="mailto:commits-subscribe@commons.apache.org">Subscribe</a></td>
+ <td><a href="mailto:commits-unsubscribe@commons.apache.org">Unsubscribe</a></td>
+ <td><i>read only</i></td>
+ <td><a href="https://mail-archives.apache.org/mod_mbox/commons-commits/">mail-archives.apache.org</a></td>
+ <td><a href="https://markmail.org/list/org.apache.commons.commits/">markmail.org</a><br />
+ <a href="https://www.mail-archive.com/commits@commons.apache.org/">www.mail-archive.com</a>
+ </td>
+ </tr>
+
+ </table>
+
+ </section>
+ <section name="Apache Mailing Lists">
+ <p>
+ Other mailing lists which you may find useful include:
+ </p>
+
+ <table>
+ <tr>
+ <th>Name</th>
+ <th>Subscribe</th>
+ <th>Unsubscribe</th>
+ <th>Post</th>
+ <th>Archive</th>
+ <th>Other Archives</th>
+ </tr>
+ <tr>
+ <td>
+ <strong>Apache Announce List</strong>
+ <br /><br />
+ General announcements of Apache project releases.
+ <br /><br />
+ </td>
+ <td><a class="externalLink" href="mailto:announce-subscribe@apache.org">Subscribe</a></td>
+ <td><a class="externalLink" href="mailto:announce-unsubscribe@apache.org">Unsubscribe</a></td>
+ <td><i>read only</i></td>
+ <td><a class="externalLink" href="https://mail-archives.apache.org/mod_mbox/www-announce/">mail-archives.apache.org</a></td>
+ <td><a class="externalLink" href="https://markmail.org/list/org.apache.announce/">markmail.org</a><br />
+ <a class="externalLink" href="https://old.nabble.com/Apache-News-and-Announce-f109.html">old.nabble.com</a><br />
+ <a class="externalLink" href="https://www.mail-archive.com/announce@apache.org/">www.mail-archive.com</a><br />
+ <a class="externalLink" href="https://news.gmane.org/gmane.comp.apache.announce">news.gmane.org</a>
+ </td>
+ </tr>
+ </table>
+
+ </section>
+ </body>
+</document>
diff --git a/src/site/xdoc/proposal.xml b/src/site/xdoc/proposal.xml
new file mode 100644
index 000000000..fc9477f25
--- /dev/null
+++ b/src/site/xdoc/proposal.xml
@@ -0,0 +1,97 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+<properties>
+<title>Proposal for Lang Package</title>
+</properties>
+<body>
+
+
+<section name="Proposal for Lang Package">
+
+
+
+<subsection name="(0) Rationale">
+
+<p>The standard Java libraries fail to provide enough methods for
+manipulation of its main components. The <em>Lang</em> Package provides
+these extra methods. There are other classes which might justifiably
+be included in java.lang someday, this package also provides for them.</p>
+
+
+</subsection>
+<subsection name="(1) Scope of the Package">
+
+<p>This proposal is to create a package of Java utility classes for the
+classes that are in java.lang's hierarchy, or are considered to be so
+standard as to justify existence in java.lang. The <em>Lang</em> Package
+also applies to primitives and arrays.</p>
+
+
+</subsection>
+<subsection name="(1.5) Interaction With Other Packages">
+
+<p><em>Lang</em> relies only on standard JDK 1.2 (or later) APIs for
+production deployment. It utilizes the JUnit unit testing framework for
+developing and executing unit tests, but this is of interest only to
+developers of the component. Lang will be a dependency for
+several existing components in the open source world.</p>
+
+<p>No external configuration files are utilized.</p>
+
+
+</subsection>
+<subsection name="(2) Initial Source of the Package">
+
+<p>The initial classes came from the Commons.Util subproject.</p>
+
+<p>The proposed package name for the new component is
+<code>org.apache.commons.lang</code>.</p>
+
+
+</subsection>
+<subsection name="(3) Required Jakarta-Commons Resources">
+
+<ul>
+<li>CVS Repository - New directory <code>lang</code> in the
+ <code>jakarta-commons</code> CVS repository.</li>
+<li>Mailing List - Discussions will take place on the general
+ <em>dev@commons.apache.org</em> mailing list. To help
+ list subscribers identify messages of interest, it is suggested that
+ the message subject of messages about this component be prefixed with
+ [lang].</li>
+<li>Bugzilla - New component "Lang" under the "Commons" product
+ category, with appropriate version identifiers as needed.</li>
+<li>Jyve FAQ - New category "commons-lang" (when available).</li>
+</ul>
+
+
+</subsection>
+<subsection name="(4) Initial Committers">
+
+<p>The initial committers on the Lang component shall be as follows:
+<ul>
+<li>Henri Yandell (bayard)</li>
+<li>Daniel Rall (dlr)</li>
+<li>Stephen Colebourne (scolebourne)</li>
+</ul>
+</p>
+
+</subsection>
+</section>
+</body>
+</document>
diff --git a/src/site/xdoc/upgradeto2_0.xml b/src/site/xdoc/upgradeto2_0.xml
new file mode 100644
index 000000000..e7434f8a9
--- /dev/null
+++ b/src/site/xdoc/upgradeto2_0.xml
@@ -0,0 +1,688 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>2.0 Release Notes</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="Lang 2.0 Release Notes">
+<p>
+These are the release notes and advice for upgrading Commons-Lang from
+version 1.0 to version 2.0.
+<source>
+INTRODUCTION:
+
+This document contains the release notes for this version of the Commons
+Lang package. Commons Lang is a set of utility functions and reusable
+components that should be a help in any Java environment.
+
+This release has involved a major clean and tidy exercise.
+Javadoc and Tests are now much more thorough.
+All methods should now be much clearer in what they do in unusual cases.
+
+
+INCOMPATIBLE CHANGES:
+Some StringUtils methods have changed functionality from 1.0:
+ isEmpty()
+ chomp(String)
+ chomp(String,String)
+ swapCase(String)
+Numerous other methods have changed null handling to accept nulls gracefully.
+As with all major version releases, check your code for incompatibilities.
+
+
+NEW FEATURES:
+
+Since the release of the 1.0 package the following classes have been added:
+
+lang package:
+ ArrayUtils
+ BitField
+ BooleanUtils
+ CharRange (previously package scoped)
+ ClassUtils
+ StringEscapeUtils
+ WordUtils
+ IllegalClassException
+ IncompleteArgumentException
+ NotImplementedException
+ NullArgumentException
+ SerializationException
+ UnhandledException
+ Validate
+
+
+math sub-package:
+ IntRange
+ LongRange
+ Range
+ DoubleRange
+ JVMRandom
+ NumberRange
+ FloatRange
+ NumberUtils
+ Fraction
+ RandomUtils
+
+time sub-package:
+ DateFormatUtils
+ FastDateFormat
+ DateUtils
+ StopWatch
+
+Since the release of the 1.0 package the following classes have been changed:
+
+lang:
+ CharSet:
+ Added factory method, equals and hashCode().
+ Better defined and tested the set syntax.
+ CharSetUtils:
+ added keep method: keep any characters specified in the CharSet string
+ RandomStringUtils:
+ random method: overloaded to allow passing in of a Random class
+ SerializationUtils:
+ added empty constructor
+ StringUtils:
+ isEmpty() changed to not trim
+ chomp() changed to be more like Perl.
+ swapCase() no longer word based, but no difference if you pass in ASCII
+ Various methods changed in the handling of null (less exceptions).
+ Many new methods.
+ Various methods deprecated.
+ SystemUtils:
+ isJavaVersionAtLeast(int) added. getJavaVersion() deprecated.
+ host of new constants.
+
+enum:
+ Enum:
+ getEnumClass(Class) added
+ EnumUtils:
+ Removed irrelevant Comparable/Serializable interfaces.
+
+exception:
+ NestableDelegate:
+ Gained many new methods for dissecting an Exception.
+ ExceptionUtils:
+ Gained many new methods to improve handling of nested stack traces.
+
+builder:
+ ReflectionToStringBuilder:
+ Handy class added for creating default toStrings.
+ All other builder classes received a set of new methods.
+
+
+BUG FIXES:
+
+ID Sev Pri Plt Owner State Result Summary
+13367 [PATCH] StringUtil enhancement
+13391 Javadoc nit
+13771 Additional Lang Method Suggestions
+14306 NullPointerException in CompareToBuilder
+14357 static option for reversing the stacktrace
+14447 ToStringBuilder doesn't work well in subclasses
+14883 StringUtils.countMatches loops forever if substring empty
+14884 NumberRange inaccurate for Long, etc.
+14985 More flexibility for getRootCause in ExceptionUtils
+15154 SystemUtils.IS_JAVA_1_5 Javadoc is wrong
+15257 Hierarchy support in ToStringBuilder.reflectionToString()
+15438 ArrayUtils.contains()
+15439 Enum does not support inner sub-classes
+15986 Infinite loop in ToStringBuilder.reflectionToString for inne
+16076 Example in Javadoc for ToStringBuilder wrong for append.
+16193 Hierarchy support in EqualsBuilder.reflectionEquals()
+16202 typo in the javadoc example code
+16204 Infinite loop in StringUtils.replace(text, repl, with) + FIX
+16227 Added class hierarchy support to CompareToBuilder.reflectionC
+16228 Added class hierarchy support to HashCodeBuilder.reflectionHa
+16284 MethodUtils: Removed unused code/unused local vars.
+16341 No Javadoc for NestableDelegate
+16622 Removed compile warning in FastDateFormat
+16669 Javadoc Errata
+16676 StackOverflow due to ToStringBuilder
+16689 ExceptionUtils new methods.
+16690 Specify initial size for Enum's HashMap.
+16787 Removed compile warning in ObjectUtils
+17250 [Lang] Should ToStringBuilder.reflectionToString handle arra
+17654 EnumUtils nit: The import java.io.Serializable is never used
+17882 Add join(..., char c) to StringUtils (and some performance f
+18077 StringUtils.chomp does not match Perl
+18723 RandomStringUtils infinite loop with length &lt; 1
+18836 test.lang fails if compiled with non iso-8859-1 locales
+18948 Resurrect the WordWrapUtils from commons-sandbox/utils
+19296 [Lang] What to do with FastDateFormat unused private constru
+19364 [Lang] time unit tests fail on Sundays
+19756 [lang] java.lang.ExceptionInInitializerError thrown by JVMRa
+19880 [lang] patch and test case fixing problem with RandomStringU
+20165 [LANG] SystemUtils does not play nice in an Applet
+20538 [lang] NumberUtils.isNumber allows illegal trailing characte
+20592 [lang] RandomStringUtils.randomAlpha methods omit 'z'
+20603 [lang] Make NestableDelegate methods public instead of packa
+20632 Refactored reflection feature of ToStringBuilder into new Re
+20652 StringUtils.chopNewLine - StringIndexOutOfBoundsException
+21021 [PATCH] reduce object creation in ToStringBuilder
+21068 [lang] [PATCH] NumberUtils min/max, BooleanUtils.xor, and Ar
+21099 [lang][PATCH] Unused field 'startFinal' in DateIterator
+21715 The javadoc says "Mac" instead of "OS/2"
+21734 [PATCH] all NumberUtils.createXXX(String) methods handle null
+21750 [lang] StringUtils javadoc and test enhancements
+21758 [lang] lang.builder classes javadoc edits (mostly typo fixes)
+21797 [lang] Add javadoc examples and tests for StringUtils
+21809 [lang] maven-beta10 checkstyle problem
+21904 NumberUtils.createBigDecimal("") NPE in Sun 1.3.1_08
+21952 [lang] Improved tests, javadoc for CharSetUtils, StringEscapeUtils
+22091 Adding tolerance to double[] search methods in ArrayUtils
+22094 A small, but important javadoc fix for Fraction proper whole/numerator
+22095 [lang] Javadoc, tests improvements for CharSet, CharSetUtils
+22098 [lang] Improve util.Validate tests
+22245 [lang] test.time fails in Japanese (non-us) locale.
+22286 [lang] Missing @since tags
+22367 Typo in documentation
+22386 [lang] Improve javadoc and overflow behavior of Fraction
+
+
+DEPRECATIONS:
+
+lang:
+ NumberRange:
+ now deprecated, see math subpackage
+ NumberUtils:
+ now deprecated, see math subpackage
+
+
+CHANGES: [In 'diff' format]
+
+Jar changes
+===========
+&gt; org.apache.commons.lang.math.Range
+&gt; org.apache.commons.lang.math.FloatRange
+&gt; org.apache.commons.lang.math.NumberUtils
+&gt; org.apache.commons.lang.math.JVMRandom
+&gt; org.apache.commons.lang.math.IntRange
+&gt; org.apache.commons.lang.math.LongRange
+&gt; org.apache.commons.lang.math.DoubleRange
+&gt; org.apache.commons.lang.math.NumberRange
+&gt; org.apache.commons.lang.math.Fraction
+&gt; org.apache.commons.lang.math.RandomUtils
+&gt; org.apache.commons.lang.time.FastDateFormat
+&gt; org.apache.commons.lang.time.DateUtils$DateIterator
+&gt; org.apache.commons.lang.time.DateUtils
+&gt; org.apache.commons.lang.time.FastDateFormat$UnpaddedMonthField
+&gt; org.apache.commons.lang.time.FastDateFormat$StringLiteral
+&gt; org.apache.commons.lang.time.FastDateFormat$TwelveHourField
+&gt; org.apache.commons.lang.time.FastDateFormat$NumberRule
+&gt; org.apache.commons.lang.time.FastDateFormat$CharacterLiteral
+&gt; org.apache.commons.lang.time.FastDateFormat$TimeZoneNumberRule
+&gt; org.apache.commons.lang.time.FastDateFormat$TimeZoneNameRule
+&gt; org.apache.commons.lang.time.DateFormatUtils
+&gt; org.apache.commons.lang.time.FastDateFormat$TwoDigitMonthField
+&gt; org.apache.commons.lang.time.DurationFormatUtils
+&gt; org.apache.commons.lang.time.FastDateFormat$TimeZoneDisplayKey
+&gt; org.apache.commons.lang.time.FastDateFormat$UnpaddedNumberField
+&gt; org.apache.commons.lang.time.FastDateFormat$PaddedNumberField
+&gt; org.apache.commons.lang.time.StopWatch
+&gt; org.apache.commons.lang.time.FastDateFormat$TwentyFourHourField
+&gt; org.apache.commons.lang.time.FastDateFormat$Rule
+&gt; org.apache.commons.lang.time.FastDateFormat$TwoDigitNumberField
+&gt; org.apache.commons.lang.time.FastDateFormat$TextField
+&gt; org.apache.commons.lang.time.FastDateFormat$Pair
+&gt; org.apache.commons.lang.time.FastDateFormat$TwoDigitYearField
+&gt; org.apache.commons.lang.util.IdentifierUtils$StringNumericIdentifierFactory
+&gt; org.apache.commons.lang.util.IdentifierUtils$StringSessionIdentifierFactory
+&gt; org.apache.commons.lang.util.IdentifierUtils$LongNumericIdentifierFactory
+&gt; org.apache.commons.lang.util.IdentifierUtils$StringAlphanumericIdentifierFactory
+&gt; org.apache.commons.lang.util.Validate
+&gt; org.apache.commons.lang.util.LongIdentifierFactory
+&gt; org.apache.commons.lang.util.IdentifierUtils$1
+&gt; org.apache.commons.lang.util.StringIdentifierFactory
+&gt; org.apache.commons.lang.util.IdentifierUtils
+&gt; org.apache.commons.lang.util.IdentifierFactory
+&gt; org.apache.commons.lang.util.BitField
+&gt; org.apache.commons.lang.Entities
+&gt; org.apache.commons.lang.Entities$LookupEntityMap
+&gt; org.apache.commons.lang.NotImplementedException
+&gt; org.apache.commons.lang.NullArgumentException
+&lt; org.apache.commons.lang.ObjectUtils$1
+---
+&gt; org.apache.commons.lang.StringPrintWriter
+&gt; org.apache.commons.lang.UnhandledException
+&gt; org.apache.commons.lang.Entities$HashEntityMap
+&gt; org.apache.commons.lang.Entities$ArrayEntityMap
+&gt; org.apache.commons.lang.Entities$EntityMap
+&gt; org.apache.commons.lang.IntHashMap
+&gt; org.apache.commons.lang.BooleanUtils
+&gt; org.apache.commons.lang.IncompleteArgumentException
+&gt; org.apache.commons.lang.Entities$PrimitiveEntityMap
+&gt; org.apache.commons.lang.Entities$TreeEntityMap
+&gt; org.apache.commons.lang.WordUtils
+&gt; org.apache.commons.lang.StringEscapeUtils
+&gt; org.apache.commons.lang.ArrayUtils
+&gt; org.apache.commons.lang.Entities$BinaryEntityMap
+&gt; org.apache.commons.lang.ClassUtils
+&gt; org.apache.commons.lang.IntHashMap$Entry
+&gt; org.apache.commons.lang.IllegalClassException
+&gt; org.apache.commons.lang.builder.ReflectionToStringBuilder$1
+&gt; org.apache.commons.lang.builder.ReflectionToStringBuilder
+&gt; org.apache.commons.lang.Entities$MapIntMap
+
+
+Class changes
+=============
+org.apache.commons.lang.enum.EnumUtils
+--------------------
+&lt; public abstract class org.apache.commons.lang.enum.EnumUtils extends java.lang.Object implements java.lang.Comparable, java.io.Serializable {
+---
+&gt; public class org.apache.commons.lang.enum.EnumUtils extends java.lang.Object {
+&gt; public org.apache.commons.lang.enum.EnumUtils();
+
+org.apache.commons.lang.enum.Enum$Entry
+--------------------
+&gt; final java.util.Map unmodifiableMap;
+&gt; final java.util.List unmodifiableList;
+
+org.apache.commons.lang.enum.Enum
+--------------------
+&gt; protected transient java.lang.String iToString;
+&gt; static java.lang.Class class$org$apache$commons$lang$enum$ValuedEnum;
+&gt; public java.lang.Class getEnumClass();
+
+org.apache.commons.lang.enum.ValuedEnum
+--------------------
+&gt; static {};
+
+org.apache.commons.lang.StringUtils
+--------------------
+&gt; public static final java.lang.String EMPTY;
+&gt; public static boolean isEmpty(java.lang.String);
+&gt; public static boolean isNotEmpty(java.lang.String);
+&gt; public static boolean isBlank(java.lang.String);
+&gt; public static boolean isNotBlank(java.lang.String);
+&lt; public static java.lang.String deleteSpaces(java.lang.String);
+&lt; public static java.lang.String deleteWhitespace(java.lang.String);
+&lt; public static boolean isNotEmpty(java.lang.String);
+&lt; public static boolean isEmpty(java.lang.String);
+---
+&gt; public static java.lang.String trimToNull(java.lang.String);
+&gt; public static java.lang.String trimToEmpty(java.lang.String);
+&gt; public static java.lang.String strip(java.lang.String);
+&gt; public static java.lang.String stripToNull(java.lang.String);
+&gt; public static java.lang.String stripToEmpty(java.lang.String);
+&gt; public static java.lang.String strip(java.lang.String, java.lang.String);
+&gt; public static java.lang.String stripStart(java.lang.String, java.lang.String);
+&gt; public static java.lang.String stripEnd(java.lang.String, java.lang.String);
+&gt; public static java.lang.String stripAll(java.lang.String[])[];
+&gt; public static java.lang.String stripAll(java.lang.String[], java.lang.String)[];
+&gt; public static int indexOf(java.lang.String, char);
+&gt; public static int indexOf(java.lang.String, char, int);
+&gt; public static int indexOf(java.lang.String, java.lang.String);
+&gt; public static int indexOf(java.lang.String, java.lang.String, int);
+&gt; public static int lastIndexOf(java.lang.String, char);
+&gt; public static int lastIndexOf(java.lang.String, char, int);
+&gt; public static int lastIndexOf(java.lang.String, java.lang.String);
+&gt; public static int lastIndexOf(java.lang.String, java.lang.String, int);
+&gt; public static boolean contains(java.lang.String, char);
+&gt; public static boolean contains(java.lang.String, java.lang.String);
+&gt; public static int indexOfAny(java.lang.String, char[]);
+&gt; public static int indexOfAny(java.lang.String, java.lang.String);
+&gt; public static int indexOfAnyBut(java.lang.String, char[]);
+&gt; public static int indexOfAnyBut(java.lang.String, java.lang.String);
+&gt; public static boolean containsOnly(java.lang.String, char[]);
+&gt; public static boolean containsOnly(java.lang.String, java.lang.String);
+&gt; public static boolean containsNone(java.lang.String, char[]);
+&gt; public static boolean containsNone(java.lang.String, java.lang.String);
+&gt; public static java.lang.String substringBefore(java.lang.String, java.lang.String);
+&gt; public static java.lang.String substringAfter(java.lang.String, java.lang.String);
+&gt; public static java.lang.String substringBeforeLast(java.lang.String, java.lang.String);
+&gt; public static java.lang.String substringAfterLast(java.lang.String, java.lang.String);
+&gt; public static java.lang.String substringBetween(java.lang.String, java.lang.String);
+&gt; public static java.lang.String substringBetween(java.lang.String, java.lang.String, java.lang.String);
+&gt; public static java.lang.String getNestedString(java.lang.String, java.lang.String);
+&gt; public static java.lang.String getNestedString(java.lang.String, java.lang.String, java.lang.String);
+&gt; public static java.lang.String split(java.lang.String, char)[];
+&gt; public static java.lang.String join(java.lang.Object[]);
+&gt; public static java.lang.String join(java.lang.Object[], char);
+&gt; public static java.lang.String join(java.util.Iterator, char);
+&gt; public static java.lang.String deleteSpaces(java.lang.String);
+&gt; public static java.lang.String deleteWhitespace(java.lang.String);
+&gt; public static java.lang.String replaceChars(java.lang.String, char, char);
+&gt; public static java.lang.String replaceChars(java.lang.String, java.lang.String, java.lang.String);
+&lt; public static java.lang.String center(java.lang.String, int);
+&lt; public static java.lang.String center(java.lang.String, int, java.lang.String);
+---
+&gt; public static java.lang.String overlay(java.lang.String, java.lang.String, int, int);
+&gt; public static java.lang.String rightPad(java.lang.String, int, char);
+&gt; public static java.lang.String leftPad(java.lang.String, int, char);
+&lt; public static java.lang.String strip(java.lang.String);
+&lt; public static java.lang.String strip(java.lang.String, java.lang.String);
+&lt; public static java.lang.String stripAll(java.lang.String[])[];
+&lt; public static java.lang.String stripAll(java.lang.String[], java.lang.String)[];
+&lt; public static java.lang.String stripEnd(java.lang.String, java.lang.String);
+&lt; public static java.lang.String stripStart(java.lang.String, java.lang.String);
+---
+&gt; public static java.lang.String center(java.lang.String, int);
+&gt; public static java.lang.String center(java.lang.String, int, char);
+&gt; public static java.lang.String center(java.lang.String, int, java.lang.String);
+&lt; public static java.lang.String uncapitalise(java.lang.String);
+---
+&gt; public static java.lang.String capitalize(java.lang.String);
+&gt; public static java.lang.String uncapitalize(java.lang.String);
+&gt; public static java.lang.String uncapitalise(java.lang.String);
+&lt; public static java.lang.String getNestedString(java.lang.String, java.lang.String);
+&lt; public static java.lang.String getNestedString(java.lang.String, java.lang.String, java.lang.String);
+&gt; public static boolean isWhitespace(java.lang.String);
+&gt; public static java.lang.String reverseDelimited(java.lang.String, char);
+&gt; public static java.lang.String abbreviate(java.lang.String, int);
+&gt; public static java.lang.String abbreviate(java.lang.String, int, int);
+&gt; public static java.lang.String difference(java.lang.String, java.lang.String);
+&gt; public static int differenceAt(java.lang.String, java.lang.String);
+&lt; public static boolean containsOnly(java.lang.String, char[]);
+---
+&gt; static {};
+
+org.apache.commons.lang.ObjectUtils
+--------------------
+&gt; public static java.lang.StringBuffer appendIdentityToString(java.lang.StringBuffer, java.lang.Object);
+&gt; public static java.lang.String toString(java.lang.Object);
+&gt; public static java.lang.String toString(java.lang.Object, java.lang.String);
+&lt; org.apache.commons.lang.ObjectUtils.Null(org.apache.commons.lang.ObjectUtils$1);
+---
+&gt; org.apache.commons.lang.ObjectUtils.Null();
+&gt; static {};
+
+org.apache.commons.lang.exception.NestableDelegate
+--------------------
+&gt; public static boolean topDown;
+&gt; public static boolean trimStackFrames;
+&lt; org.apache.commons.lang.exception.NestableDelegate(org.apache.commons.lang.exception.Nestable);
+&lt; java.lang.String getMessage(int);
+&lt; java.lang.String getMessage(java.lang.String);
+&lt; java.lang.String getMessages()[];
+&lt; java.lang.Throwable getThrowable(int);
+&lt; int getThrowableCount();
+&lt; java.lang.Throwable getThrowables()[];
+&lt; int indexOfThrowable(java.lang.Class, int);
+---
+&gt; public org.apache.commons.lang.exception.NestableDelegate(org.apache.commons.lang.exception.Nestable);
+&gt; public java.lang.String getMessage(int);
+&gt; public java.lang.String getMessage(java.lang.String);
+&gt; public java.lang.String getMessages()[];
+&gt; public java.lang.Throwable getThrowable(int);
+&gt; public int getThrowableCount();
+&gt; public java.lang.Throwable getThrowables()[];
+&gt; public int indexOfThrowable(java.lang.Class, int);
+&gt; protected java.lang.String getStackFrames(java.lang.Throwable)[];
+&gt; protected void trimStackFrames(java.util.List);
+
+org.apache.commons.lang.exception.ExceptionUtils
+--------------------
+&lt; protected static final java.lang.String CAUSE_METHOD_NAMES[];
+&lt; protected static final java.lang.Object CAUSE_METHOD_PARAMS[];
+---
+&gt; static final java.lang.String WRAPPED_MARKER;
+&lt; protected org.apache.commons.lang.exception.ExceptionUtils();
+---
+&gt; public org.apache.commons.lang.exception.ExceptionUtils();
+&gt; public static void addCauseMethodName(java.lang.String);
+&gt; public static boolean isThrowableNested();
+&gt; public static boolean isNestedThrowable(java.lang.Throwable);
+&gt; public static void printRootCauseStackTrace(java.lang.Throwable);
+&gt; public static void printRootCauseStackTrace(java.lang.Throwable, java.io.PrintStream);
+&gt; public static void printRootCauseStackTrace(java.lang.Throwable, java.io.PrintWriter);
+&gt; public static java.lang.String getRootCauseStackTrace(java.lang.Throwable)[];
+&gt; public static void removeCommonFrames(java.util.List, java.util.List);
+&gt; public static java.lang.String getFullStackTrace(java.lang.Throwable);
+&gt; static java.util.List getStackFrameList(java.lang.Throwable);
+
+org.apache.commons.lang.CharRange
+--------------------
+&lt; class org.apache.commons.lang.CharRange extends java.lang.Object {
+---
+&gt; public final class org.apache.commons.lang.CharRange extends java.lang.Object implements java.io.Serializable {
+&gt; public org.apache.commons.lang.CharRange(char,boolean);
+&lt; public org.apache.commons.lang.CharRange(java.lang.String,java.lang.String);
+---
+&gt; public org.apache.commons.lang.CharRange(char,char,boolean);
+&lt; public void setStart(char);
+&lt; public void setEnd(char);
+&lt; public boolean isRange();
+&lt; public boolean inRange(char);
+&lt; public void setNegated(boolean);
+---
+&gt; public boolean contains(char);
+&gt; public boolean contains(org.apache.commons.lang.CharRange);
+&gt; public boolean equals(java.lang.Object);
+&gt; public int hashCode();
+&gt; static {};
+
+org.apache.commons.lang.ObjectUtils$1
+--------------------
+&lt; Compiled from ObjectUtils.java
+&lt; class org.apache.commons.lang.ObjectUtils$1 extends java.lang.Object {
+&lt; }
+---
+&gt; Class 'org.apache.commons.lang.ObjectUtils$1' has been removed
+
+org.apache.commons.lang.ObjectUtils$Null
+--------------------
+&lt; org.apache.commons.lang.ObjectUtils.Null(org.apache.commons.lang.ObjectUtils$1);
+---
+&gt; org.apache.commons.lang.ObjectUtils.Null();
+&gt; static {};
+
+org.apache.commons.lang.SystemUtils
+--------------------
+&gt; public static final java.lang.String FILE_ENCODING;
+&gt; public static final java.lang.String JAVA_RUNTIME_NAME;
+&gt; public static final java.lang.String JAVA_RUNTIME_VERSION;
+&gt; public static final java.lang.String JAVA_VM_INFO;
+&gt; public static final java.lang.String USER_COUNTRY;
+&gt; public static final java.lang.String USER_LANGUAGE;
+&gt; public static final float JAVA_VERSION_FLOAT;
+&gt; public static final int JAVA_VERSION_INT;
+&gt; public static final boolean IS_OS_AIX;
+&gt; public static final boolean IS_OS_HP_UX;
+&gt; public static final boolean IS_OS_IRIX;
+&gt; public static final boolean IS_OS_LINUX;
+&gt; public static final boolean IS_OS_MAC;
+&gt; public static final boolean IS_OS_MAC_OSX;
+&gt; public static final boolean IS_OS_OS2;
+&gt; public static final boolean IS_OS_SOLARIS;
+&gt; public static final boolean IS_OS_SUN_OS;
+&gt; public static final boolean IS_OS_WINDOWS;
+&gt; public static final boolean IS_OS_WINDOWS_2000;
+&gt; public static final boolean IS_OS_WINDOWS_95;
+&gt; public static final boolean IS_OS_WINDOWS_98;
+&gt; public static final boolean IS_OS_WINDOWS_ME;
+&gt; public static final boolean IS_OS_WINDOWS_NT;
+&gt; public static final boolean IS_OS_WINDOWS_XP;
+&gt; public static boolean isJavaVersionAtLeast(int);
+
+org.apache.commons.lang.SerializationUtils
+--------------------
+&gt; public org.apache.commons.lang.SerializationUtils();
+
+org.apache.commons.lang.RandomStringUtils
+--------------------
+&gt; public static java.lang.String random(int, int, int, boolean, boolean, char[], java.util.Random);
+
+org.apache.commons.lang.CharSet
+--------------------
+&lt; public class org.apache.commons.lang.CharSet extends java.lang.Object {
+---
+&gt; public class org.apache.commons.lang.CharSet extends java.lang.Object implements java.io.Serializable {
+&gt; public static final org.apache.commons.lang.CharSet EMPTY;
+&gt; public static final org.apache.commons.lang.CharSet ASCII_ALPHA;
+&gt; public static final org.apache.commons.lang.CharSet ASCII_ALPHA_LOWER;
+&gt; public static final org.apache.commons.lang.CharSet ASCII_ALPHA_UPPER;
+&gt; public static final org.apache.commons.lang.CharSet ASCII_NUMERIC;
+&gt; protected static final java.util.Map COMMON;
+&gt; public static org.apache.commons.lang.CharSet getInstance(java.lang.String);
+&gt; protected org.apache.commons.lang.CharSet(java.lang.String);
+&lt; public boolean contains(char);
+&gt; public org.apache.commons.lang.CharRange getCharRanges()[];
+&gt; public boolean contains(char);
+&gt; public boolean equals(java.lang.Object);
+&gt; public int hashCode();
+&gt; static {};
+
+org.apache.commons.lang.CharSetUtils
+--------------------
+&gt; public static java.lang.String keep(java.lang.String, java.lang.String);
+&gt; public static java.lang.String keep(java.lang.String, java.lang.String[]);
+
+org.apache.commons.lang.builder.ToStringBuilder
+--------------------
+&lt; public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object,org.apache.commons.lang.builder.ToStringStyle);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object,org.apache.commons.lang.builder.ToStringStyle,java.lang.StringBuffer);
+&lt; public static void setDefaultStyle(org.apache.commons.lang.builder.ToStringStyle);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.Object);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object, boolean);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(long);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(int);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(short);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(char);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char);
+---
+&gt; public static java.lang.String reflectionToString(java.lang.Object, org.apache.commons.lang.builder.ToStringStyle, boolean, java.lang.Class);
+&gt; public static void setDefaultStyle(org.apache.commons.lang.builder.ToStringStyle);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object,org.apache.commons.lang.builder.ToStringStyle);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder(java.lang.Object,org.apache.commons.lang.builder.ToStringStyle,java.lang.StringBuffer);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(boolean[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, byte);
+---
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(byte[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(char);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(char[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, double);
+---
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(double[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, float);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(boolean);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.Object[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object[], boolean);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(long[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long[], boolean);
+---
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(float[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(int);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int[], boolean);
+---
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(long);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(long[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.Object);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.Object[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(short);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short[], boolean);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(char[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char[], boolean);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(byte[]);
+---
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean[], boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, byte);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(double[]);
+---
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, char[], boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, double);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(float[]);
+---
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, float);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(boolean[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean[]);
+&lt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, boolean[], boolean);
+---
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, int[], boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, long[], boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object, boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, java.lang.Object[], boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short[]);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder append(java.lang.String, short[], boolean);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder appendAsObjectToString(java.lang.Object);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder appendSuper(java.lang.String);
+&gt; public org.apache.commons.lang.builder.ToStringBuilder appendToString(java.lang.String);
+&gt; public org.apache.commons.lang.builder.ToStringStyle getStyle();
+&gt; public java.lang.Object getObject();
+
+org.apache.commons.lang.builder.StandardToStringStyle
+--------------------
+&gt; public boolean isUseShortClassName();
+&gt; public void setUseShortClassName(boolean);
+&gt; public boolean isFieldSeparatorAtStart();
+&gt; public void setFieldSeparatorAtStart(boolean);
+&gt; public boolean isFieldSeparatorAtEnd();
+&gt; public void setFieldSeparatorAtEnd(boolean);
+
+org.apache.commons.lang.builder.ToStringStyle
+--------------------
+&gt; public void appendSuper(java.lang.StringBuffer, java.lang.String);
+&gt; public void appendToString(java.lang.StringBuffer, java.lang.String);
+&gt; protected void removeLastFieldSeparator(java.lang.StringBuffer);
+&gt; protected void reflectionAppendArrayDetail(java.lang.StringBuffer, java.lang.String, java.lang.Object);
+&gt; protected boolean isUseShortClassName();
+&gt; protected void setUseShortClassName(boolean);
+&gt; protected boolean isFieldSeparatorAtStart();
+&gt; protected void setFieldSeparatorAtStart(boolean);
+&gt; protected boolean isFieldSeparatorAtEnd();
+&gt; protected void setFieldSeparatorAtEnd(boolean);
+
+org.apache.commons.lang.builder.HashCodeBuilder
+--------------------
+&gt; public static int reflectionHashCode(int, int, java.lang.Object, boolean, java.lang.Class);
+&gt; public org.apache.commons.lang.builder.HashCodeBuilder appendSuper(int);
+
+org.apache.commons.lang.builder.CompareToBuilder
+--------------------
+&gt; public static int reflectionCompare(java.lang.Object, java.lang.Object, boolean, java.lang.Class);
+&gt; public org.apache.commons.lang.builder.CompareToBuilder appendSuper(int);
+&gt; public org.apache.commons.lang.builder.CompareToBuilder append(java.lang.Object, java.lang.Object, java.util.Comparator);
+&gt; public org.apache.commons.lang.builder.CompareToBuilder append(java.lang.Object[], java.lang.Object[], java.util.Comparator);
+
+org.apache.commons.lang.builder.EqualsBuilder
+--------------------
+&gt; public static boolean reflectionEquals(java.lang.Object, java.lang.Object, boolean, java.lang.Class);
+&gt; public org.apache.commons.lang.builder.EqualsBuilder appendSuper(boolean);
+</source>
+</p>
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/upgradeto2_1.xml b/src/site/xdoc/upgradeto2_1.xml
new file mode 100644
index 000000000..bc279cf28
--- /dev/null
+++ b/src/site/xdoc/upgradeto2_1.xml
@@ -0,0 +1,163 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>2.1 Release Notes</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="Lang 2.1 Release Notes">
+<p>
+These are the release notes and advice for upgrading Commons-Lang from
+version 2.0 to version 2.1.
+<source>
+INTRODUCTION:
+
+This document contains the release notes for the 2.1 version of Apache Jakarta Commons Lang.
+Commons Lang is a set of utility functions and reusable components that
+should be of use in any Java environment.
+
+
+INCOMPATIBLE CHANGES:
+
+- The Nestable interface defines the method indexOfThrowable(Class).
+Previously the implementations checked only for a specific Class.
+Now they check for subclasses of that Class as well.
+For most situations this will be the expected behavior (i.e. it's a bug fix).
+If it causes problems, please use the ExceptionUtils.indexOfThrowable(Class) method instead.
+Note that the ExceptionUtils method is available in v1.0 and v2.0 of commons-lang and has not been changed.
+(An alternative to this is to change the public static matchSubclasses flag on NestableDelegate.
+However, we don't recommend that as a long-term solution.)
+
+- The StopWatch class has had much extra validation added.
+If your code previously relied on unusual aspects, it may no longer work.
+
+- Starting with version 2.1, Ant version 1.6.x is required to build. Copy
+junit.jar to ANT_HOME/lib. You can get JUnit from https://www.junit.org. See the developer's guide
+for more details.
+
+
+DEPRECATIONS:
+
+- The enum package has been renamed to enums for JDK 1.5 compliance.
+All functionality is identical, just the package has changed.
+This package will be removed in v3.0.
+
+- NumberUtils.stringToInt - renamed to toInt
+
+- DateUtils - four constants, MILLIS_IN_* have been deprecated as they were defined
+as int not long. The replacements are MILLIS_PER_*.
+
+
+NEW FEATURES:
+
+New:
+- Mutable package - contains basic classes that hold an Object or primitive
+and provide both get and set methods.
+- DurationFormatUtils - provides various methods for formatting durations
+- CharEncoding - definitions of constants for character encoding work
+- CharUtils - utilities for working with characters
+
+Updated:
+- ArrayUtils - many more methods, especially List-like methods
+- BooleanUtils - isTrue and isFalse methods that handle null
+- ClassUtils - primitive to wrapper class conversion methods
+- ClassUtils - class name comparator
+- IllegalClassException - extra constructor for common instanceof case
+- NotImplementedException - supports nested exceptions
+- ObjectUtils - hashcode method handling null
+- StringUtils - isAsciiPrintable to check the contents of a string
+ -- ordinalIndexOf to find the nth index of a string
+ -- various remove methods to remove parts of a string
+ -- various split methods to provide more control over splitting a string
+ -- defaultIfEmpty to default a string if null or empty
+- SystemUtils - methods to get system properties as File objects
+ -- extra constants representing system properties
+- Validate - new methods to check whether all elements in a collection are of a specific type
+- WordUtils - new methods to capitalize based on a set of specified delimiters
+
+- EqualsBuilder - now provides setter to internal state
+- ToStringStyle - new style, short prefix style
+- ReflectionToStringBuilder - more flags to control the output with regards to statics
+
+- ExceptionUtils - added indexOfType methods that check subclasses, thus leaving the existing
+indexOfThrowable method untouched (see incompatible changes section)
+
+- NumberUtils - various string to number parsing methods added
+
+- DateUtils - methods added to compare dates in various ways
+ -- method to parse a date string using multiple patterns
+- FastDateFormat - extra formatting methods that take in a millisecond long value
+ -- additional static factory methods
+- StopWatch - new methods for split behavior
+
+
+BUG FIXES:
+
+19331 General case: infinite loop: ToStringBuilder.reflectionToString
+23174 EqualsBuilder.append(Object[], Object[]) throws NPE
+23356 Make DurationFormatUtils public!
+23557 WordUtils.capitalizeFully(String str) should take a delimiters
+23683 New method for converting a primitive Class to its corresponding wrapper
+23430 Minor javadoc fixes for StringUtils.contains(String, String)
+23590 make optional parameters in FastDateFormat really optional
+24056 Documentation error in StringUtils.replace
+25227 StringEscapeUtils.unescapeHtml() doesn't handle hex entities
+25454 new StringUtils.replaceChars behaves differently from old Ch
+25560 DateUtils.truncate() is off by one hour when using a date in DST switch 'zone'
+25627 DateUtils constants should be long
+25683 Add method that validates Collection elements are a correct
+25849 Add SystemUtils methods for directory properties.
+26616 ClassCastException in Enum.equals(Object)
+26699 Tokenizer Enhancements: reset input string, static CSV
+26734 NullPointerException in EqualsBuilder.append(Object[], Object[])
+26877 Add SystemUtils.AWT_TOOLKIT and others.
+26922 public static boolean DateUtils.equals(Date dt1, Date dt2)
+27592 WordUtils capitalize improvement
+27876 ReflectionToStringBuilder.toString(null) throws exception by design
+27877 Make ClassUtils methods null-safe and not throw an IAE.
+28468 StringUtils.defaultString: Documentation error
+28554 Add hashCode-support to class ObjectUtils
+29082 Enhancement of ExceptionUtils.CAUSE_METHOD_NAMES
+29149 StringEscapeUtils.unescapeHtml() doesn't handle an empty entity
+29294 lang.math.Fraction class deficiencies
+29673 ExceptionUtils: new getCause() methodname (for tomcat)
+29794 Add convenience format(long) methods to FastDateForma
+30328 HashCodeBuilder does not use the same values as Boolean (fixed as documentation)
+30334 New class proposal: CharacterEncoding
+30674 parseDate class from HttpClient's DateParser class
+30815 ArrayUtils.isEquals() throws ClassCastException when array1
+30929 Nestable.indexOfThrowable(Class) uses Class.equals() to match
+31395 DateUtils.truncate oddity at the far end of the Date spectrum
+31478 Compile error with JDK 5 "enum" is a keyword
+31572 o.a.c.lang.enum.ValuedEnum: 'enum' is a keyword in JDK 1.5.0
+31933 ToStringStyle setArrayEnd handled null incorrectly
+32133 SystemUtils fails init on HP-UX
+32198 Error in Javadoc for StringUtils.chomp(String, String)
+32625 Can't subclass EqualsBuilder because isEquals is private
+33067 EqualsBuilder.append(Object[], Object[]) crashes with a NullPointerException if an element of the first array is null
+33069 EqualsBuilder.append(Object[], Object[]) incorrectly checks that rhs[i] is instance of lhs[i]'s class
+33574 unbalanced ReflectionToStringBuilder
+33737 ExceptionUtils.addCauseMethodName(String) does not check for duplicates.
+</source>
+</p>
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/upgradeto2_2.xml b/src/site/xdoc/upgradeto2_2.xml
new file mode 100644
index 000000000..7d7e83b65
--- /dev/null
+++ b/src/site/xdoc/upgradeto2_2.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>2.2 Release Notes</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="Lang 2.2 Release Notes">
+<p>
+These are the release notes and advice for upgrading Commons-Lang from
+version 2.1 to version 2.2.
+<source>
+INTRODUCTION:
+
+This document contains the release notes for the 2.2 version of Apache Jakarta Commons Lang.
+Commons Lang is a set of utility functions and reusable components that
+should be of use in any Java environment.
+
+INCOMPATIBLE CHANGES WITH VERSION 2.1:
+
+- None
+
+DEPRECATIONS FROM 2.1 to 2.2:
+
+- None
+
+BUG FIXES IN 2.2:
+
+<a href="https://issues.apache.org/jira/browse/LANG-2">LANG-2</a> javadoc example for StringUtils.splitByWholeSeparator incorrect
+<a href="https://issues.apache.org/jira/browse/LANG-3">LANG-3</a> PADDING array in StringUtils overflows on '\uffff'
+<a href="https://issues.apache.org/jira/browse/LANG-10">LANG-10</a> [patch] ClassUtils.primitiveToWrapper and Void
+<a href="https://issues.apache.org/jira/browse/LANG-21">LANG-21</a> escapeXML() -&gt; Not escaping low characters
+<a href="https://issues.apache.org/jira/browse/LANG-25">LANG-25</a> DurationFormatUtils.formatDurationISO() javadoc is missing T in duration string between date and time part
+<a href="https://issues.apache.org/jira/browse/LANG-37">LANG-37</a> unit test for org.apache.commons.lang.text.StrBuilder
+<a href="https://issues.apache.org/jira/browse/LANG-42">LANG-42</a> EqualsBuilder.append(Object[], Object[]) crashes with a NullPointerException if an element of the first array is null
+<a href="https://issues.apache.org/jira/browse/LANG-45">LANG-45</a> StrBuilderTest#testReplaceStringString fails.
+<a href="https://issues.apache.org/jira/browse/LANG-50">LANG-50</a> Replace Clover with Cobertura
+<a href="https://issues.apache.org/jira/browse/LANG-59">LANG-59</a> DateUtils.truncate method is buggy when dealing with DST switching hours
+<a href="https://issues.apache.org/jira/browse/LANG-100">LANG-100</a> RandomStringUtils.random() family of methods create invalid Unicode sequences
+<a href="https://issues.apache.org/jira/browse/LANG-105">LANG-105</a> ExceptionUtils goes into infinite loop in getThrowables is throwable.getCause() == throwable
+<a href="https://issues.apache.org/jira/browse/LANG-106">LANG-106</a> StringUtils#getLevenshteinDistance() performance is sub-optimal
+<a href="https://issues.apache.org/jira/browse/LANG-112">LANG-112</a> Wrong length check in StrTokenizer.StringMatcher
+<a href="https://issues.apache.org/jira/browse/LANG-117">LANG-117</a> FastDateFormat: wrong format for date "01.01.1000"
+<a href="https://issues.apache.org/jira/browse/LANG-122">LANG-122</a> EscapeUtil.escapeHtml() should clarify that it does not escape ' chars to &apos;
+<a href="https://issues.apache.org/jira/browse/LANG-123">LANG-123</a> Unclear javadoc for DateUtils.iterator()
+<a href="https://issues.apache.org/jira/browse/LANG-127">LANG-127</a> Minor tweak to fix of bug # 26616
+<a href="https://issues.apache.org/jira/browse/LANG-130">LANG-130</a> Memory "leak" in StringUtils
+<a href="https://issues.apache.org/jira/browse/LANG-140">LANG-140</a> DurationFormatUtils.formatPeriod() returns the wrong result
+<a href="https://issues.apache.org/jira/browse/LANG-141">LANG-141</a> Fraction.toProperString() returns -1/1 for -1
+<a href="https://issues.apache.org/jira/browse/LANG-148">LANG-148</a> Performance modifications on StringUtils.replace
+<a href="https://issues.apache.org/jira/browse/LANG-150">LANG-150</a> StringEscapeUtils.unescapeHtml skips first entity after standalone ampersand
+<a href="https://issues.apache.org/jira/browse/LANG-152">LANG-152</a> DurationFormatUtils.formatDurationWords "11 &lt;units&gt;" gets converted to "11 &lt;unit&gt;"
+<a href="https://issues.apache.org/jira/browse/LANG-259">LANG-259</a> ValuedEnum.compareTo(Object other) not typesafe - it easily could be...
+<a href="https://issues.apache.org/jira/browse/LANG-261">LANG-261</a> Error in an example in the javadoc of the StringUtils.splitPreserveAllTokens() method
+<a href="https://issues.apache.org/jira/browse/LANG-264">LANG-264</a> ToStringBuilder/HashCodeBuilder javadoc code examples
+<a href="https://issues.apache.org/jira/browse/LANG-271">LANG-271</a> LocaleUtils test fails under Mustang
+<a href="https://issues.apache.org/jira/browse/LANG-272">LANG-272</a> Minor build and checkstyle changes
+<a href="https://issues.apache.org/jira/browse/LANG-277">LANG-277</a> Javadoc errors on StringUtils.splitPreserveAllTokens(String, char)
+<a href="https://issues.apache.org/jira/browse/LANG-278">LANG-278</a> javadoc for StringUtils.removeEnd is incorrect
+
+IMPROVEMENTS IN 2.2:
+
+<a href="https://issues.apache.org/jira/browse/LANG-159">LANG-159</a> Add WordUtils.getInitials(String)
+<a href="https://issues.apache.org/jira/browse/LANG-161">LANG-161</a> Add methods and tests to StrBuilder
+<a href="https://issues.apache.org/jira/browse/LANG-162">LANG-162</a> replace() length calculation improvement
+<a href="https://issues.apache.org/jira/browse/LANG-165">LANG-165</a> parseDate with TimeZone
+<a href="https://issues.apache.org/jira/browse/LANG-166">LANG-166</a> New interpolation features
+<a href="https://issues.apache.org/jira/browse/LANG-169">LANG-169</a> Implementation of escape/unescapeHtml methods with Writer
+<a href="https://issues.apache.org/jira/browse/LANG-176">LANG-176</a> CompareToBuilder excludeFields for reflection method
+<a href="https://issues.apache.org/jira/browse/LANG-186">LANG-186</a> Request for MutableBoolean implementation
+<a href="https://issues.apache.org/jira/browse/LANG-194">LANG-194</a> add generic add method to DateUtils
+<a href="https://issues.apache.org/jira/browse/LANG-198">LANG-198</a> New method for EqualsBuilder
+<a href="https://issues.apache.org/jira/browse/LANG-212">LANG-212</a> New ExceptionUtils method setCause()
+<a href="https://issues.apache.org/jira/browse/LANG-216">LANG-216</a> Provides a Class.getPublicMethod which returns public invocable Method
+<a href="https://issues.apache.org/jira/browse/LANG-217">LANG-217</a> Add Mutable&lt;Type&gt; to&lt;Type&gt;() methods.
+<a href="https://issues.apache.org/jira/browse/LANG-220">LANG-220</a> Tokenizer Enhancements: reset input string, static CSV/TSV factories
+<a href="https://issues.apache.org/jira/browse/LANG-226">LANG-226</a> Using ReflectionToStringBuilder and excluding secure fields
+<a href="https://issues.apache.org/jira/browse/LANG-242">LANG-242</a> Trivial cleanup of javadoc in various files
+<a href="https://issues.apache.org/jira/browse/LANG-246">LANG-246</a> CompositeFormat
+<a href="https://issues.apache.org/jira/browse/LANG-250">LANG-250</a> Performance boost for RandomStringUtils
+<a href="https://issues.apache.org/jira/browse/LANG-254">LANG-254</a> Enhanced Class.forName version
+<a href="https://issues.apache.org/jira/browse/LANG-260">LANG-260</a> StringEscapeUtils should expose escape*() methods taking Writer argument
+<a href="https://issues.apache.org/jira/browse/LANG-263">LANG-263</a> Add StringUtils.containsIgnoreCase(...)
+<a href="https://issues.apache.org/jira/browse/LANG-267">LANG-267</a> Support char array converters on ArrayUtils
+<a href="https://issues.apache.org/jira/browse/LANG-270">LANG-270</a> minor javadoc improvements for StringUtils.stripXxx() methods
+ New ExceptionUtils methods getMessage/getRootCauseMessage
+
+</source>
+</p>
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/upgradeto2_3.xml b/src/site/xdoc/upgradeto2_3.xml
new file mode 100644
index 000000000..0a6ac3977
--- /dev/null
+++ b/src/site/xdoc/upgradeto2_3.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>2.3 Release Notes</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="Lang 2.3 Release Notes">
+<p>
+These are the release notes and advice for upgrading Commons-Lang from
+version 2.2 to version 2.3.
+<source>
+INTRODUCTION:
+
+This document contains the release notes for the 2.3 version of Apache
+Jakarta Commons Lang.
+Commons Lang is a set of utility functions and reusable components that
+should be of use in any Java environment.
+
+INCOMPATIBLE CHANGES WITH VERSION 2.2:
+
+- Calling stop on a suspended StopWatch will no longer change the underlying time.
+ It's very unlikely anyone was relying on that bug as a feature.
+
+ADDITIONAL INCOMPATIBLE CHANGES WITH VERSION 2.0:
+
+- The Nestable interface defines the method indexOfThrowable(Class).
+Previously the implementations checked only for a specific Class.
+Now they check for subclasses of that Class as well.
+For most situations this will be the expected behavior (i.e. it's a bug fix).
+If it causes problems, please use the ExceptionUtils.indexOfThrowable(Class) method instead.
+Note that the ExceptionUtils method is available in v1.0 and v2.0 of commons-lang and has not been changed.
+(An alternative to this is to change the public static matchSubclasses flag on NestableDelegate.
+However, we don't recommend that as a long-term solution.)
+
+- The StopWatch class has had much extra validation added.
+If your code previously relied on unusual aspects, it may no longer work.
+
+- Starting with version 2.1, Ant version 1.6.x is required to build. Copy
+junit.jar to ANT_HOME/lib. You can get JUnit from https://www.junit.org. See the developer's guide
+for more details.
+
+DEPRECATIONS FROM 2.2 to 2.3:
+
+- None
+
+DEPRECATIONS FROM 2.1 to 2.2:
+
+- None
+
+DEPRECATIONS FROM 2.0 to 2.1:
+
+- The enum package has been renamed to enums for JDK 1.5 compliance.
+All functionality is identical, just the package has changed.
+This package will be removed in v3.0.
+
+- NumberUtils.stringToInt - renamed to toInt
+
+- DateUtils - four constants, MILLIS_IN_* have been deprecated as they were defined
+as int not long. The replacements are MILLIS_PER_*.
+
+
+BUG FIXES IN 2.3:
+
+ * [LANG-69 ] - ToStringBuilder throws StackOverflowError when an Object cycle exists
+ * [LANG-102] - Refactor Entities methods
+ * [LANG-153] - Can't XMLDecode an Enum
+ * [LANG-262] - Use of enum prevents a classloader from being garbage collected resulting in out of memory exceptions.
+ * [LANG-279] - HashCodeBuilder throws java.lang.StackOverflowError when an object contains a cycle.
+ * [LANG-281] - DurationFormatUtils returns wrong result
+ * [LANG-286] - Serialization - not backwards compatible
+ * [LANG-292] - unescapeXml("&amp;12345678;") should be "&amp;12345678;"
+ * [LANG-294] - StrBuilder.replaceAll and StrBuilder.deleteAll can throw ArrayIndexOutOfBoundsException.
+ * [LANG-295] - StrBuilder contains usages of thisBuf.length when they should use size
+ * [LANG-299] - Bug in method appendFixedWidthPadRight of class StrBuilder causes an ArrayIndexOutOfBoundsException
+ * [LANG-300] - NumberUtils.createNumber throws NumberFormatException for one digit long
+ * [LANG-303] - FastDateFormat.mRules is not transient or serializable
+ * [LANG-304] - NullPointerException in isAvailableLocale(Locale)
+ * [LANG-313] - Wrong behavior of Entities.unescape
+ * [LANG-315] - StopWatch: suspend() acts as split(), if followed by stop()
+
+IMPROVEMENTS IN 2.3:
+
+ * [LANG-258] - Enum Javadoc
+ * [LANG-266] - Wish for StringUtils.join(Collection, *)
+ * [LANG-268] - StringUtils.join should allow you to pass a range for it (so it only joins a part of the array)
+ * [LANG-275] - StringUtils substringsBetween
+ * [LANG-282] - Create more tests to test out the +=31 replacement code in DurationFormatUtils.
+ * [LANG-287] - Optimize StringEscapeUtils.unescapeXml(String)
+ * [LANG-289] - NumberUtils.max(byte[]) and NumberUtils.min(byte[]) are missing
+ * [LANG-291] - Null-safe comparison methods for finding most recent / least recent dates.
+ * [LANG-306] - StrBuilder appendln/appendAll/appendSeparator
+ * [LANG-310] - BooleanUtils isNotTrue/isNotFalse
+ * [LANG-314] - Tests fail to pass when building with Maven 2
+
+</source>
+</p>
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/upgradeto2_4.xml b/src/site/xdoc/upgradeto2_4.xml
new file mode 100644
index 000000000..d6fcdf39a
--- /dev/null
+++ b/src/site/xdoc/upgradeto2_4.xml
@@ -0,0 +1,153 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>2.4 Release Notes</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="Lang 2.4 Release Notes">
+<p>
+These are the release notes and advice for upgrading Commons-Lang from
+version 2.3 to version 2.4. <br/><br/>See '<a href="article2_4.html">What's new in 2.4?</a>' for more information.
+<source>
+INTRODUCTION:
+
+This document contains the release notes for the 2.4 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any Java environment.
+
+Lang 2.4 no longer attempts to target the Java 1.1 environment and now targets Java 1.2. While previous versions
+were built for 1.1, some parts were using methods that were only available in 1.2, and the Enum class had
+become dependent on Java 1.3.
+
+INCOMPATIBLE CHANGES WITH VERSION 2.3:
+
+- None
+
+INCOMPATIBLE CHANGES WITH VERSION 2.2:
+
+- Calling stop on a suspended StopWatch will no longer change the underlying time.
+ It's very unlikely anyone was relying on that bug as a feature.
+
+ADDITIONAL INCOMPATIBLE CHANGES WITH VERSION 2.0:
+
+- The Nestable interface defines the method indexOfThrowable(Class).
+Previously the implementations checked only for a specific Class.
+Now they check for subclasses of that Class as well.
+For most situations this will be the expected behavior (i.e. it's a bug fix).
+If it causes problems, please use the ExceptionUtils.indexOfThrowable(Class) method instead.
+Note that the ExceptionUtils method is available in v1.0 and v2.0 of commons-lang and has not been changed.
+(An alternative to this is to change the public static matchSubclasses flag on NestableDelegate.
+However, we don't recommend that as a long-term solution.)
+
+- The StopWatch class has had much extra validation added.
+If your code previously relied on unusual aspects, it may no longer work.
+
+- Starting with version 2.1, Ant version 1.6.x is required to build. Copy
+junit.jar to ANT_HOME/lib. You can get JUnit from https://www.junit.org. See the developer's guide
+for more details.
+
+DEPRECATIONS FROM 2.3 to 2.4:
+
+- ObjectUtils.appendIdentityToString(StringBuffer, Object) - has very odd semantics, use
+ ObjectUtils.identityToString(StringBuffer, Object) instead.
+
+- public static java.util.Date add(java.util.Date, int, int) - it is not intended for this
+ method to be public. Please let us know if you use this.
+
+DEPRECATIONS FROM 2.2 to 2.3:
+
+- None
+
+DEPRECATIONS FROM 2.1 to 2.2:
+
+- None
+
+DEPRECATIONS FROM 2.0 to 2.1:
+
+- The enum package has been renamed to enums for JDK 1.5 compliance.
+All functionality is identical, just the package has changed.
+This package will be removed in v3.0.
+
+- NumberUtils.stringToInt - renamed to toInt
+
+- DateUtils - four constants, MILLIS_IN_* have been deprecated as they were defined
+as int not long. The replacements are MILLIS_PER_*.
+
+
+BUG FIXES IN 2.4:
+
+ * [LANG-76 ] - EnumUtils.getEnum() doesn't work well in 1.5
+ * [LANG-328] - LocaleUtils.toLocale() rejects strings with only language+variant
+ * [LANG-334] - Enum is not thread-safe
+ * [LANG-346] - Dates.round() behaves incorrectly for minutes and seconds
+ * [LANG-349] - Deadlock using ReflectionToStringBuilder
+ * [LANG-353] - Javadoc Example for EqualsBuilder is questionable
+ * [LANG-360] - Why does appendIdentityToString return null?
+ * [LANG-361] - BooleanUtils toBooleanObject javadoc does not match implementation
+ * [LANG-363] - StringEscapeUtils..escapeJavaScript() method did not escape '/' into '\/', it will make IE render page incorrectly
+ * [LANG-364] - Documentation bug for ignoreEmptyTokens accessors in StrTokenizer
+ * [LANG-365] - BooleanUtils.toBoolean() - invalid drop-thru in case statement causes StringIndexOutOfBoundsException
+ * [LANG-367] - FastDateFormat thread safety
+ * [LANG-368] - FastDateFormat getDateInstance() and getDateTimeInstance() assume Locale.getDefault() won't change
+ * [LANG-369] - ExceptionUtils not thread-safe
+ * [LANG-372] - ToStringBuilder: MULTI_LINE_STYLE does not print anything from appendToString methods.
+ * [LANG-380] - infinite loop in Fraction.reduce when numerator == 0
+ * [LANG-381] - NumberUtils.min(floatArray) returns wrong value if floatArray[0] happens to be Float.NaN
+ * [LANG-385] - https://commons.apache.org/proper/commons-lang/developerguide.html "Building" section is incorrect and incomplete
+ * [LANG-393] - EqualsBuilder don't compare BigDecimals correctly
+ * [LANG-399] - Javadoc bugs - cannot find object
+ * [LANG-410] - Ambiguous / confusing names in StringUtils replace* methods
+ * [LANG-412] - StrBuilder appendFixedWidth does not handle nulls
+ * [LANG-414] - DateUtils.round() often fails
+
+IMPROVEMENTS IN 2.4:
+
+ * [LANG-180] - adding a StringUtils.replace method that takes an array or List of replacement strings
+ * [LANG-192] - Split camel case strings
+ * [LANG-257] - Add new splitByWholeSeparatorPreserveAllTokens() methods to StringUtils
+ * [LANG-269] - Shouldn't Commons Lang's StringUtils have a "common" string method?
+ * [LANG-298] - ClassUtils.getShortClassName and ClassUtils.getPackageName and class of array
+ * [LANG-321] - Add toArray() method to IntRange and LongRange classes
+ * [LANG-322] - ClassUtils.getShortClassName(String) inefficient
+ * [LANG-326] - StringUtils: startsWith / endsWith / startsWithIgnoreCase / endsWithIgnoreCase / removeStartIgnoreCase / removeEndIgnoreCase methods
+ * [LANG-329] - Pointless synchronized in ThreadLocal.initialValue should be removed
+ * [LANG-333] - ArrayUtils.toClass
+ * [LANG-337] - Utility class constructor javadocs should acknowledge that they may sometimes be used, e.g. with Velocity.
+ * [LANG-338] - truncateNicely method which avoids truncating in the middle of a word
+ * [LANG-345] - Optimize HashCodeBuilder.append(Object)
+ * [LANG-351] - Extension to ClassUtils: Obtain the primitive class from a wrapper
+ * [LANG-356] - Add getStartTime to StopWatch
+ * [LANG-362] - Add ExtendedMessageFormat to org.apache.commons.lang.text
+ * [LANG-371] - ToStringStyle javadoc should show examples of styles
+ * [LANG-374] - Add escaping for CSV columns to StringEscapeUtils
+ * [LANG-375] - add SystemUtils.IS_OS_WINDOWS_VISTA field
+ * [LANG-379] - Calculating A date fragment in any time-unit
+ * [LANG-383] - Adding functionality to DateUtils to allow direct setting of various fields.
+ * [LANG-402] - OSGi-ify Lang
+ * [LANG-404] - Add Calendar flavour format methods to DateFormatUtils
+ * [LANG-407] - StringUtils.length(String) returns null-safe length
+ * [LANG-413] - Memory usage improvement for StringUtils#getLevenshteinDistance()
+
+</source>
+</p>
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/upgradeto2_5.xml b/src/site/xdoc/upgradeto2_5.xml
new file mode 100644
index 000000000..38ed30fff
--- /dev/null
+++ b/src/site/xdoc/upgradeto2_5.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>2.5 Release Notes</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="Lang 2.5 Release Notes">
+<p>
+These are the release notes and advice for upgrading Commons-Lang from
+version 2.4 to version 2.5. <br/><br/>See '<a href="article2_5.html">What's new in 2.5?</a>' for more information.
+<source>
+INTRODUCTION:
+
+This document contains the release notes for the 2.5 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any Java environment.
+
+Lang 2.5 no longer attempts to target the Java 1.2 environment and now targets Java 1.3.
+
+IMPROVEMENTS IN 2.5
+===================
+
+ * [LANG-583] - ArrayUtils - add isNotEmpty() methods
+ * [LANG-534] - ArrayUtils - add nullToEmpty() methods
+ * [LANG-454] - CharRange - provide an iterator that lets you walk the chars in the range
+ * [LANG-514] - CharRange - add more readable static builder methods
+ * [ ] - ClassUtils - new isAssignable() methods with autoboxing
+ * [LANG-535] - ClassUtils - add support to getShortClassName and getPackageName for arrays
+ * [LANG-434] - DateUtils - add ceiling() method
+ * [LANG-486] - DateUtils - add parseDateStrictly() method
+ * [LANG-466] - EqualsBuilder - add reset() method
+ * [LANG-461] - NumberUtils - add toByte() and toShort() methods
+ * [LANG-522] - Mutable numbers - add string constructors
+ * [ ] - MutableBoolean - add toBoolean(), isTrue() and isFalse() methods
+ * [LANG-422] - StrBuilder - add appendSeparator() methods with an alternative default separator if the StrBuilder is currently empty
+ * [LANG-555] - SystemUtils - add IS_OS_WINDOWS_7 constant
+ * [LANG-554] - SystemUtils - add IS_JAVA_1_7 constant for JDK 1.7
+ * [LANG-405] - StringUtils - add abbreviateMiddle() method
+ * [LANG-569] - StringUtils - add indexOfIgnoreCase() and lastIndexOfIgnoreCase() methods
+ * [LANG-471] - StringUtils - add isAllUpperCase() and isAllLowerCase() methods
+ * [LANG-469] - StringUtils - add lastOrdinalIndexOf() method to complement the existing ordinalIndexOf() method
+ * [LANG-348] - StringUtils - add repeat() method
+ * [LANG-445] - StringUtils - add startsWithAny() method
+ * [LANG-430] - StringUtils - add upperCase(String, Locale) and lowerCase(String, Locale) methods
+ * [LANG-416] - New Reflection package containing ConstructorUtils, FieldUtils, MemberUtils and MethodUtils
+
+BUG FIXES IN 2.5
+================
+
+ * [LANG-494] - CharSet - Synchronizing the COMMON Map so that getInstance doesn't miss a put from a subclass in another thread
+ * [LANG-500] - ClassUtils - improving performance of getAllInterfaces
+ * [LANG-587] - ClassUtils - toClass() throws NullPointerException on null array element
+ * [LANG-530] - DateUtils - Fix parseDate() cannot parse ISO8601 dates produced by FastDateFormat
+ * [LANG-440] - DateUtils - round() doesn't work correct for Calendar.AM_PM
+ * [LANG-443] - DateUtils - improve tests
+ * [LANG-204] - Entities - multithreaded initialization
+ * [LANG-506] - Entities - missing final modifiers; thread-safety issues
+ * [LANG-76] - EnumUtils - getEnum() doesn't work well in 1.5+
+ * [LANG-584] - ExceptionUtils - use immutable lock target
+ * [LANG-477] - ExtendedMessageFormat - OutOfMemory with a pattern containing single quotes
+ * [LANG-538] - FastDateFormat - call getTime() on a calendar to ensure timezone is in the right state
+ * [LANG-547] - FastDateFormat - Remove unused field
+ * [LANG-511] - LocaleUtils - initialization of available locales can be deferred
+ * [LANG-457] - NumberUtils - createNumber() throws a StringIndexOutOfBoundsException for "l"
+ * [LANG-521] - NumberUtils - isNumber(String) and createNumber(String) both modified to support '2.'
+ * [LANG-432] - StringUtils - improve handling of case-insensitive Strings
+ * [LANG-552] - StringUtils - replaceEach() no longer NPEs when null appears in the last String[]
+ * [LANG-460] - StringUtils - correct Javadocs for startsWith() and startsWithIgnoreCase()
+ * [LANG-421] - StringEscapeUtils - escapeJava() escapes '/' characters
+ * [LANG-450] - StringEscapeUtils - change escapeJavaStyleString() to throw UnhandledException instead swallowing IOException
+ * [LANG-419] - WordUtils - fix StringIndexOutOfBoundsException when lower is greater than the String length
+ * [LANG-523] - StrBuilder - Performance improvement by doubling the size of the String in ensureCapacity
+ * [LANG-575] - Compare, Equals and HashCode builders - use ArrayUtils to avoid creating a temporary List
+ * [LANG-467] - EqualsBuilder - removing the special handling of BigDecimal (LANG-393) to use compareTo
+ * [LANG-574] - HashCodeBuilder - Performance improvement: check for isArray to short-circuit the 9 instanceof checks
+ * [LANG-520] - HashCodeBuilder - Changing the hashCode() method to return toHashCode()
+ * [LANG-459] - HashCodeBuilder - reflectionHashCode() can generate incorrect hashcodes
+ * [LANG-586] - HashCodeBuilder and ToStringStyle - use of ThreadLocal causes memory leaks in container environments
+ * [LANG-487] - ToStringBuilder - make default style thread-safe
+ * [LANG-472] - RandomUtils - nextLong() always produces even numbers
+ * [LANG-592] - RandomUtils - RandomUtils tests are failing frequently
+
+</source>
+</p>
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/upgradeto2_6.xml b/src/site/xdoc/upgradeto2_6.xml
new file mode 100644
index 000000000..1f8c278e9
--- /dev/null
+++ b/src/site/xdoc/upgradeto2_6.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>2.6 Release Notes</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="Lang 2.6 Release Notes">
+<p>
+These are the release notes and advice for upgrading Commons-Lang from
+version 2.5 to version 2.6. <br/><br/>.
+<source>
+INTRODUCTION:
+
+This document contains the release notes for the 2.6 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any Java environment.
+
+
+COMPATIBILITY WITH 2.5
+======================
+Lang 2.6 is binary compatible release with Lang 2.5, containing bug fixes and small enhancements.
+
+Lang 2.6 requires a minimum of JDK 1.3.
+
+
+IMPROVEMENTS IN 2.6
+===================
+
+ * [LANG-633] - BooleanUtils: use same optimization in toBooleanObject(String) as in toBoolean(String)
+ * [LANG-599] - ClassUtils: allow Dots as Inner Class Separators in getClass()
+ * [LANG-594] - DateUtils: equal and compare functions up to most significant field
+ * [LANG-632] - DateUtils: provide a Date to Calendar convenience method
+ * [LANG-576] - ObjectUtils: add clone methods to ObjectUtils
+ * [LANG-667] - ObjectUtils: add a Null-safe compare() method
+ * [LANG-670] - ObjectUtils: add notEqual() method
+ * [LANG-302] - StrBuilder: implement clone() method
+ * [LANG-640] - StringUtils: add a normalizeSpace() method
+ * [LANG-614] - StringUtils: add endsWithAny() method
+ * [LANG-655] - StringUtils: add defaultIfBlank() method
+ * [LANG-596] - StrSubstitutor: add a replace(String, Properties) variant
+ * [LANG-482] - StrSubstitutor: support substitution in variable names
+ * [LANG-669] - Use StrBuilder instead of StringBuffer to improve performance where sync. is not an issue
+
+BUG FIXES IN 2.6
+================
+
+ * [LANG-629] - CharSet: make the underlying set synchronized
+ * [LANG-635] - CompareToBuilder: fix passing along compareTransients to the reflectionCompare method
+ * [LANG-636] - ExtendedMessageFormat doesn't override equals(Object)
+ * [LANG-645] - FastDateFormat: fix to properly include the locale when formatting a Date
+ * [LANG-638] - NumberUtils: createNumber() throws a StringIndexOutOfBoundsException when argument containing "e" and "E" is passed in
+ * [LANG-607] - StringUtils methods do not handle Unicode 2.0+ supplementary characters correctly
+ * [LANG-624] - SystemUtils: getJavaVersionAsFloat throws StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+ * [BEANUTILS-381] - MemberUtils: getMatchingAccessibleMethod does not correctly handle inheritance and method overloading
+
+OTHER CHANGES IN 2.6
+====================
+
+ * [LANG-600] - Javadoc is incorrect for lastIndexOf() method
+ * [LANG-628] - Javadoc for HashCodeBuilder.append(boolean) does not match implementation
+ * [LANG-643] - Javadoc StringUtils.left() claims to throw an exception on negative length, but doesn't
+ * [LANG-370] - Javadoc - document thread safety
+ * [LANG-623] - Test for StringUtils replaceChars() icelandic characters
+
+</source>
+</p>
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/upgradeto3_0.xml b/src/site/xdoc/upgradeto3_0.xml
new file mode 100644
index 000000000..c30117246
--- /dev/null
+++ b/src/site/xdoc/upgradeto3_0.xml
@@ -0,0 +1,172 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+ <properties>
+ <title>Upgrade from 2.5 to 3.0</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+
+<section name="Upgrade to 3.0">
+<p>
+For advice on upgrading Commons-Lang from version 2.5 to version 3.0 see
+'<a href="article3_0.html">What's new in 3.0?</a>'.
+<source>
+INTRODUCTION:
+
+This document contains the release notes for the 3.0 version of Apache Commons Lang.
+Commons Lang is a set of utility functions and reusable components that should be of use in any
+Java environment.
+
+Lang 3.0 now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
+variable arguments, autoboxing, concurrency and formatted output.
+
+ADDITIONS IN 3.0
+================
+
+ [LANG-276] MutableBigDecimal and MutableBigInteger.
+ [LANG-285] Wish : method unaccent.
+ [LANG-358] ObjectUtils.coalesce.
+ [LANG-386] LeftOf/RightOfNumber in Range convenience methods necessary.
+ [LANG-435] Add ClassUtils.isAssignable() variants with autoboxing.
+ [LANG-444] StringUtils.emptyToNull.
+ [LANG-482] Enhance StrSubstitutor to support nested ${var-${subvr}} expansion
+ [LANG-482] StrSubstitutor now supports substitution in variable names.
+ [LANG-496] A generic implementation of the Lazy initialization pattern.
+ [LANG-497] Addition of ContextedException and ContextedRuntimeException.
+ [LANG-498] Add StringEscapeUtils.escapeText() methods.
+ [LANG-499] Add support for the handling of ExecutionExceptions.
+ [LANG-501] Add support for background initialization.
+ [LANG-529] Add a concurrent package.
+ [LANG-533] Validate: support for validating blank strings.
+ [LANG-537] Add ArrayUtils.toArray to create generic arrays.
+ [LANG-545] Add ability to create a Future for a constant.
+ [LANG-546] Add methods to Validate to check whether the index is valid for the array/list/string.
+ [LANG-553] Add TypeUtils class to provide utility code for working with generic types.
+ [LANG-559] Added isAssignableFrom and isInstanceOf validation methods.
+ [LANG-559] Added validState validation method.
+ [LANG-560] New TimedSemaphore class.
+ [LANG-582] Provide an implementation of the ThreadFactory interface.
+ [LANG-588] Create a basic Pair&lt;L, R&gt; class.
+ [LANG-594] DateUtils equal &amp; compare functions up to most significant field.
+ [LANG-601] Add Builder Interface / Update Builders to Implement It.
+ [LANG-609] Support lazy initialization using atomic variables
+ [LANG-610] Extend exception handling in ConcurrentUtils to runtime exceptions.
+ [LANG-614] StringUtils.endsWithAny method
+ [LANG-640] Add normalizeSpace to StringUtils
+ [LANG-644] Provide documentation about the new concurrent package
+ [LANG-649] BooleanUtils.toBooleanObject to support single character input
+ [LANG-651] Add AnnotationUtils
+ [LANG-653] Provide a very basic ConcurrentInitializer implementation
+ [LANG-655] Add StringUtils.defaultIfBlank()
+ [LANG-667] Add a Null-safe compare() method to ObjectUtils
+ [LANG-676] Documented potential NPE if auto-boxing occurs for some BooleanUtils methods
+ [LANG-678] Add support for ConcurrentMap.putIfAbsent()
+ [LANG-692] Add hashCodeMulti varargs method
+
+REMOVALS IN 3.0
+===============
+
+ [LANG-438] Remove @deprecateds.
+ [LANG-492] Remove code handled now by the JDK.
+ [LANG-493] Remove code that does not hold enough value to remain.
+ [LANG-590] Remove JDK 1.2/1.3 bug handling in StringUtils.indexOf(String, String, int).
+ [LANG-673] WordUtils.abbreviate() removed
+ [LANG-691] Removed DateUtils.UTC_TIME_ZONE
+
+IMPROVEMENTS IN 3.0
+===================
+
+ [LANG-290] EnumUtils for JDK 5.0.
+ [LANG-336] Finally start using generics.
+ [LANG-355] StrBuilder should implement CharSequence and Appendable.
+ [LANG-396] Investigate for vararg usages.
+ [LANG-424] Improve Javadoc for StringUtils class.
+ [LANG-458] Refactor Validate.java to eliminate code redundancy.
+ [LANG-479] Document where in SVN trunk is.
+ [LANG-504] bring ArrayUtils.isEmpty to the generics world.
+ [LANG-505] Rewrite StringEscapeUtils.
+ [LANG-507] StringEscapeUtils.unescapeJava should support \u+ notation.
+ [LANG-510] Convert StringUtils API to take CharSequence.
+ [LANG-513] Better EnumUtils.
+ [LANG-528] Mutable classes should implement an appropriately typed Mutable interface.
+ [LANG-539] Compile commons.lang for CDC 1.1/Foundation 1.1.
+ [LANG-540] Make NumericEntityEscaper immutable.
+ [LANG-541] Replace StringBuffer with StringBuilder.
+ [LANG-548] Use Iterable on API instead of Collection.
+ [LANG-551] Replace Range classes with generic version.
+ [LANG-562] Change Maven groupId.
+ [LANG-563] Change Java package name.
+ [LANG-570] Do the test cases really still require main() and suite() methods?.
+ [LANG-579] Add new Validate methods.
+ [LANG-599] ClassUtils.getClass(): Allow Dots as Inner Class Separators.
+ [LANG-605] DefaultExceptionContext overwrites values in recursive situations.
+ [LANG-668] Change ObjectUtils min() &amp; max() functions to use varargs rather than just two parameters
+ [LANG-681] Push down WordUtils to "text" sub-package.
+
+BUG FIXES IN 3.0
+================
+
+ [LANG-11] Depend on JDK 1.5+.
+ [LANG-302] StrBuilder does not implement clone().
+ [LANG-339] StringEscapeUtils.escapeHtml() escapes multibyte characters like Chinese, Japanese, etc.
+ [LANG-369] ExceptionUtils not thread-safe.
+ [LANG-418] Javadoc incorrect for StringUtils.endsWithIgnoreCase.
+ [LANG-428] StringUtils.isAlpha, isAlphanumeric and isNumeric now return false for ""
+ [LANG-439] StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20).
+ [LANG-448] Lower Ascii Characters don't get encoded by Entities.java.
+ [LANG-468] JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder).
+ [LANG-474] Fixes for thread safety.
+ [LANG-478] StopWatch does not resist to system time changes.
+ [LANG-480] StringEscapeUtils.escapeHtml incorrectly converts Unicode characters above U+00FFFF into 2 characters.
+ [LANG-481] Possible race-conditions in hashCode of the range classes.
+ [LANG-564] Improve StrLookup API documentation.
+ [LANG-568] @SuppressWarnings("unchecked") is used too generally.
+ [LANG-571] ArrayUtils.add(T[] array, T element) can create unexpected ClassCastException.
+ [LANG-585] exception.DefaultExceptionContext.getFormattedExceptionMessage catches Throwable.
+ [LANG-596] StrSubstitutor should also handle the default properties of a java.util.Properties class
+ [LANG-600] Javadoc is incorrect for public static int lastIndexOf(String str, String searchStr).
+ [LANG-602] ContextedRuntimeException no longer an 'unchecked' exception.
+ [LANG-606] EqualsBuilder causes StackOverflowException.
+ [LANG-608] Some StringUtils methods should take an int character instead of char to use String API features.
+ [LANG-617] StringEscapeUtils.escapeXML() can't process UTF-16 supplementary characters
+ [LANG-624] SystemUtils.getJavaVersionAsFloat throws StringIndexOutOfBoundsException on Android runtime/Dalvik VM
+ [LANG-629] Charset may not be threadsafe, because the HashSet is not synch.
+ [LANG-638] NumberUtils createNumber throws a StringIndexOutOfBoundsException when argument containing "e" and "E" is passed in
+ [LANG-643] Javadoc StringUtils.left() claims to throw on negative len, but doesn't
+ [LANG-645] FastDateFormat.format() outputs incorrect week of year because locale isn't respected
+ [LANG-646] StringEscapeUtils.unescapeJava doesn't handle octal escapes and Unicode with extra u
+ [LANG-656] Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect
+ [LANG-658] Some entities like &amp;Ouml; are not matched properly against its ISO8859-1 representation
+ [LANG-659] EntityArrays typo: {"\u2122", "&amp;minus;"}, // minus sign, U+2212 ISOtech
+ [LANG-66] StringEscaper.escapeXml() escapes characters &gt; 0x7f.
+ [LANG-662] org.apache.commons.lang3.math.Fraction does not reduce (Integer.MIN_VALUE, 2^k)
+ [LANG-663] org.apache.commons.lang3.math.Fraction does not always succeed in multiplyBy and divideBy
+ [LANG-664] NumberUtils.isNumber(String) is not right when the String is "1.1L"
+ [LANG-672] Doc bug in DateUtils#ceiling
+ [LANG-677] DateUtils.isSameLocalTime compares using 12-hour clock and not 24-hour
+ [LANG-685] EqualsBuilder synchronizes on HashCodeBuilder.
+ [LANG-703] StringUtils.join throws NPE when toString returns null for one of objects in collection
+ [LANG-710] StringIndexOutOfBoundsException when calling unescapeHtml4("&amp;#03")
+
+</source>
+</p>
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/userguide.xml b/src/site/xdoc/userguide.xml
new file mode 100644
index 000000000..1e39ec861
--- /dev/null
+++ b/src/site/xdoc/userguide.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<document>
+
+ <properties>
+ <title>Commons Lang - User guide</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+
+ <body>
+
+ <section name='User guide for Commons "Lang"'>
+ Looking for the User Guide? It has been moved to the package <a href="javadocs/api-release/index.html">Javadoc</a>.
+ </section>
+
+</body>
+</document>
diff --git a/src/test/java/org/apache/commons/lang3/AbstractLangTest.java b/src/test/java/org/apache/commons/lang3/AbstractLangTest.java
new file mode 100644
index 000000000..927c9f3b1
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/AbstractLangTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.junit.jupiter.api.AfterEach;
+
+/**
+ * All tests subclass this test.
+ */
+public class AbstractLangTest {
+
+ /**
+ * All tests should leave the {@link ToStringStyle} registry empty.
+ */
+ @AfterEach
+ public void after() {
+ validateNullToStringStyleRegistry();
+ }
+
+ void validateNullToStringStyleRegistry() {
+ assertNull(ToStringStyle.getRegistry(), "Expected null, actual: " + ToStringStyle.getRegistry());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java b/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java
new file mode 100644
index 000000000..760900095
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.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;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.CURLY;
+import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.LARRY;
+import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.MOE;
+import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.SHEMP;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ */
+public class AnnotationUtilsTest extends AbstractLangTest {
+ @TestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ nest = @NestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = CURLY,
+ stooges = { MOE, LARRY, SHEMP },
+ string = "",
+ strings = { "" },
+ type = Object.class,
+ types = { Object.class }
+ ),
+ nests = {
+ @NestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = CURLY,
+ stooges = { MOE, LARRY, SHEMP },
+ string = "",
+ strings = { "" },
+ type = Object[].class,
+ types = { Object[].class }
+ )
+ },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = SHEMP,
+ stooges = { MOE, LARRY, CURLY },
+ string = "",
+ strings = { "" },
+ type = Object.class,
+ types = { Object.class }
+ )
+ public Object dummy1;
+
+ @TestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ nest = @NestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = CURLY,
+ stooges = { MOE, LARRY, SHEMP },
+ string = "",
+ strings = { "" },
+ type = Object.class,
+ types = { Object.class }
+ ),
+ nests = {
+ @NestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = CURLY,
+ stooges = { MOE, LARRY, SHEMP },
+ string = "",
+ strings = { "" },
+ type = Object[].class,
+ types = { Object[].class }
+ )
+ },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = SHEMP,
+ stooges = { MOE, LARRY, CURLY },
+ string = "",
+ strings = { "" },
+ type = Object.class,
+ types = { Object.class }
+ )
+ public Object dummy2;
+
+ @TestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ nest = @NestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = CURLY,
+ stooges = { MOE, LARRY, SHEMP },
+ string = "",
+ strings = { "" },
+ type = Object.class,
+ types = { Object.class }
+ ),
+ nests = {
+ @NestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = CURLY,
+ stooges = { MOE, LARRY, SHEMP },
+ string = "",
+ strings = { "" },
+ type = Object[].class,
+ types = { Object[].class }
+ ),
+ //add a second NestAnnotation to break equality:
+ @NestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = CURLY,
+ stooges = { MOE, LARRY, SHEMP },
+ string = "",
+ strings = { "" },
+ type = Object[].class,
+ types = { Object[].class }
+ )
+ },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = SHEMP,
+ stooges = { MOE, LARRY, CURLY },
+ string = "",
+ strings = { "" },
+ type = Object.class,
+ types = { Object.class }
+ )
+ public Object dummy3;
+
+ @NestAnnotation(
+ booleanValue = false,
+ booleanValues = { false },
+ byteValue = 0,
+ byteValues = { 0 },
+ charValue = 0,
+ charValues = { 0 },
+ doubleValue = 0,
+ doubleValues = { 0 },
+ floatValue = 0,
+ floatValues = { 0 },
+ intValue = 0,
+ intValues = { 0 },
+ longValue = 0,
+ longValues = { 0 },
+ shortValue = 0,
+ shortValues = { 0 },
+ stooge = CURLY,
+ stooges = { MOE, LARRY, SHEMP },
+ string = "",
+ strings = { "" },
+ type = Object[].class,
+ types = { Object[].class }
+ )
+ public Object dummy4;
+
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ public @interface TestAnnotation {
+ String string();
+ String[] strings();
+ Class<?> type();
+ Class<?>[] types();
+ byte byteValue();
+ byte[] byteValues();
+ short shortValue();
+ short[] shortValues();
+ int intValue();
+ int[] intValues();
+ char charValue();
+ char[] charValues();
+ long longValue();
+ long[] longValues();
+ float floatValue();
+ float[] floatValues();
+ double doubleValue();
+ double[] doubleValues();
+ boolean booleanValue();
+ boolean[] booleanValues();
+ Stooge stooge();
+ Stooge[] stooges();
+ NestAnnotation nest();
+ NestAnnotation[] nests();
+ }
+
+ @Retention(RUNTIME)
+ public @interface NestAnnotation {
+ String string();
+ String[] strings();
+ Class<?> type();
+ Class<?>[] types();
+ byte byteValue();
+ byte[] byteValues();
+ short shortValue();
+ short[] shortValues();
+ int intValue();
+ int[] intValues();
+ char charValue();
+ char[] charValues();
+ long longValue();
+ long[] longValues();
+ float floatValue();
+ float[] floatValues();
+ double doubleValue();
+ double[] doubleValues();
+ boolean booleanValue();
+ boolean[] booleanValues();
+ Stooge stooge();
+ Stooge[] stooges();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD})
+ public @interface TestMethodAnnotation {
+ Class<? extends Throwable> expected() default None.class;
+
+ long timeout() default 0L;
+
+ class None extends Throwable {
+
+ private static final long serialVersionUID = 1L;
+ }
+ }
+
+ public enum Stooge {
+ MOE, LARRY, CURLY, JOE, SHEMP
+ }
+
+ private Field field1;
+ private Field field2;
+ private Field field3;
+ private Field field4;
+
+ @BeforeEach
+ public void setup() throws Exception {
+ field1 = getClass().getDeclaredField("dummy1");
+ field2 = getClass().getDeclaredField("dummy2");
+ field3 = getClass().getDeclaredField("dummy3");
+ field4 = getClass().getDeclaredField("dummy4");
+ }
+
+ @Test
+ public void testEquivalence() {
+ assertTrue(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field2.getAnnotation(TestAnnotation.class)));
+ assertTrue(AnnotationUtils.equals(field2.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class)));
+ }
+
+ @Test
+ public void testSameInstance() {
+ assertTrue(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class)));
+ }
+
+ @Test
+ public void testNonEquivalentAnnotationsOfSameType() {
+ assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field3.getAnnotation(TestAnnotation.class)));
+ assertFalse(AnnotationUtils.equals(field3.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class)));
+ }
+
+ @Test
+ public void testAnnotationsOfDifferingTypes() {
+ assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field4.getAnnotation(NestAnnotation.class)));
+ assertFalse(AnnotationUtils.equals(field4.getAnnotation(NestAnnotation.class), field1.getAnnotation(TestAnnotation.class)));
+ }
+
+ @Test
+ public void testOneArgNull() {
+ assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), null));
+ assertFalse(AnnotationUtils.equals(null, field1.getAnnotation(TestAnnotation.class)));
+ }
+
+ @Test
+ public void testBothArgsNull() {
+ assertTrue(AnnotationUtils.equals(null, null));
+ }
+
+ @Test
+ public void testIsValidAnnotationMemberType() {
+ for (final Class<?> type : new Class[] { byte.class, short.class, int.class, char.class,
+ long.class, float.class, double.class, boolean.class, String.class, Class.class,
+ NestAnnotation.class, TestAnnotation.class, Stooge.class, ElementType.class }) {
+ assertTrue(AnnotationUtils.isValidAnnotationMemberType(type));
+ assertTrue(AnnotationUtils.isValidAnnotationMemberType(Array.newInstance(type, 0)
+ .getClass()));
+ }
+ for (final Class<?> type : new Class[] { Object.class, Map.class, Collection.class }) {
+ assertFalse(AnnotationUtils.isValidAnnotationMemberType(type));
+ assertFalse(AnnotationUtils.isValidAnnotationMemberType(Array.newInstance(type, 0)
+ .getClass()));
+ }
+ }
+
+ @Test
+ public void testGeneratedAnnotationEquivalentToRealAnnotation() {
+ assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> {
+ final Test real = getClass().getDeclaredMethod(
+ "testGeneratedAnnotationEquivalentToRealAnnotation").getAnnotation(Test.class);
+
+ final InvocationHandler generatedTestInvocationHandler = (proxy, method, args) -> {
+ if ("equals".equals(method.getName()) && method.getParameterTypes().length == 1) {
+ return Boolean.valueOf(proxy == args[0]);
+ }
+ if ("hashCode".equals(method.getName()) && method.getParameterTypes().length == 0) {
+ return Integer.valueOf(System.identityHashCode(proxy));
+ }
+ if ("toString".equals(method.getName()) && method.getParameterTypes().length == 0) {
+ return "Test proxy";
+ }
+ return method.invoke(real, args);
+ };
+
+ final Test generated = (Test) Proxy.newProxyInstance(Thread.currentThread()
+ .getContextClassLoader(), new Class[]{Test.class},
+ generatedTestInvocationHandler);
+ assertEquals(real, generated);
+ assertNotEquals(generated, real);
+ assertTrue(AnnotationUtils.equals(generated, real));
+ assertTrue(AnnotationUtils.equals(real, generated));
+
+ final Test generated2 = (Test) Proxy.newProxyInstance(Thread.currentThread()
+ .getContextClassLoader(), new Class[]{Test.class},
+ generatedTestInvocationHandler);
+ assertNotEquals(generated, generated2);
+ assertNotEquals(generated2, generated);
+ assertTrue(AnnotationUtils.equals(generated, generated2));
+ assertTrue(AnnotationUtils.equals(generated2, generated));
+ });
+ }
+
+ @Test
+ public void testHashCode() {
+ assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> {
+ final Test test = getClass().getDeclaredMethod("testHashCode").getAnnotation(Test.class);
+ assertEquals(test.hashCode(), AnnotationUtils.hashCode(test));
+ final TestAnnotation testAnnotation1 = field1.getAnnotation(TestAnnotation.class);
+ assertEquals(testAnnotation1.hashCode(), AnnotationUtils.hashCode(testAnnotation1));
+ final TestAnnotation testAnnotation3 = field3.getAnnotation(TestAnnotation.class);
+ assertEquals(testAnnotation3.hashCode(), AnnotationUtils.hashCode(testAnnotation3));
+ });
+ }
+
+ @Test
+ @TestMethodAnnotation(timeout = 666000)
+ public void testToString() {
+ assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> {
+ final TestMethodAnnotation testAnnotation =
+ getClass().getDeclaredMethod("testToString").getAnnotation(TestMethodAnnotation.class);
+
+ final String annotationString = AnnotationUtils.toString(testAnnotation);
+ assertTrue(annotationString.startsWith("@org.apache.commons.lang3.AnnotationUtilsTest$TestMethodAnnotation("));
+ assertTrue(annotationString.endsWith(")"));
+ assertTrue(annotationString.contains("expected=class org.apache.commons.lang3.AnnotationUtilsTest$TestMethodAnnotation$None"));
+ assertTrue(annotationString.contains("timeout=666000"));
+ assertTrue(annotationString.contains(", "));
+ });
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/ArchUtilsTest.java b/src/test/java/org/apache/commons/lang3/ArchUtilsTest.java
new file mode 100644
index 000000000..342e3870b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArchUtilsTest.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.arch.Processor;
+import org.apache.commons.lang3.arch.Processor.Arch;
+import org.apache.commons.lang3.arch.Processor.Type;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@link ArchUtils}.
+ */
+public class ArchUtilsTest extends AbstractLangTest {
+
+ private static final String IA64 = "ia64";
+ private static final String IA64_32 = "ia64_32";
+ private static final String PPC = "ppc";
+ private static final String PPC64 = "ppc64";
+ private static final String X86 = "x86";
+ private static final String X86_64 = "x86_64";
+ private static final String AARCH_64 = "aarch64";
+
+ private void assertEqualsArchNotNull(final Processor.Arch arch, final Processor processor) {
+ assertNotNull(arch);
+ assertNotNull(processor);
+ assertEquals(arch, processor.getArch());
+ }
+
+ private void assertEqualsTypeNotNull(final Processor.Type type, final Processor processor) {
+ assertNotNull(type);
+ assertNotNull(processor);
+ assertEquals(type, processor.getType());
+ }
+
+ private void assertNotEqualsArchNotNull(final Processor.Arch arch, final Processor processor) {
+ assertNotNull(arch);
+ assertNotNull(processor);
+ assertNotEquals(arch, processor.getArch());
+ }
+
+ private void assertNotEqualsTypeNotNull(final Processor.Type type, final Processor processor) {
+ assertNotNull(type);
+ assertNotNull(processor);
+ assertNotEquals(type, processor.getType());
+ }
+
+ @Test
+ public void testArch() {
+ Processor processor = ArchUtils.getProcessor(X86);
+ assertEqualsTypeNotNull(Processor.Type.X86, processor);
+ assertTrue(processor.isX86());
+ assertNotEqualsTypeNotNull(Processor.Type.PPC, processor);
+ assertFalse(processor.isPPC());
+
+ processor = ArchUtils.getProcessor(X86_64);
+ assertEqualsTypeNotNull(Processor.Type.X86, processor);
+ assertTrue(processor.isX86());
+
+ processor = ArchUtils.getProcessor(IA64_32);
+ assertEqualsTypeNotNull(Processor.Type.IA_64, processor);
+ assertTrue(processor.isIA64());
+
+ processor = ArchUtils.getProcessor(IA64);
+ assertEqualsTypeNotNull(Processor.Type.IA_64, processor);
+ assertTrue(processor.isIA64());
+ assertNotEqualsTypeNotNull(Processor.Type.X86, processor);
+ assertFalse(processor.isX86());
+
+ processor = ArchUtils.getProcessor(PPC);
+ assertEqualsTypeNotNull(Processor.Type.PPC, processor);
+ assertTrue(processor.isPPC());
+ assertNotEqualsTypeNotNull(Processor.Type.IA_64, processor);
+ assertFalse(processor.isIA64());
+
+ processor = ArchUtils.getProcessor(PPC64);
+ assertEqualsTypeNotNull(Processor.Type.PPC, processor);
+ assertTrue(processor.isPPC());
+
+ processor = ArchUtils.getProcessor(AARCH_64);
+ assertEqualsTypeNotNull(Processor.Type.AARCH_64, processor);
+ assertTrue(processor.isAarch64());
+ }
+
+ @Test
+ public void testArchLabels() {
+ for (final Arch arch : Arch.values()) {
+ // Only test label presence.
+ assertFalse(arch.getLabel().isEmpty());
+ }
+ }
+
+ @Test
+ public void testTypeLabels() {
+ for (final Type type : Type.values()) {
+ // Only test label presence.
+ assertFalse(type.getLabel().isEmpty());
+ }
+ }
+
+ @Test
+ public void testGetProcessor() {
+ assertNotNull(ArchUtils.getProcessor(X86));
+ assertNull(ArchUtils.getProcessor("NA"));
+ assertNull(ArchUtils.getProcessor(null));
+
+ final Processor processor = ArchUtils.getProcessor();
+ assertTrue(processor.isX86());
+ assertNotEquals(ObjectUtils.identityToString(processor), processor.toString());
+ }
+
+ @Test
+ public void testIs32BitJVM() {
+ Processor processor = ArchUtils.getProcessor(X86);
+ assertEqualsArchNotNull(Processor.Arch.BIT_32, processor);
+ assertTrue(processor.is32Bit());
+
+ processor = ArchUtils.getProcessor(IA64_32);
+ assertEqualsArchNotNull(Processor.Arch.BIT_32, processor);
+ assertTrue(processor.is32Bit());
+
+ processor = ArchUtils.getProcessor(PPC);
+ assertEqualsArchNotNull(Processor.Arch.BIT_32, processor);
+ processor.is32Bit();
+
+ processor = ArchUtils.getProcessor(X86_64);
+ assertNotEqualsArchNotNull(Processor.Arch.BIT_32, processor);
+ assertFalse(processor.is32Bit());
+
+ processor = ArchUtils.getProcessor(PPC64);
+ assertNotEqualsArchNotNull(Processor.Arch.BIT_32, processor);
+ assertFalse(processor.is32Bit());
+
+ processor = ArchUtils.getProcessor(IA64);
+ assertNotEqualsArchNotNull(Processor.Arch.BIT_32, processor);
+ assertFalse(processor.is32Bit());
+ }
+
+ @Test
+ public void testIs64BitJVM() {
+ Processor processor = ArchUtils.getProcessor(X86_64);
+ assertEqualsArchNotNull(Processor.Arch.BIT_64, processor);
+ assertTrue(processor.is64Bit());
+
+ processor = ArchUtils.getProcessor(PPC64);
+ assertEqualsArchNotNull(Processor.Arch.BIT_64, processor);
+ assertTrue(processor.is64Bit());
+
+ processor = ArchUtils.getProcessor(IA64);
+ assertEqualsArchNotNull(Processor.Arch.BIT_64, processor);
+ assertTrue(processor.is64Bit());
+
+ processor = ArchUtils.getProcessor(X86);
+ assertNotEqualsArchNotNull(Processor.Arch.BIT_64, processor);
+ assertFalse(processor.is64Bit());
+
+ processor = ArchUtils.getProcessor(PPC);
+ assertNotEqualsArchNotNull(Processor.Arch.BIT_64, processor);
+ assertFalse(processor.is64Bit());
+
+ processor = ArchUtils.getProcessor(IA64_32);
+ assertNotEqualsArchNotNull(Processor.Arch.BIT_64, processor);
+ assertFalse(processor.is64Bit());
+
+ processor = ArchUtils.getProcessor(AARCH_64);
+ assertEqualsArchNotNull(Processor.Arch.BIT_64, processor);
+ assertNotEqualsArchNotNull(Processor.Arch.BIT_32, processor);
+ assertTrue(processor.is64Bit());
+ assertFalse(processor.is32Bit());
+}
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/ArraySorterTest.java b/src/test/java/org/apache/commons/lang3/ArraySorterTest.java
new file mode 100644
index 000000000..d3664321b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArraySorterTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+public class ArraySorterTest extends AbstractLangTest {
+
+ @Test
+ public void testSortByteArray() {
+ final byte[] array1 = {2, 1};
+ final byte[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2));
+ }
+
+ @Test
+ public void testSortCharArray() {
+ final char[] array1 = {2, 1};
+ final char[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2));
+ }
+
+ @Test
+ public void testSortComparable() {
+ final String[] array1 = ArrayUtils.toArray("foo", "bar");
+ final String[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2, String::compareTo));
+ }
+
+ @Test
+ public void testSortDoubleArray() {
+ final double[] array1 = {2, 1};
+ final double[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2));
+ }
+
+ @Test
+ public void testSortFloatArray() {
+ final float[] array1 = {2, 1};
+ final float[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2));
+ }
+
+ @Test
+ public void testSortIntArray() {
+ final int[] array1 = {2, 1};
+ final int[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2));
+ }
+
+ @Test
+ public void testSortLongArray() {
+ final long[] array1 = {2, 1};
+ final long[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2));
+ }
+
+ @Test
+ public void testSortObjects() {
+ final String[] array1 = ArrayUtils.toArray("foo", "bar");
+ final String[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2));
+ }
+
+ @Test
+ public void testSortShortArray() {
+ final short[] array1 = {2, 1};
+ final short[] array2 = array1.clone();
+ Arrays.sort(array1);
+ assertArrayEquals(array1, ArraySorter.sort(array2));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsAddTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsAddTest.java
new file mode 100644
index 000000000..f8ced87c4
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsAddTest.java
@@ -0,0 +1,675 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests ArrayUtils add methods.
+ */
+public class ArrayUtilsAddTest extends AbstractLangTest {
+
+ @Test
+ public void testAddFirstBoolean() {
+ boolean[] newArray;
+ newArray = ArrayUtils.addFirst(null, false);
+ assertArrayEquals(new boolean[]{false}, newArray);
+ assertEquals(Boolean.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst(null, true);
+ assertArrayEquals(new boolean[]{true}, newArray);
+ assertEquals(Boolean.TYPE, newArray.getClass().getComponentType());
+ final boolean[] array1 = {true, false, true};
+ newArray = ArrayUtils.addFirst(array1, false);
+ assertArrayEquals(new boolean[]{false, true, false, true}, newArray);
+ assertEquals(Boolean.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddFirstByte() {
+ byte[] newArray;
+ newArray = ArrayUtils.addFirst((byte[]) null, (byte) 0);
+ assertArrayEquals(new byte[]{0}, newArray);
+ assertEquals(Byte.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst((byte[]) null, (byte) 1);
+ assertArrayEquals(new byte[]{1}, newArray);
+ assertEquals(Byte.TYPE, newArray.getClass().getComponentType());
+ final byte[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.addFirst(array1, (byte) 0);
+ assertArrayEquals(new byte[]{0, 1, 2, 3}, newArray);
+ assertEquals(Byte.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst(array1, (byte) 4);
+ assertArrayEquals(new byte[]{4, 1, 2, 3}, newArray);
+ assertEquals(Byte.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddFirstChar() {
+ char[] newArray;
+ newArray = ArrayUtils.addFirst((char[]) null, (char) 0);
+ assertArrayEquals(new char[]{0}, newArray);
+ assertEquals(Character.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst((char[]) null, (char) 1);
+ assertArrayEquals(new char[]{1}, newArray);
+ assertEquals(Character.TYPE, newArray.getClass().getComponentType());
+ final char[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.addFirst(array1, (char) 0);
+ assertArrayEquals(new char[]{0, 1, 2, 3}, newArray);
+ assertEquals(Character.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst(array1, (char) 4);
+ assertArrayEquals(new char[]{4, 1, 2, 3}, newArray);
+ assertEquals(Character.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddFirstDouble() {
+ double[] newArray;
+ newArray = ArrayUtils.addFirst((double[]) null, 0);
+ assertArrayEquals(new double[]{0}, newArray);
+ assertEquals(Double.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst((double[]) null, 1);
+ assertArrayEquals(new double[]{1}, newArray);
+ assertEquals(Double.TYPE, newArray.getClass().getComponentType());
+ final double[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.addFirst(array1, 0);
+ assertArrayEquals(new double[]{0, 1, 2, 3}, newArray);
+ assertEquals(Double.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst(array1, 4);
+ assertArrayEquals(new double[]{4, 1, 2, 3}, newArray);
+ assertEquals(Double.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddFirstFloat() {
+ float[] newArray;
+ newArray = ArrayUtils.addFirst((float[]) null, 0);
+ assertArrayEquals(new float[]{0}, newArray);
+ assertEquals(Float.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst((float[]) null, 1);
+ assertArrayEquals(new float[]{1}, newArray);
+ assertEquals(Float.TYPE, newArray.getClass().getComponentType());
+ final float[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.addFirst(array1, 0);
+ assertArrayEquals(new float[]{0, 1, 2, 3}, newArray);
+ assertEquals(Float.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst(array1, 4);
+ assertArrayEquals(new float[]{4, 1, 2, 3}, newArray);
+ assertEquals(Float.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddFirstInt() {
+ int[] newArray;
+ newArray = ArrayUtils.addFirst((int[]) null, 0);
+ assertArrayEquals(new int[]{0}, newArray);
+ assertEquals(Integer.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst((int[]) null, 1);
+ assertArrayEquals(new int[]{1}, newArray);
+ assertEquals(Integer.TYPE, newArray.getClass().getComponentType());
+ final int[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.addFirst(array1, 0);
+ assertArrayEquals(new int[]{0, 1, 2, 3}, newArray);
+ assertEquals(Integer.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst(array1, 4);
+ assertArrayEquals(new int[]{4, 1, 2, 3}, newArray);
+ assertEquals(Integer.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddFirstLong() {
+ long[] newArray;
+ newArray = ArrayUtils.addFirst((long[]) null, 0);
+ assertArrayEquals(new long[]{0}, newArray);
+ assertEquals(Long.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst((long[]) null, 1);
+ assertArrayEquals(new long[]{1}, newArray);
+ assertEquals(Long.TYPE, newArray.getClass().getComponentType());
+ final long[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.addFirst(array1, 0);
+ assertArrayEquals(new long[]{0, 1, 2, 3}, newArray);
+ assertEquals(Long.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst(array1, 4);
+ assertArrayEquals(new long[]{4, 1, 2, 3}, newArray);
+ assertEquals(Long.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddFirstObject() {
+ Object[] newArray;
+
+ //show that not casting is okay
+ newArray = ArrayUtils.add((Object[]) null, "a");
+ assertArrayEquals(new String[]{"a"}, newArray);
+ assertArrayEquals(new Object[]{"a"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+
+ //show that not casting to Object[] is okay and will assume String based on "a"
+ final String[] newStringArray = ArrayUtils.add(null, "a");
+ assertArrayEquals(new String[]{"a"}, newStringArray);
+ assertArrayEquals(new Object[]{"a"}, newStringArray);
+ assertEquals(String.class, newStringArray.getClass().getComponentType());
+
+ final String[] stringArray1 = { "a", "b", "c" };
+ newArray = ArrayUtils.addFirst(stringArray1, null);
+ assertArrayEquals(new String[] { null, "a", "b", "c" }, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+
+ newArray = ArrayUtils.addFirst(stringArray1, "d");
+ assertArrayEquals(new String[] { "d", "a", "b", "c" }, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+
+ Number[] numberArray1 = { Integer.valueOf(1), Double.valueOf(2) };
+ newArray = ArrayUtils.addFirst(numberArray1, Float.valueOf(3));
+ assertArrayEquals(new Number[] { Float.valueOf(3), Integer.valueOf(1), Double.valueOf(2) }, newArray);
+ assertEquals(Number.class, newArray.getClass().getComponentType());
+
+ numberArray1 = null;
+ newArray = ArrayUtils.addFirst(numberArray1, Float.valueOf(3));
+ assertArrayEquals(new Float[] { Float.valueOf(3) }, newArray);
+ assertEquals(Float.class, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddFirstShort() {
+ short[] newArray;
+ newArray = ArrayUtils.addFirst((short[]) null, (short) 0);
+ assertArrayEquals(new short[]{0}, newArray);
+ assertEquals(Short.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst((short[]) null, (short) 1);
+ assertArrayEquals(new short[]{1}, newArray);
+ assertEquals(Short.TYPE, newArray.getClass().getComponentType());
+ final short[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.addFirst(array1, (short) 0);
+ assertArrayEquals(new short[]{0, 1, 2, 3}, newArray);
+ assertEquals(Short.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addFirst(array1, (short) 4);
+ assertArrayEquals(new short[]{4, 1, 2, 3}, newArray);
+ assertEquals(Short.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayBoolean() {
+ boolean[] newArray;
+ newArray = ArrayUtils.add(null, false);
+ assertArrayEquals(new boolean[]{false}, newArray);
+ assertEquals(Boolean.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(null, true);
+ assertArrayEquals(new boolean[]{true}, newArray);
+ assertEquals(Boolean.TYPE, newArray.getClass().getComponentType());
+ final boolean[] array1 = {true, false, true};
+ newArray = ArrayUtils.add(array1, false);
+ assertArrayEquals(new boolean[]{true, false, true, false}, newArray);
+ assertEquals(Boolean.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayByte() {
+ byte[] newArray;
+ newArray = ArrayUtils.add((byte[]) null, (byte) 0);
+ assertArrayEquals(new byte[]{0}, newArray);
+ assertEquals(Byte.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add((byte[]) null, (byte) 1);
+ assertArrayEquals(new byte[]{1}, newArray);
+ assertEquals(Byte.TYPE, newArray.getClass().getComponentType());
+ final byte[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.add(array1, (byte) 0);
+ assertArrayEquals(new byte[]{1, 2, 3, 0}, newArray);
+ assertEquals(Byte.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(array1, (byte) 4);
+ assertArrayEquals(new byte[]{1, 2, 3, 4}, newArray);
+ assertEquals(Byte.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayChar() {
+ char[] newArray;
+ newArray = ArrayUtils.add((char[]) null, (char) 0);
+ assertArrayEquals(new char[]{0}, newArray);
+ assertEquals(Character.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add((char[]) null, (char) 1);
+ assertArrayEquals(new char[]{1}, newArray);
+ assertEquals(Character.TYPE, newArray.getClass().getComponentType());
+ final char[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.add(array1, (char) 0);
+ assertArrayEquals(new char[]{1, 2, 3, 0}, newArray);
+ assertEquals(Character.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(array1, (char) 4);
+ assertArrayEquals(new char[]{1, 2, 3, 4}, newArray);
+ assertEquals(Character.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayDouble() {
+ double[] newArray;
+ newArray = ArrayUtils.add((double[]) null, 0);
+ assertArrayEquals(new double[]{0}, newArray);
+ assertEquals(Double.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add((double[]) null, 1);
+ assertArrayEquals(new double[]{1}, newArray);
+ assertEquals(Double.TYPE, newArray.getClass().getComponentType());
+ final double[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.add(array1, 0);
+ assertArrayEquals(new double[]{1, 2, 3, 0}, newArray);
+ assertEquals(Double.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(array1, 4);
+ assertArrayEquals(new double[]{1, 2, 3, 4}, newArray);
+ assertEquals(Double.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayFloat() {
+ float[] newArray;
+ newArray = ArrayUtils.add((float[]) null, 0);
+ assertArrayEquals(new float[]{0}, newArray);
+ assertEquals(Float.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add((float[]) null, 1);
+ assertArrayEquals(new float[]{1}, newArray);
+ assertEquals(Float.TYPE, newArray.getClass().getComponentType());
+ final float[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.add(array1, 0);
+ assertArrayEquals(new float[]{1, 2, 3, 0}, newArray);
+ assertEquals(Float.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(array1, 4);
+ assertArrayEquals(new float[]{1, 2, 3, 4}, newArray);
+ assertEquals(Float.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayInt() {
+ int[] newArray;
+ newArray = ArrayUtils.add((int[]) null, 0);
+ assertArrayEquals(new int[]{0}, newArray);
+ assertEquals(Integer.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add((int[]) null, 1);
+ assertArrayEquals(new int[]{1}, newArray);
+ assertEquals(Integer.TYPE, newArray.getClass().getComponentType());
+ final int[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.add(array1, 0);
+ assertArrayEquals(new int[]{1, 2, 3, 0}, newArray);
+ assertEquals(Integer.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(array1, 4);
+ assertArrayEquals(new int[]{1, 2, 3, 4}, newArray);
+ assertEquals(Integer.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayLong() {
+ long[] newArray;
+ newArray = ArrayUtils.add((long[]) null, 0);
+ assertArrayEquals(new long[]{0}, newArray);
+ assertEquals(Long.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add((long[]) null, 1);
+ assertArrayEquals(new long[]{1}, newArray);
+ assertEquals(Long.TYPE, newArray.getClass().getComponentType());
+ final long[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.add(array1, 0);
+ assertArrayEquals(new long[]{1, 2, 3, 0}, newArray);
+ assertEquals(Long.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(array1, 4);
+ assertArrayEquals(new long[]{1, 2, 3, 4}, newArray);
+ assertEquals(Long.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayObject() {
+ Object[] newArray;
+
+ //show that not casting is okay
+ newArray = ArrayUtils.add((Object[]) null, "a");
+ assertArrayEquals(new String[]{"a"}, newArray);
+ assertArrayEquals(new Object[]{"a"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+
+ //show that not casting to Object[] is okay and will assume String based on "a"
+ final String[] newStringArray = ArrayUtils.add(null, "a");
+ assertArrayEquals(new String[]{"a"}, newStringArray);
+ assertArrayEquals(new Object[]{"a"}, newStringArray);
+ assertEquals(String.class, newStringArray.getClass().getComponentType());
+
+ final String[] stringArray1 = {"a", "b", "c"};
+ newArray = ArrayUtils.add(stringArray1, null);
+ assertArrayEquals(new String[]{"a", "b", "c", null}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+
+ newArray = ArrayUtils.add(stringArray1, "d");
+ assertArrayEquals(new String[]{"a", "b", "c", "d"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+
+ Number[] numberArray1 = {Integer.valueOf(1), Double.valueOf(2)};
+ newArray = ArrayUtils.add(numberArray1, Float.valueOf(3));
+ assertArrayEquals(new Number[]{Integer.valueOf(1), Double.valueOf(2), Float.valueOf(3)}, newArray);
+ assertEquals(Number.class, newArray.getClass().getComponentType());
+
+ numberArray1 = null;
+ newArray = ArrayUtils.add(numberArray1, Float.valueOf(3));
+ assertArrayEquals(new Float[]{Float.valueOf(3)}, newArray);
+ assertEquals(Float.class, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayShort() {
+ short[] newArray;
+ newArray = ArrayUtils.add((short[]) null, (short) 0);
+ assertArrayEquals(new short[]{0}, newArray);
+ assertEquals(Short.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add((short[]) null, (short) 1);
+ assertArrayEquals(new short[]{1}, newArray);
+ assertEquals(Short.TYPE, newArray.getClass().getComponentType());
+ final short[] array1 = {1, 2, 3};
+ newArray = ArrayUtils.add(array1, (short) 0);
+ assertArrayEquals(new short[]{1, 2, 3, 0}, newArray);
+ assertEquals(Short.TYPE, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(array1, (short) 4);
+ assertArrayEquals(new short[]{1, 2, 3, 4}, newArray);
+ assertEquals(Short.TYPE, newArray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testAddObjectArrayToObjectArray() {
+ assertNull(ArrayUtils.addAll(null, (Object[]) null));
+ Object[] newArray;
+ final String[] stringArray1 = {"a", "b", "c"};
+ final String[] stringArray2 = {"1", "2", "3"};
+ newArray = ArrayUtils.addAll(stringArray1, (String[]) null);
+ assertNotSame(stringArray1, newArray);
+ assertArrayEquals(stringArray1, newArray);
+ assertArrayEquals(new String[]{"a", "b", "c"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addAll(null, stringArray2);
+ assertNotSame(stringArray2, newArray);
+ assertArrayEquals(stringArray2, newArray);
+ assertArrayEquals(new String[]{"1", "2", "3"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addAll(stringArray1, stringArray2);
+ assertArrayEquals(new String[]{"a", "b", "c", "1", "2", "3"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addAll(ArrayUtils.EMPTY_STRING_ARRAY, (String[]) null);
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, newArray);
+ assertArrayEquals(new String[]{}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addAll(null, ArrayUtils.EMPTY_STRING_ARRAY);
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, newArray);
+ assertArrayEquals(new String[]{}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.addAll(ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.EMPTY_STRING_ARRAY);
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, newArray);
+ assertArrayEquals(new String[]{}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ final String[] stringArrayNull = {null};
+ newArray = ArrayUtils.addAll(stringArrayNull, stringArrayNull);
+ assertArrayEquals(new String[]{null, null}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+
+ // boolean
+ assertArrayEquals(new boolean[]{true, false, false, true}, ArrayUtils.addAll(new boolean[]{true, false}, false, true));
+
+ assertArrayEquals(new boolean[]{false, true}, ArrayUtils.addAll(null, new boolean[]{false, true}));
+
+ assertArrayEquals(new boolean[]{true, false}, ArrayUtils.addAll(new boolean[]{true, false}, null));
+
+ // char
+ assertArrayEquals(new char[]{'a', 'b', 'c', 'd'}, ArrayUtils.addAll(new char[]{'a', 'b'}, 'c', 'd'));
+
+ assertArrayEquals(new char[]{'c', 'd'}, ArrayUtils.addAll(null, new char[]{'c', 'd'}));
+
+ assertArrayEquals(new char[]{'a', 'b'}, ArrayUtils.addAll(new char[]{'a', 'b'}, null));
+
+ // byte
+ assertArrayEquals(new byte[]{(byte) 0, (byte) 1, (byte) 2, (byte) 3}, ArrayUtils.addAll(new byte[]{(byte) 0, (byte) 1}, (byte) 2, (byte) 3));
+
+ assertArrayEquals(new byte[]{(byte) 2, (byte) 3}, ArrayUtils.addAll(null, new byte[]{(byte) 2, (byte) 3}));
+
+ assertArrayEquals(new byte[]{(byte) 0, (byte) 1}, ArrayUtils.addAll(new byte[]{(byte) 0, (byte) 1}, null));
+
+ // short
+ assertArrayEquals(new short[]{(short) 10, (short) 20, (short) 30, (short) 40}, ArrayUtils.addAll(new short[]{(short) 10, (short) 20}, (short) 30, (short) 40));
+
+ assertArrayEquals(new short[]{(short) 30, (short) 40}, ArrayUtils.addAll(null, new short[]{(short) 30, (short) 40}));
+
+ assertArrayEquals(new short[]{(short) 10, (short) 20}, ArrayUtils.addAll(new short[]{(short) 10, (short) 20}, null));
+
+ // int
+ assertArrayEquals(new int[]{1, 1000, -1000, -1}, ArrayUtils.addAll(new int[]{1, 1000}, -1000, -1));
+
+ assertArrayEquals(new int[]{-1000, -1}, ArrayUtils.addAll(null, new int[]{-1000, -1}));
+
+ assertArrayEquals(new int[]{1, 1000}, ArrayUtils.addAll(new int[]{1, 1000}, null));
+
+ // long
+ assertArrayEquals(new long[]{1L, -1L, 1000L, -1000L}, ArrayUtils.addAll(new long[]{1L, -1L}, 1000L, -1000L));
+
+ assertArrayEquals(new long[]{1000L, -1000L}, ArrayUtils.addAll(null, new long[]{1000L, -1000L}));
+
+ assertArrayEquals(new long[]{1L, -1L}, ArrayUtils.addAll(new long[]{1L, -1L}, null));
+
+ // float
+ assertArrayEquals(new float[]{10.5f, 10.1f, 1.6f, 0.01f}, ArrayUtils.addAll(new float[]{10.5f, 10.1f}, 1.6f, 0.01f));
+
+ assertArrayEquals(new float[]{1.6f, 0.01f}, ArrayUtils.addAll(null, new float[]{1.6f, 0.01f}));
+
+ assertArrayEquals(new float[]{10.5f, 10.1f}, ArrayUtils.addAll(new float[]{10.5f, 10.1f}, null));
+
+ // double
+ assertArrayEquals(new double[]{Math.PI, -Math.PI, 0, 9.99}, ArrayUtils.addAll(new double[]{Math.PI, -Math.PI}, 0, 9.99));
+
+ assertArrayEquals(new double[]{0, 9.99}, ArrayUtils.addAll(null, new double[]{0, 9.99}));
+
+ assertArrayEquals(new double[]{Math.PI, -Math.PI}, ArrayUtils.addAll(new double[]{Math.PI, -Math.PI}, null));
+
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testAddObjectAtIndex() {
+ Object[] newArray;
+ newArray = ArrayUtils.add((Object[]) null, 0, "a");
+ assertArrayEquals(new String[]{"a"}, newArray);
+ assertArrayEquals(new Object[]{"a"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ final String[] stringArray1 = {"a", "b", "c"};
+ newArray = ArrayUtils.add(stringArray1, 0, null);
+ assertArrayEquals(new String[]{null, "a", "b", "c"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(stringArray1, 1, null);
+ assertArrayEquals(new String[]{"a", null, "b", "c"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(stringArray1, 3, null);
+ assertArrayEquals(new String[]{"a", "b", "c", null}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ newArray = ArrayUtils.add(stringArray1, 3, "d");
+ assertArrayEquals(new String[]{"a", "b", "c", "d"}, newArray);
+ assertEquals(String.class, newArray.getClass().getComponentType());
+ assertEquals(String.class, newArray.getClass().getComponentType());
+
+ final Object[] o = {"1", "2", "4"};
+ final Object[] result = ArrayUtils.add(o, 2, "3");
+ final Object[] result2 = ArrayUtils.add(o, 3, "5");
+
+ assertNotNull(result);
+ assertEquals(4, result.length);
+ assertEquals("1", result[0]);
+ assertEquals("2", result[1]);
+ assertEquals("3", result[2]);
+ assertEquals("4", result[3]);
+ assertNotNull(result2);
+ assertEquals(4, result2.length);
+ assertEquals("1", result2[0]);
+ assertEquals("2", result2[1]);
+ assertEquals("4", result2[2]);
+ assertEquals("5", result2[3]);
+
+ // boolean tests
+ boolean[] booleanArray = ArrayUtils.add( null, 0, true );
+ assertArrayEquals(new boolean[]{true}, booleanArray);
+ IndexOutOfBoundsException e =
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( null, -1, true));
+ assertEquals("Index: -1, Length: 0", e.getMessage());
+ booleanArray = ArrayUtils.add( new boolean[] { true }, 0, false);
+ assertArrayEquals(new boolean[]{false, true}, booleanArray);
+ booleanArray = ArrayUtils.add( new boolean[] { false }, 1, true);
+ assertArrayEquals(new boolean[]{false, true}, booleanArray);
+ booleanArray = ArrayUtils.add( new boolean[] { true, false }, 1, true);
+ assertArrayEquals(new boolean[]{true, true, false}, booleanArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add(new boolean[] { true, false }, 4, true));
+ assertEquals("Index: 4, Length: 2", e.getMessage());
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add(new boolean[] { true, false }, -1, true));
+ assertEquals("Index: -1, Length: 2", e.getMessage());
+
+ // char tests
+ char[] charArray = ArrayUtils.add( (char[]) null, 0, 'a' );
+ assertArrayEquals(new char[]{'a'}, charArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( (char[]) null, -1, 'a' ));
+ assertEquals("Index: -1, Length: 0", e.getMessage());
+ charArray = ArrayUtils.add( new char[] { 'a' }, 0, 'b');
+ assertArrayEquals(new char[]{'b', 'a'}, charArray);
+ charArray = ArrayUtils.add( new char[] { 'a', 'b' }, 0, 'c');
+ assertArrayEquals(new char[]{'c', 'a', 'b'}, charArray);
+ charArray = ArrayUtils.add( new char[] { 'a', 'b' }, 1, 'k');
+ assertArrayEquals(new char[]{'a', 'k', 'b'}, charArray);
+ charArray = ArrayUtils.add( new char[] { 'a', 'b', 'c' }, 1, 't');
+ assertArrayEquals(new char[]{'a', 't', 'b', 'c'}, charArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new char[] { 'a', 'b' }, 4, 'c'));
+ assertEquals("Index: 4, Length: 2", e.getMessage());
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new char[] { 'a', 'b' }, -1, 'c'));
+ assertEquals("Index: -1, Length: 2", e.getMessage());
+
+ // short tests
+ short[] shortArray = ArrayUtils.add( new short[] { 1 }, 0, (short) 2);
+ assertArrayEquals(new short[]{2, 1}, shortArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( (short[]) null, -1, (short) 2));
+ assertEquals("Index: -1, Length: 0", e.getMessage());
+ shortArray = ArrayUtils.add( new short[] { 2, 6 }, 2, (short) 10);
+ assertArrayEquals(new short[]{2, 6, 10}, shortArray);
+ shortArray = ArrayUtils.add( new short[] { 2, 6 }, 0, (short) -4);
+ assertArrayEquals(new short[]{-4, 2, 6}, shortArray);
+ shortArray = ArrayUtils.add( new short[] { 2, 6, 3 }, 2, (short) 1);
+ assertArrayEquals(new short[]{2, 6, 1, 3}, shortArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new short[] { 2, 6 }, 4, (short) 10));
+ assertEquals("Index: 4, Length: 2", e.getMessage());
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new short[] { 2, 6 }, -1, (short) 10));
+ assertEquals("Index: -1, Length: 2", e.getMessage());
+
+ // byte tests
+ byte[] byteArray = ArrayUtils.add( new byte[] { 1 }, 0, (byte) 2);
+ assertArrayEquals(new byte[]{2, 1}, byteArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( (byte[]) null, -1, (byte) 2));
+ assertEquals("Index: -1, Length: 0", e.getMessage());
+ byteArray = ArrayUtils.add( new byte[] { 2, 6 }, 2, (byte) 3);
+ assertArrayEquals(new byte[]{2, 6, 3}, byteArray);
+ byteArray = ArrayUtils.add( new byte[] { 2, 6 }, 0, (byte) 1);
+ assertArrayEquals(new byte[]{1, 2, 6}, byteArray);
+ byteArray = ArrayUtils.add( new byte[] { 2, 6, 3 }, 2, (byte) 1);
+ assertArrayEquals(new byte[]{2, 6, 1, 3}, byteArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new byte[] { 2, 6 }, 4, (byte) 3));
+ assertEquals("Index: 4, Length: 2", e.getMessage());
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new byte[] { 2, 6 }, -1, (byte) 3));
+ assertEquals("Index: -1, Length: 2", e.getMessage());
+
+ // int tests
+ int[] intArray = ArrayUtils.add( new int[] { 1 }, 0, 2);
+ assertArrayEquals(new int[]{2, 1}, intArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( (int[]) null, -1, 2));
+ assertEquals("Index: -1, Length: 0", e.getMessage());
+ intArray = ArrayUtils.add( new int[] { 2, 6 }, 2, 10);
+ assertArrayEquals(new int[]{2, 6, 10}, intArray);
+ intArray = ArrayUtils.add( new int[] { 2, 6 }, 0, -4);
+ assertArrayEquals(new int[]{-4, 2, 6}, intArray);
+ intArray = ArrayUtils.add( new int[] { 2, 6, 3 }, 2, 1);
+ assertArrayEquals(new int[]{2, 6, 1, 3}, intArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new int[] { 2, 6 }, 4, 10));
+ assertEquals("Index: 4, Length: 2", e.getMessage());
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new int[] { 2, 6 }, -1, 10));
+ assertEquals("Index: -1, Length: 2", e.getMessage());
+
+ // long tests
+ long[] longArray = ArrayUtils.add( new long[] { 1L }, 0, 2L);
+ assertArrayEquals(new long[]{2L, 1L}, longArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( (long[]) null, -1, 2L));
+ assertEquals("Index: -1, Length: 0", e.getMessage());
+ longArray = ArrayUtils.add( new long[] { 2L, 6L }, 2, 10L);
+ assertArrayEquals(new long[]{2L, 6L, 10L}, longArray);
+ longArray = ArrayUtils.add( new long[] { 2L, 6L }, 0, -4L);
+ assertArrayEquals(new long[]{-4L, 2L, 6L}, longArray);
+ longArray = ArrayUtils.add( new long[] { 2L, 6L, 3L }, 2, 1L);
+ assertArrayEquals(new long[]{2L, 6L, 1L, 3L}, longArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new long[] { 2L, 6L }, 4, 10L));
+ assertEquals("Index: 4, Length: 2", e.getMessage());
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new long[] { 2L, 6L }, -1, 10L));
+ assertEquals("Index: -1, Length: 2", e.getMessage());
+
+ // float tests
+ float[] floatArray = ArrayUtils.add( new float[] { 1.1f }, 0, 2.2f);
+ assertArrayEquals(new float[]{2.2f, 1.1f}, floatArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( (float[]) null, -1, 2.2f));
+ assertEquals("Index: -1, Length: 0", e.getMessage());
+ floatArray = ArrayUtils.add( new float[] { 2.3f, 6.4f }, 2, 10.5f);
+ assertArrayEquals(new float[]{2.3f, 6.4f, 10.5f}, floatArray);
+ floatArray = ArrayUtils.add( new float[] { 2.6f, 6.7f }, 0, -4.8f);
+ assertArrayEquals(new float[]{-4.8f, 2.6f, 6.7f}, floatArray);
+ floatArray = ArrayUtils.add( new float[] { 2.9f, 6.0f, 0.3f }, 2, 1.0f);
+ assertArrayEquals(new float[]{2.9f, 6.0f, 1.0f, 0.3f}, floatArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new float[] { 2.3f, 6.4f }, 4, 10.5f));
+ assertEquals("Index: 4, Length: 2", e.getMessage());
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new float[] { 2.3f, 6.4f }, -1, 10.5f));
+ assertEquals("Index: -1, Length: 2", e.getMessage());
+
+ // double tests
+ double[] doubleArray = ArrayUtils.add( new double[] { 1.1 }, 0, 2.2);
+ assertArrayEquals(new double[]{2.2, 1.1}, doubleArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add(null, -1, 2.2));
+ assertEquals("Index: -1, Length: 0", e.getMessage());
+ doubleArray = ArrayUtils.add( new double[] { 2.3, 6.4 }, 2, 10.5);
+ assertArrayEquals(new double[]{2.3, 6.4, 10.5}, doubleArray);
+ doubleArray = ArrayUtils.add( new double[] { 2.6, 6.7 }, 0, -4.8);
+ assertArrayEquals(new double[]{-4.8, 2.6, 6.7}, doubleArray);
+ doubleArray = ArrayUtils.add( new double[] { 2.9, 6.0, 0.3 }, 2, 1.0);
+ assertArrayEquals(new double[]{2.9, 6.0, 1.0, 0.3}, doubleArray);
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new double[] { 2.3, 6.4 }, 4, 10.5));
+ assertEquals("Index: 4, Length: 2", e.getMessage());
+ e = assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.add( new double[] { 2.3, 6.4 }, -1, 10.5));
+ assertEquals("Index: -1, Length: 2", e.getMessage());
+ }
+
+ @Test
+ public void testJira567() {
+ final Number[] n;
+ // Valid array construction
+ n = ArrayUtils.addAll(new Number[]{Integer.valueOf(1)}, new Long[]{Long.valueOf(2)});
+ assertEquals(2, n.length);
+ assertEquals(Number.class, n.getClass().getComponentType());
+ // Invalid - can't store Long in Integer array
+ assertThrows(IllegalArgumentException.class,
+ () -> ArrayUtils.addAll(new Integer[]{Integer.valueOf(1)}, new Long[]{Long.valueOf(2)}));
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testLANG571() {
+ final String[] stringArray=null;
+ final String aString=null;
+ assertThrows(IllegalArgumentException.class, () -> ArrayUtils.add(stringArray, aString));
+ assertThrows(IllegalArgumentException.class, () -> ArrayUtils.add(stringArray, 0, aString));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsInsertTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsInsertTest.java
new file mode 100644
index 000000000..834bd5b7c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsInsertTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests ArrayUtils insert methods.
+ */
+public class ArrayUtilsInsertTest extends AbstractLangTest {
+
+ @Test
+ public void testInsertBooleans() {
+ final boolean[] array = {true, false, true};
+ final boolean[] values = {false, true, false};
+
+ final boolean[] result = ArrayUtils.insert(42, array, null);
+ assertArrayEquals(array, result);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new boolean[0], ArrayUtils.insert(0, new boolean[0], null));
+ assertNull(ArrayUtils.insert(42, (boolean[]) null, null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new boolean[]{false, true, false, true}, ArrayUtils.insert(0, array, false));
+ assertArrayEquals(new boolean[]{true, false, false, true}, ArrayUtils.insert(1, array, false));
+ assertArrayEquals(new boolean[]{true, false, true, false}, ArrayUtils.insert(array.length, array, false));
+ assertArrayEquals(new boolean[]{false, true, false, true, false, true}, ArrayUtils.insert(0, array, values));
+ assertArrayEquals(new boolean[]{true, false, true, false, false, true}, ArrayUtils.insert(1, array, values));
+ assertArrayEquals(new boolean[]{true, false, true, false, true, false}, ArrayUtils.insert(array.length, array, values));
+ }
+
+
+ @Test
+ public void testInsertBytes() {
+ final byte[] array = {1, 2, 3};
+ final byte[] values = {4, 5, 6};
+
+ final byte[] result = ArrayUtils.insert(42, array, null);
+ assertArrayEquals(array, result);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new byte[0], ArrayUtils.insert(0, new byte[0], null));
+ assertNull(ArrayUtils.insert(42, (byte[]) null, null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new byte[]{0, 1, 2, 3}, ArrayUtils.insert(0, array, (byte) 0));
+ assertArrayEquals(new byte[]{1, 0, 2, 3}, ArrayUtils.insert(1, array, (byte) 0));
+ assertArrayEquals(new byte[]{1, 2, 3, 0}, ArrayUtils.insert(array.length, array, (byte) 0));
+ assertArrayEquals(new byte[]{4, 5, 6, 1, 2, 3}, ArrayUtils.insert(0, array, values));
+ assertArrayEquals(new byte[]{1, 4, 5, 6, 2, 3}, ArrayUtils.insert(1, array, values));
+ assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6}, ArrayUtils.insert(array.length, array, values));
+ }
+
+ @Test
+ public void testInsertChars() {
+ final char[] array = {'a', 'b', 'c'};
+ final char[] values = {'d', 'e', 'f'};
+
+ final char[] result = ArrayUtils.insert(42, array, null);
+ assertArrayEquals(array, result);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new char[0], ArrayUtils.insert(0, new char[0], null));
+ assertNull(ArrayUtils.insert(42, (char[]) null, null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new char[]{'z', 'a', 'b', 'c'}, ArrayUtils.insert(0, array, 'z'));
+ assertArrayEquals(new char[]{'a', 'z', 'b', 'c'}, ArrayUtils.insert(1, array, 'z'));
+ assertArrayEquals(new char[]{'a', 'b', 'c', 'z'}, ArrayUtils.insert(array.length, array, 'z'));
+ assertArrayEquals(new char[]{'d', 'e', 'f', 'a', 'b', 'c'}, ArrayUtils.insert(0, array, values));
+ assertArrayEquals(new char[]{'a', 'd', 'e', 'f', 'b', 'c'}, ArrayUtils.insert(1, array, values));
+ assertArrayEquals(new char[]{'a', 'b', 'c', 'd', 'e', 'f'}, ArrayUtils.insert(array.length, array, values));
+ }
+
+ @Test
+ public void testInsertDoubles() {
+ final double[] array = {1, 2, 3};
+ final double[] values = {4, 5, 6};
+ final double delta = 0.000001;
+
+ final double[] result = ArrayUtils.insert(42, array, null);
+ assertArrayEquals(array, result, delta);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new double[0], ArrayUtils.insert(0, new double[0], null), delta);
+ assertNull(ArrayUtils.insert(42, (double[]) null, null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new double[]{0, 1, 2, 3}, ArrayUtils.insert(0, array, 0), delta);
+ assertArrayEquals(new double[]{1, 0, 2, 3}, ArrayUtils.insert(1, array, 0), delta);
+ assertArrayEquals(new double[]{1, 2, 3, 0}, ArrayUtils.insert(array.length, array, 0), delta);
+ assertArrayEquals(new double[]{4, 5, 6, 1, 2, 3}, ArrayUtils.insert(0, array, values), delta);
+ assertArrayEquals(new double[]{1, 4, 5, 6, 2, 3}, ArrayUtils.insert(1, array, values), delta);
+ assertArrayEquals(new double[]{1, 2, 3, 4, 5, 6}, ArrayUtils.insert(array.length, array, values), delta);
+ }
+
+ @Test
+ public void testInsertFloats() {
+ final float[] array = {1, 2, 3};
+ final float[] values = {4, 5, 6};
+ final float delta = 0.000001f;
+
+ final float[] result = ArrayUtils.insert(42, array, null);
+ assertArrayEquals(array, result, delta);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new float[0], ArrayUtils.insert(0, new float[0], null), delta);
+ assertNull(ArrayUtils.insert(42, (float[]) null, null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new float[]{0, 1, 2, 3}, ArrayUtils.insert(0, array, 0), delta);
+ assertArrayEquals(new float[]{1, 0, 2, 3}, ArrayUtils.insert(1, array, 0), delta);
+ assertArrayEquals(new float[]{1, 2, 3, 0}, ArrayUtils.insert(array.length, array, 0), delta);
+ assertArrayEquals(new float[]{4, 5, 6, 1, 2, 3}, ArrayUtils.insert(0, array, values), delta);
+ assertArrayEquals(new float[]{1, 4, 5, 6, 2, 3}, ArrayUtils.insert(1, array, values), delta);
+ assertArrayEquals(new float[]{1, 2, 3, 4, 5, 6}, ArrayUtils.insert(array.length, array, values), delta);
+ }
+
+ @Test
+ public void testInsertGenericArray() {
+ final String[] array = {"a", "b", "c"};
+ final String[] values = {"d", "e", "f"};
+
+ final String[] result = ArrayUtils.insert(42, array, (String[]) null);
+ assertArrayEquals(array, result);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new String[0], ArrayUtils.insert(0, new String[0], (String[]) null));
+ assertNull(ArrayUtils.insert(42, null, (String[]) null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new String[]{"z", "a", "b", "c"}, ArrayUtils.insert(0, array, "z"));
+ assertArrayEquals(new String[]{"a", "z", "b", "c"}, ArrayUtils.insert(1, array, "z"));
+ assertArrayEquals(new String[]{"a", "b", "c", "z"}, ArrayUtils.insert(array.length, array, "z"));
+ assertArrayEquals(new String[]{"d", "e", "f", "a", "b", "c"}, ArrayUtils.insert(0, array, values));
+ assertArrayEquals(new String[]{"a", "d", "e", "f", "b", "c"}, ArrayUtils.insert(1, array, values));
+ assertArrayEquals(new String[]{"a", "b", "c", "d", "e", "f"}, ArrayUtils.insert(array.length, array, values));
+ }
+
+
+ @Test
+ public void testInsertInts() {
+ final int[] array = {1, 2, 3};
+ final int[] values = {4, 5, 6};
+
+ final int[] result = ArrayUtils.insert(42, array, null);
+ assertArrayEquals(array, result);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new int[0], ArrayUtils.insert(0, new int[0], null));
+ assertNull(ArrayUtils.insert(42, (int[]) null, null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new int[]{0, 1, 2, 3}, ArrayUtils.insert(0, array, 0));
+ assertArrayEquals(new int[]{1, 0, 2, 3}, ArrayUtils.insert(1, array, 0));
+ assertArrayEquals(new int[]{1, 2, 3, 0}, ArrayUtils.insert(array.length, array, 0));
+ assertArrayEquals(new int[]{4, 5, 6, 1, 2, 3}, ArrayUtils.insert(0, array, values));
+ assertArrayEquals(new int[]{1, 4, 5, 6, 2, 3}, ArrayUtils.insert(1, array, values));
+ assertArrayEquals(new int[]{1, 2, 3, 4, 5, 6}, ArrayUtils.insert(array.length, array, values));
+ }
+
+
+ @Test
+ public void testInsertLongs() {
+ final long[] array = {1, 2, 3};
+ final long[] values = {4, 5, 6};
+
+ final long[] result = ArrayUtils.insert(42, array, null);
+ assertArrayEquals(array, result);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new long[0], ArrayUtils.insert(0, new long[0], null));
+ assertNull(ArrayUtils.insert(42, (long[]) null, null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new long[]{0, 1, 2, 3}, ArrayUtils.insert(0, array, 0));
+ assertArrayEquals(new long[]{1, 0, 2, 3}, ArrayUtils.insert(1, array, 0));
+ assertArrayEquals(new long[]{1, 2, 3, 0}, ArrayUtils.insert(array.length, array, 0));
+ assertArrayEquals(new long[]{4, 5, 6, 1, 2, 3}, ArrayUtils.insert(0, array, values));
+ assertArrayEquals(new long[]{1, 4, 5, 6, 2, 3}, ArrayUtils.insert(1, array, values));
+ assertArrayEquals(new long[]{1, 2, 3, 4, 5, 6}, ArrayUtils.insert(array.length, array, values));
+ }
+
+
+ @Test
+ public void testInsertShorts() {
+ final short[] array = {1, 2, 3};
+ final short[] values = {4, 5, 6};
+
+ final short[] result = ArrayUtils.insert(42, array, null);
+ assertArrayEquals(array, result);
+ assertNotSame(array, result);
+
+ assertNull(ArrayUtils.insert(42, null, array));
+ assertArrayEquals(new short[0], ArrayUtils.insert(0, new short[0], null));
+ assertNull(ArrayUtils.insert(42, (short[]) null, null));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(-1, array, array));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.insert(array.length + 1, array, array));
+
+ assertArrayEquals(new short[]{0, 1, 2, 3}, ArrayUtils.insert(0, array, (short) 0));
+ assertArrayEquals(new short[]{1, 0, 2, 3}, ArrayUtils.insert(1, array, (short) 0));
+ assertArrayEquals(new short[]{1, 2, 3, 0}, ArrayUtils.insert(array.length, array, (short) 0));
+ assertArrayEquals(new short[]{4, 5, 6, 1, 2, 3}, ArrayUtils.insert(0, array, values));
+ assertArrayEquals(new short[]{1, 4, 5, 6, 2, 3}, ArrayUtils.insert(1, array, values));
+ assertArrayEquals(new short[]{1, 2, 3, 4, 5, 6}, ArrayUtils.insert(array.length, array, values));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsRemoveMultipleTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsRemoveMultipleTest.java
new file mode 100644
index 000000000..a5151d015
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsRemoveMultipleTest.java
@@ -0,0 +1,1264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests ArrayUtils remove and removeElement methods.
+ */
+public class ArrayUtilsRemoveMultipleTest extends AbstractLangTest {
+
+ @Test
+ public void testRemoveAllBooleanArray() {
+ boolean[] array;
+
+ array = ArrayUtils.removeAll(new boolean[] { true }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false }, 0);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false }, 1);
+ assertArrayEquals(new boolean[]{true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, true }, 1);
+ assertArrayEquals(new boolean[]{true, true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, false }, 0, 1);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, false }, 0, 2);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, false }, 1, 2);
+ assertArrayEquals(new boolean[]{true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, true, false, true }, 0, 2, 4);
+ assertArrayEquals(new boolean[]{false, false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, true, false, true }, 1, 3);
+ assertArrayEquals(new boolean[]{true, true, true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, true, false, true }, 1, 3, 4);
+ assertArrayEquals(new boolean[]{true, true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, true, false, true, false, true }, 0, 2, 4, 6);
+ assertArrayEquals(new boolean[]{false, false, false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, true, false, true, false, true }, 1, 3, 5);
+ assertArrayEquals(new boolean[]{true, true, true, true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new boolean[] { true, false, true, false, true, false, true }, 0, 1, 2);
+ assertArrayEquals(new boolean[]{false, true, false, true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllBooleanArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new boolean[] { true, false }, -1));
+ }
+
+ @Test
+ public void testRemoveAllBooleanArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new boolean[] { true, false }, 2));
+ }
+
+ @Test
+ public void testRemoveAllBooleanArrayRemoveNone() {
+ final boolean[] array1 = { true, false };
+ final boolean[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(boolean.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllByteArray() {
+ byte[] array;
+
+ array = ArrayUtils.removeAll(new byte[] { 1 }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2 }, 0);
+ assertArrayEquals(new byte[]{2}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2 }, 1);
+ assertArrayEquals(new byte[]{1}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2, 1 }, 1);
+ assertArrayEquals(new byte[]{1, 1}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2 }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2, 3 }, 0, 1);
+ assertArrayEquals(new byte[]{3}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2, 3 }, 1, 2);
+ assertArrayEquals(new byte[]{1}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2, 3 }, 0, 2);
+ assertArrayEquals(new byte[]{2}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2, 3, 4, 5 }, 1, 3);
+ assertArrayEquals(new byte[]{1, 3, 5}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2, 3, 4, 5 }, 0, 2, 4);
+ assertArrayEquals(new byte[]{2, 4}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2, 3, 4, 5, 6, 7 }, 1, 3, 5);
+ assertArrayEquals(new byte[]{1, 3, 5, 7}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new byte[] { 1, 2, 3, 4, 5, 6, 7 }, 0, 2, 4, 6);
+ assertArrayEquals(new byte[]{2, 4, 6}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllByteArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new byte[] { 1, 2 }, -1));
+ }
+
+ @Test
+ public void testRemoveAllByteArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new byte[] { 1, 2 }, 2));
+ }
+
+ @Test
+ public void testRemoveAllByteArrayRemoveNone() {
+ final byte[] array1 = { 1, 2 };
+ final byte[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(byte.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllCharArray() {
+ char[] array;
+
+ array = ArrayUtils.removeAll(new char[] { 'a' }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b' }, 0);
+ assertArrayEquals(new char[]{'b'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b' }, 1);
+ assertArrayEquals(new char[]{'a'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b', 'c' }, 1);
+ assertArrayEquals(new char[]{'a', 'c'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b' }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b', 'c' }, 0, 1);
+ assertArrayEquals(new char[]{'c'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b', 'c' }, 1, 2);
+ assertArrayEquals(new char[]{'a'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b', 'c' }, 0, 2);
+ assertArrayEquals(new char[]{'b'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b', 'c', 'd', 'e' }, 1, 3);
+ assertArrayEquals(new char[]{'a', 'c', 'e'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b', 'c', 'd', 'e' }, 0, 2, 4);
+ assertArrayEquals(new char[]{'b', 'd'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }, 1, 3, 5);
+ assertArrayEquals(new char[]{'a', 'c', 'e', 'g'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }, 0, 2, 4, 6);
+ assertArrayEquals(new char[]{'b', 'd', 'f'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllCharArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new char[] { 'a', 'b' }, -1));
+ }
+
+ @Test
+ public void testRemoveAllCharArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new char[] { 'a', 'b' }, 2));
+ }
+
+ @Test
+ public void testRemoveAllCharArrayRemoveNone() {
+ final char[] array1 = { 'a', 'b' };
+ final char[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(char.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllDoubleArray() {
+ double[] array;
+
+ array = ArrayUtils.removeAll(new double[] { 1 }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2 }, 0);
+ assertArrayEquals(new double[]{2}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2 }, 1);
+ assertArrayEquals(new double[]{1}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2, 1 }, 1);
+ assertArrayEquals(new double[]{1, 1}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2 }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2, 3 }, 0, 1);
+ assertArrayEquals(new double[]{3}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2, 3 }, 1, 2);
+ assertArrayEquals(new double[]{1}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2, 3 }, 0, 2);
+ assertArrayEquals(new double[]{2}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2, 3, 4, 5 }, 1, 3);
+ assertArrayEquals(new double[]{1, 3, 5}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2, 3, 4, 5 }, 0, 2, 4);
+ assertArrayEquals(new double[]{2, 4}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2, 3, 4, 5, 6, 7 }, 1, 3, 5);
+ assertArrayEquals(new double[]{1, 3, 5, 7}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new double[] { 1, 2, 3, 4, 5, 6, 7 }, 0, 2, 4, 6);
+ assertArrayEquals(new double[]{2, 4, 6}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllDoubleArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new double[] { 1, 2 }, -1));
+ }
+
+ @Test
+ public void testRemoveAllDoubleArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new double[] { 1, 2 }, 2));
+ }
+
+ @Test
+ public void testRemoveAllDoubleArrayRemoveNone() {
+ final double[] array1 = { 1, 2 };
+ final double[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(double.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllFloatArray() {
+ float[] array;
+
+ array = ArrayUtils.removeAll(new float[] { 1 }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2 }, 0);
+ assertArrayEquals(new float[]{2}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2 }, 1);
+ assertArrayEquals(new float[]{1}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2, 1 }, 1);
+ assertArrayEquals(new float[]{1, 1}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2 }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2, 3 }, 0, 1);
+ assertArrayEquals(new float[]{3}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2, 3 }, 1, 2);
+ assertArrayEquals(new float[]{1}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2, 3 }, 0, 2);
+ assertArrayEquals(new float[]{2}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2, 3, 4, 5 }, 1, 3);
+ assertArrayEquals(new float[]{1, 3, 5}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2, 3, 4, 5 }, 0, 2, 4);
+ assertArrayEquals(new float[]{2, 4}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2, 3, 4, 5, 6, 7 }, 1, 3, 5);
+ assertArrayEquals(new float[]{1, 3, 5, 7}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new float[] { 1, 2, 3, 4, 5, 6, 7 }, 0, 2, 4, 6);
+ assertArrayEquals(new float[]{2, 4, 6}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllFloatArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new float[] { 1, 2 }, -1));
+ }
+
+ @Test
+ public void testRemoveAllFloatArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new float[] { 1, 2 }, 2));
+ }
+
+ @Test
+ public void testRemoveAllFloatArrayRemoveNone() {
+ final float[] array1 = { 1, 2 };
+ final float[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(float.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllIntArray() {
+ int[] array;
+
+ array = ArrayUtils.removeAll(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.EMPTY_INT_ARRAY);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+
+ array = ArrayUtils.removeAll(new int[] { 1 }, ArrayUtils.EMPTY_INT_ARRAY);
+ assertArrayEquals(new int[]{1}, array);
+
+ array = ArrayUtils.removeAll(new int[] { 1 }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2 }, 0);
+ assertArrayEquals(new int[]{2}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2 }, 1);
+ assertArrayEquals(new int[]{1}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2, 1 }, 1);
+ assertArrayEquals(new int[]{1, 1}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2 }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2, 3 }, 0, 1);
+ assertArrayEquals(new int[]{3}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2, 3 }, 1, 2);
+ assertArrayEquals(new int[]{1}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2, 3 }, 0, 2);
+ assertArrayEquals(new int[]{2}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2, 3, 4, 5 }, 1, 3);
+ assertArrayEquals(new int[]{1, 3, 5}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2, 3, 4, 5 }, 0, 2, 4);
+ assertArrayEquals(new int[]{2, 4}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2, 3, 4, 5, 6, 7 }, 1, 3, 5);
+ assertArrayEquals(new int[]{1, 3, 5, 7}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new int[] { 1, 2, 3, 4, 5, 6, 7 }, 0, 2, 4, 6);
+ assertArrayEquals(new int[]{2, 4, 6}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllIntArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new int[] { 1, 2 }, -1));
+ }
+
+ @Test
+ public void testRemoveAllIntArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new int[] { 1, 2 }, 2));
+ }
+
+ @Test
+ public void testRemoveAllIntArrayRemoveNone() {
+ final int[] array1 = { 1, 2 };
+ final int[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(int.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllLongArray() {
+ long[] array;
+
+ array = ArrayUtils.removeAll(new long[] { 1 }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2 }, 0);
+ assertArrayEquals(new long[]{2}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2 }, 1);
+ assertArrayEquals(new long[]{1}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2, 1 }, 1);
+ assertArrayEquals(new long[]{1, 1}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2 }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2, 3 }, 0, 1);
+ assertArrayEquals(new long[]{3}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2, 3 }, 1, 2);
+ assertArrayEquals(new long[]{1}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2, 3 }, 0, 2);
+ assertArrayEquals(new long[]{2}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2, 3, 4, 5 }, 1, 3);
+ assertArrayEquals(new long[]{1, 3, 5}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2, 3, 4, 5 }, 0, 2, 4);
+ assertArrayEquals(new long[]{2, 4}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2, 3, 4, 5, 6, 7 }, 1, 3, 5);
+ assertArrayEquals(new long[]{1, 3, 5, 7}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new long[] { 1, 2, 3, 4, 5, 6, 7 }, 0, 2, 4, 6);
+ assertArrayEquals(new long[]{2, 4, 6}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllLongArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new long[] { 1, 2 }, -1));
+ }
+
+ @Test
+ public void testRemoveAllLongArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new long[] { 1, 2 }, 2));
+ }
+
+ @Test
+ public void testRemoveAllLongArrayRemoveNone() {
+ final long[] array1 = { 1, 2 };
+ final long[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(long.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllNullBooleanArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll((boolean[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNullByteArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll((byte[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNullCharArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll((char[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNullDoubleArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll((double[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNullFloatArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll((float[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNullIntArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll((int[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNullLongArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll((long[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNullObjectArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((Object[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNullShortArray() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll((short[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveAllNumberArray() {
+ final Number[] inarray = { Integer.valueOf(1), Long.valueOf(2L), Byte.valueOf((byte) 3) };
+ assertEquals(3, inarray.length);
+ Number[] outarray;
+
+ outarray = ArrayUtils.removeAll(inarray, 1);
+ assertArrayEquals(new Number[] { Integer.valueOf(1), Byte.valueOf((byte) 3) }, outarray);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+
+ outarray = ArrayUtils.removeAll(outarray, 1);
+ assertArrayEquals(new Number[] { Integer.valueOf(1) }, outarray);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+
+ outarray = ArrayUtils.removeAll(outarray, 0);
+ assertEquals(0, outarray.length);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+
+ outarray = ArrayUtils.removeAll(inarray, 0, 1);
+ assertArrayEquals(new Number[] { Byte.valueOf((byte) 3) }, outarray);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+
+ outarray = ArrayUtils.removeAll(inarray, 0, 2);
+ assertArrayEquals(new Number[] { Long.valueOf(2L) }, outarray);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+
+ outarray = ArrayUtils.removeAll(inarray, 1, 2);
+ assertArrayEquals(new Number[] { Integer.valueOf(1) }, outarray);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllObjectArray() {
+ Object[] array;
+
+ array = ArrayUtils.removeAll(new Object[] { "a" }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b" }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c" }, 1, 2);
+ assertArrayEquals(new Object[] { "a" }, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c", "d" }, 1, 2);
+ assertArrayEquals(new Object[] { "a", "d" }, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c", "d" }, 0, 3);
+ assertArrayEquals(new Object[] { "b", "c" }, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c", "d" }, 0, 1, 3);
+ assertArrayEquals(new Object[] { "c" }, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c", "d", "e" }, 0, 1, 3);
+ assertArrayEquals(new Object[] { "c", "e" }, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c", "d", "e" }, 0, 2, 4);
+ assertArrayEquals(new Object[] { "b", "d" }, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c", "d" }, 0, 1, 3, 0, 1, 3);
+ assertArrayEquals(new Object[] { "c" }, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c", "d" }, 2, 1, 0, 3);
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new Object[] { "a", "b", "c", "d" }, 2, 0, 1, 3, 0, 2, 1, 3);
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllObjectArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new Object[] { "a", "b" }, -1));
+ }
+
+ @Test
+ public void testRemoveAllObjectArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new Object[] { "a", "b" }, 2));
+ }
+
+ @Test
+ public void testRemoveAllObjectArrayRemoveNone() {
+ final Object[] array1 = { "foo", "bar", "baz" };
+ final Object[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(Object.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllShortArray() {
+ short[] array;
+
+ array = ArrayUtils.removeAll(new short[] { 1 }, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2 }, 0);
+ assertArrayEquals(new short[]{2}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2 }, 1);
+ assertArrayEquals(new short[]{1}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2, 1 }, 1);
+ assertArrayEquals(new short[]{1, 1}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2 }, 0, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2, 3 }, 0, 1);
+ assertArrayEquals(new short[]{3}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2, 3 }, 1, 2);
+ assertArrayEquals(new short[]{1}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2, 3 }, 0, 2);
+ assertArrayEquals(new short[]{2}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2, 3, 4, 5 }, 1, 3);
+ assertArrayEquals(new short[]{1, 3, 5}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2, 3, 4, 5 }, 0, 2, 4);
+ assertArrayEquals(new short[]{2, 4}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2, 3, 4, 5, 6, 7 }, 1, 3, 5);
+ assertArrayEquals(new short[]{1, 3, 5, 7}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeAll(new short[] { 1, 2, 3, 4, 5, 6, 7 }, 0, 2, 4, 6);
+ assertArrayEquals(new short[]{2, 4, 6}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveAllShortArrayNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new short[] { 1, 2 }, -1, 0));
+ }
+
+ @Test
+ public void testRemoveAllShortArrayOutOfBoundsIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.removeAll(new short[] { 1, 2 }, 2, 0));
+ }
+
+ @Test
+ public void testRemoveAllShortArrayRemoveNone() {
+ final short[] array1 = { 1, 2 };
+ final short[] array2 = ArrayUtils.removeAll(array1);
+ assertNotSame(array1, array2);
+ assertArrayEquals(array1, array2);
+ assertEquals(short.class, array2.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementBooleanArray() {
+ boolean[] array;
+
+ array = ArrayUtils.removeElements((boolean[]) null, true);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_BOOLEAN_ARRAY, true);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true }, true);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true, false }, true);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true, false, true }, true);
+ assertArrayEquals(new boolean[]{false, true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((boolean[]) null, true, false);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_BOOLEAN_ARRAY, true, false);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true }, true, false);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true, false }, true, false);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true, false }, true, true);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true, false, true }, true, false);
+ assertArrayEquals(new boolean[]{true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true, false, true }, true, true);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new boolean[] { true, false, true }, true, true, true, true);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementByteArray() {
+ byte[] array;
+
+ array = ArrayUtils.removeElements((byte[]) null, (byte) 1);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_BYTE_ARRAY, (byte) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1 }, (byte) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1, 2 }, (byte) 1);
+ assertArrayEquals(new byte[]{2}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1, 2, 1 }, (byte) 1);
+ assertArrayEquals(new byte[]{2, 1}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((byte[]) null, (byte) 1, (byte) 2);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_BYTE_ARRAY, (byte) 1, (byte) 2);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1 }, (byte) 1, (byte) 2);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1, 2 }, (byte) 1, (byte) 2);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1, 2 }, (byte) 1, (byte) 1);
+ assertArrayEquals(new byte[]{2}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1, 2, 1 }, (byte) 1, (byte) 2);
+ assertArrayEquals(new byte[]{1}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1, 2, 1 }, (byte) 1, (byte) 1);
+ assertArrayEquals(new byte[]{2}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new byte[] { 1, 2, 1 }, (byte) 1, (byte) 1, (byte) 1, (byte) 1);
+ assertArrayEquals(new byte[]{2}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementCharArray() {
+ char[] array;
+
+ array = ArrayUtils.removeElements((char[]) null, 'a');
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_CHAR_ARRAY, 'a');
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a' }, 'a');
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a', 'b' }, 'a');
+ assertArrayEquals(new char[]{'b'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a', 'b', 'a' }, 'a');
+ assertArrayEquals(new char[]{'b', 'a'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((char[]) null, 'a', 'b');
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_CHAR_ARRAY, 'a', 'b');
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a' }, 'a', 'b');
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a', 'b' }, 'a', 'b');
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a', 'b' }, 'a', 'a');
+ assertArrayEquals(new char[]{'b'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a', 'b', 'a' }, 'a', 'b');
+ assertArrayEquals(new char[]{'a'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a', 'b', 'a' }, 'a', 'a');
+ assertArrayEquals(new char[]{'b'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new char[] { 'a', 'b', 'a' }, 'a', 'a', 'a', 'a');
+ assertArrayEquals(new char[]{'b'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ @SuppressWarnings("cast")
+ public void testRemoveElementDoubleArray() {
+ double[] array;
+
+ array = ArrayUtils.removeElements((double[]) null, (double) 1);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_DOUBLE_ARRAY, (double) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1 }, (double) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1, 2 }, (double) 1);
+ assertArrayEquals(new double[]{2}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1, 2, 1 }, (double) 1);
+ assertArrayEquals(new double[]{2, 1}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((double[]) null, (double) 1, (double) 2);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_DOUBLE_ARRAY, (double) 1, (double) 2);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1 }, (double) 1, (double) 2);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1, 2 }, (double) 1, (double) 2);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1, 2 }, (double) 1, (double) 1);
+ assertArrayEquals(new double[]{2}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1, 2, 1 }, (double) 1, (double) 2);
+ assertArrayEquals(new double[]{1}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1, 2, 1 }, (double) 1, (double) 1);
+ assertArrayEquals(new double[]{2}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new double[] { 1, 2, 1 }, (double) 1, (double) 1, (double) 1, (double) 1);
+ assertArrayEquals(new double[]{2}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ @SuppressWarnings("cast")
+ public void testRemoveElementFloatArray() {
+ float[] array;
+
+ array = ArrayUtils.removeElements((float[]) null, (float) 1);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_FLOAT_ARRAY, (float) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1 }, (float) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1, 2 }, (float) 1);
+ assertArrayEquals(new float[]{2}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1, 2, 1 }, (float) 1);
+ assertArrayEquals(new float[]{2, 1}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((float[]) null, (float) 1, (float) 1);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_FLOAT_ARRAY, (float) 1, (float) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1 }, (float) 1, (float) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1, 2 }, (float) 1, (float) 2);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1, 2 }, (float) 1, (float) 1);
+ assertArrayEquals(new float[]{2}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1, 2, 1 }, (float) 1, (float) 1);
+ assertArrayEquals(new float[]{2}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1, 2, 1 }, (float) 1, (float) 2);
+ assertArrayEquals(new float[]{1}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new float[] { 1, 2, 1 }, (float) 1, (float) 1, (float) 1, (float) 1);
+ assertArrayEquals(new float[]{2}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementIntArray() {
+ int[] array;
+
+ array = ArrayUtils.removeElements((int[]) null, 1);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_INT_ARRAY, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1 }, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1, 2 }, 1);
+ assertArrayEquals(new int[]{2}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1, 2, 1 }, 1);
+ assertArrayEquals(new int[]{2, 1}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((int[]) null, 1);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_INT_ARRAY, 1, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1 }, 1, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1, 2 }, 1, 2);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1, 2 }, 1, 1);
+ assertArrayEquals(new int[]{2}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1, 2, 1 }, 1, 2);
+ assertArrayEquals(new int[]{1}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1, 2, 1 }, 1, 1);
+ assertArrayEquals(new int[]{2}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new int[] { 1, 2, 1 }, 1, 1, 1, 1);
+ assertArrayEquals(new int[]{2}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementLongArray() {
+ long[] array;
+
+ array = ArrayUtils.removeElements((long[]) null, 1L);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_LONG_ARRAY, 1L);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1 }, 1L);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1, 2 }, 1L);
+ assertArrayEquals(new long[]{2}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1, 2, 1 }, 1L);
+ assertArrayEquals(new long[]{2, 1}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((long[]) null, 1L, 1L);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_LONG_ARRAY, 1L, 1L);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1 }, 1L, 1L);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1, 2 }, 1L, 2L);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1, 2 }, 1L, 1L);
+ assertArrayEquals(new long[]{2}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1, 2, 1 }, 1L, 1L);
+ assertArrayEquals(new long[]{2}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1, 2, 1 }, 1L, 2L);
+ assertArrayEquals(new long[]{1}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new long[] { 1, 2, 1 }, 1L, 1L, 1L, 1L);
+ assertArrayEquals(new long[]{2}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementShortArray() {
+ short[] array;
+
+ array = ArrayUtils.removeElements((short[]) null, (short) 1);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_SHORT_ARRAY, (short) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1 }, (short) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1, 2 }, (short) 1);
+ assertArrayEquals(new short[]{2}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1, 2, 1 }, (short) 1);
+ assertArrayEquals(new short[]{2, 1}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((short[]) null, (short) 1, (short) 1);
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_SHORT_ARRAY, (short) 1, (short) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1 }, (short) 1, (short) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1, 2 }, (short) 1, (short) 2);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1, 2 }, (short) 1, (short) 1);
+ assertArrayEquals(new short[]{2}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1, 2, 1 }, (short) 1, (short) 1);
+ assertArrayEquals(new short[]{2}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1, 2, 1 }, (short) 1, (short) 2);
+ assertArrayEquals(new short[]{1}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new short[] { 1, 2, 1 }, (short) 1, (short) 1, (short) 1, (short) 1);
+ assertArrayEquals(new short[]{2}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementsObjectArray() {
+ Object[] array;
+
+ array = ArrayUtils.removeElements((Object[]) null, "a");
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_OBJECT_ARRAY, "a");
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a" }, "a");
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a", "b" }, "a");
+ assertArrayEquals(new Object[]{"b"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a", "b", "a" }, "a");
+ assertArrayEquals(new Object[]{"b", "a"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements((Object[]) null, "a", "b");
+ assertNull(array);
+
+ array = ArrayUtils.removeElements(ArrayUtils.EMPTY_OBJECT_ARRAY, "a", "b");
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a" }, "a", "b");
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a", "b" }, "a", "c");
+ assertArrayEquals(new Object[]{"b"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a", "b", "a" }, "a");
+ assertArrayEquals(new Object[]{"b", "a"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a", "b", "a" }, "a", "b");
+ assertArrayEquals(new Object[]{"a"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a", "b", "a" }, "a", "a");
+ assertArrayEquals(new Object[]{"b"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+
+ array = ArrayUtils.removeElements(new Object[] { "a", "b", "a" }, "a", "a", "a", "a");
+ assertArrayEquals(new Object[]{"b"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsRemoveTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsRemoveTest.java
new file mode 100644
index 000000000..fd3fcf204
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsRemoveTest.java
@@ -0,0 +1,779 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests ArrayUtils remove and removeElement methods.
+ */
+public class ArrayUtilsRemoveTest extends AbstractLangTest {
+
+ @Test
+ public void testRemoveAllBooleanOccurences() {
+ boolean[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, true));
+
+ a = new boolean[0];
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.removeAllOccurences(a, true));
+
+ a = new boolean[] { true };
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.removeAllOccurences(a, true));
+
+ a = new boolean[] { true, true };
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.removeAllOccurences(a, true));
+
+ a = new boolean[] { false, true, true, false, true };
+ assertArrayEquals(new boolean[]{false, false}, ArrayUtils.removeAllOccurences(a, true));
+
+ a = new boolean[] { false, true, true, false, true };
+ assertArrayEquals(new boolean[]{true, true, true}, ArrayUtils.removeAllOccurences(a, false));
+ }
+
+ @Test
+ public void testRemoveAllBooleanOccurrences() {
+ boolean[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, true));
+
+ a = new boolean[0];
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.removeAllOccurrences(a, true));
+
+ a = new boolean[] { true };
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.removeAllOccurrences(a, true));
+
+ a = new boolean[] { true, true };
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.removeAllOccurrences(a, true));
+
+ a = new boolean[] { false, true, true, false, true };
+ assertArrayEquals(new boolean[]{false, false}, ArrayUtils.removeAllOccurrences(a, true));
+
+ a = new boolean[] { false, true, true, false, true };
+ assertArrayEquals(new boolean[]{true, true, true}, ArrayUtils.removeAllOccurrences(a, false));
+ }
+
+ @Test
+ public void testRemoveAllByteOccurences() {
+ byte[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, (byte) 2));
+
+ a = new byte[0];
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.removeAllOccurences(a, (byte) 2));
+
+ a = new byte[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.removeAllOccurences(a, (byte) 2));
+
+ a = new byte[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.removeAllOccurences(a, (byte) 2));
+
+ a = new byte[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new byte[]{1, 3}, ArrayUtils.removeAllOccurences(a, (byte) 2));
+
+ a = new byte[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new byte[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurences(a, (byte) 4));
+ }
+
+ @Test
+ public void testRemoveAllByteOccurrences() {
+ byte[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, (byte) 2));
+
+ a = new byte[0];
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.removeAllOccurrences(a, (byte) 2));
+
+ a = new byte[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.removeAllOccurrences(a, (byte) 2));
+
+ a = new byte[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.removeAllOccurrences(a, (byte) 2));
+
+ a = new byte[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new byte[]{1, 3}, ArrayUtils.removeAllOccurrences(a, (byte) 2));
+
+ a = new byte[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new byte[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurrences(a, (byte) 4));
+ }
+
+ @Test
+ public void testRemoveAllCharOccurences() {
+ char[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, '2'));
+
+ a = new char[0];
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.removeAllOccurences(a, '2'));
+
+ a = new char[] { '2' };
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.removeAllOccurences(a, '2'));
+
+ a = new char[] { '2', '2' };
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.removeAllOccurences(a, '2'));
+
+ a = new char[] { '1', '2', '2', '3', '2' };
+ assertArrayEquals(new char[]{'1', '3'}, ArrayUtils.removeAllOccurences(a, '2'));
+
+ a = new char[] { '1', '2', '2', '3', '2' };
+ assertArrayEquals(new char[]{'1', '2', '2', '3', '2'}, ArrayUtils.removeAllOccurences(a, '4'));
+ }
+
+ @Test
+ public void testRemoveAllCharOccurrences() {
+ char[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, '2'));
+
+ a = new char[0];
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.removeAllOccurrences(a, '2'));
+
+ a = new char[] { '2' };
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.removeAllOccurrences(a, '2'));
+
+ a = new char[] { '2', '2' };
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.removeAllOccurrences(a, '2'));
+
+ a = new char[] { '1', '2', '2', '3', '2' };
+ assertArrayEquals(new char[]{'1', '3'}, ArrayUtils.removeAllOccurrences(a, '2'));
+
+ a = new char[] { '1', '2', '2', '3', '2' };
+ assertArrayEquals(new char[]{'1', '2', '2', '3', '2'}, ArrayUtils.removeAllOccurrences(a, '4'));
+ }
+
+ @Test
+ public void testRemoveAllDoubleOccurences() {
+ double[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new double[0];
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new double[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new double[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new double[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new double[]{1, 3}, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new double[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new double[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurences(a, 4));
+ }
+
+ @Test
+ public void testRemoveAllDoubleOccurrences() {
+ double[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new double[0];
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new double[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new double[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new double[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new double[]{1, 3}, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new double[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new double[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurrences(a, 4));
+ }
+
+ @Test
+ public void testRemoveAllFloatOccurences() {
+ float[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new float[0];
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new float[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new float[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new float[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new float[]{1, 3}, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new float[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new float[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurences(a, 4));
+ }
+
+ @Test
+ public void testRemoveAllFloatOccurrences() {
+ float[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new float[0];
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new float[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new float[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new float[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new float[]{1, 3}, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new float[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new float[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurrences(a, 4));
+ }
+
+ @Test
+ public void testRemoveAllIntOccurences() {
+ int[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new int[0];
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new int[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new int[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new int[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new int[]{1, 3}, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new int[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new int[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurences(a, 4));
+ }
+
+ @Test
+ public void testRemoveAllIntOccurrences() {
+ int[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new int[0];
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new int[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new int[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new int[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new int[]{1, 3}, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new int[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new int[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurrences(a, 4));
+ }
+
+ @Test
+ public void testRemoveAllLongOccurences() {
+ long[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new long[0];
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new long[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new long[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new long[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new long[]{1, 3}, ArrayUtils.removeAllOccurences(a, 2));
+
+ a = new long[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new long[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurences(a, 4));
+ }
+
+ @Test
+ public void testRemoveAllLongOccurrences() {
+ long[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new long[0];
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new long[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new long[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new long[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new long[]{1, 3}, ArrayUtils.removeAllOccurrences(a, 2));
+
+ a = new long[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new long[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurrences(a, 4));
+ }
+
+ @Test
+ public void testRemoveAllObjectOccurences() {
+ String[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, "2"));
+
+ a = new String[0];
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.removeAllOccurences(a, "2"));
+
+ a = new String[] { "2" };
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.removeAllOccurences(a, "2"));
+
+ a = new String[] { "2", "2" };
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.removeAllOccurences(a, "2"));
+
+ a = new String[] { "1", "2", "2", "3", "2" };
+ assertArrayEquals(new String[]{"1", "3"}, ArrayUtils.removeAllOccurences(a, "2"));
+
+ a = new String[] { "1", "2", "2", "3", "2" };
+ assertArrayEquals(new String[]{"1", "2", "2", "3", "2"}, ArrayUtils.removeAllOccurences(a, "4"));
+ }
+
+ @Test
+ public void testRemoveAllObjectOccurrences() {
+ String[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, "2"));
+
+ a = new String[0];
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.removeAllOccurrences(a, "2"));
+
+ a = new String[] { "2" };
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.removeAllOccurrences(a, "2"));
+
+ a = new String[] { "2", "2" };
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.removeAllOccurrences(a, "2"));
+
+ a = new String[] { "1", "2", "2", "3", "2" };
+ assertArrayEquals(new String[]{"1", "3"}, ArrayUtils.removeAllOccurrences(a, "2"));
+
+ a = new String[] { "1", "2", "2", "3", "2" };
+ assertArrayEquals(new String[]{"1", "2", "2", "3", "2"}, ArrayUtils.removeAllOccurrences(a, "4"));
+ }
+
+ @Test
+ public void testRemoveAllShortOccurences() {
+ short[] a = null;
+ assertNull(ArrayUtils.removeAllOccurences(a, (short) 2));
+
+ a = new short[0];
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.removeAllOccurences(a, (short) 2));
+
+ a = new short[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.removeAllOccurences(a, (short) 2));
+
+ a = new short[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.removeAllOccurences(a, (short) 2));
+
+ a = new short[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new short[]{1, 3}, ArrayUtils.removeAllOccurences(a, (short) 2));
+
+ a = new short[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new short[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurences(a, (short) 4));
+ }
+
+ @Test
+ public void testRemoveAllShortOccurrences() {
+ short[] a = null;
+ assertNull(ArrayUtils.removeAllOccurrences(a, (short) 2));
+
+ a = new short[0];
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.removeAllOccurrences(a, (short) 2));
+
+ a = new short[] { 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.removeAllOccurrences(a, (short) 2));
+
+ a = new short[] { 2, 2 };
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.removeAllOccurrences(a, (short) 2));
+
+ a = new short[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new short[]{1, 3}, ArrayUtils.removeAllOccurrences(a, (short) 2));
+
+ a = new short[] { 1, 2, 2, 3, 2 };
+ assertArrayEquals(new short[]{1, 2, 2, 3, 2}, ArrayUtils.removeAllOccurrences(a, (short) 4));
+ }
+
+ @Test
+ public void testRemoveBooleanArray() {
+ boolean[] array;
+ array = ArrayUtils.remove(new boolean[] {true}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new boolean[] {true, false}, 0);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new boolean[] {true, false}, 1);
+ assertArrayEquals(new boolean[]{true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new boolean[] {true, false, true}, 1);
+ assertArrayEquals(new boolean[]{true, true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new boolean[] {true, false}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new boolean[] {true, false}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((boolean[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveByteArray() {
+ byte[] array;
+ array = ArrayUtils.remove(new byte[] {1}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new byte[] {1, 2}, 0);
+ assertArrayEquals(new byte[]{2}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new byte[] {1, 2}, 1);
+ assertArrayEquals(new byte[]{1}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new byte[] {1, 2, 1}, 1);
+ assertArrayEquals(new byte[]{1, 1}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new byte[] {1, 2}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new byte[] {1, 2}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((byte[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveCharArray() {
+ char[] array;
+ array = ArrayUtils.remove(new char[] {'a'}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new char[] {'a', 'b'}, 0);
+ assertArrayEquals(new char[]{'b'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new char[] {'a', 'b'}, 1);
+ assertArrayEquals(new char[]{'a'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new char[] {'a', 'b', 'c'}, 1);
+ assertArrayEquals(new char[]{'a', 'c'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new char[] {'a', 'b'}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new char[] {'a', 'b'}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((char[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveDoubleArray() {
+ double[] array;
+ array = ArrayUtils.remove(new double[] {1}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new double[] {1, 2}, 0);
+ assertArrayEquals(new double[]{2}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new double[] {1, 2}, 1);
+ assertArrayEquals(new double[]{1}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new double[] {1, 2, 1}, 1);
+ assertArrayEquals(new double[]{1, 1}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new double[] {1, 2}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new double[] {1, 2}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((double[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveElementBooleanArray() {
+ boolean[] array;
+ array = ArrayUtils.removeElement(null, true);
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_BOOLEAN_ARRAY, true);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new boolean[] {true}, true);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new boolean[] {true, false}, true);
+ assertArrayEquals(new boolean[]{false}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new boolean[] {true, false, true}, true);
+ assertArrayEquals(new boolean[]{false, true}, array);
+ assertEquals(Boolean.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementByteArray() {
+ byte[] array;
+ array = ArrayUtils.removeElement((byte[]) null, (byte) 1);
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_BYTE_ARRAY, (byte) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new byte[] {1}, (byte) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new byte[] {1, 2}, (byte) 1);
+ assertArrayEquals(new byte[]{2}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new byte[] {1, 2, 1}, (byte) 1);
+ assertArrayEquals(new byte[]{2, 1}, array);
+ assertEquals(Byte.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementCharArray() {
+ char[] array;
+ array = ArrayUtils.removeElement((char[]) null, 'a');
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_CHAR_ARRAY, 'a');
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new char[] {'a'}, 'a');
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new char[] {'a', 'b'}, 'a');
+ assertArrayEquals(new char[]{'b'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new char[] {'a', 'b', 'a'}, 'a');
+ assertArrayEquals(new char[]{'b', 'a'}, array);
+ assertEquals(Character.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ @SuppressWarnings("cast")
+ public void testRemoveElementDoubleArray() {
+ double[] array;
+ array = ArrayUtils.removeElement(null, (double) 1);
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_DOUBLE_ARRAY, (double) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new double[] {1}, (double) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new double[] {1, 2}, (double) 1);
+ assertArrayEquals(new double[]{2}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new double[] {1, 2, 1}, (double) 1);
+ assertArrayEquals(new double[]{2, 1}, array);
+ assertEquals(Double.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ @SuppressWarnings("cast")
+ public void testRemoveElementFloatArray() {
+ float[] array;
+ array = ArrayUtils.removeElement((float[]) null, (float) 1);
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_FLOAT_ARRAY, (float) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new float[] {1}, (float) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new float[] {1, 2}, (float) 1);
+ assertArrayEquals(new float[]{2}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new float[] {1, 2, 1}, (float) 1);
+ assertArrayEquals(new float[]{2, 1}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementIntArray() {
+ int[] array;
+ array = ArrayUtils.removeElement((int[]) null, 1);
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_INT_ARRAY, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new int[] {1}, 1);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new int[] {1, 2}, 1);
+ assertArrayEquals(new int[]{2}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new int[] {1, 2, 1}, 1);
+ assertArrayEquals(new int[]{2, 1}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ }
+
+
+ @Test
+ public void testRemoveElementLongArray() {
+ long[] array;
+ array = ArrayUtils.removeElement((long[]) null, 1L);
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_LONG_ARRAY, 1L);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new long[] {1}, 1L);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new long[] {1, 2}, 1L);
+ assertArrayEquals(new long[]{2}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new long[] {1, 2, 1}, 1L);
+ assertArrayEquals(new long[]{2, 1}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementObjectArray() {
+ Object[] array;
+ array = ArrayUtils.removeElement(null, "a");
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_OBJECT_ARRAY, "a");
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new Object[] {"a"}, "a");
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new Object[] {"a", "b"}, "a");
+ assertArrayEquals(new Object[]{"b"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new Object[] {"a", "b", "a"}, "a");
+ assertArrayEquals(new Object[]{"b", "a"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveElementShortArray() {
+ short[] array;
+ array = ArrayUtils.removeElement((short[]) null, (short) 1);
+ assertNull(array);
+ array = ArrayUtils.removeElement(ArrayUtils.EMPTY_SHORT_ARRAY, (short) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new short[] {1}, (short) 1);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new short[] {1, 2}, (short) 1);
+ assertArrayEquals(new short[]{2}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.removeElement(new short[] {1, 2, 1}, (short) 1);
+ assertArrayEquals(new short[]{2, 1}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveFloatArray() {
+ float[] array;
+ array = ArrayUtils.remove(new float[] {1}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new float[] {1, 2}, 0);
+ assertArrayEquals(new float[]{2}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new float[] {1, 2}, 1);
+ assertArrayEquals(new float[]{1}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new float[] {1, 2, 1}, 1);
+ assertArrayEquals(new float[]{1, 1}, array);
+ assertEquals(Float.TYPE, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new float[] {1, 2}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new float[] {1, 2}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((float[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveIntArray() {
+ int[] array;
+ array = ArrayUtils.remove(new int[] {1}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new int[] {1, 2}, 0);
+ assertArrayEquals(new int[]{2}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new int[] {1, 2}, 1);
+ assertArrayEquals(new int[]{1}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new int[] {1, 2, 1}, 1);
+ assertArrayEquals(new int[]{1, 1}, array);
+ assertEquals(Integer.TYPE, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new int[] {1, 2}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new int[] {1, 2}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((int[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveLongArray() {
+ long[] array;
+ array = ArrayUtils.remove(new long[] {1}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new long[] {1, 2}, 0);
+ assertArrayEquals(new long[]{2}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new long[] {1, 2}, 1);
+ assertArrayEquals(new long[]{1}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new long[] {1, 2, 1}, 1);
+ assertArrayEquals(new long[]{1, 1}, array);
+ assertEquals(Long.TYPE, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new long[] {1, 2}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new long[] {1, 2}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((long[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveNumberArray() {
+ final Number[] inarray = {Integer.valueOf(1), Long.valueOf(2), Byte.valueOf((byte) 3)};
+ assertEquals(3, inarray.length);
+ Number[] outarray;
+ outarray = ArrayUtils.remove(inarray, 1);
+ assertEquals(2, outarray.length);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+ outarray = ArrayUtils.remove(outarray, 1);
+ assertEquals(1, outarray.length);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+ outarray = ArrayUtils.remove(outarray, 0);
+ assertEquals(0, outarray.length);
+ assertEquals(Number.class, outarray.getClass().getComponentType());
+ }
+
+ @Test
+ public void testRemoveObjectArray() {
+ Object[] array;
+ array = ArrayUtils.remove(new Object[] {"a"}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new Object[] {"a", "b"}, 0);
+ assertArrayEquals(new Object[]{"b"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new Object[] {"a", "b"}, 1);
+ assertArrayEquals(new Object[]{"a"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new Object[] {"a", "b", "c"}, 1);
+ assertArrayEquals(new Object[]{"a", "c"}, array);
+ assertEquals(Object.class, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new Object[] {"a", "b"}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new Object[] {"a", "b"}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((Object[]) null, 0));
+ }
+
+ @Test
+ public void testRemoveShortArray() {
+ short[] array;
+ array = ArrayUtils.remove(new short[] {1}, 0);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_ARRAY, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new short[] {1, 2}, 0);
+ assertArrayEquals(new short[]{2}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new short[] {1, 2}, 1);
+ assertArrayEquals(new short[]{1}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ array = ArrayUtils.remove(new short[] {1, 2, 1}, 1);
+ assertArrayEquals(new short[]{1, 1}, array);
+ assertEquals(Short.TYPE, array.getClass().getComponentType());
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new short[] {1, 2}, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove(new short[] {1, 2}, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> ArrayUtils.remove((short[]) null, 0));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsSetTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsSetTest.java
new file mode 100644
index 000000000..9ac7fba87
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsSetTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+import org.junit.jupiter.api.Test;
+
+public class ArrayUtilsSetTest extends AbstractLangTest {
+
+ @Test
+ public void testSetAll_IntFunction() {
+ final IntFunction<?> nullIntFunction = null;
+ assertNull(ArrayUtils.setAll(null, nullIntFunction));
+ assertArrayEquals(null, ArrayUtils.setAll(null, nullIntFunction));
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY, ArrayUtils.setAll(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY, nullIntFunction));
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, ArrayUtils.setAll(ArrayUtils.EMPTY_OBJECT_ARRAY, nullIntFunction));
+ final Integer[] array = new Integer[10];
+ final Integer[] array2 = ArrayUtils.setAll(array, Integer::valueOf);
+ assertSame(array, array2);
+ for (int i = 0; i < array.length; i++) {
+ assertEquals(i, array[i].intValue());
+ }
+ }
+
+ @Test
+ public void testSetAll_Suppiler() {
+ final Supplier<?> nullSupplier = null;
+ assertNull(ArrayUtils.setAll(null, nullSupplier));
+ assertArrayEquals(null, ArrayUtils.setAll(null, nullSupplier));
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY, ArrayUtils.setAll(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY, nullSupplier));
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, ArrayUtils.setAll(ArrayUtils.EMPTY_OBJECT_ARRAY, nullSupplier));
+ final String[] array = new String[10];
+ final String[] array2 = ArrayUtils.setAll(array, () -> StringUtils.EMPTY);
+ assertSame(array, array2);
+ for (final String s : array) {
+ assertEquals(StringUtils.EMPTY, s);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java
new file mode 100644
index 000000000..4f751c56d
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java
@@ -0,0 +1,6584 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Map;
+import java.util.Random;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.ArrayUtils}.
+ */
+@SuppressWarnings("deprecation") // deliberate use of deprecated code
+public class ArrayUtilsTest extends AbstractLangTest {
+
+ private class TestClass {
+ // empty
+ }
+
+ /** A predefined seed used to initialize {@link Random} in order to get predictable results */
+ private static final long SEED = 16111981L;
+
+ @SafeVarargs
+ private static <T> T[] toArrayPropagatingType(final T... items) {
+ return ArrayUtils.toArray(items);
+ }
+
+ private void assertIsEquals(final Object array1, final Object array2, final Object array3) {
+ assertTrue(ArrayUtils.isEquals(array1, array1));
+ assertTrue(ArrayUtils.isEquals(array2, array2));
+ assertTrue(ArrayUtils.isEquals(array3, array3));
+ assertFalse(ArrayUtils.isEquals(array1, array2));
+ assertFalse(ArrayUtils.isEquals(array2, array1));
+ assertFalse(ArrayUtils.isEquals(array1, array3));
+ assertFalse(ArrayUtils.isEquals(array3, array1));
+ assertFalse(ArrayUtils.isEquals(array1, array2));
+ assertFalse(ArrayUtils.isEquals(array2, array1));
+ }
+
+ /**
+ * Tests generic array creation with parameters of same type.
+ */
+ @Test
+ public void testArrayCreation() {
+ final String[] array = ArrayUtils.toArray("foo", "bar");
+ assertEquals(2, array.length);
+ assertEquals("foo", array[0]);
+ assertEquals("bar", array[1]);
+ }
+ /**
+ * Tests generic array creation with parameters of common base type.
+ */
+ @Test
+ public void testArrayCreationWithDifferentTypes() {
+ final Number[] array = ArrayUtils.<Number>toArray(Integer.valueOf(42), Double.valueOf(Math.PI));
+ assertEquals(2, array.length);
+ assertEquals(Integer.valueOf(42), array[0]);
+ assertEquals(Double.valueOf(Math.PI), array[1]);
+ }
+
+ /**
+ * Tests generic array creation with general return type.
+ */
+ @Test
+ public void testArrayCreationWithGeneralReturnType() {
+ final Object obj = ArrayUtils.toArray("foo", "bar");
+ assertTrue(obj instanceof String[]);
+ }
+
+ @Test
+ public void testClone() {
+ assertArrayEquals(null, ArrayUtils.clone((Object[]) null));
+ Object[] original1 = {};
+ Object[] cloned1 = ArrayUtils.clone(original1);
+ assertArrayEquals(original1, cloned1);
+ assertNotSame(original1, cloned1);
+
+ final StringBuilder builder = new StringBuilder("pick");
+ original1 = new Object[]{builder, "a", new String[]{"stick"}};
+ cloned1 = ArrayUtils.clone(original1);
+ assertArrayEquals(original1, cloned1);
+ assertNotSame(original1, cloned1);
+ assertSame(original1[0], cloned1[0]);
+ assertSame(original1[1], cloned1[1]);
+ assertSame(original1[2], cloned1[2]);
+ }
+
+ @Test
+ public void testCloneBoolean() {
+ assertNull(ArrayUtils.clone((boolean[]) null));
+ final boolean[] original = {true, false};
+ final boolean[] cloned = ArrayUtils.clone(original);
+ assertArrayEquals(original, cloned);
+ assertNotSame(original, cloned);
+ }
+
+ @Test
+ public void testCloneByte() {
+ assertNull(ArrayUtils.clone((byte[]) null));
+ final byte[] original = {1, 6};
+ final byte[] cloned = ArrayUtils.clone(original);
+ assertArrayEquals(original, cloned);
+ assertNotSame(original, cloned);
+ }
+
+ @Test
+ public void testCloneChar() {
+ assertNull(ArrayUtils.clone((char[]) null));
+ final char[] original = {'a', '4'};
+ final char[] cloned = ArrayUtils.clone(original);
+ assertArrayEquals(original, cloned);
+ assertNotSame(original, cloned);
+ }
+
+ @Test
+ public void testCloneDouble() {
+ assertNull(ArrayUtils.clone((double[]) null));
+ final double[] original = {2.4d, 5.7d};
+ final double[] cloned = ArrayUtils.clone(original);
+ assertArrayEquals(original, cloned);
+ assertNotSame(original, cloned);
+ }
+
+ @Test
+ public void testCloneFloat() {
+ assertNull(ArrayUtils.clone((float[]) null));
+ final float[] original = {2.6f, 6.4f};
+ final float[] cloned = ArrayUtils.clone(original);
+ assertArrayEquals(original, cloned);
+ assertNotSame(original, cloned);
+ }
+
+ @Test
+ public void testCloneInt() {
+ assertNull(ArrayUtils.clone((int[]) null));
+ final int[] original = {5, 8};
+ final int[] cloned = ArrayUtils.clone(original);
+ assertArrayEquals(original, cloned);
+ assertNotSame(original, cloned);
+ }
+
+ @Test
+ public void testCloneLong() {
+ assertNull(ArrayUtils.clone((long[]) null));
+ final long[] original = {0L, 1L};
+ final long[] cloned = ArrayUtils.clone(original);
+ assertArrayEquals(original, cloned);
+ assertNotSame(original, cloned);
+ }
+
+ @Test
+ public void testCloneShort() {
+ assertNull(ArrayUtils.clone((short[]) null));
+ final short[] original = {1, 4};
+ final short[] cloned = ArrayUtils.clone(original);
+ assertArrayEquals(original, cloned);
+ assertNotSame(original, cloned);
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new ArrayUtils());
+ final Constructor<?>[] cons = ArrayUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(ArrayUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(ArrayUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testContains() {
+ final Object[] array = {"0", "1", "2", "3", null, "0"};
+ assertFalse(ArrayUtils.contains(null, null));
+ assertFalse(ArrayUtils.contains(null, "1"));
+ assertTrue(ArrayUtils.contains(array, "0"));
+ assertTrue(ArrayUtils.contains(array, "1"));
+ assertTrue(ArrayUtils.contains(array, "2"));
+ assertTrue(ArrayUtils.contains(array, "3"));
+ assertFalse(ArrayUtils.contains(array, "notInArray"));
+ assertTrue(ArrayUtils.contains(array, null));
+ }
+
+ @Test
+ public void testContainsAny() {
+ final Object[] array = {"0", "1", "2", "3", null, "0"};
+ assertFalse(ArrayUtils.containsAny(null, null));
+ assertFalse(ArrayUtils.containsAny(null, "1"));
+ assertTrue(ArrayUtils.containsAny(array, "0"));
+ assertTrue(ArrayUtils.containsAny(array, "1"));
+ assertTrue(ArrayUtils.containsAny(array, "2"));
+ assertTrue(ArrayUtils.containsAny(array, "3"));
+ assertFalse(ArrayUtils.containsAny(array, "notInArray"));
+ assertTrue(ArrayUtils.containsAny(array, new String[] {null}));
+ }
+
+ @Test
+ public void testContains_LANG_1261() {
+
+ class LANG1261ParentObject {
+ @Override
+ public boolean equals(final Object o) {
+ return true;
+ }
+ }
+
+ class LANG1261ChildObject extends LANG1261ParentObject {
+ // empty.
+ }
+
+ final Object[] array = new LANG1261ChildObject[]{new LANG1261ChildObject()};
+
+ assertTrue(ArrayUtils.contains(array, new LANG1261ParentObject()));
+ }
+
+ @Test
+ public void testContainsBoolean() {
+ boolean[] array = null;
+ assertFalse(ArrayUtils.contains(array, true));
+ array = new boolean[]{true, false, true};
+ assertTrue(ArrayUtils.contains(array, true));
+ assertTrue(ArrayUtils.contains(array, false));
+ array = new boolean[]{true, true};
+ assertTrue(ArrayUtils.contains(array, true));
+ assertFalse(ArrayUtils.contains(array, false));
+ }
+
+ @Test
+ public void testContainsByte() {
+ byte[] array = null;
+ assertFalse(ArrayUtils.contains(array, (byte) 1));
+ array = new byte[]{0, 1, 2, 3, 0};
+ assertTrue(ArrayUtils.contains(array, (byte) 0));
+ assertTrue(ArrayUtils.contains(array, (byte) 1));
+ assertTrue(ArrayUtils.contains(array, (byte) 2));
+ assertTrue(ArrayUtils.contains(array, (byte) 3));
+ assertFalse(ArrayUtils.contains(array, (byte) 99));
+ }
+
+ @Test
+ public void testContainsChar() {
+ char[] array = null;
+ assertFalse(ArrayUtils.contains(array, 'b'));
+ array = new char[]{'a', 'b', 'c', 'd', 'a'};
+ assertTrue(ArrayUtils.contains(array, 'a'));
+ assertTrue(ArrayUtils.contains(array, 'b'));
+ assertTrue(ArrayUtils.contains(array, 'c'));
+ assertTrue(ArrayUtils.contains(array, 'd'));
+ assertFalse(ArrayUtils.contains(array, 'e'));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testContainsDouble() {
+ double[] array = null;
+ assertFalse(ArrayUtils.contains(array, (double) 1));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertTrue(ArrayUtils.contains(array, (double) 0));
+ assertTrue(ArrayUtils.contains(array, (double) 1));
+ assertTrue(ArrayUtils.contains(array, (double) 2));
+ assertTrue(ArrayUtils.contains(array, (double) 3));
+ assertFalse(ArrayUtils.contains(array, (double) 99));
+ }
+
+ @Test
+ public void testContainsDoubleNaN() {
+ final double[] a = { Double.NEGATIVE_INFINITY, Double.NaN, Double.POSITIVE_INFINITY };
+ assertTrue(ArrayUtils.contains(a, Double.POSITIVE_INFINITY));
+ assertTrue(ArrayUtils.contains(a, Double.NEGATIVE_INFINITY));
+ assertTrue(ArrayUtils.contains(a, Double.NaN));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testContainsDoubleTolerance() {
+ double[] array = null;
+ assertFalse(ArrayUtils.contains(array, (double) 1, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertFalse(ArrayUtils.contains(array, 4.0, 0.33));
+ assertFalse(ArrayUtils.contains(array, 2.5, 0.49));
+ assertTrue(ArrayUtils.contains(array, 2.5, 0.50));
+ assertTrue(ArrayUtils.contains(array, 2.5, 0.51));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testContainsFloat() {
+ float[] array = null;
+ assertFalse(ArrayUtils.contains(array, (float) 1));
+ array = new float[]{0, 1, 2, 3, 0};
+ assertTrue(ArrayUtils.contains(array, (float) 0));
+ assertTrue(ArrayUtils.contains(array, (float) 1));
+ assertTrue(ArrayUtils.contains(array, (float) 2));
+ assertTrue(ArrayUtils.contains(array, (float) 3));
+ assertFalse(ArrayUtils.contains(array, (float) 99));
+ }
+
+ @Test
+ public void testContainsFloatNaN() {
+ final float[] array = { Float.NEGATIVE_INFINITY, Float.NaN, Float.POSITIVE_INFINITY };
+ assertTrue(ArrayUtils.contains(array, Float.POSITIVE_INFINITY));
+ assertTrue(ArrayUtils.contains(array, Float.NEGATIVE_INFINITY));
+ assertTrue(ArrayUtils.contains(array, Float.NaN));
+ }
+
+ @Test
+ public void testContainsInt() {
+ int[] array = null;
+ assertFalse(ArrayUtils.contains(array, 1));
+ array = new int[]{0, 1, 2, 3, 0};
+ assertTrue(ArrayUtils.contains(array, 0));
+ assertTrue(ArrayUtils.contains(array, 1));
+ assertTrue(ArrayUtils.contains(array, 2));
+ assertTrue(ArrayUtils.contains(array, 3));
+ assertFalse(ArrayUtils.contains(array, 99));
+ }
+
+ @Test
+ public void testContainsLong() {
+ long[] array = null;
+ assertFalse(ArrayUtils.contains(array, 1));
+ array = new long[]{0, 1, 2, 3, 0};
+ assertTrue(ArrayUtils.contains(array, 0));
+ assertTrue(ArrayUtils.contains(array, 1));
+ assertTrue(ArrayUtils.contains(array, 2));
+ assertTrue(ArrayUtils.contains(array, 3));
+ assertFalse(ArrayUtils.contains(array, 99));
+ }
+
+ @Test
+ public void testContainsShort() {
+ short[] array = null;
+ assertFalse(ArrayUtils.contains(array, (short) 1));
+ array = new short[]{0, 1, 2, 3, 0};
+ assertTrue(ArrayUtils.contains(array, (short) 0));
+ assertTrue(ArrayUtils.contains(array, (short) 1));
+ assertTrue(ArrayUtils.contains(array, (short) 2));
+ assertTrue(ArrayUtils.contains(array, (short) 3));
+ assertFalse(ArrayUtils.contains(array, (short) 99));
+ }
+
+ @Test
+ public void testCreatePrimitiveArray() {
+ assertNull(ArrayUtils.toPrimitive((Object[]) null));
+ assertArrayEquals(new boolean[]{true}, ArrayUtils.toPrimitive(new Boolean[]{true}));
+ assertArrayEquals(new char[]{'a'}, ArrayUtils.toPrimitive(new Character[]{'a'}));
+ assertArrayEquals(new byte[]{1}, ArrayUtils.toPrimitive(new Byte[]{1}));
+ assertArrayEquals(new int[]{}, ArrayUtils.toPrimitive(new Integer[]{}));
+ assertArrayEquals(new short[]{2}, ArrayUtils.toPrimitive(new Short[]{2}));
+ assertArrayEquals(new long[]{2, 3}, ArrayUtils.toPrimitive(new Long[]{2L, 3L}));
+ assertArrayEquals(new float[]{3.14f}, ArrayUtils.toPrimitive(new Float[]{3.14f}), 0.1f);
+ assertArrayEquals(new double[]{2.718}, ArrayUtils.toPrimitive(new Double[]{2.718}), 0.1);
+ }
+
+ @Test
+ public void testCreatePrimitiveArrayViaObjectArray() {
+ assertNull(ArrayUtils.toPrimitive((Object) null));
+ assertArrayEquals(new boolean[]{true}, (boolean[]) ArrayUtils.toPrimitive((Object) new Boolean[]{true}));
+ assertArrayEquals(new char[]{'a'}, (char[]) ArrayUtils.toPrimitive((Object) new Character[]{'a'}));
+ assertArrayEquals(new byte[]{1}, (byte[]) ArrayUtils.toPrimitive((Object) new Byte[]{1}));
+ assertArrayEquals(new int[]{}, (int[]) ArrayUtils.toPrimitive((Object) new Integer[]{}));
+ assertArrayEquals(new short[]{2}, (short[]) ArrayUtils.toPrimitive((Object) new Short[]{2}));
+ assertArrayEquals(new long[]{2, 3}, (long[]) ArrayUtils.toPrimitive((Object) new Long[]{2L, 3L}));
+ assertArrayEquals(new float[]{3.14f}, (float[]) ArrayUtils.toPrimitive((Object) new Float[]{3.14f}), 0.1f);
+ assertArrayEquals(new double[]{2.718}, (double[]) ArrayUtils.toPrimitive((Object) new Double[]{2.718}), 0.1);
+ }
+
+ /**
+ * Tests generic empty array creation with generic type.
+ */
+ @Test
+ public void testEmptyArrayCreation() {
+ final String[] array = ArrayUtils.<String>toArray();
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testGet() {
+ assertNull(ArrayUtils.get(null, -1));
+ assertNull(ArrayUtils.get(null, 0));
+ assertNull(ArrayUtils.get(null, 1));
+ final String[] array0 = {};
+ assertNull(ArrayUtils.get(array0, -1));
+ assertNull(ArrayUtils.get(array0, 0));
+ assertNull(ArrayUtils.get(array0, 1));
+ final String[] array1 = { StringUtils.EMPTY };
+ assertNull(ArrayUtils.get(array1, -1));
+ assertEquals(StringUtils.EMPTY, ArrayUtils.get(array1, 0));
+ assertNull(ArrayUtils.get(array1, 1));
+ }
+
+ @Test
+ public void testGetComponentType() {
+ final TestClass[] newArray = {};
+ // No type-cast required.
+ final Class<TestClass> componentType = ArrayUtils.getComponentType(newArray);
+ assertEquals(TestClass.class, componentType);
+ assertNull(ArrayUtils.getComponentType(null));
+ }
+
+ @Test
+ public void testGetDefault() {
+ // null default
+ {
+ assertNull(ArrayUtils.get(null, -1, null));
+ assertNull(ArrayUtils.get(null, 0, null));
+ assertNull(ArrayUtils.get(null, 1, null));
+ final String[] array0 = {};
+ assertNull(ArrayUtils.get(array0, -1, null));
+ assertNull(ArrayUtils.get(array0, 0, null));
+ assertNull(ArrayUtils.get(array0, 1, null));
+ final String[] array1 = { StringUtils.EMPTY };
+ assertNull(ArrayUtils.get(array1, -1, null));
+ assertEquals(StringUtils.EMPTY, ArrayUtils.get(array1, 0, null));
+ assertNull(ArrayUtils.get(array1, 1, null));
+ }
+ // non-null default
+ {
+ final String defaultValue = "defaultValue";
+ final String[] array1 = { StringUtils.EMPTY };
+ assertEquals(defaultValue, ArrayUtils.get(array1, -1, defaultValue));
+ assertEquals(StringUtils.EMPTY, ArrayUtils.get(array1, 0, defaultValue));
+ assertEquals(defaultValue, ArrayUtils.get(array1, 1, defaultValue));
+ }
+ }
+
+ @Test
+ public void testGetLength() {
+ assertEquals(0, ArrayUtils.getLength(null));
+
+ final Object[] emptyObjectArray = {};
+ final Object[] notEmptyObjectArray = {"aValue"};
+ assertEquals(0, ArrayUtils.getLength(null));
+ assertEquals(0, ArrayUtils.getLength(emptyObjectArray));
+ assertEquals(1, ArrayUtils.getLength(notEmptyObjectArray));
+
+ final int[] emptyIntArray = {};
+ final int[] notEmptyIntArray = {1};
+ assertEquals(0, ArrayUtils.getLength(null));
+ assertEquals(0, ArrayUtils.getLength(emptyIntArray));
+ assertEquals(1, ArrayUtils.getLength(notEmptyIntArray));
+
+ final short[] emptyShortArray = {};
+ final short[] notEmptyShortArray = {1};
+ assertEquals(0, ArrayUtils.getLength(null));
+ assertEquals(0, ArrayUtils.getLength(emptyShortArray));
+ assertEquals(1, ArrayUtils.getLength(notEmptyShortArray));
+
+ final char[] emptyCharArray = {};
+ final char[] notEmptyCharArray = {1};
+ assertEquals(0, ArrayUtils.getLength(null));
+ assertEquals(0, ArrayUtils.getLength(emptyCharArray));
+ assertEquals(1, ArrayUtils.getLength(notEmptyCharArray));
+
+ final byte[] emptyByteArray = {};
+ final byte[] notEmptyByteArray = {1};
+ assertEquals(0, ArrayUtils.getLength(null));
+ assertEquals(0, ArrayUtils.getLength(emptyByteArray));
+ assertEquals(1, ArrayUtils.getLength(notEmptyByteArray));
+
+ final double[] emptyDoubleArray = {};
+ final double[] notEmptyDoubleArray = {1.0};
+ assertEquals(0, ArrayUtils.getLength(null));
+ assertEquals(0, ArrayUtils.getLength(emptyDoubleArray));
+ assertEquals(1, ArrayUtils.getLength(notEmptyDoubleArray));
+
+ final float[] emptyFloatArray = {};
+ final float[] notEmptyFloatArray = {1.0F};
+ assertEquals(0, ArrayUtils.getLength(null));
+ assertEquals(0, ArrayUtils.getLength(emptyFloatArray));
+ assertEquals(1, ArrayUtils.getLength(notEmptyFloatArray));
+
+ final boolean[] emptyBooleanArray = {};
+ final boolean[] notEmptyBooleanArray = {true};
+ assertEquals(0, ArrayUtils.getLength(null));
+ assertEquals(0, ArrayUtils.getLength(emptyBooleanArray));
+ assertEquals(1, ArrayUtils.getLength(notEmptyBooleanArray));
+
+ assertThrows(IllegalArgumentException.class, () -> ArrayUtils.getLength("notAnArray"));
+ }
+
+ @Test
+ public void testHashCode() {
+ final long[][] array1 = {{2, 5}, {4, 5}};
+ final long[][] array2 = {{2, 5}, {4, 6}};
+ assertEquals(ArrayUtils.hashCode(array1), ArrayUtils.hashCode(array1));
+ assertNotEquals(ArrayUtils.hashCode(array1), ArrayUtils.hashCode(array2));
+
+ final Object[] array3 = {new String(new char[]{'A', 'B'})};
+ final Object[] array4 = {"AB"};
+ assertEquals(ArrayUtils.hashCode(array3), ArrayUtils.hashCode(array3));
+ assertEquals(ArrayUtils.hashCode(array3), ArrayUtils.hashCode(array4));
+
+ final Object[] arrayA = {new boolean[]{true, false}, new int[]{6, 7}};
+ final Object[] arrayB = {new boolean[]{true, false}, new int[]{6, 7}};
+ assertEquals(ArrayUtils.hashCode(arrayB), ArrayUtils.hashCode(arrayA));
+ }
+
+ @Test
+ public void testIndexesOf() {
+ final Object[] array = {"0", "1", "2", "3", null, "0"};
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf((Object[]) null, null));
+ assertEquals(emptySet, ArrayUtils.indexesOf(new Object[0], "0"));
+ testSet.set(5);
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "0"));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "2"));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "3"));
+ testSet.clear();
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, null));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, "notInArray"));
+ }
+
+ @Test
+ public void testIndexesOfBoolean() {
+ boolean[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, true));
+ array = new boolean[0];
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, true));
+ array = new boolean[]{true, false, true};
+ testSet.set(0);
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, true));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, false));
+ array = new boolean[]{true, true};
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, false));
+ }
+
+ @Test
+ public void testIndexesOfBooleanWithStartIndex() {
+ boolean[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, true, 0));
+ array = new boolean[0];
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, true, 0));
+ array = new boolean[]{true, false, true};
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, true, 1));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, true, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, false, 1));
+ array = new boolean[]{true, true};
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, false, 0));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, false, -1));
+ }
+
+ @Test
+ public void testIndexesOfByte() {
+ byte[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (byte) 0));
+ array = new byte[]{0, 1, 2, 3, 0};
+ testSet.set(0);
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 2));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 3));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (byte) 99));
+ }
+
+ @Test
+ public void testIndexesOfByteWithStartIndex() {
+ byte[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (byte) 0, 2));
+ array = new byte[]{0, 1, 2, 3, 0};
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 0, 2));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 0, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 1, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 2, 0));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 3, 0));
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (byte) 3, -1));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (byte) 99, 0));
+ }
+
+ @Test
+ public void testIndexesOfChar() {
+ char[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 'a'));
+ array = new char[]{'a', 'b', 'c', 'd', 'a'};
+ testSet.set(0);
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'a'));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'b'));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'c'));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'd'));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 'e'));
+ }
+
+ @Test
+ public void testIndexesOfCharWithStartIndex() {
+ char[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 'a', 0));
+ array = new char[]{'a', 'b', 'c', 'd', 'a'};
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'a', 2));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'a', 0));
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'a', -1));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'b', 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'c', 0));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 'd', 0));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 'd', 5));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 'e', 0));
+ }
+
+ @Test
+ public void testIndexesOfDouble() {
+ double[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ testSet.set(0);
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 99));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testIndexesOfDoubleTolerance() {
+ double[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (double) 0, (double) 0));
+ array = new double[0];
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (double) 0, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ testSet.set(0);
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (double) 0, 0.3));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 4.15, 2.0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1.00001324, 0.0001));
+ }
+
+ @Test
+ public void testIndexesOfDoubleWithStartIndex() {
+ double[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 0, 2));
+ array = new double[]{0, 1, 2, 3, 0};
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0, 2));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2, 0));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3, 0));
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3, -1));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 99, 0));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testIndexesOfDoubleWithStartIndexTolerance() {
+ double[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (double) 0, 0, (double) 0));
+ array = new double[0];
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (double) 0, 0, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (double) 0, 1, 0.3));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (double) 0, 0, 0.3));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2, 0, 0.35));
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2, 2, 0.35));
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2, -1, 0.35));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 2, 3, 0.35));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 4.15, 0, 2.0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1.00001324, 0, 0.0001));
+ }
+
+ @Test
+ public void testIndexesOfFloat() {
+ float[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 0));
+ array = new float[]{0, 1, 2, 3, 0};
+ testSet.set(0);
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 99));
+ }
+
+ @Test
+ public void testIndexesOfFloatWithStartIndex() {
+ float[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 0, 2));
+ array = new float[]{0, 1, 2, 3, 0};
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0, 2));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2, 0));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3, 0));
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3, -1));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 99, 0));
+ }
+
+ @Test
+ public void testIndexesOfIntWithStartIndex() {
+ int[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 0, 2));
+ array = new int[]{0, 1, 2, 3, 0};
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0, 2));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2, 0));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3, 0));
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3, -1));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 99, 0));
+ }
+
+ @Test
+ public void testIndexesOfLong() {
+ final long[] array = {0, 1, 2, 3};
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf((long[]) null, 0));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 4));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3));
+ }
+
+ @Test
+ public void testIndexesOfLongWithStartIndex() {
+ final long[] array = {0, 1, 2, 3, 2, 1, 0, 1};
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf((long[]) null, 0, 0));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 4, 0));
+ testSet.set(6);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0, 1));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0, 0));
+ testSet.clear();
+ testSet.set(1);
+ testSet.set(5);
+ testSet.set(7);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1, 0));
+ testSet.clear();
+ testSet.set(2);
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2, 0));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3, 0));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 3, 8));
+ }
+
+ @Test
+ public void testIndexesOfShort() {
+ short[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (short) 0));
+ array = new short[]{0, 1, 2, 3, 0};
+ testSet.set(0);
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 2));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 3));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (short) 99));
+ }
+
+ @Test
+ public void testIndexesOfShortWithStartIndex() {
+ short[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (short) 0, 2));
+ array = new short[]{0, 1, 2, 3, 0};
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 0, 2));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 0, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 1, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 2, 0));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 3, 0));
+ assertEquals(testSet, ArrayUtils.indexesOf(array, (short) 3, -1));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, (short) 99, 0));
+ }
+
+ @Test
+ public void testIndexesOfWithStartIndex() {
+ final Object[] array = {"0", "1", "2", "3", "2", "3", "1", null, "0"};
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(null, null, 2));
+ assertEquals(emptySet, ArrayUtils.indexesOf(new Object[0], "0", 0));
+ assertEquals(emptySet, ArrayUtils.indexesOf(null, "0", 2));
+ testSet.set(8);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "0", 8));
+ testSet.set(0);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "0", 0));
+ testSet.clear();
+ testSet.set(6);
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "1", 0));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, "1", 9));
+ testSet.clear();
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "2", 3));
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "2", 0));
+ testSet.clear();
+ testSet.set(3);
+ testSet.set(5);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, "3", 0));
+ testSet.clear();
+ testSet.set(7);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, null, 0));
+
+ }
+
+ @Test
+ public void testIndexOf() {
+ final Object[] array = {"0", "1", "2", "3", null, "0"};
+ assertEquals(-1, ArrayUtils.indexOf(null, null));
+ assertEquals(-1, ArrayUtils.indexOf(null, "0"));
+ assertEquals(-1, ArrayUtils.indexOf(new Object[0], "0"));
+ assertEquals(0, ArrayUtils.indexOf(array, "0"));
+ assertEquals(1, ArrayUtils.indexOf(array, "1"));
+ assertEquals(2, ArrayUtils.indexOf(array, "2"));
+ assertEquals(3, ArrayUtils.indexOf(array, "3"));
+ assertEquals(4, ArrayUtils.indexOf(array, null));
+ assertEquals(-1, ArrayUtils.indexOf(array, "notInArray"));
+ }
+
+ @Test
+ public void testIndexOfBoolean() {
+ boolean[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, true));
+ array = new boolean[0];
+ assertEquals(-1, ArrayUtils.indexOf(array, true));
+ array = new boolean[]{true, false, true};
+ assertEquals(0, ArrayUtils.indexOf(array, true));
+ assertEquals(1, ArrayUtils.indexOf(array, false));
+ array = new boolean[]{true, true};
+ assertEquals(-1, ArrayUtils.indexOf(array, false));
+ }
+
+ @Test
+ public void testIndexOfBooleanWithStartIndex() {
+ boolean[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, true, 2));
+ array = new boolean[0];
+ assertEquals(-1, ArrayUtils.indexOf(array, true, 2));
+ array = new boolean[]{true, false, true};
+ assertEquals(2, ArrayUtils.indexOf(array, true, 1));
+ assertEquals(-1, ArrayUtils.indexOf(array, false, 2));
+ assertEquals(1, ArrayUtils.indexOf(array, false, 0));
+ assertEquals(1, ArrayUtils.indexOf(array, false, -1));
+ array = new boolean[]{true, true};
+ assertEquals(-1, ArrayUtils.indexOf(array, false, 0));
+ assertEquals(-1, ArrayUtils.indexOf(array, false, -1));
+ }
+
+ @Test
+ public void testIndexOfByte() {
+ byte[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (byte) 0));
+ array = new byte[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.indexOf(array, (byte) 0));
+ assertEquals(1, ArrayUtils.indexOf(array, (byte) 1));
+ assertEquals(2, ArrayUtils.indexOf(array, (byte) 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (byte) 3));
+ assertEquals(-1, ArrayUtils.indexOf(array, (byte) 99));
+ }
+
+ @Test
+ public void testIndexOfByteWithStartIndex() {
+ byte[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (byte) 0, 2));
+ array = new byte[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.indexOf(array, (byte) 0, 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, (byte) 1, 2));
+ assertEquals(2, ArrayUtils.indexOf(array, (byte) 2, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (byte) 3, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (byte) 3, -1));
+ assertEquals(-1, ArrayUtils.indexOf(array, (byte) 99, 0));
+ assertEquals(-1, ArrayUtils.indexOf(array, (byte) 0, 6));
+ }
+
+ @Test
+ public void testIndexOfChar() {
+ char[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, 'a'));
+ array = new char[]{'a', 'b', 'c', 'd', 'a'};
+ assertEquals(0, ArrayUtils.indexOf(array, 'a'));
+ assertEquals(1, ArrayUtils.indexOf(array, 'b'));
+ assertEquals(2, ArrayUtils.indexOf(array, 'c'));
+ assertEquals(3, ArrayUtils.indexOf(array, 'd'));
+ assertEquals(-1, ArrayUtils.indexOf(array, 'e'));
+ }
+
+ @Test
+ public void testIndexOfCharWithStartIndex() {
+ char[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, 'a', 2));
+ array = new char[]{'a', 'b', 'c', 'd', 'a'};
+ assertEquals(4, ArrayUtils.indexOf(array, 'a', 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, 'b', 2));
+ assertEquals(2, ArrayUtils.indexOf(array, 'c', 2));
+ assertEquals(3, ArrayUtils.indexOf(array, 'd', 2));
+ assertEquals(3, ArrayUtils.indexOf(array, 'd', -1));
+ assertEquals(-1, ArrayUtils.indexOf(array, 'e', 0));
+ assertEquals(-1, ArrayUtils.indexOf(array, 'a', 6));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testIndexOfDouble() {
+ double[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0));
+ array = new double[0];
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.indexOf(array, (double) 0));
+ assertEquals(1, ArrayUtils.indexOf(array, (double) 1));
+ assertEquals(2, ArrayUtils.indexOf(array, (double) 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (double) 3));
+ assertEquals(3, ArrayUtils.indexOf(array, (double) 3, -1));
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 99));
+ }
+
+ @Test
+ public void testIndexOfDoubleNaN() {
+ final double[] array = { Double.NEGATIVE_INFINITY, Double.NaN, Double.POSITIVE_INFINITY, Double.NaN };
+ assertEquals(0, ArrayUtils.indexOf(array, Double.NEGATIVE_INFINITY));
+ assertEquals(1, ArrayUtils.indexOf(array, Double.NaN));
+ assertEquals(2, ArrayUtils.indexOf(array, Double.POSITIVE_INFINITY));
+
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testIndexOfDoubleTolerance() {
+ double[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0, (double) 0));
+ array = new double[0];
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.indexOf(array, (double) 0, 0.3));
+ assertEquals(2, ArrayUtils.indexOf(array, 2.2, 0.35));
+ assertEquals(3, ArrayUtils.indexOf(array, 4.15, 2.0));
+ assertEquals(1, ArrayUtils.indexOf(array, 1.00001324, 0.0001));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testIndexOfDoubleWithStartIndex() {
+ double[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0, 2));
+ array = new double[0];
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0, 2));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.indexOf(array, (double) 0, 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 1, 2));
+ assertEquals(2, ArrayUtils.indexOf(array, (double) 2, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (double) 3, 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 99, 0));
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0, 6));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testIndexOfDoubleWithStartIndexTolerance() {
+ double[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0, 2, (double) 0));
+ array = new double[0];
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0, 2, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertEquals(-1, ArrayUtils.indexOf(array, (double) 0, 99, 0.3));
+ assertEquals(0, ArrayUtils.indexOf(array, (double) 0, 0, 0.3));
+ assertEquals(4, ArrayUtils.indexOf(array, (double) 0, 3, 0.3));
+ assertEquals(2, ArrayUtils.indexOf(array, 2.2, 0, 0.35));
+ assertEquals(3, ArrayUtils.indexOf(array, 4.15, 0, 2.0));
+ assertEquals(1, ArrayUtils.indexOf(array, 1.00001324, 0, 0.0001));
+ assertEquals(3, ArrayUtils.indexOf(array, 4.15, -1, 2.0));
+ assertEquals(1, ArrayUtils.indexOf(array, 1.00001324, -300, 0.0001));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testIndexOfFloat() {
+ float[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (float) 0));
+ array = new float[0];
+ assertEquals(-1, ArrayUtils.indexOf(array, (float) 0));
+ array = new float[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.indexOf(array, (float) 0));
+ assertEquals(1, ArrayUtils.indexOf(array, (float) 1));
+ assertEquals(2, ArrayUtils.indexOf(array, (float) 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (float) 3));
+ assertEquals(-1, ArrayUtils.indexOf(array, (float) 99));
+ }
+
+ @Test
+ public void testIndexOfFloatNaN() {
+ final float[] array = { Float.NEGATIVE_INFINITY, Float.NaN, Float.POSITIVE_INFINITY, Float.NaN };
+ assertEquals(0, ArrayUtils.indexOf(array, Float.NEGATIVE_INFINITY));
+ assertEquals(1, ArrayUtils.indexOf(array, Float.NaN));
+ assertEquals(2, ArrayUtils.indexOf(array, Float.POSITIVE_INFINITY));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testIndexOfFloatWithStartIndex() {
+ float[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (float) 0, 2));
+ array = new float[0];
+ assertEquals(-1, ArrayUtils.indexOf(array, (float) 0, 2));
+ array = new float[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.indexOf(array, (float) 0, 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, (float) 1, 2));
+ assertEquals(2, ArrayUtils.indexOf(array, (float) 2, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (float) 3, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (float) 3, -1));
+ assertEquals(-1, ArrayUtils.indexOf(array, (float) 99, 0));
+ assertEquals(-1, ArrayUtils.indexOf(array, (float) 0, 6));
+ }
+
+ @Test
+ public void testIndexOfInt() {
+ int[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, 0));
+ array = new int[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.indexOf(array, 0));
+ assertEquals(1, ArrayUtils.indexOf(array, 1));
+ assertEquals(2, ArrayUtils.indexOf(array, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, 3));
+ assertEquals(-1, ArrayUtils.indexOf(array, 99));
+ }
+
+ @Test
+ public void testIndexOfIntWithStartIndex() {
+ int[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, 0, 2));
+ array = new int[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.indexOf(array, 0, 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, 1, 2));
+ assertEquals(2, ArrayUtils.indexOf(array, 2, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, 3, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, 3, -1));
+ assertEquals(-1, ArrayUtils.indexOf(array, 99, 0));
+ assertEquals(-1, ArrayUtils.indexOf(array, 0, 6));
+ }
+
+ @Test
+ public void testIndexOfLong() {
+ long[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, 0));
+ array = new long[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.indexOf(array, 0));
+ assertEquals(1, ArrayUtils.indexOf(array, 1));
+ assertEquals(2, ArrayUtils.indexOf(array, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, 3));
+ assertEquals(-1, ArrayUtils.indexOf(array, 99));
+ }
+
+ @Test
+ public void testIndexOfLongWithStartIndex() {
+ long[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, 0, 2));
+ array = new long[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.indexOf(array, 0, 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, 1, 2));
+ assertEquals(2, ArrayUtils.indexOf(array, 2, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, 3, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, 3, -1));
+ assertEquals(-1, ArrayUtils.indexOf(array, 99, 0));
+ assertEquals(-1, ArrayUtils.indexOf(array, 0, 6));
+ }
+
+ @Test
+ public void testIndexOfShort() {
+ short[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (short) 0));
+ array = new short[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.indexOf(array, (short) 0));
+ assertEquals(1, ArrayUtils.indexOf(array, (short) 1));
+ assertEquals(2, ArrayUtils.indexOf(array, (short) 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (short) 3));
+ assertEquals(-1, ArrayUtils.indexOf(array, (short) 99));
+ }
+
+ @Test
+ public void testIndexOfShortWithStartIndex() {
+ short[] array = null;
+ assertEquals(-1, ArrayUtils.indexOf(array, (short) 0, 2));
+ array = new short[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.indexOf(array, (short) 0, 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, (short) 1, 2));
+ assertEquals(2, ArrayUtils.indexOf(array, (short) 2, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (short) 3, 2));
+ assertEquals(3, ArrayUtils.indexOf(array, (short) 3, -1));
+ assertEquals(-1, ArrayUtils.indexOf(array, (short) 99, 0));
+ assertEquals(-1, ArrayUtils.indexOf(array, (short) 0, 6));
+ }
+
+ @Test
+ public void testIndexOfWithStartIndex() {
+ final Object[] array = {"0", "1", "2", "3", null, "0"};
+ assertEquals(-1, ArrayUtils.indexOf(null, null, 2));
+ assertEquals(-1, ArrayUtils.indexOf(new Object[0], "0", 0));
+ assertEquals(-1, ArrayUtils.indexOf(null, "0", 2));
+ assertEquals(5, ArrayUtils.indexOf(array, "0", 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, "1", 2));
+ assertEquals(2, ArrayUtils.indexOf(array, "2", 2));
+ assertEquals(3, ArrayUtils.indexOf(array, "3", 2));
+ assertEquals(4, ArrayUtils.indexOf(array, null, 2));
+ assertEquals(-1, ArrayUtils.indexOf(array, "notInArray", 2));
+
+ assertEquals(4, ArrayUtils.indexOf(array, null, -1));
+ assertEquals(-1, ArrayUtils.indexOf(array, null, 8));
+ assertEquals(-1, ArrayUtils.indexOf(array, "0", 8));
+ }
+
+ /**
+ * Tests generic array creation with generic type.
+ */
+ @Test
+ public void testIndirectArrayCreation() {
+ final String[] array = toArrayPropagatingType("foo", "bar");
+ assertEquals(2, array.length);
+ assertEquals("foo", array[0]);
+ assertEquals("bar", array[1]);
+ }
+
+ /**
+ * Tests indirect generic empty array creation with generic type.
+ */
+ @Test
+ public void testIndirectEmptyArrayCreation() {
+ final String[] array = ArrayUtilsTest.<String>toArrayPropagatingType();
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testIsArrayIndexValid() {
+ assertFalse(ArrayUtils.isArrayIndexValid(null, 0));
+ final String[] array = new String[1];
+
+ //too big
+ assertFalse(ArrayUtils.isArrayIndexValid(array, 1));
+
+ //negative index
+ assertFalse(ArrayUtils.isArrayIndexValid(array, -1));
+
+ //good to go
+ assertTrue(ArrayUtils.isArrayIndexValid(array, 0));
+ }
+
+ /**
+ * Test for {@link ArrayUtils#isEmpty(java.lang.Object[])}.
+ */
+ @Test
+ public void testIsEmptyObject() {
+ final Object[] emptyArray = {};
+ final Object[] notEmptyArray = {"Value"};
+ assertTrue(ArrayUtils.isEmpty((Object[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyArray));
+ }
+
+ /**
+ * Tests for {@link ArrayUtils#isEmpty(long[])},
+ * {@link ArrayUtils#isEmpty(int[])},
+ * {@link ArrayUtils#isEmpty(short[])},
+ * {@link ArrayUtils#isEmpty(char[])},
+ * {@link ArrayUtils#isEmpty(byte[])},
+ * {@link ArrayUtils#isEmpty(double[])},
+ * {@link ArrayUtils#isEmpty(float[])} and
+ * {@link ArrayUtils#isEmpty(boolean[])}.
+ */
+ @Test
+ public void testIsEmptyPrimitives() {
+ final long[] emptyLongArray = {};
+ final long[] notEmptyLongArray = {1L};
+ assertTrue(ArrayUtils.isEmpty((long[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyLongArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyLongArray));
+
+ final int[] emptyIntArray = {};
+ final int[] notEmptyIntArray = {1};
+ assertTrue(ArrayUtils.isEmpty((int[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyIntArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyIntArray));
+
+ final short[] emptyShortArray = {};
+ final short[] notEmptyShortArray = {1};
+ assertTrue(ArrayUtils.isEmpty((short[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyShortArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyShortArray));
+
+ final char[] emptyCharArray = {};
+ final char[] notEmptyCharArray = {1};
+ assertTrue(ArrayUtils.isEmpty((char[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyCharArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyCharArray));
+
+ final byte[] emptyByteArray = {};
+ final byte[] notEmptyByteArray = {1};
+ assertTrue(ArrayUtils.isEmpty((byte[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyByteArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyByteArray));
+
+ final double[] emptyDoubleArray = {};
+ final double[] notEmptyDoubleArray = {1.0};
+ assertTrue(ArrayUtils.isEmpty((double[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyDoubleArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyDoubleArray));
+
+ final float[] emptyFloatArray = {};
+ final float[] notEmptyFloatArray = {1.0F};
+ assertTrue(ArrayUtils.isEmpty((float[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyFloatArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyFloatArray));
+
+ final boolean[] emptyBooleanArray = {};
+ final boolean[] notEmptyBooleanArray = {true};
+ assertTrue(ArrayUtils.isEmpty((boolean[]) null));
+ assertTrue(ArrayUtils.isEmpty(emptyBooleanArray));
+ assertFalse(ArrayUtils.isEmpty(notEmptyBooleanArray));
+ }
+
+ @Test
+ public void testIsEquals() {
+ final long[][] larray1 = {{2, 5}, {4, 5}};
+ final long[][] larray2 = {{2, 5}, {4, 6}};
+ final long[] larray3 = {2, 5};
+ this.assertIsEquals(larray1, larray2, larray3);
+
+ final int[][] iarray1 = {{2, 5}, {4, 5}};
+ final int[][] iarray2 = {{2, 5}, {4, 6}};
+ final int[] iarray3 = {2, 5};
+ this.assertIsEquals(iarray1, iarray2, iarray3);
+
+ final short[][] sarray1 = {{2, 5}, {4, 5}};
+ final short[][] sarray2 = {{2, 5}, {4, 6}};
+ final short[] sarray3 = {2, 5};
+ this.assertIsEquals(sarray1, sarray2, sarray3);
+
+ final float[][] farray1 = {{2, 5}, {4, 5}};
+ final float[][] farray2 = {{2, 5}, {4, 6}};
+ final float[] farray3 = {2, 5};
+ this.assertIsEquals(farray1, farray2, farray3);
+
+ final double[][] darray1 = {{2, 5}, {4, 5}};
+ final double[][] darray2 = {{2, 5}, {4, 6}};
+ final double[] darray3 = {2, 5};
+ this.assertIsEquals(darray1, darray2, darray3);
+
+ final byte[][] byteArray1 = {{2, 5}, {4, 5}};
+ final byte[][] byteArray2 = {{2, 5}, {4, 6}};
+ final byte[] byteArray3 = {2, 5};
+ this.assertIsEquals(byteArray1, byteArray2, byteArray3);
+
+ final char[][] charArray1 = {{2, 5}, {4, 5}};
+ final char[][] charArray2 = {{2, 5}, {4, 6}};
+ final char[] charArray3 = {2, 5};
+ this.assertIsEquals(charArray1, charArray2, charArray3);
+
+ final boolean[][] barray1 = {{true, false}, {true, true}};
+ final boolean[][] barray2 = {{true, false}, {true, false}};
+ final boolean[] barray3 = {false, true};
+ this.assertIsEquals(barray1, barray2, barray3);
+
+ final Object[] array3 = {new String(new char[]{'A', 'B'})};
+ final Object[] array4 = {"AB"};
+ assertTrue(ArrayUtils.isEquals(array3, array3));
+ assertTrue(ArrayUtils.isEquals(array3, array4));
+
+ assertTrue(ArrayUtils.isEquals(null, null));
+ assertFalse(ArrayUtils.isEquals(null, array4));
+ }
+
+ /**
+ * Test for {@link ArrayUtils#isNotEmpty(java.lang.Object[])}.
+ */
+ @Test
+ public void testIsNotEmptyObject() {
+ final Object[] emptyArray = {};
+ final Object[] notEmptyArray = {"Value"};
+ assertFalse(ArrayUtils.isNotEmpty((Object[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyArray));
+ }
+
+ /**
+ * Tests for {@link ArrayUtils#isNotEmpty(long[])},
+ * {@link ArrayUtils#isNotEmpty(int[])},
+ * {@link ArrayUtils#isNotEmpty(short[])},
+ * {@link ArrayUtils#isNotEmpty(char[])},
+ * {@link ArrayUtils#isNotEmpty(byte[])},
+ * {@link ArrayUtils#isNotEmpty(double[])},
+ * {@link ArrayUtils#isNotEmpty(float[])} and
+ * {@link ArrayUtils#isNotEmpty(boolean[])}.
+ */
+ @Test
+ public void testIsNotEmptyPrimitives() {
+ final long[] emptyLongArray = {};
+ final long[] notEmptyLongArray = {1L};
+ assertFalse(ArrayUtils.isNotEmpty((long[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyLongArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyLongArray));
+
+ final int[] emptyIntArray = {};
+ final int[] notEmptyIntArray = {1};
+ assertFalse(ArrayUtils.isNotEmpty((int[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyIntArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyIntArray));
+
+ final short[] emptyShortArray = {};
+ final short[] notEmptyShortArray = {1};
+ assertFalse(ArrayUtils.isNotEmpty((short[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyShortArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyShortArray));
+
+ final char[] emptyCharArray = {};
+ final char[] notEmptyCharArray = {1};
+ assertFalse(ArrayUtils.isNotEmpty((char[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyCharArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyCharArray));
+
+ final byte[] emptyByteArray = {};
+ final byte[] notEmptyByteArray = {1};
+ assertFalse(ArrayUtils.isNotEmpty((byte[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyByteArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyByteArray));
+
+ final double[] emptyDoubleArray = {};
+ final double[] notEmptyDoubleArray = {1.0};
+ assertFalse(ArrayUtils.isNotEmpty((double[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyDoubleArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyDoubleArray));
+
+ final float[] emptyFloatArray = {};
+ final float[] notEmptyFloatArray = {1.0F};
+ assertFalse(ArrayUtils.isNotEmpty((float[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyFloatArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyFloatArray));
+
+ final boolean[] emptyBooleanArray = {};
+ final boolean[] notEmptyBooleanArray = {true};
+ assertFalse(ArrayUtils.isNotEmpty((boolean[]) null));
+ assertFalse(ArrayUtils.isNotEmpty(emptyBooleanArray));
+ assertTrue(ArrayUtils.isNotEmpty(notEmptyBooleanArray));
+ }
+
+ @Test
+ public void testIsSorted() {
+ Integer[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new Integer[]{1};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new Integer[]{1, 2, 3};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new Integer[]{1, 3, 2};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testIsSortedBool() {
+ boolean[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new boolean[]{true};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new boolean[]{false, true};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new boolean[]{true, false};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testIsSortedByte() {
+ byte[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new byte[]{0x10};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new byte[]{0x10, 0x20, 0x30};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new byte[]{0x10, 0x30, 0x20};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testIsSortedChar() {
+ char[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new char[]{'a'};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new char[]{'a', 'b', 'c'};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new char[]{'a', 'c', 'b'};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testIsSortedComparator() {
+ final Comparator<Integer> c = Comparator.reverseOrder();
+
+ Integer[] array = null;
+ assertTrue(ArrayUtils.isSorted(array, c));
+
+ array = new Integer[]{1};
+ assertTrue(ArrayUtils.isSorted(array, c));
+
+ array = new Integer[]{3, 2, 1};
+ assertTrue(ArrayUtils.isSorted(array, c));
+
+ array = new Integer[]{1, 3, 2};
+ assertFalse(ArrayUtils.isSorted(array, c));
+ }
+
+ @Test
+ public void testIsSortedDouble() {
+ double[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new double[]{0.0};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new double[]{-1.0, 0.0, 0.1, 0.2};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new double[]{-1.0, 0.2, 0.1, 0.0};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testIsSortedFloat() {
+ float[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new float[]{0f};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new float[]{-1f, 0f, 0.1f, 0.2f};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new float[]{-1f, 0.2f, 0.1f, 0f};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testIsSortedInt() {
+ int[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new int[]{1};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new int[]{1, 2, 3};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new int[]{1, 3, 2};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testIsSortedLong() {
+ long[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new long[]{0L};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new long[]{-1L, 0L, 1L};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new long[]{-1L, 1L, 0L};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testIsSortedNullComparator() {
+ assertThrows(NullPointerException.class, () -> ArrayUtils.isSorted(null, null));
+ }
+
+ @Test
+ public void testIsSortedShort() {
+ short[] array = null;
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new short[]{0};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new short[]{-1, 0, 1};
+ assertTrue(ArrayUtils.isSorted(array));
+
+ array = new short[]{-1, 1, 0};
+ assertFalse(ArrayUtils.isSorted(array));
+ }
+
+ @Test
+ public void testLastIndexOf() {
+ final Object[] array = {"0", "1", "2", "3", null, "0"};
+ assertEquals(-1, ArrayUtils.lastIndexOf(null, null));
+ assertEquals(-1, ArrayUtils.lastIndexOf(null, "0"));
+ assertEquals(5, ArrayUtils.lastIndexOf(array, "0"));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, "1"));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, "2"));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, "3"));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, null));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, "notInArray"));
+ }
+
+ @Test
+ public void testLastIndexOfBoolean() {
+ boolean[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, true));
+ array = new boolean[0];
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, true));
+ array = new boolean[]{true, false, true};
+ assertEquals(2, ArrayUtils.lastIndexOf(array, true));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, false));
+ array = new boolean[]{true, true};
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, false));
+ }
+
+ @Test
+ public void testLastIndexOfBooleanWithStartIndex() {
+ boolean[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, true, 2));
+ array = new boolean[0];
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, true, 2));
+ array = new boolean[]{true, false, true};
+ assertEquals(2, ArrayUtils.lastIndexOf(array, true, 2));
+ assertEquals(0, ArrayUtils.lastIndexOf(array, true, 1));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, false, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, true, -1));
+ array = new boolean[]{true, true};
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, false, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, true, -1));
+ }
+
+ @Test
+ public void testLastIndexOfByte() {
+ byte[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (byte) 0));
+ array = new byte[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (byte) 0));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, (byte) 1));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, (byte) 2));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, (byte) 3));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (byte) 99));
+ }
+
+ @Test
+ public void testLastIndexOfByteWithStartIndex() {
+ byte[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (byte) 0, 2));
+ array = new byte[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.lastIndexOf(array, (byte) 0, 2));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, (byte) 1, 2));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, (byte) 2, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (byte) 3, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (byte) 3, -1));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (byte) 99));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (byte) 0, 88));
+ }
+
+ @Test
+ public void testLastIndexOfChar() {
+ char[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 'a'));
+ array = new char[]{'a', 'b', 'c', 'd', 'a'};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, 'a'));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, 'b'));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, 'c'));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, 'd'));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 'e'));
+ }
+
+ @Test
+ public void testLastIndexOfCharWithStartIndex() {
+ char[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 'a', 2));
+ array = new char[]{'a', 'b', 'c', 'd', 'a'};
+ assertEquals(0, ArrayUtils.lastIndexOf(array, 'a', 2));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, 'b', 2));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, 'c', 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 'd', 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 'd', -1));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 'e'));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, 'a', 88));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testLastIndexOfDouble() {
+ double[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 0));
+ array = new double[0];
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (double) 0));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, (double) 1));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, (double) 2));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, (double) 3));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 99));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testLastIndexOfDoubleTolerance() {
+ double[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 0, (double) 0));
+ array = new double[0];
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 0, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (double) 0, 0.3));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, 2.2, 0.35));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, 4.15, 2.0));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, 1.00001324, 0.0001));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testLastIndexOfDoubleWithStartIndex() {
+ double[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 0, 2));
+ array = new double[0];
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 0, 2));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.lastIndexOf(array, (double) 0, 2));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, (double) 1, 2));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, (double) 2, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 3, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 3, -1));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 99));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (double) 0, 88));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testLastIndexOfDoubleWithStartIndexTolerance() {
+ double[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 0, 2, (double) 0));
+ array = new double[0];
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 0, 2, (double) 0));
+ array = new double[]{(double) 3};
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (double) 1, 0, (double) 0));
+ array = new double[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (double) 0, 99, 0.3));
+ assertEquals(0, ArrayUtils.lastIndexOf(array, (double) 0, 3, 0.3));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, 2.2, 3, 0.35));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, 4.15, array.length, 2.0));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, 1.00001324, array.length, 0.0001));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 4.15, -200, 2.0));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testLastIndexOfFloat() {
+ float[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (float) 0));
+ array = new float[0];
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (float) 0));
+ array = new float[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (float) 0));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, (float) 1));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, (float) 2));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, (float) 3));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (float) 99));
+ }
+
+ @SuppressWarnings("cast")
+ @Test
+ public void testLastIndexOfFloatWithStartIndex() {
+ float[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (float) 0, 2));
+ array = new float[0];
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (float) 0, 2));
+ array = new float[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.lastIndexOf(array, (float) 0, 2));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, (float) 1, 2));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, (float) 2, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (float) 3, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (float) 3, -1));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (float) 99));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (float) 0, 88));
+ }
+
+ @Test
+ public void testLastIndexOfInt() {
+ int[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 0));
+ array = new int[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, 0));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, 1));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, 2));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, 3));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 99));
+ }
+
+ @Test
+ public void testLastIndexOfIntWithStartIndex() {
+ int[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 0, 2));
+ array = new int[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.lastIndexOf(array, 0, 2));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, 1, 2));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, 2, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 3, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 3, -1));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 99));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, 0, 88));
+ }
+
+ @Test
+ public void testLastIndexOfLong() {
+ long[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 0));
+ array = new long[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, 0));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, 1));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, 2));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, 3));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 99));
+ }
+
+ @Test
+ public void testLastIndexOfLongWithStartIndex() {
+ long[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 0, 2));
+ array = new long[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.lastIndexOf(array, 0, 2));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, 1, 2));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, 2, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 3, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 3, -1));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, 99, 4));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, 0, 88));
+ }
+
+ @Test
+ public void testLastIndexOfShort() {
+ short[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (short) 0));
+ array = new short[]{0, 1, 2, 3, 0};
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (short) 0));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, (short) 1));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, (short) 2));
+ assertEquals(3, ArrayUtils.lastIndexOf(array, (short) 3));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (short) 99));
+ }
+
+ @Test
+ public void testLastIndexOfShortWithStartIndex() {
+ short[] array = null;
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (short) 0, 2));
+ array = new short[]{0, 1, 2, 3, 0};
+ assertEquals(0, ArrayUtils.lastIndexOf(array, (short) 0, 2));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, (short) 1, 2));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, (short) 2, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (short) 3, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (short) 3, -1));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, (short) 99));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, (short) 0, 88));
+ }
+
+ @Test
+ public void testLastIndexOfWithStartIndex() {
+ final Object[] array = {"0", "1", "2", "3", null, "0"};
+ assertEquals(-1, ArrayUtils.lastIndexOf(null, null, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(null, "0", 2));
+ assertEquals(0, ArrayUtils.lastIndexOf(array, "0", 2));
+ assertEquals(1, ArrayUtils.lastIndexOf(array, "1", 2));
+ assertEquals(2, ArrayUtils.lastIndexOf(array, "2", 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, "3", 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, "3", -1));
+ assertEquals(4, ArrayUtils.lastIndexOf(array, null, 5));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, null, 2));
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, "notInArray", 5));
+
+ assertEquals(-1, ArrayUtils.lastIndexOf(array, null, -1));
+ assertEquals(5, ArrayUtils.lastIndexOf(array, "0", 88));
+ }
+
+ @Test
+ public void testNullToEmptyBoolean() {
+ final boolean[] original = {true, false};
+ assertEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyBooleanEmptyArray() {
+ final boolean[] empty = {};
+ final boolean[] result = ArrayUtils.nullToEmpty(empty);
+ assertEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyBooleanNull() {
+ assertEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.nullToEmpty((boolean[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyBooleanObject() {
+ final Boolean[] original = {Boolean.TRUE, Boolean.FALSE};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyBooleanObjectEmptyArray() {
+ final Boolean[] empty = {};
+ final Boolean[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyBooleanObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Boolean[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyByte() {
+ final byte[] original = {0x0F, 0x0E};
+ assertEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyByteEmptyArray() {
+ final byte[] empty = {};
+ final byte[] result = ArrayUtils.nullToEmpty(empty);
+ assertEquals(ArrayUtils.EMPTY_BYTE_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyByteNull() {
+ assertEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.nullToEmpty((byte[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyByteObject() {
+ final Byte[] original = {0x0F, 0x0E};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyByteObjectEmptyArray() {
+ final Byte[] empty = {};
+ final Byte[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyByteObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Byte[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyChar() {
+ final char[] original = {'a', 'b'};
+ assertEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyCharEmptyArray() {
+ final char[] empty = {};
+ final char[] result = ArrayUtils.nullToEmpty(empty);
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyCharNull() {
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.nullToEmpty((char[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyCharObject() {
+ final Character[] original = {'a', 'b'};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyCharObjectEmptyArray() {
+ final Character[] empty = {};
+ final Character[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNUllToEmptyCharObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Character[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyClass() {
+ final Class<?>[] original = {Object.class, String.class};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyClassEmptyArray() {
+ final Class<?>[] empty = {};
+ final Class<?>[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_CLASS_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyClassNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_CLASS_ARRAY, ArrayUtils.nullToEmpty((Class<?>[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyDouble() {
+ final double[] original = {1L, 2L};
+ assertEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyDoubleEmptyArray() {
+ final double[] empty = {};
+ final double[] result = ArrayUtils.nullToEmpty(empty);
+ assertEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyDoubleNull() {
+ assertEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.nullToEmpty((double[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyDoubleObject() {
+ final Double[] original = {1D, 2D};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyDoubleObjectEmptyArray() {
+ final Double[] empty = {};
+ final Double[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyDoubleObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_DOUBLE_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Double[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyFloat() {
+ final float[] original = {2.6f, 3.8f};
+ assertEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyFloatEmptyArray() {
+ final float[] empty = {};
+ final float[] result = ArrayUtils.nullToEmpty(empty);
+ assertEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyFloatNull() {
+ assertEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.nullToEmpty((float[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyFloatObject() {
+ final Float[] original = {2.6f, 3.8f};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyFloatObjectEmptyArray() {
+ final Float[] empty = {};
+ final Float[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyFloatObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_FLOAT_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Float[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyGeneric() {
+ final TestClass[] input = {new TestClass(), new TestClass()};
+ final TestClass[] output = ArrayUtils.nullToEmpty(input, TestClass[].class);
+
+ assertSame(input, output);
+ }
+
+ @Test
+ public void testNullToEmptyGenericEmpty() {
+ final TestClass[] input = {};
+ final TestClass[] output = ArrayUtils.nullToEmpty(input, TestClass[].class);
+
+ assertSame(input, output);
+ }
+
+ @Test
+ public void testNullToEmptyGenericNull() {
+ final TestClass[] output = ArrayUtils.nullToEmpty(null, TestClass[].class);
+
+ assertNotNull(output);
+ assertEquals(0, output.length);
+ }
+
+ @Test
+ public void testNullToEmptyGenericNullType() {
+ final TestClass[] input = {};
+ assertThrows(IllegalArgumentException.class, () -> ArrayUtils.nullToEmpty(input, null));
+ }
+
+ @Test
+ public void testNullToEmptyInt() {
+ final int[] original = {1, 2};
+ assertEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyIntEmptyArray() {
+ final int[] empty = {};
+ final int[] result = ArrayUtils.nullToEmpty(empty);
+ assertEquals(ArrayUtils.EMPTY_INT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyIntNull() {
+ assertEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.nullToEmpty((int[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyIntObject() {
+ final Integer[] original = {1, 2};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyIntObjectEmptyArray() {
+ final Integer[] empty = {};
+ final Integer[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_INTEGER_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyIntObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_INTEGER_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Integer[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyLong() {
+ final long[] original = {1L, 2L};
+ assertEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyLongEmptyArray() {
+ final long[] empty = {};
+ final long[] result = ArrayUtils.nullToEmpty(empty);
+ assertEquals(ArrayUtils.EMPTY_LONG_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyLongNull() {
+ assertEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.nullToEmpty((long[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyLongObject() {
+ @SuppressWarnings("boxing") final Long[] original = {1L, 2L};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyLongObjectEmptyArray() {
+ final Long[] empty = {};
+ final Long[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyLongObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Long[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyObject() {
+ final Object[] original = {Boolean.TRUE, Boolean.FALSE};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyObjectEmptyArray() {
+ final Object[] empty = {};
+ final Object[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Object[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyShort() {
+ final short[] original = {1, 2};
+ assertEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyShortEmptyArray() {
+ final short[] empty = {};
+ final short[] result = ArrayUtils.nullToEmpty(empty);
+ assertEquals(ArrayUtils.EMPTY_SHORT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyShortNull() {
+ assertEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.nullToEmpty((short[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyShortObject() {
+ @SuppressWarnings("boxing") final Short[] original = {1, 2};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyShortObjectEmptyArray() {
+ final Short[] empty = {};
+ final Short[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_OBJECT_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyShortObjectNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_SHORT_OBJECT_ARRAY, ArrayUtils.nullToEmpty((Short[]) null));
+ }
+
+ @Test
+ public void testNullToEmptyString() {
+ final String[] original = {"abc", "def"};
+ assertArrayEquals(original, ArrayUtils.nullToEmpty(original));
+ }
+
+ @Test
+ public void testNullToEmptyStringEmptyArray() {
+ final String[] empty = {};
+ final String[] result = ArrayUtils.nullToEmpty(empty);
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, result);
+ assertNotSame(empty, result);
+ }
+
+ @Test
+ public void testNullToEmptyStringNull() {
+ assertArrayEquals(ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.nullToEmpty((String[]) null));
+ }
+
+ @Test
+ public void testReverse() {
+ final StringBuffer str1 = new StringBuffer("pick");
+ final String str2 = "a";
+ final String[] str3 = {"stick"};
+ final String str4 = "up";
+
+ Object[] array = {str1, str2, str3};
+ ArrayUtils.reverse(array);
+ assertEquals(array[0], str3);
+ assertEquals(array[1], str2);
+ assertEquals(array[2], str1);
+
+ array = new Object[]{str1, str2, str3, str4};
+ ArrayUtils.reverse(array);
+ assertEquals(array[0], str4);
+ assertEquals(array[1], str3);
+ assertEquals(array[2], str2);
+ assertEquals(array[3], str1);
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertArrayEquals(null, array);
+ }
+
+ @Test
+ public void testReverseBoolean() {
+ boolean[] array = {false, false, true};
+ ArrayUtils.reverse(array);
+ assertTrue(array[0]);
+ assertFalse(array[1]);
+ assertFalse(array[2]);
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseBooleanRange() {
+ boolean[] array = {false, false, true};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertTrue(array[0]);
+ assertFalse(array[1]);
+ assertFalse(array[2]);
+ // a range
+ array = new boolean[]{false, false, true};
+ ArrayUtils.reverse(array, 0, 2);
+ assertFalse(array[0]);
+ assertFalse(array[1]);
+ assertTrue(array[2]);
+ // a range with a negative start
+ array = new boolean[]{false, false, true};
+ ArrayUtils.reverse(array, -1, 3);
+ assertTrue(array[0]);
+ assertFalse(array[1]);
+ assertFalse(array[2]);
+ // a range with a large stop index
+ array = new boolean[]{false, false, true};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertTrue(array[0]);
+ assertFalse(array[1]);
+ assertFalse(array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseByte() {
+ byte[] array = {2, 3, 4};
+ ArrayUtils.reverse(array);
+ assertEquals(array[0], 4);
+ assertEquals(array[1], 3);
+ assertEquals(array[2], 2);
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseByteRange() {
+ byte[] array = {1, 2, 3};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range
+ array = new byte[]{1, 2, 3};
+ ArrayUtils.reverse(array, 0, 2);
+ assertEquals(2, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(3, array[2]);
+ // a range with a negative start
+ array = new byte[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range with a large stop index
+ array = new byte[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseChar() {
+ char[] array = {'a', 'f', 'C'};
+ ArrayUtils.reverse(array);
+ assertEquals(array[0], 'C');
+ assertEquals(array[1], 'f');
+ assertEquals(array[2], 'a');
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseCharRange() {
+ char[] array = {1, 2, 3};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range
+ array = new char[]{1, 2, 3};
+ ArrayUtils.reverse(array, 0, 2);
+ assertEquals(2, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(3, array[2]);
+ // a range with a negative start
+ array = new char[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range with a large stop index
+ array = new char[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseDouble() {
+ double[] array = {0.3d, 0.4d, 0.5d};
+ ArrayUtils.reverse(array);
+ assertEquals(0.5d, array[0]);
+ assertEquals(0.4d, array[1]);
+ assertEquals(0.3d, array[2]);
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseDoubleRange() {
+ double[] array = {1, 2, 3};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range
+ array = new double[]{1, 2, 3};
+ ArrayUtils.reverse(array, 0, 2);
+ assertEquals(2, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(3, array[2]);
+ // a range with a negative start
+ array = new double[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range with a large stop index
+ array = new double[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseFloat() {
+ float[] array = {0.3f, 0.4f, 0.5f};
+ ArrayUtils.reverse(array);
+ assertEquals(0.5f, array[0]);
+ assertEquals(0.4f, array[1]);
+ assertEquals(0.3f, array[2]);
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseFloatRange() {
+ float[] array = {1, 2, 3};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range
+ array = new float[]{1, 2, 3};
+ ArrayUtils.reverse(array, 0, 2);
+ assertEquals(2, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(3, array[2]);
+ // a range with a negative start
+ array = new float[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range with a large stop index
+ array = new float[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseInt() {
+ int[] array = {1, 2, 3};
+ ArrayUtils.reverse(array);
+ assertEquals(array[0], 3);
+ assertEquals(array[1], 2);
+ assertEquals(array[2], 1);
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseIntRange() {
+ int[] array = {1, 2, 3};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range
+ array = new int[]{1, 2, 3};
+ ArrayUtils.reverse(array, 0, 2);
+ assertEquals(2, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(3, array[2]);
+ // a range with a negative start
+ array = new int[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range with a large stop index
+ array = new int[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseLong() {
+ long[] array = {1L, 2L, 3L};
+ ArrayUtils.reverse(array);
+ assertEquals(array[0], 3L);
+ assertEquals(array[1], 2L);
+ assertEquals(array[2], 1L);
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseLongRange() {
+ long[] array = {1, 2, 3};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range
+ array = new long[]{1, 2, 3};
+ ArrayUtils.reverse(array, 0, 2);
+ assertEquals(2, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(3, array[2]);
+ // a range with a negative start
+ array = new long[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range with a large stop index
+ array = new long[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseObjectRange() {
+ String[] array = {"1", "2", "3"};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertEquals("3", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("1", array[2]);
+ // a range
+ array = new String[]{"1", "2", "3"};
+ ArrayUtils.reverse(array, 0, 2);
+ assertEquals("2", array[0]);
+ assertEquals("1", array[1]);
+ assertEquals("3", array[2]);
+ // a range with a negative start
+ array = new String[]{"1", "2", "3"};
+ ArrayUtils.reverse(array, -1, 3);
+ assertEquals("3", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("1", array[2]);
+ // a range with a large stop index
+ array = new String[]{"1", "2", "3"};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertEquals("3", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("1", array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseShort() {
+ short[] array = {1, 2, 3};
+ ArrayUtils.reverse(array);
+ assertEquals(array[0], 3);
+ assertEquals(array[1], 2);
+ assertEquals(array[2], 1);
+
+ array = null;
+ ArrayUtils.reverse(array);
+ assertNull(array);
+ }
+
+ @Test
+ public void testReverseShortRange() {
+ short[] array = {1, 2, 3};
+ // The whole array
+ ArrayUtils.reverse(array, 0, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range
+ array = new short[]{1, 2, 3};
+ ArrayUtils.reverse(array, 0, 2);
+ assertEquals(2, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(3, array[2]);
+ // a range with a negative start
+ array = new short[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, 3);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // a range with a large stop index
+ array = new short[]{1, 2, 3};
+ ArrayUtils.reverse(array, -1, array.length + 1000);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ // null
+ array = null;
+ ArrayUtils.reverse(array, 0, 3);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSameLength() {
+ final Object[] nullArray = null;
+ final Object[] emptyArray = {};
+ final Object[] oneArray = {"pick"};
+ final Object[] twoArray = {"pick", "stick"};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameLengthAll() {
+ final Object[] nullArrayObject = null;
+ final Object[] emptyArrayObject = {};
+ final Object[] oneArrayObject = {"pick"};
+ final Object[] twoArrayObject = {"pick", "stick"};
+ final boolean[] nullArrayBoolean = null;
+ final boolean[] emptyArrayBoolean = {};
+ final boolean[] oneArrayBoolean = {true};
+ final boolean[] twoArrayBoolean = {true, false};
+ final long[] nullArrayLong = null;
+ final long[] emptyArrayLong = {};
+ final long[] oneArrayLong = {0L};
+ final long[] twoArrayLong = {0L, 76L};
+ final int[] nullArrayInt = null;
+ final int[] emptyArrayInt = {};
+ final int[] oneArrayInt = {4};
+ final int[] twoArrayInt = {5, 7};
+ final short[] nullArrayShort = null;
+ final short[] emptyArrayShort = {};
+ final short[] oneArrayShort = {4};
+ final short[] twoArrayShort = {6, 8};
+ final char[] nullArrayChar = null;
+ final char[] emptyArrayChar = {};
+ final char[] oneArrayChar = {'f'};
+ final char[] twoArrayChar = {'d', 't'};
+ final byte[] nullArrayByte = null;
+ final byte[] emptyArrayByte = {};
+ final byte[] oneArrayByte = {3};
+ final byte[] twoArrayByte = {4, 6};
+ final double[] nullArrayDouble = null;
+ final double[] emptyArrayDouble = {};
+ final double[] oneArrayDouble = {1.3d};
+ final double[] twoArrayDouble = {4.5d, 6.3d};
+ final float[] nullArrayFloat = null;
+ final float[] emptyArrayFloat = {};
+ final float[] oneArrayFloat = {2.5f};
+ final float[] twoArrayFloat = {6.4f, 5.8f};
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayObject, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayBoolean, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayLong, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayInt, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayShort, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayChar, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayByte, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayDouble, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(nullArrayFloat, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayObject, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayBoolean, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayLong, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayInt, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayShort, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayChar, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayByte, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayDouble, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(nullArrayFloat, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, nullArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayObject, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayBoolean, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayLong, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayInt, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayShort, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayChar, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayByte, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayDouble, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayObject));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayLong));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayInt));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayShort));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayChar));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayByte));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(emptyArrayFloat, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayObject, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayBoolean, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayLong, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayInt, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayShort, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayChar, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayByte, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayDouble, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(emptyArrayFloat, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, emptyArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayObject, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayBoolean, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayLong, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayInt, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayShort, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayChar, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayByte, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayDouble, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayObject));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayLong));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayInt));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayShort));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayChar));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayByte));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(oneArrayFloat, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayObject, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayBoolean, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayLong, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayInt, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayShort, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayChar, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayByte, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayDouble, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayObject));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayLong));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayInt));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayShort));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayChar));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayByte));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(oneArrayFloat, twoArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, nullArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, emptyArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayObject, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayBoolean, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayLong, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayInt, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayShort, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayChar, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayByte, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayDouble, oneArrayFloat));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayObject));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayBoolean));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayLong));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayInt));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayShort));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayChar));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayByte));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayDouble));
+ assertFalse(ArrayUtils.isSameLength(twoArrayFloat, oneArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayObject, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayBoolean, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayLong, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayInt, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayShort, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayChar, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayByte, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayDouble, twoArrayFloat));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayObject));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayBoolean));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayLong));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayInt));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayShort));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayChar));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayByte));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayDouble));
+ assertTrue(ArrayUtils.isSameLength(twoArrayFloat, twoArrayFloat));
+ }
+
+ @Test
+ public void testSameLengthBoolean() {
+ final boolean[] nullArray = null;
+ final boolean[] emptyArray = {};
+ final boolean[] oneArray = {true};
+ final boolean[] twoArray = {true, false};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameLengthByte() {
+ final byte[] nullArray = null;
+ final byte[] emptyArray = {};
+ final byte[] oneArray = {3};
+ final byte[] twoArray = {4, 6};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameLengthChar() {
+ final char[] nullArray = null;
+ final char[] emptyArray = {};
+ final char[] oneArray = {'f'};
+ final char[] twoArray = {'d', 't'};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameLengthDouble() {
+ final double[] nullArray = null;
+ final double[] emptyArray = {};
+ final double[] oneArray = {1.3d};
+ final double[] twoArray = {4.5d, 6.3d};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameLengthFloat() {
+ final float[] nullArray = null;
+ final float[] emptyArray = {};
+ final float[] oneArray = {2.5f};
+ final float[] twoArray = {6.4f, 5.8f};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameLengthInt() {
+ final int[] nullArray = null;
+ final int[] emptyArray = {};
+ final int[] oneArray = {4};
+ final int[] twoArray = {5, 7};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameLengthLong() {
+ final long[] nullArray = null;
+ final long[] emptyArray = {};
+ final long[] oneArray = {0L};
+ final long[] twoArray = {0L, 76L};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameLengthShort() {
+ final short[] nullArray = null;
+ final short[] emptyArray = {};
+ final short[] oneArray = {4};
+ final short[] twoArray = {6, 8};
+
+ assertTrue(ArrayUtils.isSameLength(nullArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(nullArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(nullArray, twoArray));
+
+ assertTrue(ArrayUtils.isSameLength(emptyArray, nullArray));
+ assertTrue(ArrayUtils.isSameLength(emptyArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(emptyArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(oneArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, emptyArray));
+ assertTrue(ArrayUtils.isSameLength(oneArray, oneArray));
+ assertFalse(ArrayUtils.isSameLength(oneArray, twoArray));
+
+ assertFalse(ArrayUtils.isSameLength(twoArray, nullArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, emptyArray));
+ assertFalse(ArrayUtils.isSameLength(twoArray, oneArray));
+ assertTrue(ArrayUtils.isSameLength(twoArray, twoArray));
+ }
+
+ @Test
+ public void testSameType() {
+ assertThrows(IllegalArgumentException.class, () -> ArrayUtils.isSameType(null, null));
+ assertThrows(IllegalArgumentException.class, () -> ArrayUtils.isSameType(null, new Object[0]));
+ assertThrows(IllegalArgumentException.class, () -> ArrayUtils.isSameType(new Object[0], null));
+
+ assertTrue(ArrayUtils.isSameType(new Object[0], new Object[0]));
+ assertFalse(ArrayUtils.isSameType(new String[0], new Object[0]));
+ assertTrue(ArrayUtils.isSameType(new String[0][0], new String[0][0]));
+ assertFalse(ArrayUtils.isSameType(new String[0], new String[0][0]));
+ assertFalse(ArrayUtils.isSameType(new String[0][0], new String[0]));
+ }
+
+ @Test
+ public void testShiftAllByte() {
+ final byte[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, -4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftAllChar() {
+ final char[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, -4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftAllDouble() {
+ final double[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, -4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftAllFloat() {
+ final float[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, -4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftAllInt() {
+ final int[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, -4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftAllLong() {
+ final long[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, -4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftAllObject() {
+ final String[] array = {"1", "2", "3", "4"};
+ ArrayUtils.shift(array, 4);
+ assertEquals("1", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("3", array[2]);
+ assertEquals("4", array[3]);
+ ArrayUtils.shift(array, -4);
+ assertEquals("1", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("3", array[2]);
+ assertEquals("4", array[3]);
+ }
+
+ @Test
+ public void testShiftAllShort() {
+ final short[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, -4);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftBoolean() {
+ final boolean[] array = {true, true, false, false};
+
+ ArrayUtils.shift(array, 1);
+ assertFalse(array[0]);
+ assertTrue(array[1]);
+ assertTrue(array[2]);
+ assertFalse(array[3]);
+
+ ArrayUtils.shift(array, -1);
+ assertTrue(array[0]);
+ assertTrue(array[1]);
+ assertFalse(array[2]);
+ assertFalse(array[3]);
+
+ ArrayUtils.shift(array, 5);
+ assertFalse(array[0]);
+ assertTrue(array[1]);
+ assertTrue(array[2]);
+ assertFalse(array[3]);
+
+ ArrayUtils.shift(array, -3);
+ assertFalse(array[0]);
+ assertFalse(array[1]);
+ assertTrue(array[2]);
+ assertTrue(array[3]);
+ }
+
+ @Test
+ public void testShiftByte() {
+ final byte[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, 5);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -3);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+ }
+
+ @Test
+ public void testShiftChar() {
+ final char[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, 5);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -3);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+ }
+
+ @Test
+ public void testShiftDouble() {
+ final double[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, 5);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -3);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+ }
+
+ @Test
+ public void testShiftFloat() {
+ final float[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, 5);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -3);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+ }
+
+
+ @Test
+ public void testShiftInt() {
+ final int[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, 5);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -3);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+ }
+
+ @Test
+ public void testShiftLong() {
+ final long[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, 5);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -3);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+ }
+
+ @Test
+ public void testShiftNullBoolean() {
+ final boolean[] array = null;
+
+ ArrayUtils.shift(array, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftNullDouble() {
+ final double[] array = null;
+
+ ArrayUtils.shift(array, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftNullFloat() {
+ final float[] array = null;
+
+ ArrayUtils.shift(array, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftNullInt() {
+ final int[] array = null;
+
+ ArrayUtils.shift(array, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftNullLong() {
+ final long[] array = null;
+
+ ArrayUtils.shift(array, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftNullObject() {
+ final String[] array = null;
+
+ ArrayUtils.shift(array, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftNullShort() {
+ final short[] array = null;
+
+ ArrayUtils.shift(array, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftObject() {
+ final String[] array = {"1", "2", "3", "4"};
+ ArrayUtils.shift(array, 1);
+ assertEquals("4", array[0]);
+ assertEquals("1", array[1]);
+ assertEquals("2", array[2]);
+ assertEquals("3", array[3]);
+ ArrayUtils.shift(array, -1);
+ assertEquals("1", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("3", array[2]);
+ assertEquals("4", array[3]);
+ ArrayUtils.shift(array, 5);
+ assertEquals("4", array[0]);
+ assertEquals("1", array[1]);
+ assertEquals("2", array[2]);
+ assertEquals("3", array[3]);
+ ArrayUtils.shift(array, -3);
+ assertEquals("3", array[0]);
+ assertEquals("4", array[1]);
+ assertEquals("1", array[2]);
+ assertEquals("2", array[3]);
+ }
+
+ @Test
+ public void testShiftRangeByte() {
+ final byte[] array = {1, 2, 3, 4, 5};
+ ArrayUtils.shift(array, 1, 3, 1);
+ assertEquals(1, array[0]);
+ assertEquals(3, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(4, array[3]);
+ assertEquals(5, array[4]);
+ ArrayUtils.shift(array, 1, 4, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(4, array[2]);
+ assertEquals(3, array[3]);
+ assertEquals(5, array[4]);
+ }
+
+ @Test
+ public void testShiftRangeChar() {
+ final char[] array = {1, 2, 3, 4, 5};
+ ArrayUtils.shift(array, 1, 3, 1);
+ assertEquals(1, array[0]);
+ assertEquals(3, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(4, array[3]);
+ assertEquals(5, array[4]);
+ ArrayUtils.shift(array, 1, 4, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(4, array[2]);
+ assertEquals(3, array[3]);
+ assertEquals(5, array[4]);
+ }
+
+ @Test
+ public void testShiftRangeDouble() {
+ final double[] array = {1, 2, 3, 4, 5};
+ ArrayUtils.shift(array, 1, 3, 1);
+ assertEquals(1, array[0]);
+ assertEquals(3, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(4, array[3]);
+ assertEquals(5, array[4]);
+ ArrayUtils.shift(array, 1, 4, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(4, array[2]);
+ assertEquals(3, array[3]);
+ assertEquals(5, array[4]);
+ }
+
+ @Test
+ public void testShiftRangeFloat() {
+ final float[] array = {1, 2, 3, 4, 5};
+ ArrayUtils.shift(array, 1, 3, 1);
+ assertEquals(1, array[0]);
+ assertEquals(3, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(4, array[3]);
+ assertEquals(5, array[4]);
+ ArrayUtils.shift(array, 1, 4, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(4, array[2]);
+ assertEquals(3, array[3]);
+ assertEquals(5, array[4]);
+ }
+
+ @Test
+ public void testShiftRangeInt() {
+ final int[] array = {1, 2, 3, 4, 5};
+ ArrayUtils.shift(array, 1, 3, 1);
+ assertEquals(1, array[0]);
+ assertEquals(3, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(4, array[3]);
+ assertEquals(5, array[4]);
+ ArrayUtils.shift(array, 1, 4, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(4, array[2]);
+ assertEquals(3, array[3]);
+ assertEquals(5, array[4]);
+ }
+
+ @Test
+ public void testShiftRangeLong() {
+ final long[] array = {1, 2, 3, 4, 5};
+ ArrayUtils.shift(array, 1, 3, 1);
+ assertEquals(1, array[0]);
+ assertEquals(3, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(4, array[3]);
+ assertEquals(5, array[4]);
+ ArrayUtils.shift(array, 1, 4, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(4, array[2]);
+ assertEquals(3, array[3]);
+ assertEquals(5, array[4]);
+ }
+
+ @Test
+ public void testShiftRangeNoElemByte() {
+ final byte[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftRangeNoElemChar() {
+ final char[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftRangeNoElemDouble() {
+ final double[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftRangeNoElemFloat() {
+ final float[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftRangeNoElemInt() {
+ final int[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftRangeNoElemLong() {
+ final long[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftRangeNoElemObject() {
+ final String[] array = {"1", "2", "3", "4"};
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertEquals("1", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("3", array[2]);
+ assertEquals("4", array[3]);
+ }
+
+ @Test
+ public void testShiftRangeNoElemShort() {
+ final short[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ }
+
+ @Test
+ public void testShiftRangeNullByte() {
+ final byte[] array = null;
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftRangeNullChar() {
+ final char[] array = null;
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftRangeNullDouble() {
+ final double[] array = null;
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftRangeNullFloat() {
+ final float[] array = null;
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftRangeNullInt() {
+ final int[] array = null;
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftRangeNullLong() {
+ final long[] array = null;
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftRangeNullObject() {
+ final String[] array = null;
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftRangeNullShort() {
+ final short[] array = null;
+
+ ArrayUtils.shift(array, 1, 1, 1);
+ assertNull(array);
+ }
+
+ @Test
+ public void testShiftRangeObject() {
+ final String[] array = {"1", "2", "3", "4", "5"};
+ ArrayUtils.shift(array, 1, 3, 1);
+ assertEquals("1", array[0]);
+ assertEquals("3", array[1]);
+ assertEquals("2", array[2]);
+ assertEquals("4", array[3]);
+ assertEquals("5", array[4]);
+ ArrayUtils.shift(array, 1, 4, 2);
+ assertEquals("1", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("4", array[2]);
+ assertEquals("3", array[3]);
+ assertEquals("5", array[4]);
+ }
+
+ @Test
+ public void testShiftRangeShort() {
+ final short[] array = {1, 2, 3, 4, 5};
+ ArrayUtils.shift(array, 1, 3, 1);
+ assertEquals(1, array[0]);
+ assertEquals(3, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(4, array[3]);
+ assertEquals(5, array[4]);
+ ArrayUtils.shift(array, 1, 4, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(4, array[2]);
+ assertEquals(3, array[3]);
+ assertEquals(5, array[4]);
+ }
+
+ @Test
+ public void testShiftShort() {
+ short[] array = {1, 2, 3, 4};
+ ArrayUtils.shift(array, 1);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -1);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ assertEquals(4, array[3]);
+ ArrayUtils.shift(array, 5);
+ assertEquals(4, array[0]);
+ assertEquals(1, array[1]);
+ assertEquals(2, array[2]);
+ assertEquals(3, array[3]);
+ ArrayUtils.shift(array, -3);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+ array = new short[]{1, 2, 3, 4, 5};
+ ArrayUtils.shift(array, 2);
+ assertEquals(4, array[0]);
+ assertEquals(5, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+ assertEquals(3, array[4]);
+ }
+
+ @Test
+ public void testShuffle() {
+ final String[] array1 = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"};
+ final String[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ for (final String element : array2) {
+ assertTrue(ArrayUtils.contains(array1, element), "Element " + element + " not found");
+ }
+ }
+
+ @Test
+ public void testShuffleBoolean() {
+ final boolean[] array1 = {true, false, true, true, false, false, true, false, false, true};
+ final boolean[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ assertEquals(5, ArrayUtils.removeAllOccurrences(array1, true).length);
+ }
+
+ @Test
+ public void testShuffleByte() {
+ final byte[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ final byte[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ for (final byte element : array2) {
+ assertTrue(ArrayUtils.contains(array1, element), "Element " + element + " not found");
+ }
+ }
+
+ @Test
+ public void testShuffleChar() {
+ final char[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ final char[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ for (final char element : array2) {
+ assertTrue(ArrayUtils.contains(array1, element), "Element " + element + " not found");
+ }
+ }
+
+ @Test
+ public void testShuffleDouble() {
+ final double[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ final double[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ for (final double element : array2) {
+ assertTrue(ArrayUtils.contains(array1, element), "Element " + element + " not found");
+ }
+ }
+
+ @Test
+ public void testShuffleFloat() {
+ final float[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ final float[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ for (final float element : array2) {
+ assertTrue(ArrayUtils.contains(array1, element), "Element " + element + " not found");
+ }
+ }
+
+ @Test
+ public void testShuffleInt() {
+ final int[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ final int[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ for (final int element : array2) {
+ assertTrue(ArrayUtils.contains(array1, element), "Element " + element + " not found");
+ }
+ }
+
+ @Test
+ public void testShuffleLong() {
+ final long[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ final long[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ for (final long element : array2) {
+ assertTrue(ArrayUtils.contains(array1, element), "Element " + element + " not found");
+ }
+ }
+
+ @Test
+ public void testShuffleShort() {
+ final short[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ final short[] array2 = ArrayUtils.clone(array1);
+
+ ArrayUtils.shuffle(array1, new Random(SEED));
+ assertFalse(Arrays.equals(array1, array2));
+ for (final short element : array2) {
+ assertTrue(ArrayUtils.contains(array1, element), "Element " + element + " not found");
+ }
+ }
+
+ @Test
+ public void testSubarrayBoolean() {
+ final boolean[] nullArray = null;
+ final boolean[] array = {true, true, false, true, false, true};
+ final boolean[] leftSubarray = {true, true, false, true};
+ final boolean[] midSubarray = {true, false, true, false};
+ final boolean[] rightSubarray = {false, true, false, true};
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, 0, 4)), "0 start, mid end");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, 0, array.length)), "0 start, length end");
+ assertTrue(ArrayUtils.isEquals(midSubarray, ArrayUtils.subarray(array, 1, 5)), "mid start, mid end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, array.length)),
+ "mid start, length end");
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+ assertEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_BOOLEAN_ARRAY, 1, 2),
+ "empty array");
+ assertEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.subarray(array, 4, 2), "start > end");
+ assertEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end");
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, -2, 4)),
+ "start undershoot, normal end");
+ assertEquals(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.subarray(array, 33, 4), "start overshoot, any end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, 33)),
+ "normal start, end overshoot");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, -2, 12)), "start undershoot, end overshoot");
+
+ // empty-return tests
+
+ assertSame(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_BOOLEAN_ARRAY, 1, 2),
+ "empty array, object test");
+ assertSame(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.subarray(array, 4, 1), "start > end, object test");
+ assertSame(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end, object test");
+ assertSame(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.subarray(array, 8733, 4),
+ "start overshoot, any end, object test");
+
+ // array type tests
+
+ assertSame(boolean.class, ArrayUtils.subarray(array, 2, 4).getClass().getComponentType(), "boolean type");
+ }
+
+ @Test
+ public void testSubarrayByte() {
+ final byte[] nullArray = null;
+ final byte[] array = {10, 11, 12, 13, 14, 15};
+ final byte[] leftSubarray = {10, 11, 12, 13};
+ final byte[] midSubarray = {11, 12, 13, 14};
+ final byte[] rightSubarray = {12, 13, 14, 15};
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, 0, 4)), "0 start, mid end");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, 0, array.length)), "0 start, length end");
+ assertTrue(ArrayUtils.isEquals(midSubarray, ArrayUtils.subarray(array, 1, 5)), "mid start, mid end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, array.length)),
+ "mid start, length end");
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+ assertEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_BYTE_ARRAY, 1, 2),
+ "empty array");
+ assertEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.subarray(array, 4, 2), "start > end");
+ assertEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end");
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, -2, 4)),
+ "start undershoot, normal end");
+ assertEquals(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.subarray(array, 33, 4), "start overshoot, any end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, 33)),
+ "normal start, end overshoot");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, -2, 12)), "start undershoot, end overshoot");
+
+ // empty-return tests
+
+ assertSame(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_BYTE_ARRAY, 1, 2),
+ "empty array, object test");
+ assertSame(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.subarray(array, 4, 1), "start > end, object test");
+ assertSame(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end, object test");
+ assertSame(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.subarray(array, 8733, 4),
+ "start overshoot, any end, object test");
+
+ // array type tests
+
+ assertSame(byte.class, ArrayUtils.subarray(array, 2, 4).getClass().getComponentType(), "byte type");
+ }
+
+ @Test
+ public void testSubarrayDouble() {
+ final double[] nullArray = null;
+ final double[] array = {10.123, 11.234, 12.345, 13.456, 14.567, 15.678};
+ final double[] leftSubarray = {10.123, 11.234, 12.345, 13.456};
+ final double[] midSubarray = {11.234, 12.345, 13.456, 14.567};
+ final double[] rightSubarray = {12.345, 13.456, 14.567, 15.678};
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, 0, 4)), "0 start, mid end");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, 0, array.length)), "0 start, length end");
+ assertTrue(ArrayUtils.isEquals(midSubarray, ArrayUtils.subarray(array, 1, 5)), "mid start, mid end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, array.length)),
+ "mid start, length end");
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+ assertEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_DOUBLE_ARRAY, 1, 2),
+ "empty array");
+ assertEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.subarray(array, 4, 2), "start > end");
+ assertEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end");
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, -2, 4)),
+ "start undershoot, normal end");
+ assertEquals(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.subarray(array, 33, 4), "start overshoot, any end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, 33)),
+ "normal start, end overshoot");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, -2, 12)), "start undershoot, end overshoot");
+
+ // empty-return tests
+
+ assertSame(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_DOUBLE_ARRAY, 1, 2),
+ "empty array, object test");
+ assertSame(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.subarray(array, 4, 1), "start > end, object test");
+ assertSame(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end, object test");
+ assertSame(ArrayUtils.EMPTY_DOUBLE_ARRAY, ArrayUtils.subarray(array, 8733, 4),
+ "start overshoot, any end, object test");
+
+ // array type tests
+
+ assertSame(double.class, ArrayUtils.subarray(array, 2, 4).getClass().getComponentType(), "double type");
+ }
+
+ @Test
+ public void testSubarrayFloat() {
+ final float[] nullArray = null;
+ final float[] array = {10, 11, 12, 13, 14, 15};
+ final float[] leftSubarray = {10, 11, 12, 13};
+ final float[] midSubarray = {11, 12, 13, 14};
+ final float[] rightSubarray = {12, 13, 14, 15};
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, 0, 4)), "0 start, mid end");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, 0, array.length)), "0 start, length end");
+ assertTrue(ArrayUtils.isEquals(midSubarray, ArrayUtils.subarray(array, 1, 5)), "mid start, mid end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, array.length)),
+ "mid start, length end");
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+ assertEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_FLOAT_ARRAY, 1, 2),
+ "empty array");
+ assertEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.subarray(array, 4, 2), "start > end");
+ assertEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end");
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, -2, 4)),
+ "start undershoot, normal end");
+ assertEquals(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.subarray(array, 33, 4), "start overshoot, any end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, 33)),
+ "normal start, end overshoot");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, -2, 12)), "start undershoot, end overshoot");
+
+ // empty-return tests
+
+ assertSame(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_FLOAT_ARRAY, 1, 2),
+ "empty array, object test");
+ assertSame(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.subarray(array, 4, 1), "start > end, object test");
+ assertSame(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end, object test");
+ assertSame(ArrayUtils.EMPTY_FLOAT_ARRAY, ArrayUtils.subarray(array, 8733, 4),
+ "start overshoot, any end, object test");
+
+ // array type tests
+
+ assertSame(float.class, ArrayUtils.subarray(array, 2, 4).getClass().getComponentType(), "float type");
+ }
+
+ @Test
+ public void testSubarrayInt() {
+ final int[] nullArray = null;
+ final int[] array = {10, 11, 12, 13, 14, 15};
+ final int[] leftSubarray = {10, 11, 12, 13};
+ final int[] midSubarray = {11, 12, 13, 14};
+ final int[] rightSubarray = {12, 13, 14, 15};
+
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, 0, 4)), "0 start, mid end");
+
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, 0, array.length)), "0 start, length end");
+
+ assertTrue(ArrayUtils.isEquals(midSubarray, ArrayUtils.subarray(array, 1, 5)), "mid start, mid end");
+
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, array.length)),
+ "mid start, length end");
+
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+
+ assertEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_INT_ARRAY, 1, 2), "empty array");
+
+ assertEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.subarray(array, 4, 2), "start > end");
+
+ assertEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end");
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, -2, 4)),
+ "start undershoot, normal end");
+
+ assertEquals(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.subarray(array, 33, 4), "start overshoot, any end");
+
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, 33)),
+ "normal start, end overshoot");
+
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, -2, 12)), "start undershoot, end overshoot");
+
+ // empty-return tests
+
+ assertSame(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_INT_ARRAY, 1, 2),
+ "empty array, object test");
+
+ assertSame(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.subarray(array, 4, 1), "start > end, object test");
+
+ assertSame(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end, object test");
+
+ assertSame(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.subarray(array, 8733, 4),
+ "start overshoot, any end, object test");
+
+ // array type tests
+
+ assertSame(int.class, ArrayUtils.subarray(array, 2, 4).getClass().getComponentType(), "int type");
+ }
+
+ @Test
+ public void testSubarrayLong() {
+ final long[] nullArray = null;
+ final long[] array = {999910, 999911, 999912, 999913, 999914, 999915};
+ final long[] leftSubarray = {999910, 999911, 999912, 999913};
+ final long[] midSubarray = {999911, 999912, 999913, 999914};
+ final long[] rightSubarray = {999912, 999913, 999914, 999915};
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, 0, 4)), "0 start, mid end");
+
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, 0, array.length)), "0 start, length end");
+
+ assertTrue(ArrayUtils.isEquals(midSubarray, ArrayUtils.subarray(array, 1, 5)), "mid start, mid end");
+
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, array.length)),
+ "mid start, length end");
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+
+ assertEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_LONG_ARRAY, 1, 2),
+ "empty array");
+
+ assertEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.subarray(array, 4, 2), "start > end");
+
+ assertEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end");
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, -2, 4)),
+ "start undershoot, normal end");
+
+ assertEquals(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.subarray(array, 33, 4), "start overshoot, any end");
+
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, 33)),
+ "normal start, end overshoot");
+
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, -2, 12)), "start undershoot, end overshoot");
+
+ // empty-return tests
+
+ assertSame(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_LONG_ARRAY, 1, 2),
+ "empty array, object test");
+
+ assertSame(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.subarray(array, 4, 1), "start > end, object test");
+
+ assertSame(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end, object test");
+
+ assertSame(ArrayUtils.EMPTY_LONG_ARRAY, ArrayUtils.subarray(array, 8733, 4),
+ "start overshoot, any end, object test");
+
+ // array type tests
+
+ assertSame(long.class, ArrayUtils.subarray(array, 2, 4).getClass().getComponentType(), "long type");
+
+ }
+
+ @Test
+ public void testSubarrayObject() {
+ final Object[] nullArray = null;
+ final Object[] objectArray = {"a", "b", "c", "d", "e", "f"};
+
+ assertEquals("abcd", StringUtils.join(ArrayUtils.subarray(objectArray, 0, 4)), "0 start, mid end");
+ assertEquals("abcdef", StringUtils.join(ArrayUtils.subarray(objectArray, 0, objectArray.length)),
+ "0 start, length end");
+ assertEquals("bcd", StringUtils.join(ArrayUtils.subarray(objectArray, 1, 4)), "mid start, mid end");
+ assertEquals("bcdef", StringUtils.join(ArrayUtils.subarray(objectArray, 1, objectArray.length)),
+ "mid start, length end");
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+ assertEquals("", StringUtils.join(ArrayUtils.subarray(ArrayUtils.EMPTY_OBJECT_ARRAY, 1, 2)), "empty array");
+ assertEquals("", StringUtils.join(ArrayUtils.subarray(objectArray, 4, 2)), "start > end");
+ assertEquals("", StringUtils.join(ArrayUtils.subarray(objectArray, 3, 3)), "start == end");
+ assertEquals("abcd", StringUtils.join(ArrayUtils.subarray(objectArray, -2, 4)), "start undershoot, normal end");
+ assertEquals("", StringUtils.join(ArrayUtils.subarray(objectArray, 33, 4)), "start overshoot, any end");
+ assertEquals("cdef", StringUtils.join(ArrayUtils.subarray(objectArray, 2, 33)), "normal start, end overshoot");
+ assertEquals("abcdef", StringUtils.join(ArrayUtils.subarray(objectArray, -2, 12)),
+ "start undershoot, end overshoot");
+
+ // array type tests
+ final Date[] dateArray = {new java.sql.Date(new Date().getTime()),
+ new Date(), new Date(), new Date(), new Date()};
+
+ assertSame(Object.class, ArrayUtils.subarray(objectArray, 2, 4).getClass().getComponentType(), "Object type");
+ assertSame(Date.class, ArrayUtils.subarray(dateArray, 1, 4).getClass().getComponentType(),
+ "java.util.Date type");
+ assertNotSame(java.sql.Date.class, ArrayUtils.subarray(dateArray, 1, 4).getClass().getComponentType(),
+ "java.sql.Date type");
+ assertThrows(ClassCastException.class,
+ () -> java.sql.Date[].class.cast(ArrayUtils.subarray(dateArray, 1, 3)),
+ "Invalid downcast");
+ }
+
+ @Test
+ public void testSubarrayShort() {
+ final short[] nullArray = null;
+ final short[] array = {10, 11, 12, 13, 14, 15};
+ final short[] leftSubarray = {10, 11, 12, 13};
+ final short[] midSubarray = {11, 12, 13, 14};
+ final short[] rightSubarray = {12, 13, 14, 15};
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, 0, 4)), "0 start, mid end");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, 0, array.length)), "0 start, length end");
+ assertTrue(ArrayUtils.isEquals(midSubarray, ArrayUtils.subarray(array, 1, 5)), "mid start, mid end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, array.length)),
+ "mid start, length end");
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+ assertEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_SHORT_ARRAY, 1, 2),
+ "empty array");
+ assertEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.subarray(array, 4, 2), "start > end");
+ assertEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end");
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, -2, 4)),
+ "start undershoot, normal end");
+ assertEquals(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.subarray(array, 33, 4), "start overshoot, any end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, 33)),
+ "normal start, end overshoot");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, -2, 12)), "start undershoot, end overshoot");
+
+ // empty-return tests
+
+ assertSame(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_SHORT_ARRAY, 1, 2),
+ "empty array, object test");
+ assertSame(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.subarray(array, 4, 1), "start > end, object test");
+ assertSame(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end, object test");
+ assertSame(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.subarray(array, 8733, 4),
+ "start overshoot, any end, object test");
+
+ // array type tests
+
+ assertSame(short.class, ArrayUtils.subarray(array, 2, 4).getClass().getComponentType(), "short type");
+ }
+
+ @Test
+ public void testSubarrChar() {
+ final char[] nullArray = null;
+ final char[] array = {'a', 'b', 'c', 'd', 'e', 'f'};
+ final char[] leftSubarray = {'a', 'b', 'c', 'd'};
+ final char[] midSubarray = {'b', 'c', 'd', 'e'};
+ final char[] rightSubarray = {'c', 'd', 'e', 'f'};
+
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, 0, 4)), "0 start, mid end");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, 0, array.length)), "0 start, length end");
+ assertTrue(ArrayUtils.isEquals(midSubarray, ArrayUtils.subarray(array, 1, 5)), "mid start, mid end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, array.length)),
+ "mid start, length end");
+
+ assertNull(ArrayUtils.subarray(nullArray, 0, 3), "null input");
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_CHAR_ARRAY, 1, 2),
+ "empty array");
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.subarray(array, 4, 2), "start > end");
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end");
+ assertTrue(ArrayUtils.isEquals(leftSubarray, ArrayUtils.subarray(array, -2, 4)),
+ "start undershoot, normal end");
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.subarray(array, 33, 4), "start overshoot, any end");
+ assertTrue(ArrayUtils.isEquals(rightSubarray, ArrayUtils.subarray(array, 2, 33)),
+ "normal start, end overshoot");
+ assertTrue(ArrayUtils.isEquals(array, ArrayUtils.subarray(array, -2, 12)), "start undershoot, end overshoot");
+
+ // empty-return tests
+
+ assertSame(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.subarray(ArrayUtils.EMPTY_CHAR_ARRAY, 1, 2),
+ "empty array, object test");
+ assertSame(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.subarray(array, 4, 1), "start > end, object test");
+ assertSame(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.subarray(array, 3, 3), "start == end, object test");
+ assertSame(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.subarray(array, 8733, 4),
+ "start overshoot, any end, object test");
+
+ // array type tests
+
+ assertSame(char.class, ArrayUtils.subarray(array, 2, 4).getClass().getComponentType(), "char type");
+ }
+
+ @Test
+ public void testSwapBoolean() {
+ final boolean[] array = {true, false, false};
+ ArrayUtils.swap(array, 0, 2);
+ assertFalse(array[0]);
+ assertFalse(array[1]);
+ assertTrue(array[2]);
+ }
+
+ @Test
+ public void testSwapBooleanRange() {
+ boolean[] array = {false, false, true, true};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertTrue(array[0]);
+ assertTrue(array[1]);
+ assertFalse(array[2]);
+ assertFalse(array[3]);
+
+ array = new boolean[]{false, true, false};
+ ArrayUtils.swap(array, 0, 3);
+ assertFalse(array[0]);
+ assertTrue(array[1]);
+ assertFalse(array[2]);
+
+ array = new boolean[]{true, true, false};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertFalse(array[0]);
+ assertTrue(array[1]);
+ assertTrue(array[2]);
+
+ array = new boolean[]{true, true, false};
+ ArrayUtils.swap(array, -1, 2, 2);
+ assertFalse(array[0]);
+ assertTrue(array[1]);
+ assertTrue(array[2]);
+
+ array = new boolean[]{true, true, false};
+ ArrayUtils.swap(array, 0, -1, 2);
+ assertTrue(array[0]);
+ assertTrue(array[1]);
+ assertFalse(array[2]);
+
+ array = new boolean[]{true, true, false};
+ ArrayUtils.swap(array, -1, -1, 2);
+ assertTrue(array[0]);
+ assertTrue(array[1]);
+ assertFalse(array[2]);
+ }
+
+ @Test
+ public void testSwapByte() {
+ final byte[] array = {1, 2, 3};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ }
+
+ @Test
+ public void testSwapByteRange() {
+ byte[] array = {1, 2, 3, 4};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+
+ array = new byte[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 3);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new byte[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new byte[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new byte[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new byte[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ }
+
+ @Test
+ public void testSwapChar() {
+ char[] array = {1, 2, 3};
+ ArrayUtils.swap(array, 0, 2);
+ assertArrayEquals(new char[]{3, 2, 1}, array);
+
+ array = new char[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 0);
+ assertArrayEquals(new char[]{1, 2, 3}, array);
+
+ array = new char[]{1, 2, 3};
+ ArrayUtils.swap(array, 1, 0);
+ assertArrayEquals(new char[]{2, 1, 3}, array);
+ }
+
+ @Test
+ public void testSwapCharRange() {
+ char[] array = {1, 2, 3, 4};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+
+ array = new char[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 3);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new char[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new char[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new char[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new char[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ }
+
+ @Test
+ public void testSwapDouble() {
+ final double[] array = {1, 2, 3};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ }
+
+ @Test
+ public void testSwapDoubleRange() {
+ double[] array = {1, 2, 3, 4};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+
+ array = new double[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 3);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new double[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new double[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new double[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new double[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ }
+
+ @Test
+ public void testSwapEmptyBooleanArray() {
+ final boolean[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapEmptyByteArray() {
+ final byte[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapEmptyCharArray() {
+ final char[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapEmptyDoubleArray() {
+ final double[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapEmptyFloatArray() {
+ final float[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapEmptyIntArray() {
+ final int[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapEmptyLongArray() {
+ final long[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapEmptyObjectArray() {
+ final String[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapEmptyShortArray() {
+ final short[] array = {};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(0, array.length);
+ }
+
+ @Test
+ public void testSwapFloat() {
+ final float[] array = {1, 2, 3};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ }
+
+ @Test
+ public void testSwapFloatRange() {
+ float[] array = {1, 2, 3, 4};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+
+ array = new float[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 3);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new float[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new float[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new float[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new float[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ }
+
+ @Test
+ public void testSwapInt() {
+ final int[] array = {1, 2, 3};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ }
+
+ @Test
+ public void testSwapIntExchangedOffsets() {
+ int[] array;
+ array = new int[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 1, 2);
+ assertArrayEquals(new int[]{2, 3, 1}, array);
+
+ array = new int[]{1, 2, 3};
+ ArrayUtils.swap(array, 1, 0, 2);
+ assertArrayEquals(new int[]{2, 3, 1}, array);
+ }
+
+ @Test
+ public void testSwapIntRange() {
+ int[] array = {1, 2, 3, 4};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+
+ array = new int[]{1, 2, 3};
+ ArrayUtils.swap(array, 3, 0);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new int[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new int[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new int[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new int[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ }
+
+ @Test
+ public void testSwapLong() {
+ final long[] array = {1, 2, 3};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ }
+
+ @Test
+ public void testSwapLongRange() {
+ long[] array = {1, 2, 3, 4};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+
+ array = new long[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 3);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new long[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new long[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new long[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new long[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ }
+
+ @Test
+ public void testSwapNullBooleanArray() {
+ final boolean[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapNullByteArray() {
+ final byte[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapNullCharArray() {
+ final char[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapNullDoubleArray() {
+ final double[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapNullFloatArray() {
+ final float[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapNullIntArray() {
+ final int[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapNullLongArray() {
+ final long[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapNullObjectArray() {
+ final String[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapNullShortArray() {
+ final short[] array = null;
+ ArrayUtils.swap(array, 0, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapObject() {
+ final String[] array = {"1", "2", "3"};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals("3", array[0]);
+ assertEquals("2", array[1]);
+ assertEquals("1", array[2]);
+ }
+
+ @Test
+ public void testSwapObjectRange() {
+ String[] array = {"1", "2", "3", "4"};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals("3", array[0]);
+ assertEquals("4", array[1]);
+ assertEquals("1", array[2]);
+ assertEquals("2", array[3]);
+
+ array = new String[]{"1", "2", "3", "4"};
+ ArrayUtils.swap(array, -1, 2, 3);
+ assertEquals("3", array[0]);
+ assertEquals("4", array[1]);
+ assertEquals("1", array[2]);
+ assertEquals("2", array[3]);
+
+ array = new String[]{"1", "2", "3", "4", "5"};
+ ArrayUtils.swap(array, -3, 2, 3);
+ assertEquals("3", array[0]);
+ assertEquals("4", array[1]);
+ assertEquals("5", array[2]);
+ assertEquals("2", array[3]);
+ assertEquals("1", array[4]);
+
+ array = new String[]{"1", "2", "3", "4", "5"};
+ ArrayUtils.swap(array, 2, -2, 3);
+ assertEquals("3", array[0]);
+ assertEquals("4", array[1]);
+ assertEquals("5", array[2]);
+ assertEquals("2", array[3]);
+ assertEquals("1", array[4]);
+
+ array = new String[0];
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(0, array.length);
+
+ array = null;
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertNull(array);
+ }
+
+ @Test
+ public void testSwapShort() {
+ final short[] array = {1, 2, 3};
+ ArrayUtils.swap(array, 0, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+ }
+
+ @Test
+ public void testSwapShortRange() {
+ short[] array = {1, 2, 3, 4};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(4, array[1]);
+ assertEquals(1, array[2]);
+ assertEquals(2, array[3]);
+
+ array = new short[]{1, 2, 3};
+ ArrayUtils.swap(array, 3, 0);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new short[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new short[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, 2, 2);
+ assertEquals(3, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(1, array[2]);
+
+ array = new short[]{1, 2, 3};
+ ArrayUtils.swap(array, 0, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+
+ array = new short[]{1, 2, 3};
+ ArrayUtils.swap(array, -1, -1, 2);
+ assertEquals(1, array[0]);
+ assertEquals(2, array[1]);
+ assertEquals(3, array[2]);
+ }
+
+ @Test
+ public void testToMap() {
+ Map<?, ?> map = ArrayUtils.toMap(new String[][]{{"foo", "bar"}, {"hello", "world"}});
+
+ assertEquals("bar", map.get("foo"));
+ assertEquals("world", map.get("hello"));
+
+ assertNull(ArrayUtils.toMap(null));
+ assertThrows(IllegalArgumentException.class, () ->
+ ArrayUtils.toMap(new String[][]{{"foo", "bar"}, {"short"}}));
+ assertThrows(IllegalArgumentException.class, () ->
+ ArrayUtils.toMap(new Object[]{new Object[]{"foo", "bar"}, "illegal type"}));
+ assertThrows(IllegalArgumentException.class, () ->
+ ArrayUtils.toMap(new Object[]{new Object[]{"foo", "bar"}, null}));
+
+ map = ArrayUtils.toMap(new Object[]{new Map.Entry<Object, Object>() {
+ @Override
+ public boolean equals(final Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getKey() {
+ return "foo";
+ }
+
+ @Override
+ public Object getValue() {
+ return "bar";
+ }
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object setValue(final Object value) {
+ throw new UnsupportedOperationException();
+ }
+ }});
+ assertEquals("bar", map.get("foo"));
+
+ // Return empty map when got input array with length = 0
+ assertEquals(Collections.emptyMap(), ArrayUtils.toMap(new Object[0]));
+
+ // Test all null values
+ map = ArrayUtils.toMap(new Object[][] { {null, null}, {null, null} });
+ assertEquals(Collections.singletonMap(null, null), map);
+
+ // Test duplicate keys
+ map = ArrayUtils.toMap(new Object[][] { {"key", "value2"}, {"key", "value1"} });
+ assertEquals(Collections.singletonMap("key", "value1"), map);
+ }
+
+ @Test
+ public void testToObject_boolean() {
+ final boolean[] b = null;
+ assertArrayEquals(null, ArrayUtils.toObject(b));
+ assertSame(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY, ArrayUtils.toObject(new boolean[0]));
+ assertArrayEquals(new Boolean[]{Boolean.TRUE, Boolean.FALSE, Boolean.TRUE}, ArrayUtils.toObject(new boolean[]{true, false, true}));
+ }
+
+ @Test
+ public void testToObject_byte() {
+ final byte[] b = null;
+ assertArrayEquals(null, ArrayUtils.toObject(b));
+
+ assertSame(ArrayUtils.EMPTY_BYTE_OBJECT_ARRAY,
+ ArrayUtils.toObject(new byte[0]));
+
+ assertArrayEquals(new Byte[]{Byte.valueOf(Byte.MIN_VALUE),
+ Byte.valueOf(Byte.MAX_VALUE), Byte.valueOf((byte) 9999999)}, ArrayUtils.toObject(new byte[]{Byte.MIN_VALUE, Byte.MAX_VALUE,
+ (byte) 9999999}));
+ }
+
+ @Test
+ public void testToObject_char() {
+ final char[] b = null;
+ assertArrayEquals(null, ArrayUtils.toObject(b));
+
+ assertSame(ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY,
+ ArrayUtils.toObject(new char[0]));
+
+ assertArrayEquals(new Character[]{Character.valueOf(Character.MIN_VALUE),
+ Character.valueOf(Character.MAX_VALUE), Character.valueOf('0')}, ArrayUtils.toObject(new char[]{Character.MIN_VALUE, Character.MAX_VALUE,
+ '0'}));
+ }
+
+ @Test
+ public void testToObject_double() {
+ final double[] b = null;
+ assertArrayEquals(null, ArrayUtils.toObject(b));
+
+ assertSame(
+ ArrayUtils.EMPTY_DOUBLE_OBJECT_ARRAY,
+ ArrayUtils.toObject(new double[0]));
+
+ assertArrayEquals(new Double[]{
+ Double.valueOf(Double.MIN_VALUE),
+ Double.valueOf(Double.MAX_VALUE),
+ Double.valueOf(9999999)}, ArrayUtils.toObject(
+ new double[]{Double.MIN_VALUE, Double.MAX_VALUE, 9999999}));
+ }
+
+ @Test
+ public void testToObject_float() {
+ final float[] b = null;
+ assertArrayEquals(null, ArrayUtils.toObject(b));
+
+ assertSame(
+ ArrayUtils.EMPTY_FLOAT_OBJECT_ARRAY,
+ ArrayUtils.toObject(new float[0]));
+
+ assertArrayEquals(new Float[]{
+ Float.valueOf(Float.MIN_VALUE),
+ Float.valueOf(Float.MAX_VALUE),
+ Float.valueOf(9999999)}, ArrayUtils.toObject(
+ new float[]{Float.MIN_VALUE, Float.MAX_VALUE, 9999999}));
+ }
+
+ @Test
+ public void testToObject_int() {
+ final int[] b = null;
+ assertArrayEquals(null, ArrayUtils.toObject(b));
+
+ assertSame(
+ ArrayUtils.EMPTY_INTEGER_OBJECT_ARRAY,
+ ArrayUtils.toObject(new int[0]));
+
+ assertArrayEquals(new Integer[]{
+ Integer.valueOf(Integer.MIN_VALUE),
+ Integer.valueOf(Integer.MAX_VALUE),
+ Integer.valueOf(9999999)}, ArrayUtils.toObject(
+ new int[]{Integer.MIN_VALUE, Integer.MAX_VALUE, 9999999}));
+ }
+
+ @Test
+ public void testToObject_long() {
+ final long[] b = null;
+ assertArrayEquals(null, ArrayUtils.toObject(b));
+
+ assertSame(
+ ArrayUtils.EMPTY_LONG_OBJECT_ARRAY,
+ ArrayUtils.toObject(new long[0]));
+
+ assertArrayEquals(new Long[]{
+ Long.valueOf(Long.MIN_VALUE),
+ Long.valueOf(Long.MAX_VALUE),
+ Long.valueOf(9999999)}, ArrayUtils.toObject(
+ new long[]{Long.MIN_VALUE, Long.MAX_VALUE, 9999999}));
+ }
+
+ @Test
+ public void testToObject_short() {
+ final short[] b = null;
+ assertArrayEquals(null, ArrayUtils.toObject(b));
+
+ assertSame(ArrayUtils.EMPTY_SHORT_OBJECT_ARRAY,
+ ArrayUtils.toObject(new short[0]));
+
+ assertArrayEquals(new Short[]{Short.valueOf(Short.MIN_VALUE), Short.valueOf(Short.MAX_VALUE),
+ Short.valueOf((short) 9999999)}, ArrayUtils.toObject(new short[]{Short.MIN_VALUE, Short.MAX_VALUE,
+ (short) 9999999}));
+ }
+
+ /** testToPrimitive/Object for boolean */
+ @Test
+ public void testToPrimitive_boolean() {
+ final Boolean[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b));
+ assertSame(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.toPrimitive(new Boolean[0]));
+ assertArrayEquals(new boolean[]{true, false, true}, ArrayUtils.toPrimitive(new Boolean[]{Boolean.TRUE, Boolean.FALSE, Boolean.TRUE}));
+ assertArrayEquals(new boolean[]{true, false}, ArrayUtils.toPrimitive(new Boolean[]{Boolean.TRUE, null}));
+ }
+
+ @Test
+ public void testToPrimitive_boolean_boolean() {
+ assertNull(ArrayUtils.toPrimitive(null, false));
+ assertSame(ArrayUtils.EMPTY_BOOLEAN_ARRAY, ArrayUtils.toPrimitive(new Boolean[0], false));
+ assertArrayEquals(new boolean[]{true, false, true}, ArrayUtils.toPrimitive(new Boolean[]{Boolean.TRUE, Boolean.FALSE, Boolean.TRUE}, false));
+ assertArrayEquals(new boolean[]{true, false, false}, ArrayUtils.toPrimitive(new Boolean[]{Boolean.TRUE, null, Boolean.FALSE}, false));
+ assertArrayEquals(new boolean[]{true, true, false}, ArrayUtils.toPrimitive(new Boolean[]{Boolean.TRUE, null, Boolean.FALSE}, true));
+ }
+
+ /** testToPrimitive/Object for byte */
+ @Test
+ public void testToPrimitive_byte() {
+ final Byte[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b));
+
+ assertSame(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.toPrimitive(new Byte[0]));
+
+ assertArrayEquals(new byte[]{Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 9999999}, ArrayUtils.toPrimitive(new Byte[]{Byte.valueOf(Byte.MIN_VALUE),
+ Byte.valueOf(Byte.MAX_VALUE), Byte.valueOf((byte) 9999999)}));
+
+ assertThrows(NullPointerException.class,
+ () -> ArrayUtils.toPrimitive(new Byte[]{Byte.valueOf(Byte.MIN_VALUE), null}));
+ }
+
+ @Test
+ public void testToPrimitive_byte_byte() {
+ final Byte[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b, Byte.MIN_VALUE));
+
+ assertSame(ArrayUtils.EMPTY_BYTE_ARRAY,
+ ArrayUtils.toPrimitive(new Byte[0], (byte) 1));
+
+ assertArrayEquals(new byte[]{Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 9999999}, ArrayUtils.toPrimitive(new Byte[]{Byte.valueOf(Byte.MIN_VALUE),
+ Byte.valueOf(Byte.MAX_VALUE), Byte.valueOf((byte) 9999999)},
+ Byte.MIN_VALUE));
+
+ assertArrayEquals(new byte[]{Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 9999999}, ArrayUtils.toPrimitive(new Byte[]{Byte.valueOf(Byte.MIN_VALUE), null,
+ Byte.valueOf((byte) 9999999)}, Byte.MAX_VALUE));
+ }
+
+ /** testToPrimitive/Object for byte */
+ @Test
+ public void testToPrimitive_char() {
+ final Character[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b));
+
+ assertSame(ArrayUtils.EMPTY_CHAR_ARRAY, ArrayUtils.toPrimitive(new Character[0]));
+
+ assertArrayEquals(new char[]{Character.MIN_VALUE, Character.MAX_VALUE, '0'}, ArrayUtils.toPrimitive(new Character[]{Character.valueOf(Character.MIN_VALUE),
+ Character.valueOf(Character.MAX_VALUE), Character.valueOf('0')}));
+
+ assertThrows(NullPointerException.class,
+ () -> ArrayUtils.toPrimitive(new Character[]{Character.valueOf(Character.MIN_VALUE), null}));
+ }
+
+ @Test
+ public void testToPrimitive_char_char() {
+ final Character[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b, Character.MIN_VALUE));
+
+ assertSame(ArrayUtils.EMPTY_CHAR_ARRAY,
+ ArrayUtils.toPrimitive(new Character[0], (char) 0));
+
+ assertArrayEquals(new char[]{Character.MIN_VALUE, Character.MAX_VALUE, '0'}, ArrayUtils.toPrimitive(new Character[]{Character.valueOf(Character.MIN_VALUE),
+ Character.valueOf(Character.MAX_VALUE), Character.valueOf('0')},
+ Character.MIN_VALUE));
+
+ assertArrayEquals(new char[]{Character.MIN_VALUE, Character.MAX_VALUE, '0'}, ArrayUtils.toPrimitive(new Character[]{Character.valueOf(Character.MIN_VALUE), null,
+ Character.valueOf('0')}, Character.MAX_VALUE));
+ }
+
+ /** testToPrimitive/Object for double */
+ @Test
+ public void testToPrimitive_double() {
+ final Double[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b));
+
+ assertSame(ArrayUtils.EMPTY_DOUBLE_ARRAY,
+ ArrayUtils.toPrimitive(new Double[0]));
+
+ assertArrayEquals(new double[]{Double.MIN_VALUE, Double.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Double[]{Double.valueOf(Double.MIN_VALUE),
+ Double.valueOf(Double.MAX_VALUE), Double.valueOf(9999999)}));
+
+ assertThrows(NullPointerException.class,
+ () -> ArrayUtils.toPrimitive(new Float[]{Float.valueOf(Float.MIN_VALUE), null}));
+ }
+
+ @Test
+ public void testToPrimitive_double_double() {
+ final Double[] l = null;
+ assertNull(ArrayUtils.toPrimitive(l, Double.MIN_VALUE));
+
+ assertSame(ArrayUtils.EMPTY_DOUBLE_ARRAY,
+ ArrayUtils.toPrimitive(new Double[0], 1));
+
+ assertArrayEquals(new double[]{Double.MIN_VALUE, Double.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Double[]{Double.valueOf(Double.MIN_VALUE),
+ Double.valueOf(Double.MAX_VALUE), Double.valueOf(9999999)}, 1));
+
+ assertArrayEquals(new double[]{Double.MIN_VALUE, Double.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Double[]{Double.valueOf(Double.MIN_VALUE),
+ null, Double.valueOf(9999999)}, Double.MAX_VALUE));
+ }
+
+ /** testToPrimitive/Object for float */
+ @Test
+ public void testToPrimitive_float() {
+ final Float[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b));
+
+ assertSame(ArrayUtils.EMPTY_FLOAT_ARRAY,
+ ArrayUtils.toPrimitive(new Float[0]));
+
+ assertArrayEquals(new float[]{Float.MIN_VALUE, Float.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Float[]{Float.valueOf(Float.MIN_VALUE),
+ Float.valueOf(Float.MAX_VALUE), Float.valueOf(9999999)}));
+
+ assertThrows(NullPointerException.class,
+ () -> ArrayUtils.toPrimitive(new Float[]{Float.valueOf(Float.MIN_VALUE), null}));
+ }
+
+ @Test
+ public void testToPrimitive_float_float() {
+ final Float[] l = null;
+ assertNull(ArrayUtils.toPrimitive(l, Float.MIN_VALUE));
+
+ assertSame(ArrayUtils.EMPTY_FLOAT_ARRAY,
+ ArrayUtils.toPrimitive(new Float[0], 1));
+
+ assertArrayEquals(new float[]{Float.MIN_VALUE, Float.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Float[]{Float.valueOf(Float.MIN_VALUE),
+ Float.valueOf(Float.MAX_VALUE), Float.valueOf(9999999)}, 1));
+
+ assertArrayEquals(new float[]{Float.MIN_VALUE, Float.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Float[]{Float.valueOf(Float.MIN_VALUE),
+ null, Float.valueOf(9999999)}, Float.MAX_VALUE));
+ }
+
+ /** testToPrimitive/Object for int */
+ @Test
+ public void testToPrimitive_int() {
+ final Integer[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b));
+ assertSame(ArrayUtils.EMPTY_INT_ARRAY, ArrayUtils.toPrimitive(new Integer[0]));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, Integer.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Integer[]{Integer.valueOf(Integer.MIN_VALUE),
+ Integer.valueOf(Integer.MAX_VALUE), Integer.valueOf(9999999)}));
+
+ assertThrows(NullPointerException.class,
+ () -> ArrayUtils.toPrimitive(new Integer[]{Integer.valueOf(Integer.MIN_VALUE), null}));
+ }
+
+ @Test
+ public void testToPrimitive_int_int() {
+ final Long[] l = null;
+ assertNull(ArrayUtils.toPrimitive(l, Integer.MIN_VALUE));
+ assertSame(ArrayUtils.EMPTY_INT_ARRAY,
+ ArrayUtils.toPrimitive(new Integer[0], 1));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, Integer.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Integer[]{Integer.valueOf(Integer.MIN_VALUE),
+ Integer.valueOf(Integer.MAX_VALUE), Integer.valueOf(9999999)}, 1));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, Integer.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Integer[]{Integer.valueOf(Integer.MIN_VALUE),
+ null, Integer.valueOf(9999999)}, Integer.MAX_VALUE));
+ }
+
+ @Test
+ public void testToPrimitive_intNull() {
+ final Integer[] iArray = null;
+ assertNull(ArrayUtils.toPrimitive(iArray, Integer.MIN_VALUE));
+ }
+
+ /** testToPrimitive/Object for long */
+ @Test
+ public void testToPrimitive_long() {
+ final Long[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b));
+
+ assertSame(ArrayUtils.EMPTY_LONG_ARRAY,
+ ArrayUtils.toPrimitive(new Long[0]));
+
+ assertArrayEquals(new long[]{Long.MIN_VALUE, Long.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Long[]{Long.valueOf(Long.MIN_VALUE),
+ Long.valueOf(Long.MAX_VALUE), Long.valueOf(9999999)}));
+
+ assertThrows(NullPointerException.class,
+ () -> ArrayUtils.toPrimitive(new Long[]{Long.valueOf(Long.MIN_VALUE), null}));
+ }
+
+ @Test
+ public void testToPrimitive_long_long() {
+ final Long[] l = null;
+ assertNull(ArrayUtils.toPrimitive(l, Long.MIN_VALUE));
+
+ assertSame(ArrayUtils.EMPTY_LONG_ARRAY,
+ ArrayUtils.toPrimitive(new Long[0], 1));
+
+ assertArrayEquals(new long[]{Long.MIN_VALUE, Long.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Long[]{Long.valueOf(Long.MIN_VALUE),
+ Long.valueOf(Long.MAX_VALUE), Long.valueOf(9999999)}, 1));
+
+ assertArrayEquals(new long[]{Long.MIN_VALUE, Long.MAX_VALUE, 9999999}, ArrayUtils.toPrimitive(new Long[]{Long.valueOf(Long.MIN_VALUE),
+ null, Long.valueOf(9999999)}, Long.MAX_VALUE));
+ }
+
+ /** testToPrimitive/Object for short */
+ @Test
+ public void testToPrimitive_short() {
+ final Short[] b = null;
+ assertNull(ArrayUtils.toPrimitive(b));
+
+ assertSame(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.toPrimitive(new Short[0]));
+
+ assertArrayEquals(new short[]{Short.MIN_VALUE, Short.MAX_VALUE, (short) 9999999}, ArrayUtils.toPrimitive(new Short[]{Short.valueOf(Short.MIN_VALUE),
+ Short.valueOf(Short.MAX_VALUE), Short.valueOf((short) 9999999)}));
+
+ assertThrows(NullPointerException.class,
+ () -> ArrayUtils.toPrimitive(new Short[]{Short.valueOf(Short.MIN_VALUE), null}));
+ }
+
+ @Test
+ public void testToPrimitive_short_short() {
+ final Short[] s = null;
+ assertNull(ArrayUtils.toPrimitive(s, Short.MIN_VALUE));
+
+ assertSame(ArrayUtils.EMPTY_SHORT_ARRAY, ArrayUtils.toPrimitive(new Short[0],
+ Short.MIN_VALUE));
+
+ assertArrayEquals(new short[]{Short.MIN_VALUE, Short.MAX_VALUE, (short) 9999999}, ArrayUtils.toPrimitive(new Short[]{Short.valueOf(Short.MIN_VALUE),
+ Short.valueOf(Short.MAX_VALUE), Short.valueOf((short) 9999999)}, Short.MIN_VALUE));
+
+ assertArrayEquals(new short[]{Short.MIN_VALUE, Short.MAX_VALUE, (short) 9999999}, ArrayUtils.toPrimitive(new Short[]{Short.valueOf(Short.MIN_VALUE), null,
+ Short.valueOf((short) 9999999)}, Short.MAX_VALUE));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("{}", ArrayUtils.toString(null));
+ assertEquals("{}", ArrayUtils.toString(new Object[0]));
+ assertEquals("{}", ArrayUtils.toString(new String[0]));
+ assertEquals("{<null>}", ArrayUtils.toString(new String[]{null}));
+ assertEquals("{pink,blue}", ArrayUtils.toString(new String[]{"pink", "blue"}));
+
+ assertEquals("<empty>", ArrayUtils.toString(null, "<empty>"));
+ assertEquals("{}", ArrayUtils.toString(new Object[0], "<empty>"));
+ assertEquals("{}", ArrayUtils.toString(new String[0], "<empty>"));
+ assertEquals("{<null>}", ArrayUtils.toString(new String[]{null}, "<empty>"));
+ assertEquals("{pink,blue}", ArrayUtils.toString(new String[]{"pink", "blue"}, "<empty>"));
+ }
+
+ @Test
+ public void testToStringArray_array() {
+ assertNull(ArrayUtils.toStringArray(null));
+
+ assertArrayEquals(new String[0], ArrayUtils.toStringArray(new Object[0]));
+
+ final Object[] array = {1, 2, 3, "array", "test"};
+ assertArrayEquals(new String[]{"1", "2", "3", "array", "test"}, ArrayUtils.toStringArray(array));
+
+ assertThrows(NullPointerException.class, () -> ArrayUtils.toStringArray(new Object[]{null}));
+ }
+
+ @Test
+ public void testToStringArray_array_string() {
+ assertNull(ArrayUtils.toStringArray(null, ""));
+
+ assertArrayEquals(new String[0], ArrayUtils.toStringArray(new Object[0], ""));
+
+ final Object[] array = {1, null, "test"};
+ assertArrayEquals(new String[]{"1", "valueForNullElements", "test"},
+ ArrayUtils.toStringArray(array, "valueForNullElements"));
+ }
+
+ @Test
+ public void textIndexesOfInt() {
+ int[] array = null;
+ final BitSet emptySet = new BitSet();
+ final BitSet testSet = new BitSet();
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 0));
+ array = new int[]{0, 1, 2, 3, 0};
+ testSet.set(0);
+ testSet.set(4);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 0));
+ testSet.clear();
+ testSet.set(1);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 1));
+ testSet.clear();
+ testSet.set(2);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 2));
+ testSet.clear();
+ testSet.set(3);
+ assertEquals(testSet, ArrayUtils.indexesOf(array, 3));
+ assertEquals(emptySet, ArrayUtils.indexesOf(array, 99));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/BitFieldTest.java b/src/test/java/org/apache/commons/lang3/BitFieldTest.java
new file mode 100644
index 000000000..a3924f150
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/BitFieldTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Class to test BitField functionality
+ */
+public class BitFieldTest extends AbstractLangTest {
+
+ private static final BitField bf_multi = new BitField(0x3F80);
+ private static final BitField bf_single = new BitField(0x4000);
+ private static final BitField bf_zero = new BitField(0);
+
+ /**
+ * test the getValue() method
+ */
+ @Test
+ public void testGetValue() {
+ assertEquals(bf_multi.getValue(-1), 127);
+ assertEquals(bf_multi.getValue(0), 0);
+ assertEquals(bf_single.getValue(-1), 1);
+ assertEquals(bf_single.getValue(0), 0);
+ assertEquals(bf_zero.getValue(-1), 0);
+ assertEquals(bf_zero.getValue(0), 0);
+ }
+
+ /**
+ * test the getShortValue() method
+ */
+ @Test
+ public void testGetShortValue() {
+ assertEquals(bf_multi.getShortValue((short) - 1), (short) 127);
+ assertEquals(bf_multi.getShortValue((short) 0), (short) 0);
+ assertEquals(bf_single.getShortValue((short) - 1), (short) 1);
+ assertEquals(bf_single.getShortValue((short) 0), (short) 0);
+ assertEquals(bf_zero.getShortValue((short) -1), (short) 0);
+ assertEquals(bf_zero.getShortValue((short) 0), (short) 0);
+ }
+
+ /**
+ * test the getRawValue() method
+ */
+ @Test
+ public void testGetRawValue() {
+ assertEquals(bf_multi.getRawValue(-1), 0x3F80);
+ assertEquals(bf_multi.getRawValue(0), 0);
+ assertEquals(bf_single.getRawValue(-1), 0x4000);
+ assertEquals(bf_single.getRawValue(0), 0);
+ assertEquals(bf_zero.getRawValue(-1), 0);
+ assertEquals(bf_zero.getRawValue(0), 0);
+ }
+
+ /**
+ * test the getShortRawValue() method
+ */
+ @Test
+ public void testGetShortRawValue() {
+ assertEquals(bf_multi.getShortRawValue((short) - 1), (short) 0x3F80);
+ assertEquals(bf_multi.getShortRawValue((short) 0), (short) 0);
+ assertEquals(bf_single.getShortRawValue((short) - 1), (short) 0x4000);
+ assertEquals(bf_single.getShortRawValue((short) 0), (short) 0);
+ assertEquals(bf_zero.getShortRawValue((short) -1), (short) 0);
+ assertEquals(bf_zero.getShortRawValue((short) 0), (short) 0);
+ }
+
+ /**
+ * test the isSet() method
+ */
+ @Test
+ public void testIsSet() {
+ assertFalse(bf_multi.isSet(0));
+ assertFalse(bf_zero.isSet(0));
+ for (int j = 0x80; j <= 0x3F80; j += 0x80) {
+ assertTrue(bf_multi.isSet(j));
+ }
+ for (int j = 0x80; j <= 0x3F80; j += 0x80) {
+ assertFalse(bf_zero.isSet(j));
+ }
+ assertFalse(bf_single.isSet(0));
+ assertTrue(bf_single.isSet(0x4000));
+ }
+
+ /**
+ * test the isAllSet() method
+ */
+ @Test
+ public void testIsAllSet() {
+ for (int j = 0; j < 0x3F80; j += 0x80) {
+ assertFalse(bf_multi.isAllSet(j));
+ assertTrue(bf_zero.isAllSet(j));
+ }
+ assertTrue(bf_multi.isAllSet(0x3F80));
+ assertFalse(bf_single.isAllSet(0));
+ assertTrue(bf_single.isAllSet(0x4000));
+ }
+
+ /**
+ * test the setValue() method
+ */
+ @Test
+ public void testSetValue() {
+ for (int j = 0; j < 128; j++) {
+ assertEquals(bf_multi.getValue(bf_multi.setValue(0, j)), j);
+ assertEquals(bf_multi.setValue(0, j), j << 7);
+ }
+ for (int j = 0; j < 128; j++) {
+ assertEquals(bf_zero.getValue(bf_zero.setValue(0, j)), 0);
+ assertEquals(bf_zero.setValue(0, j), 0);
+ }
+
+ // verify that excess bits are stripped off
+ assertEquals(bf_multi.setValue(0x3f80, 128), 0);
+ for (int j = 0; j < 2; j++) {
+ assertEquals(bf_single.getValue(bf_single.setValue(0, j)), j);
+ assertEquals(bf_single.setValue(0, j), j << 14);
+ }
+
+ // verify that excess bits are stripped off
+ assertEquals(bf_single.setValue(0x4000, 2), 0);
+ }
+
+ /**
+ * test the setShortValue() method
+ */
+ @Test
+ public void testSetShortValue() {
+ for (int j = 0; j < 128; j++) {
+ assertEquals(bf_multi.getShortValue(bf_multi.setShortValue((short) 0, (short) j)), (short) j);
+ assertEquals(bf_multi.setShortValue((short) 0, (short) j), (short) (j << 7));
+ }
+ for (int j = 0; j < 128; j++) {
+ assertEquals(bf_zero.getShortValue(bf_zero.setShortValue((short) 0, (short) j)), (short) 0);
+ assertEquals(bf_zero.setShortValue((short) 0, (short) j), (short) 0);
+ }
+
+ // verify that excess bits are stripped off
+ assertEquals(bf_multi.setShortValue((short) 0x3f80, (short) 128), (short) 0);
+ for (int j = 0; j < 2; j++) {
+ assertEquals(bf_single.getShortValue(bf_single.setShortValue((short) 0, (short) j)), (short) j);
+ assertEquals(bf_single.setShortValue((short) 0, (short) j), (short) (j << 14));
+ }
+
+ // verify that excess bits are stripped off
+ assertEquals(bf_single.setShortValue((short) 0x4000, (short) 2), (short) 0);
+ }
+
+ @Test
+ public void testByte() {
+ assertEquals(0, new BitField(0).setByteBoolean((byte) 0, true));
+ assertEquals(1, new BitField(1).setByteBoolean((byte) 0, true));
+ assertEquals(2, new BitField(2).setByteBoolean((byte) 0, true));
+ assertEquals(4, new BitField(4).setByteBoolean((byte) 0, true));
+ assertEquals(8, new BitField(8).setByteBoolean((byte) 0, true));
+ assertEquals(16, new BitField(16).setByteBoolean((byte) 0, true));
+ assertEquals(32, new BitField(32).setByteBoolean((byte) 0, true));
+ assertEquals(64, new BitField(64).setByteBoolean((byte) 0, true));
+ assertEquals(-128, new BitField(128).setByteBoolean((byte) 0, true));
+ assertEquals(1, new BitField(0).setByteBoolean((byte) 1, false));
+ assertEquals(0, new BitField(1).setByteBoolean((byte) 1, false));
+ assertEquals(0, new BitField(2).setByteBoolean((byte) 2, false));
+ assertEquals(0, new BitField(4).setByteBoolean((byte) 4, false));
+ assertEquals(0, new BitField(8).setByteBoolean((byte) 8, false));
+ assertEquals(0, new BitField(16).setByteBoolean((byte) 16, false));
+ assertEquals(0, new BitField(32).setByteBoolean((byte) 32, false));
+ assertEquals(0, new BitField(64).setByteBoolean((byte) 64, false));
+ assertEquals(0, new BitField(128).setByteBoolean((byte) 128, false));
+ assertEquals(-2, new BitField(1).setByteBoolean((byte) 255, false));
+ final byte clearedBit = new BitField(0x40).setByteBoolean((byte) - 63, false);
+
+ assertFalse(new BitField(0x40).isSet(clearedBit));
+ }
+
+ /**
+ * test the clear() method
+ */
+ @Test
+ public void testClear() {
+ assertEquals(bf_multi.clear(-1), 0xFFFFC07F);
+ assertEquals(bf_single.clear(-1), 0xFFFFBFFF);
+ assertEquals(bf_zero.clear(-1), 0xFFFFFFFF);
+ }
+
+ /**
+ * test the clearShort() method
+ */
+ @Test
+ public void testClearShort() {
+ assertEquals(bf_multi.clearShort((short) - 1), (short) 0xC07F);
+ assertEquals(bf_single.clearShort((short) - 1), (short) 0xBFFF);
+ assertEquals(bf_zero.clearShort((short) -1), (short) 0xFFFF);
+ }
+
+ /**
+ * test the set() method
+ */
+ @Test
+ public void testSet() {
+ assertEquals(bf_multi.set(0), 0x3F80);
+ assertEquals(bf_single.set(0), 0x4000);
+ assertEquals(bf_zero.set(0), 0);
+ }
+
+ /**
+ * test the setShort() method
+ */
+ @Test
+ public void testSetShort() {
+ assertEquals(bf_multi.setShort((short) 0), (short) 0x3F80);
+ assertEquals(bf_single.setShort((short) 0), (short) 0x4000);
+ assertEquals(bf_zero.setShort((short) 0), (short) 0);
+ }
+
+ /**
+ * test the setBoolean() method
+ */
+ @Test
+ public void testSetBoolean() {
+ assertEquals(bf_multi.set(0), bf_multi.setBoolean(0, true));
+ assertEquals(bf_single.set(0), bf_single.setBoolean(0, true));
+ assertEquals(bf_zero.set(0), bf_zero.setBoolean(0, true));
+ assertEquals(bf_multi.clear(-1), bf_multi.setBoolean(-1, false));
+ assertEquals(bf_single.clear(-1), bf_single.setBoolean(-1, false));
+ assertEquals(bf_zero.clear(-1), bf_zero.setBoolean(-1, false));
+ }
+
+ /**
+ * test the setShortBoolean() method
+ */
+ @Test
+ public void testSetShortBoolean() {
+ assertEquals(bf_multi.setShort((short) 0), bf_multi.setShortBoolean((short) 0, true));
+ assertEquals(bf_single.setShort((short) 0), bf_single.setShortBoolean((short) 0, true));
+ assertEquals(bf_zero.setShort((short) 0), bf_zero.setShortBoolean((short) 0, true));
+ assertEquals(bf_multi.clearShort((short) - 1), bf_multi.setShortBoolean((short) - 1, false));
+ assertEquals(bf_single.clearShort((short) - 1), bf_single.setShortBoolean((short) - 1, false));
+ assertEquals(bf_zero.clearShort((short) -1), bf_zero.setShortBoolean((short) -1, false));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/BooleanUtilsTest.java b/src/test/java/org/apache/commons/lang3/BooleanUtilsTest.java
new file mode 100644
index 000000000..5b6197e1c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/BooleanUtilsTest.java
@@ -0,0 +1,1173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.apache.commons.lang3.ArraySorter.sort;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link BooleanUtils}.
+ */
+public class BooleanUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void test_booleanValues() {
+ final Boolean[] expected = {Boolean.FALSE, Boolean.TRUE};
+ assertArrayEquals(sort(expected), BooleanUtils.booleanValues());
+ }
+
+ @Test
+ public void test_values() {
+ final List<Boolean> expected = Arrays.asList(Boolean.FALSE, Boolean.TRUE);
+ Collections.sort(expected);
+ assertEquals(expected, BooleanUtils.values());
+ }
+
+ @Test
+ public void test_forEach() {
+ final List<Boolean> list = new ArrayList<>();
+ BooleanUtils.forEach(list::add);
+ assertEquals(Arrays.asList(Boolean.FALSE, Boolean.TRUE), list);
+ }
+
+ @Test
+ public void test_isFalse_Boolean() {
+ assertFalse(BooleanUtils.isFalse(Boolean.TRUE));
+ assertTrue(BooleanUtils.isFalse(Boolean.FALSE));
+ assertFalse(BooleanUtils.isFalse(null));
+ }
+
+ @Test
+ public void test_isNotFalse_Boolean() {
+ assertTrue(BooleanUtils.isNotFalse(Boolean.TRUE));
+ assertFalse(BooleanUtils.isNotFalse(Boolean.FALSE));
+ assertTrue(BooleanUtils.isNotFalse(null));
+ }
+
+ @Test
+ public void test_isNotTrue_Boolean() {
+ assertFalse(BooleanUtils.isNotTrue(Boolean.TRUE));
+ assertTrue(BooleanUtils.isNotTrue(Boolean.FALSE));
+ assertTrue(BooleanUtils.isNotTrue(null));
+ }
+
+ @Test
+ public void test_isTrue_Boolean() {
+ assertTrue(BooleanUtils.isTrue(Boolean.TRUE));
+ assertFalse(BooleanUtils.isTrue(Boolean.FALSE));
+ assertFalse(BooleanUtils.isTrue(null));
+ }
+
+ @Test
+ public void test_negate_Boolean() {
+ assertSame(null, BooleanUtils.negate(null));
+ assertSame(Boolean.TRUE, BooleanUtils.negate(Boolean.FALSE));
+ assertSame(Boolean.FALSE, BooleanUtils.negate(Boolean.TRUE));
+ }
+
+ @Test
+ public void test_primitiveValues() {
+ assertArrayEquals(new boolean[] {false, true}, BooleanUtils.primitiveValues());
+ }
+
+ @Test
+ public void test_toBoolean_Boolean() {
+ assertTrue(BooleanUtils.toBoolean(Boolean.TRUE));
+ assertFalse(BooleanUtils.toBoolean(Boolean.FALSE));
+ assertFalse(BooleanUtils.toBoolean((Boolean) null));
+ }
+
+ @Test
+ public void test_toBoolean_int() {
+ assertTrue(BooleanUtils.toBoolean(1));
+ assertTrue(BooleanUtils.toBoolean(-1));
+ assertFalse(BooleanUtils.toBoolean(0));
+ }
+
+ @Test
+ public void test_toBoolean_int_int_int() {
+ assertTrue(BooleanUtils.toBoolean(6, 6, 7));
+ assertFalse(BooleanUtils.toBoolean(7, 6, 7));
+ }
+
+ @Test
+ public void test_toBoolean_int_int_int_noMatch() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.toBoolean(8, 6, 7));
+ }
+
+ @Test
+ public void test_toBoolean_Integer_Integer_Integer() {
+ final Integer six = Integer.valueOf(6);
+ final Integer seven = Integer.valueOf(7);
+
+ assertTrue(BooleanUtils.toBoolean(null, null, seven));
+ assertFalse(BooleanUtils.toBoolean(null, six, null));
+
+ assertTrue(BooleanUtils.toBoolean(Integer.valueOf(6), six, seven));
+ assertFalse(BooleanUtils.toBoolean(Integer.valueOf(7), six, seven));
+ }
+
+ @Test
+ public void test_toBoolean_Integer_Integer_Integer_noMatch() {
+ assertThrows(IllegalArgumentException.class,
+ () -> BooleanUtils.toBoolean(Integer.valueOf(8), Integer.valueOf(6), Integer.valueOf(7)));
+ }
+
+ @Test
+ public void test_toBoolean_Integer_Integer_Integer_nullValue() {
+ assertThrows(IllegalArgumentException.class,
+ () -> BooleanUtils.toBoolean(null, Integer.valueOf(6), Integer.valueOf(7)));
+ }
+
+ @Test
+ public void test_toBoolean_String() {
+ assertFalse(BooleanUtils.toBoolean((String) null));
+ assertFalse(BooleanUtils.toBoolean(""));
+ assertFalse(BooleanUtils.toBoolean("off"));
+ assertFalse(BooleanUtils.toBoolean("oof"));
+ assertFalse(BooleanUtils.toBoolean("yep"));
+ assertFalse(BooleanUtils.toBoolean("trux"));
+ assertFalse(BooleanUtils.toBoolean("false"));
+ assertFalse(BooleanUtils.toBoolean("a"));
+ assertTrue(BooleanUtils.toBoolean("true")); // interned handled differently
+ assertTrue(BooleanUtils.toBoolean(new StringBuilder("tr").append("ue").toString()));
+ assertTrue(BooleanUtils.toBoolean("truE"));
+ assertTrue(BooleanUtils.toBoolean("trUe"));
+ assertTrue(BooleanUtils.toBoolean("trUE"));
+ assertTrue(BooleanUtils.toBoolean("tRue"));
+ assertTrue(BooleanUtils.toBoolean("tRuE"));
+ assertTrue(BooleanUtils.toBoolean("tRUe"));
+ assertTrue(BooleanUtils.toBoolean("tRUE"));
+ assertTrue(BooleanUtils.toBoolean("TRUE"));
+ assertTrue(BooleanUtils.toBoolean("TRUe"));
+ assertTrue(BooleanUtils.toBoolean("TRuE"));
+ assertTrue(BooleanUtils.toBoolean("TRue"));
+ assertTrue(BooleanUtils.toBoolean("TrUE"));
+ assertTrue(BooleanUtils.toBoolean("TrUe"));
+ assertTrue(BooleanUtils.toBoolean("TruE"));
+ assertTrue(BooleanUtils.toBoolean("True"));
+ assertTrue(BooleanUtils.toBoolean("on"));
+ assertTrue(BooleanUtils.toBoolean("oN"));
+ assertTrue(BooleanUtils.toBoolean("On"));
+ assertTrue(BooleanUtils.toBoolean("ON"));
+ assertTrue(BooleanUtils.toBoolean("yes"));
+ assertTrue(BooleanUtils.toBoolean("yeS"));
+ assertTrue(BooleanUtils.toBoolean("yEs"));
+ assertTrue(BooleanUtils.toBoolean("yES"));
+ assertTrue(BooleanUtils.toBoolean("Yes"));
+ assertTrue(BooleanUtils.toBoolean("YeS"));
+ assertTrue(BooleanUtils.toBoolean("YEs"));
+ assertTrue(BooleanUtils.toBoolean("YES"));
+ assertTrue(BooleanUtils.toBoolean("1"));
+ assertFalse(BooleanUtils.toBoolean("yes?"));
+ assertFalse(BooleanUtils.toBoolean("0"));
+ assertFalse(BooleanUtils.toBoolean("tru"));
+
+ assertFalse(BooleanUtils.toBoolean("no"));
+ assertFalse(BooleanUtils.toBoolean("off"));
+ assertFalse(BooleanUtils.toBoolean("yoo"));
+ }
+
+ @Test
+ public void test_toBoolean_String_String_String() {
+ assertTrue(BooleanUtils.toBoolean(null, null, "N"));
+ assertFalse(BooleanUtils.toBoolean(null, "Y", null));
+ assertTrue(BooleanUtils.toBoolean("Y", "Y", "N"));
+ assertTrue(BooleanUtils.toBoolean("Y", "Y", "N"));
+ assertFalse(BooleanUtils.toBoolean("N", "Y", "N"));
+ assertFalse(BooleanUtils.toBoolean("N", "Y", "N"));
+ assertTrue(BooleanUtils.toBoolean((String) null, null, null));
+ assertTrue(BooleanUtils.toBoolean("Y", "Y", "Y"));
+ assertTrue(BooleanUtils.toBoolean("Y", "Y", "Y"));
+ }
+
+ @Test
+ public void test_toBoolean_String_String_String_noMatch() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.toBoolean("X", "Y", "N"));
+ }
+
+ @Test
+ public void test_toBoolean_String_String_String_nullValue() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.toBoolean(null, "Y", "N"));
+ }
+
+ @Test
+ public void test_toBooleanDefaultIfNull_Boolean_boolean() {
+ assertTrue(BooleanUtils.toBooleanDefaultIfNull(Boolean.TRUE, true));
+ assertTrue(BooleanUtils.toBooleanDefaultIfNull(Boolean.TRUE, false));
+ assertFalse(BooleanUtils.toBooleanDefaultIfNull(Boolean.FALSE, true));
+ assertFalse(BooleanUtils.toBooleanDefaultIfNull(Boolean.FALSE, false));
+ assertTrue(BooleanUtils.toBooleanDefaultIfNull(null, true));
+ assertFalse(BooleanUtils.toBooleanDefaultIfNull(null, false));
+ }
+
+ @Test
+ public void test_toBooleanObject_int() {
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject(1));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject(-1));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject(0));
+ }
+
+ @Test
+ public void test_toBooleanObject_int_int_int() {
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject(6, 6, 7, 8));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject(7, 6, 7, 8));
+ assertNull(BooleanUtils.toBooleanObject(8, 6, 7, 8));
+ }
+
+ @Test
+ public void test_toBooleanObject_int_int_int_noMatch() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.toBooleanObject(9, 6, 7, 8));
+ }
+
+ @Test
+ public void test_toBooleanObject_Integer() {
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject(Integer.valueOf(1)));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject(Integer.valueOf(-1)));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject(Integer.valueOf(0)));
+ assertNull(BooleanUtils.toBooleanObject((Integer) null));
+ }
+
+ @Test
+ public void test_toBooleanObject_Integer_Integer_Integer_Integer() {
+ final Integer six = Integer.valueOf(6);
+ final Integer seven = Integer.valueOf(7);
+ final Integer eight = Integer.valueOf(8);
+
+ assertSame(Boolean.TRUE, BooleanUtils.toBooleanObject(null, null, seven, eight));
+ assertSame(Boolean.FALSE, BooleanUtils.toBooleanObject(null, six, null, eight));
+ assertSame(null, BooleanUtils.toBooleanObject(null, six, seven, null));
+
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject(Integer.valueOf(6), six, seven, eight));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject(Integer.valueOf(7), six, seven, eight));
+ assertNull(BooleanUtils.toBooleanObject(Integer.valueOf(8), six, seven, eight));
+ }
+
+ @Test
+ public void test_toBooleanObject_Integer_Integer_Integer_Integer_noMatch() {
+ assertThrows(IllegalArgumentException.class,
+ () -> BooleanUtils.toBooleanObject(Integer.valueOf(9), Integer.valueOf(6), Integer.valueOf(7), Integer.valueOf(8)));
+ }
+
+ @Test
+ public void test_toBooleanObject_Integer_Integer_Integer_Integer_nullValue() {
+ assertThrows(IllegalArgumentException.class,
+ () -> BooleanUtils.toBooleanObject(null, Integer.valueOf(6), Integer.valueOf(7), Integer.valueOf(8)));
+ }
+
+ @Test
+ public void test_toBooleanObject_String() {
+ assertNull(BooleanUtils.toBooleanObject((String) null));
+ assertNull(BooleanUtils.toBooleanObject(""));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("false"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("no"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("off"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("FALSE"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("NO"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("OFF"));
+ assertNull(BooleanUtils.toBooleanObject("oof"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("true"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("yes"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("on"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("TRUE"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("ON"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("YES"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("TruE"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("TruE"));
+
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("y")); // yes
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("Y"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("t")); // true
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("T"));
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("1"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("f")); // false
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("F"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("n")); // No
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("N"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("0"));
+ assertNull(BooleanUtils.toBooleanObject("z"));
+
+ assertNull(BooleanUtils.toBooleanObject("ab"));
+ assertNull(BooleanUtils.toBooleanObject("yoo"));
+ assertNull(BooleanUtils.toBooleanObject("true "));
+ assertNull(BooleanUtils.toBooleanObject("ono"));
+ }
+
+ @Test
+ public void test_toBooleanObject_String_String_String_String() {
+ assertSame(Boolean.TRUE, BooleanUtils.toBooleanObject(null, null, "N", "U"));
+ assertSame(Boolean.FALSE, BooleanUtils.toBooleanObject(null, "Y", null, "U"));
+ assertSame(null, BooleanUtils.toBooleanObject(null, "Y", "N", null));
+
+ assertEquals(Boolean.TRUE, BooleanUtils.toBooleanObject("Y", "Y", "N", "U"));
+ assertEquals(Boolean.FALSE, BooleanUtils.toBooleanObject("N", "Y", "N", "U"));
+ assertNull(BooleanUtils.toBooleanObject("U", "Y", "N", "U"));
+ }
+
+ @Test
+ public void test_toBooleanObject_String_String_String_String_noMatch() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.toBooleanObject("X", "Y", "N", "U"));
+ }
+
+ @Test
+ public void test_toBooleanObject_String_String_String_String_nullValue() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.toBooleanObject(null, "Y", "N", "U"));
+ }
+
+ @Test
+ public void test_toInteger_boolean() {
+ assertEquals(1, BooleanUtils.toInteger(true));
+ assertEquals(0, BooleanUtils.toInteger(false));
+ }
+
+ @Test
+ public void test_toInteger_boolean_int_int() {
+ assertEquals(6, BooleanUtils.toInteger(true, 6, 7));
+ assertEquals(7, BooleanUtils.toInteger(false, 6, 7));
+ }
+
+ @Test
+ public void test_toInteger_Boolean_int_int_int() {
+ assertEquals(6, BooleanUtils.toInteger(Boolean.TRUE, 6, 7, 8));
+ assertEquals(7, BooleanUtils.toInteger(Boolean.FALSE, 6, 7, 8));
+ assertEquals(8, BooleanUtils.toInteger(null, 6, 7, 8));
+ }
+
+ @Test
+ public void test_toIntegerObject_boolean() {
+ assertEquals(Integer.valueOf(1), BooleanUtils.toIntegerObject(true));
+ assertEquals(Integer.valueOf(0), BooleanUtils.toIntegerObject(false));
+ }
+
+ @Test
+ public void test_toIntegerObject_Boolean() {
+ assertEquals(Integer.valueOf(1), BooleanUtils.toIntegerObject(Boolean.TRUE));
+ assertEquals(Integer.valueOf(0), BooleanUtils.toIntegerObject(Boolean.FALSE));
+ assertNull(BooleanUtils.toIntegerObject(null));
+ }
+
+ @Test
+ public void test_toIntegerObject_boolean_Integer_Integer() {
+ final Integer six = Integer.valueOf(6);
+ final Integer seven = Integer.valueOf(7);
+ assertEquals(six, BooleanUtils.toIntegerObject(true, six, seven));
+ assertEquals(seven, BooleanUtils.toIntegerObject(false, six, seven));
+ }
+
+ @Test
+ public void test_toIntegerObject_Boolean_Integer_Integer_Integer() {
+ final Integer six = Integer.valueOf(6);
+ final Integer seven = Integer.valueOf(7);
+ final Integer eight = Integer.valueOf(8);
+ assertEquals(six, BooleanUtils.toIntegerObject(Boolean.TRUE, six, seven, eight));
+ assertEquals(seven, BooleanUtils.toIntegerObject(Boolean.FALSE, six, seven, eight));
+ assertEquals(eight, BooleanUtils.toIntegerObject(null, six, seven, eight));
+ assertNull(BooleanUtils.toIntegerObject(null, six, seven, null));
+ }
+
+ @Test
+ public void test_toString_boolean_String_String_String() {
+ assertEquals("Y", BooleanUtils.toString(true, "Y", "N"));
+ assertEquals("N", BooleanUtils.toString(false, "Y", "N"));
+ }
+
+ @Test
+ public void test_toString_Boolean_String_String_String() {
+ assertEquals("U", BooleanUtils.toString(null, "Y", "N", "U"));
+ assertEquals("Y", BooleanUtils.toString(Boolean.TRUE, "Y", "N", "U"));
+ assertEquals("N", BooleanUtils.toString(Boolean.FALSE, "Y", "N", "U"));
+ }
+
+ @Test
+ public void test_toStringOnOff_boolean() {
+ assertEquals("on", BooleanUtils.toStringOnOff(true));
+ assertEquals("off", BooleanUtils.toStringOnOff(false));
+ }
+
+ @Test
+ public void test_toStringOnOff_Boolean() {
+ assertNull(BooleanUtils.toStringOnOff(null));
+ assertEquals("on", BooleanUtils.toStringOnOff(Boolean.TRUE));
+ assertEquals("off", BooleanUtils.toStringOnOff(Boolean.FALSE));
+ }
+
+ @Test
+ public void test_toStringTrueFalse_boolean() {
+ assertEquals("true", BooleanUtils.toStringTrueFalse(true));
+ assertEquals("false", BooleanUtils.toStringTrueFalse(false));
+ }
+
+ @Test
+ public void test_toStringTrueFalse_Boolean() {
+ assertNull(BooleanUtils.toStringTrueFalse(null));
+ assertEquals("true", BooleanUtils.toStringTrueFalse(Boolean.TRUE));
+ assertEquals("false", BooleanUtils.toStringTrueFalse(Boolean.FALSE));
+ }
+
+ @Test
+ public void test_toStringYesNo_boolean() {
+ assertEquals("yes", BooleanUtils.toStringYesNo(true));
+ assertEquals("no", BooleanUtils.toStringYesNo(false));
+ }
+
+ @Test
+ public void test_toStringYesNo_Boolean() {
+ assertNull(BooleanUtils.toStringYesNo(null));
+ assertEquals("yes", BooleanUtils.toStringYesNo(Boolean.TRUE));
+ assertEquals("no", BooleanUtils.toStringYesNo(Boolean.FALSE));
+ }
+
+ @Test
+ public void testAnd_object_emptyInput() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.and(new Boolean[] {}));
+ }
+
+ @Test
+ public void testAnd_object_nullElementInput() {
+ assertEquals(Boolean.FALSE, BooleanUtils.and(new Boolean[] {null}));
+ }
+
+ @Test
+ public void testAnd_object_nullInput() {
+ assertThrows(NullPointerException.class, () -> BooleanUtils.and((Boolean[]) null));
+ }
+
+ @Test
+ public void testAnd_object_validInput_2items() {
+ assertTrue(
+ BooleanUtils
+ .and(new Boolean[] { Boolean.TRUE, Boolean.TRUE })
+ .booleanValue(),
+ "False result for (true, true)");
+
+ assertFalse(
+ BooleanUtils
+ .and(new Boolean[] { Boolean.FALSE, Boolean.FALSE })
+ .booleanValue(),
+ "True result for (false, false)");
+
+ assertFalse(
+ BooleanUtils
+ .and(new Boolean[] { Boolean.TRUE, Boolean.FALSE })
+ .booleanValue(),
+ "True result for (true, false)");
+
+ assertFalse(
+ BooleanUtils
+ .and(new Boolean[] { Boolean.FALSE, Boolean.TRUE })
+ .booleanValue(),
+ "True result for (false, true)");
+ }
+
+ @Test
+ public void testAnd_object_validInput_3items() {
+ assertFalse(
+ BooleanUtils
+ .and(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.FALSE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "True result for (false, false, true)");
+
+ assertFalse(
+ BooleanUtils
+ .and(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.TRUE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "True result for (false, true, false)");
+
+ assertFalse(
+ BooleanUtils
+ .and(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.FALSE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "True result for (true, false, false)");
+
+ assertTrue(
+ BooleanUtils
+ .and(new Boolean[] { Boolean.TRUE, Boolean.TRUE, Boolean.TRUE })
+ .booleanValue(),
+ "False result for (true, true, true)");
+
+ assertFalse(
+ BooleanUtils.and(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.FALSE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "True result for (false, false)");
+
+ assertFalse(
+ BooleanUtils.and(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.TRUE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "True result for (true, true, false)");
+
+ assertFalse(
+ BooleanUtils.and(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.FALSE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "True result for (true, false, true)");
+
+ assertFalse(
+ BooleanUtils.and(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.TRUE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "True result for (false, true, true)");
+ }
+
+ @Test
+ public void testAnd_primitive_emptyInput() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.and(new boolean[] {}));
+ }
+
+ @Test
+ public void testAnd_primitive_nullInput() {
+ assertThrows(NullPointerException.class, () -> BooleanUtils.and((boolean[]) null));
+ }
+
+ @Test
+ public void testAnd_primitive_validInput_2items() {
+ assertTrue(
+ BooleanUtils.and(new boolean[] { true, true }),
+ "False result for (true, true)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { false, false }),
+ "True result for (false, false)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { true, false }),
+ "True result for (true, false)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { false, true }),
+ "True result for (false, true)");
+ }
+
+ @Test
+ public void testAnd_primitive_validInput_3items() {
+ assertFalse(
+ BooleanUtils.and(new boolean[] { false, false, true }),
+ "True result for (false, false, true)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { false, true, false }),
+ "True result for (false, true, false)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { true, false, false }),
+ "True result for (true, false, false)");
+
+ assertTrue(
+ BooleanUtils.and(new boolean[] { true, true, true }),
+ "False result for (true, true, true)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { false, false, false }),
+ "True result for (false, false)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { true, true, false }),
+ "True result for (true, true, false)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { true, false, true }),
+ "True result for (true, false, true)");
+
+ assertFalse(
+ BooleanUtils.and(new boolean[] { false, true, true }),
+ "True result for (false, true, true)");
+ }
+
+ @Test
+ public void testCompare() {
+ assertTrue(BooleanUtils.compare(true, false) > 0);
+ assertEquals(0, BooleanUtils.compare(true, true));
+ assertEquals(0, BooleanUtils.compare(false, false));
+ assertTrue(BooleanUtils.compare(false, true) < 0);
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new BooleanUtils());
+ final Constructor<?>[] cons = BooleanUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(BooleanUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(BooleanUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testOneHot_object_emptyInput() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.oneHot(new Boolean[] {}));
+ }
+
+ @Test
+ public void testOneHot_object_nullElementInput() {
+ assertEquals(Boolean.FALSE, BooleanUtils.oneHot(new Boolean[] {null}));
+ }
+
+ @Test
+ public void testOneHot_object_nullInput() {
+ assertThrows(NullPointerException.class, () -> BooleanUtils.oneHot((Boolean[]) null));
+ }
+
+ @Test
+ public void testOneHot_object_validInput_1item() {
+ assertTrue(BooleanUtils.oneHot(new Boolean[]{Boolean.TRUE}), "true");
+
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{Boolean.FALSE}), "false");
+
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{null}), "false");
+ }
+
+ @Test
+ public void testOneHot_object_validInput_2items() {
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{true, true}), "both true");
+
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{false, false}), "both false");
+
+ assertTrue(BooleanUtils.oneHot(new Boolean[]{true, false}), "first true");
+
+ assertTrue(BooleanUtils.oneHot(new Boolean[]{false, true}), "last true");
+ }
+
+ @Test
+ public void testOneHot_object_validInput_2ItemsNullsTreatedAsFalse() {
+ assertFalse(BooleanUtils.oneHot(null, null), "both null");
+
+ assertTrue(BooleanUtils.oneHot(true, null), "first true");
+
+ assertTrue(BooleanUtils.oneHot(null, true), "last true");
+ }
+
+ @Test
+ public void testOneHot_object_validInput_3items() {
+ // none true
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{false, false, false}), "all false");
+
+ // one true
+ assertTrue(BooleanUtils.oneHot(new Boolean[]{true, false, false}), "first true");
+
+ assertTrue(BooleanUtils.oneHot(new Boolean[]{false, true, false}), "middle true");
+
+ assertTrue(BooleanUtils.oneHot(new Boolean[]{false, false, true}), "last true");
+
+ // two true
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{false, true, true}), "first false");
+
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{true, false, true}), "middle false");
+
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{true, true, false}), "last false");
+
+ // three true
+ assertFalse(BooleanUtils.oneHot(new Boolean[]{true, true, true}), "all true");
+ }
+
+ @Test
+ public void testOneHot_primitive_emptyInput() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.oneHot(new boolean[] {}));
+ }
+
+ @Test
+ public void testOneHot_primitive_nullInput() {
+ assertThrows(NullPointerException.class, () -> BooleanUtils.oneHot((boolean[]) null));
+ }
+
+ @Test
+ public void testOneHot_primitive_validInput_1item() {
+ assertTrue(BooleanUtils.oneHot(new boolean[]{true}), "true");
+
+ assertFalse(BooleanUtils.oneHot(new boolean[]{false}), "false");
+ }
+
+ @Test
+ public void testOneHot_primitive_validInput_2items() {
+ assertFalse(BooleanUtils.oneHot(new boolean[]{true, true}), "both true");
+
+ assertFalse(BooleanUtils.oneHot(new boolean[]{false, false}), "both false");
+
+ assertTrue(BooleanUtils.oneHot(new boolean[]{true, false}), "first true");
+
+ assertTrue(BooleanUtils.oneHot(new boolean[]{false, true}), "last true");
+ }
+
+ @Test
+ public void testOneHot_primitive_validInput_3items() {
+ // none true
+ assertFalse(BooleanUtils.oneHot(new boolean[]{false, false, false}), "all false");
+
+ // one true
+ assertTrue(BooleanUtils.oneHot(new boolean[]{true, false, false}), "first true");
+
+ assertTrue(BooleanUtils.oneHot(new boolean[]{false, true, false}), "middle true");
+
+ assertTrue(BooleanUtils.oneHot(new boolean[]{false, false, true}), "last true");
+
+ // two true
+ assertFalse(BooleanUtils.oneHot(new boolean[]{false, true, true}), "first false");
+
+ assertFalse(BooleanUtils.oneHot(new boolean[]{true, false, true}), "middle false");
+
+ assertFalse(BooleanUtils.oneHot(new boolean[]{true, true, false}), "last false");
+
+ // three true
+ assertFalse(BooleanUtils.oneHot(new boolean[]{true, true, true}), "all true");
+ }
+
+ @Test
+ public void testOr_object_emptyInput() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.or(new Boolean[] {}));
+ }
+
+ @Test
+ public void testOr_object_nullElementInput() {
+ assertEquals(Boolean.FALSE, BooleanUtils.or(new Boolean[] {null}));
+ }
+
+ @Test
+ public void testOr_object_nullInput() {
+ assertThrows(NullPointerException.class, () -> BooleanUtils.or((Boolean[]) null));
+ }
+
+ @Test
+ public void testOr_object_validInput_2items() {
+ assertTrue(
+ BooleanUtils
+ .or(new Boolean[] { Boolean.TRUE, Boolean.TRUE })
+ .booleanValue(),
+ "False result for (true, true)");
+
+ assertFalse(
+ BooleanUtils
+ .or(new Boolean[] { Boolean.FALSE, Boolean.FALSE })
+ .booleanValue(),
+ "True result for (false, false)");
+
+ assertTrue(
+ BooleanUtils
+ .or(new Boolean[] { Boolean.TRUE, Boolean.FALSE })
+ .booleanValue(),
+ "False result for (true, false)");
+
+ assertTrue(
+ BooleanUtils
+ .or(new Boolean[] { Boolean.FALSE, Boolean.TRUE })
+ .booleanValue(),
+ "False result for (false, true)");
+ }
+
+ @Test
+ public void testOr_object_validInput_3items() {
+ assertTrue(
+ BooleanUtils
+ .or(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.FALSE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "False result for (false, false, true)");
+
+ assertTrue(
+ BooleanUtils
+ .or(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.TRUE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "False result for (false, true, false)");
+
+ assertTrue(
+ BooleanUtils
+ .or(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.FALSE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "False result for (true, false, false)");
+
+ assertTrue(
+ BooleanUtils
+ .or(new Boolean[] { Boolean.TRUE, Boolean.TRUE, Boolean.TRUE })
+ .booleanValue(),
+ "False result for (true, true, true)");
+
+ assertFalse(
+ BooleanUtils.or(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.FALSE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "True result for (false, false)");
+
+ assertTrue(
+ BooleanUtils.or(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.TRUE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "False result for (true, true, false)");
+
+ assertTrue(
+ BooleanUtils.or(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.FALSE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "False result for (true, false, true)");
+
+ assertTrue(
+ BooleanUtils.or(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.TRUE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "False result for (false, true, true)");
+ }
+
+ @Test
+ public void testOr_primitive_emptyInput() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.or(new boolean[] {}));
+ }
+
+ @Test
+ public void testOr_primitive_nullInput() {
+ assertThrows(NullPointerException.class, () -> BooleanUtils.or((boolean[]) null));
+ }
+
+ @Test
+ public void testOr_primitive_validInput_2items() {
+ assertTrue(
+ BooleanUtils.or(new boolean[] { true, true }),
+ "False result for (true, true)");
+
+ assertFalse(
+ BooleanUtils.or(new boolean[] { false, false }),
+ "True result for (false, false)");
+
+ assertTrue(
+ BooleanUtils.or(new boolean[] { true, false }),
+ "False result for (true, false)");
+
+ assertTrue(
+ BooleanUtils.or(new boolean[] { false, true }),
+ "False result for (false, true)");
+ }
+
+ @Test
+ public void testOr_primitive_validInput_3items() {
+ assertTrue(
+ BooleanUtils.or(new boolean[] { false, false, true }),
+ "False result for (false, false, true)");
+
+ assertTrue(
+ BooleanUtils.or(new boolean[] { false, true, false }),
+ "False result for (false, true, false)");
+
+ assertTrue(
+ BooleanUtils.or(new boolean[] { true, false, false }),
+ "False result for (true, false, false)");
+
+ assertTrue(
+ BooleanUtils.or(new boolean[] { true, true, true }),
+ "False result for (true, true, true)");
+
+ assertFalse(
+ BooleanUtils.or(new boolean[] { false, false, false }),
+ "True result for (false, false)");
+
+ assertTrue(
+ BooleanUtils.or(new boolean[] { true, true, false }),
+ "False result for (true, true, false)");
+
+ assertTrue(
+ BooleanUtils.or(new boolean[] { true, false, true }),
+ "False result for (true, false, true)");
+
+ assertTrue(
+ BooleanUtils.or(new boolean[] { false, true, true }),
+ "False result for (false, true, true)");
+ }
+
+ @Test
+ public void testXor_object_emptyInput() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.xor(new Boolean[] {}));
+ }
+
+ @Test
+ public void testXor_object_nullElementInput() {
+ assertEquals(Boolean.FALSE, BooleanUtils.xor(new Boolean[] {null}));
+ }
+
+ @Test
+ public void testXor_object_nullInput() {
+ assertThrows(NullPointerException.class, () -> BooleanUtils.xor((Boolean[]) null));
+ }
+
+ @Test
+ public void testXor_object_validInput_1items() {
+ assertEquals(
+ true,
+ BooleanUtils.xor(new Boolean[] { Boolean.TRUE }).booleanValue(),
+ "true");
+
+ assertEquals(
+ false,
+ BooleanUtils.xor(new Boolean[] { Boolean.FALSE }).booleanValue(),
+ "false");
+ }
+
+ @Test
+ public void testXor_object_validInput_2items() {
+ assertEquals(
+ false ^ false,
+ BooleanUtils.xor(new Boolean[] { Boolean.FALSE, Boolean.FALSE }).booleanValue(),
+ "false ^ false");
+
+ assertEquals(
+ false ^ true,
+ BooleanUtils.xor(new Boolean[] { Boolean.FALSE, Boolean.TRUE }).booleanValue(),
+ "false ^ true");
+
+ assertEquals(
+ true ^ false,
+ BooleanUtils.xor(new Boolean[] { Boolean.TRUE, Boolean.FALSE }).booleanValue(),
+ "true ^ false");
+
+ assertEquals(
+ true ^ true,
+ BooleanUtils.xor(new Boolean[] { Boolean.TRUE, Boolean.TRUE }).booleanValue(),
+ "true ^ true");
+ }
+
+ @Test
+ public void testXor_object_validInput_3items() {
+ assertEquals(
+ false ^ false ^ false,
+ BooleanUtils.xor(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.FALSE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "false ^ false ^ false");
+
+ assertEquals(
+ false ^ false ^ true,
+ BooleanUtils
+ .xor(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.FALSE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "false ^ false ^ true");
+
+ assertEquals(
+ false ^ true ^ false,
+ BooleanUtils
+ .xor(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.TRUE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "false ^ true ^ false");
+
+ assertEquals(
+ true ^ false ^ false,
+ BooleanUtils
+ .xor(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.FALSE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "true ^ false ^ false");
+
+ assertEquals(
+ true ^ false ^ true,
+ BooleanUtils.xor(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.FALSE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "true ^ false ^ true");
+
+ assertEquals(
+ true ^ true ^ false,
+ BooleanUtils.xor(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.TRUE,
+ Boolean.FALSE })
+ .booleanValue(),
+ "true ^ true ^ false");
+
+ assertEquals(
+ false ^ true ^ true,
+ BooleanUtils.xor(
+ new Boolean[] {
+ Boolean.FALSE,
+ Boolean.TRUE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "false ^ true ^ true");
+
+ assertEquals(
+ true ^ true ^ true,
+ BooleanUtils.xor(
+ new Boolean[] {
+ Boolean.TRUE,
+ Boolean.TRUE,
+ Boolean.TRUE })
+ .booleanValue(),
+ "true ^ true ^ true");
+ }
+
+ @Test
+ public void testXor_primitive_emptyInput() {
+ assertThrows(IllegalArgumentException.class, () -> BooleanUtils.xor(new boolean[] {}));
+ }
+
+ @Test
+ public void testXor_primitive_nullInput() {
+ assertThrows(NullPointerException.class, () -> BooleanUtils.xor((boolean[]) null));
+ }
+
+ @Test
+ public void testXor_primitive_validInput_1items() {
+ assertEquals(
+ true,
+ BooleanUtils.xor(new boolean[] { true }),
+ "true");
+
+ assertEquals(
+ false,
+ BooleanUtils.xor(new boolean[] { false }),
+ "false");
+ }
+
+ @Test
+ public void testXor_primitive_validInput_2items() {
+ assertEquals(
+ true ^ true,
+ BooleanUtils.xor(new boolean[] { true, true }),
+ "true ^ true");
+
+ assertEquals(
+ false ^ false,
+ BooleanUtils.xor(new boolean[] { false, false }),
+ "false ^ false");
+
+ assertEquals(
+ true ^ false,
+ BooleanUtils.xor(new boolean[] { true, false }),
+ "true ^ false");
+
+ assertEquals(
+ false ^ true,
+ BooleanUtils.xor(new boolean[] { false, true }),
+ "false ^ true");
+ }
+
+ @Test
+ public void testXor_primitive_validInput_3items() {
+ assertEquals(
+ false ^ false ^ false,
+ BooleanUtils.xor(new boolean[] { false, false, false }),
+ "false ^ false ^ false");
+
+ assertEquals(
+ false ^ false ^ true,
+ BooleanUtils.xor(new boolean[] { false, false, true }),
+ "false ^ false ^ true");
+
+ assertEquals(
+ false ^ true ^ false,
+ BooleanUtils.xor(new boolean[] { false, true, false }),
+ "false ^ true ^ false");
+
+ assertEquals(
+ false ^ true ^ true,
+ BooleanUtils.xor(new boolean[] { false, true, true }),
+ "false ^ true ^ true");
+
+ assertEquals(
+ true ^ false ^ false,
+ BooleanUtils.xor(new boolean[] { true, false, false }),
+ "true ^ false ^ false");
+
+ assertEquals(
+ true ^ false ^ true,
+ BooleanUtils.xor(new boolean[] { true, false, true }),
+ "true ^ false ^ true");
+
+ assertEquals(
+ true ^ true ^ false,
+ BooleanUtils.xor(new boolean[] { true, true, false }),
+ "true ^ true ^ false");
+
+ assertEquals(
+ true ^ true ^ true,
+ BooleanUtils.xor(new boolean[] { true, true, true }),
+ "true ^ true ^ true");
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/CharEncodingTest.java b/src/test/java/org/apache/commons/lang3/CharEncodingTest.java
new file mode 100644
index 000000000..2035605c7
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharEncodingTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests CharEncoding.
+ *
+ * @see CharEncoding
+ */
+@SuppressWarnings("deprecation")
+public class CharEncodingTest extends AbstractLangTest {
+
+ private void assertSupportedEncoding(final String name) {
+ assertTrue(CharEncoding.isSupported(name), "Encoding should be supported: " + name);
+ }
+
+ /**
+ * The class can be instantiated.
+ */
+ @Test
+ public void testConstructor() {
+ new CharEncoding();
+ }
+
+ @Test
+ public void testMustBeSupportedJava1_3_1_and_above() {
+ this.assertSupportedEncoding(CharEncoding.ISO_8859_1);
+ this.assertSupportedEncoding(CharEncoding.US_ASCII);
+ this.assertSupportedEncoding(CharEncoding.UTF_16);
+ this.assertSupportedEncoding(CharEncoding.UTF_16BE);
+ this.assertSupportedEncoding(CharEncoding.UTF_16LE);
+ this.assertSupportedEncoding(CharEncoding.UTF_8);
+ }
+
+ @Test
+ public void testSupported() {
+ assertTrue(CharEncoding.isSupported("UTF8"));
+ assertTrue(CharEncoding.isSupported("UTF-8"));
+ assertTrue(CharEncoding.isSupported("ASCII"));
+ }
+
+ @Test
+ public void testNotSupported() {
+ assertFalse(CharEncoding.isSupported(null));
+ assertFalse(CharEncoding.isSupported(""));
+ assertFalse(CharEncoding.isSupported(" "));
+ assertFalse(CharEncoding.isSupported("\t\r\n"));
+ assertFalse(CharEncoding.isSupported("DOESNOTEXIST"));
+ assertFalse(CharEncoding.isSupported("this is not a valid encoding name"));
+ }
+
+ @Test
+ public void testStandardCharsetsEquality() {
+ assertEquals(StandardCharsets.ISO_8859_1.name(), CharEncoding.ISO_8859_1);
+ assertEquals(StandardCharsets.US_ASCII.name(), CharEncoding.US_ASCII);
+ assertEquals(StandardCharsets.UTF_8.name(), CharEncoding.UTF_8);
+ assertEquals(StandardCharsets.UTF_16.name(), CharEncoding.UTF_16);
+ assertEquals(StandardCharsets.UTF_16BE.name(), CharEncoding.UTF_16BE);
+ assertEquals(StandardCharsets.UTF_16LE.name(), CharEncoding.UTF_16LE);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/CharRangeTest.java b/src/test/java/org/apache/commons/lang3/CharRangeTest.java
new file mode 100644
index 000000000..e5485eec6
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharRangeTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Modifier;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.CharRange}.
+ */
+public class CharRangeTest extends AbstractLangTest {
+
+ @Test
+ public void testClass() {
+ // class changed to non-public in 3.0
+ assertFalse(Modifier.isPublic(CharRange.class.getModifiers()));
+ assertTrue(Modifier.isFinal(CharRange.class.getModifiers()));
+ }
+
+ @Test
+ public void testConstructorAccessors_is() {
+ final CharRange rangea = CharRange.is('a');
+ assertEquals('a', rangea.getStart());
+ assertEquals('a', rangea.getEnd());
+ assertFalse(rangea.isNegated());
+ assertEquals("a", rangea.toString());
+ }
+
+ @Test
+ public void testConstructorAccessors_isNot() {
+ final CharRange rangea = CharRange.isNot('a');
+ assertEquals('a', rangea.getStart());
+ assertEquals('a', rangea.getEnd());
+ assertTrue(rangea.isNegated());
+ assertEquals("^a", rangea.toString());
+ }
+
+ @Test
+ public void testConstructorAccessors_isIn_Same() {
+ final CharRange rangea = CharRange.isIn('a', 'a');
+ assertEquals('a', rangea.getStart());
+ assertEquals('a', rangea.getEnd());
+ assertFalse(rangea.isNegated());
+ assertEquals("a", rangea.toString());
+ }
+
+ @Test
+ public void testConstructorAccessors_isIn_Normal() {
+ final CharRange rangea = CharRange.isIn('a', 'e');
+ assertEquals('a', rangea.getStart());
+ assertEquals('e', rangea.getEnd());
+ assertFalse(rangea.isNegated());
+ assertEquals("a-e", rangea.toString());
+ }
+
+ @Test
+ public void testConstructorAccessors_isIn_Reversed() {
+ final CharRange rangea = CharRange.isIn('e', 'a');
+ assertEquals('a', rangea.getStart());
+ assertEquals('e', rangea.getEnd());
+ assertFalse(rangea.isNegated());
+ assertEquals("a-e", rangea.toString());
+ }
+
+ @Test
+ public void testConstructorAccessors_isNotIn_Same() {
+ final CharRange rangea = CharRange.isNotIn('a', 'a');
+ assertEquals('a', rangea.getStart());
+ assertEquals('a', rangea.getEnd());
+ assertTrue(rangea.isNegated());
+ assertEquals("^a", rangea.toString());
+ }
+
+ @Test
+ public void testConstructorAccessors_isNotIn_Normal() {
+ final CharRange rangea = CharRange.isNotIn('a', 'e');
+ assertEquals('a', rangea.getStart());
+ assertEquals('e', rangea.getEnd());
+ assertTrue(rangea.isNegated());
+ assertEquals("^a-e", rangea.toString());
+ }
+
+ @Test
+ public void testConstructorAccessors_isNotIn_Reversed() {
+ final CharRange rangea = CharRange.isNotIn('e', 'a');
+ assertEquals('a', rangea.getStart());
+ assertEquals('e', rangea.getEnd());
+ assertTrue(rangea.isNegated());
+ assertEquals("^a-e", rangea.toString());
+ }
+
+ @Test
+ public void testEquals_Object() {
+ final CharRange rangea = CharRange.is('a');
+ final CharRange rangeae = CharRange.isIn('a', 'e');
+ final CharRange rangenotbf = CharRange.isIn('b', 'f');
+
+ assertNotEquals(null, rangea);
+
+ assertEquals(rangea, rangea);
+ assertEquals(rangea, CharRange.is('a'));
+ assertEquals(rangeae, rangeae);
+ assertEquals(rangeae, CharRange.isIn('a', 'e'));
+ assertEquals(rangenotbf, rangenotbf);
+ assertEquals(rangenotbf, CharRange.isIn('b', 'f'));
+
+ assertNotEquals(rangea, rangeae);
+ assertNotEquals(rangea, rangenotbf);
+ assertNotEquals(rangeae, rangea);
+ assertNotEquals(rangeae, rangenotbf);
+ assertNotEquals(rangenotbf, rangea);
+ assertNotEquals(rangenotbf, rangeae);
+ }
+
+ @Test
+ public void testHashCode() {
+ final CharRange rangea = CharRange.is('a');
+ final CharRange rangeae = CharRange.isIn('a', 'e');
+ final CharRange rangenotbf = CharRange.isIn('b', 'f');
+
+ assertEquals(rangea.hashCode(), rangea.hashCode());
+ assertEquals(rangea.hashCode(), CharRange.is('a').hashCode());
+ assertEquals(rangeae.hashCode(), rangeae.hashCode());
+ assertEquals(rangeae.hashCode(), CharRange.isIn('a', 'e').hashCode());
+ assertEquals(rangenotbf.hashCode(), rangenotbf.hashCode());
+ assertEquals(rangenotbf.hashCode(), CharRange.isIn('b', 'f').hashCode());
+
+ assertNotEquals(rangea.hashCode(), rangeae.hashCode());
+ assertNotEquals(rangea.hashCode(), rangenotbf.hashCode());
+ assertNotEquals(rangeae.hashCode(), rangea.hashCode());
+ assertNotEquals(rangeae.hashCode(), rangenotbf.hashCode());
+ assertNotEquals(rangenotbf.hashCode(), rangea.hashCode());
+ assertNotEquals(rangenotbf.hashCode(), rangeae.hashCode());
+ }
+
+ @Test
+ public void testContains_Char() {
+ CharRange range = CharRange.is('c');
+ assertFalse(range.contains('b'));
+ assertTrue(range.contains('c'));
+ assertFalse(range.contains('d'));
+ assertFalse(range.contains('e'));
+
+ range = CharRange.isIn('c', 'd');
+ assertFalse(range.contains('b'));
+ assertTrue(range.contains('c'));
+ assertTrue(range.contains('d'));
+ assertFalse(range.contains('e'));
+
+ range = CharRange.isIn('d', 'c');
+ assertFalse(range.contains('b'));
+ assertTrue(range.contains('c'));
+ assertTrue(range.contains('d'));
+ assertFalse(range.contains('e'));
+
+ range = CharRange.isNotIn('c', 'd');
+ assertTrue(range.contains('b'));
+ assertFalse(range.contains('c'));
+ assertFalse(range.contains('d'));
+ assertTrue(range.contains('e'));
+ assertTrue(range.contains((char) 0));
+ assertTrue(range.contains(Character.MAX_VALUE));
+ }
+
+ @Test
+ public void testContains_Charrange() {
+ final CharRange a = CharRange.is('a');
+ final CharRange b = CharRange.is('b');
+ final CharRange c = CharRange.is('c');
+ final CharRange c2 = CharRange.is('c');
+ final CharRange d = CharRange.is('d');
+ final CharRange e = CharRange.is('e');
+ final CharRange cd = CharRange.isIn('c', 'd');
+ final CharRange bd = CharRange.isIn('b', 'd');
+ final CharRange bc = CharRange.isIn('b', 'c');
+ final CharRange ab = CharRange.isIn('a', 'b');
+ final CharRange de = CharRange.isIn('d', 'e');
+ final CharRange ef = CharRange.isIn('e', 'f');
+ final CharRange ae = CharRange.isIn('a', 'e');
+
+ // normal/normal
+ assertFalse(c.contains(b));
+ assertTrue(c.contains(c));
+ assertTrue(c.contains(c2));
+ assertFalse(c.contains(d));
+
+ assertFalse(c.contains(cd));
+ assertFalse(c.contains(bd));
+ assertFalse(c.contains(bc));
+ assertFalse(c.contains(ab));
+ assertFalse(c.contains(de));
+
+ assertTrue(cd.contains(c));
+ assertTrue(bd.contains(c));
+ assertTrue(bc.contains(c));
+ assertFalse(ab.contains(c));
+ assertFalse(de.contains(c));
+
+ assertTrue(ae.contains(b));
+ assertTrue(ae.contains(ab));
+ assertTrue(ae.contains(bc));
+ assertTrue(ae.contains(cd));
+ assertTrue(ae.contains(de));
+
+ final CharRange notb = CharRange.isNot('b');
+ final CharRange notc = CharRange.isNot('c');
+ final CharRange notd = CharRange.isNot('d');
+ final CharRange notab = CharRange.isNotIn('a', 'b');
+ final CharRange notbc = CharRange.isNotIn('b', 'c');
+ final CharRange notbd = CharRange.isNotIn('b', 'd');
+ final CharRange notcd = CharRange.isNotIn('c', 'd');
+ final CharRange notde = CharRange.isNotIn('d', 'e');
+ final CharRange notae = CharRange.isNotIn('a', 'e');
+ final CharRange all = CharRange.isIn((char) 0, Character.MAX_VALUE);
+ final CharRange allbutfirst = CharRange.isIn((char) 1, Character.MAX_VALUE);
+
+ // normal/negated
+ assertFalse(c.contains(notc));
+ assertFalse(c.contains(notbd));
+ assertTrue(all.contains(notc));
+ assertTrue(all.contains(notbd));
+ assertFalse(allbutfirst.contains(notc));
+ assertFalse(allbutfirst.contains(notbd));
+
+ // negated/normal
+ assertTrue(notc.contains(a));
+ assertTrue(notc.contains(b));
+ assertFalse(notc.contains(c));
+ assertTrue(notc.contains(d));
+ assertTrue(notc.contains(e));
+
+ assertTrue(notc.contains(ab));
+ assertFalse(notc.contains(bc));
+ assertFalse(notc.contains(bd));
+ assertFalse(notc.contains(cd));
+ assertTrue(notc.contains(de));
+ assertFalse(notc.contains(ae));
+ assertFalse(notc.contains(all));
+ assertFalse(notc.contains(allbutfirst));
+
+ assertTrue(notbd.contains(a));
+ assertFalse(notbd.contains(b));
+ assertFalse(notbd.contains(c));
+ assertFalse(notbd.contains(d));
+ assertTrue(notbd.contains(e));
+
+ assertTrue(notcd.contains(ab));
+ assertFalse(notcd.contains(bc));
+ assertFalse(notcd.contains(bd));
+ assertFalse(notcd.contains(cd));
+ assertFalse(notcd.contains(de));
+ assertFalse(notcd.contains(ae));
+ assertTrue(notcd.contains(ef));
+ assertFalse(notcd.contains(all));
+ assertFalse(notcd.contains(allbutfirst));
+
+ // negated/negated
+ assertFalse(notc.contains(notb));
+ assertTrue(notc.contains(notc));
+ assertFalse(notc.contains(notd));
+
+ assertFalse(notc.contains(notab));
+ assertTrue(notc.contains(notbc));
+ assertTrue(notc.contains(notbd));
+ assertTrue(notc.contains(notcd));
+ assertFalse(notc.contains(notde));
+
+ assertFalse(notbd.contains(notb));
+ assertFalse(notbd.contains(notc));
+ assertFalse(notbd.contains(notd));
+
+ assertFalse(notbd.contains(notab));
+ assertFalse(notbd.contains(notbc));
+ assertTrue(notbd.contains(notbd));
+ assertFalse(notbd.contains(notcd));
+ assertFalse(notbd.contains(notde));
+ assertTrue(notbd.contains(notae));
+ }
+
+ @Test
+ public void testContainsNullArg() {
+ final CharRange range = CharRange.is('a');
+ final NullPointerException e = assertThrows(NullPointerException.class, () -> range.contains(null));
+ assertEquals("range", e.getMessage());
+ }
+
+ @Test
+ public void testIterator() {
+ final CharRange a = CharRange.is('a');
+ final CharRange ad = CharRange.isIn('a', 'd');
+ final CharRange nota = CharRange.isNot('a');
+ final CharRange emptySet = CharRange.isNotIn((char) 0, Character.MAX_VALUE);
+ final CharRange notFirst = CharRange.isNotIn((char) 1, Character.MAX_VALUE);
+ final CharRange notLast = CharRange.isNotIn((char) 0, (char) (Character.MAX_VALUE - 1));
+
+ final Iterator<Character> aIt = a.iterator();
+ assertNotNull(aIt);
+ assertTrue(aIt.hasNext());
+ assertEquals(Character.valueOf('a'), aIt.next());
+ assertFalse(aIt.hasNext());
+
+ final Iterator<Character> adIt = ad.iterator();
+ assertNotNull(adIt);
+ assertTrue(adIt.hasNext());
+ assertEquals(Character.valueOf('a'), adIt.next());
+ assertEquals(Character.valueOf('b'), adIt.next());
+ assertEquals(Character.valueOf('c'), adIt.next());
+ assertEquals(Character.valueOf('d'), adIt.next());
+ assertFalse(adIt.hasNext());
+
+ final Iterator<Character> notaIt = nota.iterator();
+ assertNotNull(notaIt);
+ assertTrue(notaIt.hasNext());
+ while (notaIt.hasNext()) {
+ final Character c = notaIt.next();
+ assertNotEquals('a', c.charValue());
+ }
+
+ final Iterator<Character> emptySetIt = emptySet.iterator();
+ assertNotNull(emptySetIt);
+ assertFalse(emptySetIt.hasNext());
+ assertThrows(NoSuchElementException.class, emptySetIt::next);
+
+ final Iterator<Character> notFirstIt = notFirst.iterator();
+ assertNotNull(notFirstIt);
+ assertTrue(notFirstIt.hasNext());
+ assertEquals(Character.valueOf((char) 0), notFirstIt.next());
+ assertFalse(notFirstIt.hasNext());
+ assertThrows(NoSuchElementException.class, notFirstIt::next);
+
+ final Iterator<Character> notLastIt = notLast.iterator();
+ assertNotNull(notLastIt);
+ assertTrue(notLastIt.hasNext());
+ assertEquals(Character.valueOf(Character.MAX_VALUE), notLastIt.next());
+ assertFalse(notLastIt.hasNext());
+ assertThrows(NoSuchElementException.class, notLastIt::next);
+ }
+
+ @Test
+ public void testSerialization() {
+ CharRange range = CharRange.is('a');
+ assertEquals(range, SerializationUtils.clone(range));
+ range = CharRange.isIn('a', 'e');
+ assertEquals(range, SerializationUtils.clone(range));
+ range = CharRange.isNotIn('a', 'e');
+ assertEquals(range, SerializationUtils.clone(range));
+ }
+
+ @Test
+ public void testIteratorRemove() {
+ final CharRange a = CharRange.is('a');
+ final Iterator<Character> aIt = a.iterator();
+ assertThrows(UnsupportedOperationException.class, aIt::remove);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java b/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java
new file mode 100644
index 000000000..7d095d3dd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java
@@ -0,0 +1,311 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.Random;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Tests CharSequenceUtils
+ */
+public class CharSequenceUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new CharSequenceUtils());
+ final Constructor<?>[] cons = CharSequenceUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(CharSequenceUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(CharSequenceUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testSubSequence() {
+ //
+ // null input
+ //
+ assertNull(CharSequenceUtils.subSequence(null, -1));
+ assertNull(CharSequenceUtils.subSequence(null, 0));
+ assertNull(CharSequenceUtils.subSequence(null, 1));
+ //
+ // non-null input
+ //
+ assertEquals(StringUtils.EMPTY, CharSequenceUtils.subSequence(StringUtils.EMPTY, 0));
+ assertEquals("012", CharSequenceUtils.subSequence("012", 0));
+ assertEquals("12", CharSequenceUtils.subSequence("012", 1));
+ assertEquals("2", CharSequenceUtils.subSequence("012", 2));
+ assertEquals(StringUtils.EMPTY, CharSequenceUtils.subSequence("012", 3));
+ }
+
+ @Test
+ public void testSubSequenceNegativeStart() {
+ assertThrows(IndexOutOfBoundsException.class, () -> CharSequenceUtils.subSequence(StringUtils.EMPTY, -1));
+ }
+
+ @Test
+ public void testSubSequenceTooLong() {
+ assertThrows(IndexOutOfBoundsException.class, () -> CharSequenceUtils.subSequence(StringUtils.EMPTY, 1));
+ }
+
+ static class TestData{
+ final String source;
+ final boolean ignoreCase;
+ final int toffset;
+ final String other;
+ final int ooffset;
+ final int len;
+ final boolean expected;
+ final Class<? extends Throwable> throwable;
+ TestData(final String source, final boolean ignoreCase, final int toffset,
+ final String other, final int ooffset, final int len, final boolean expected) {
+ this.source = source;
+ this.ignoreCase = ignoreCase;
+ this.toffset = toffset;
+ this.other = other;
+ this.ooffset = ooffset;
+ this.len = len;
+ this.expected = expected;
+ this.throwable = null;
+ }
+ TestData(final String source, final boolean ignoreCase, final int toffset,
+ final String other, final int ooffset, final int len, final Class<? extends Throwable> throwable) {
+ this.source = source;
+ this.ignoreCase = ignoreCase;
+ this.toffset = toffset;
+ this.other = other;
+ this.ooffset = ooffset;
+ this.len = len;
+ this.expected = false;
+ this.throwable = throwable;
+ }
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(source).append("[").append(toffset).append("]");
+ sb.append(ignoreCase? " caseblind ":" samecase ");
+ sb.append(other).append("[").append(ooffset).append("]");
+ sb.append(" ").append(len).append(" => ");
+ if (throwable != null) {
+ sb.append(throwable);
+ } else {
+ sb.append(expected);
+ }
+ return sb.toString();
+ }
+ }
+
+ private static final TestData[] TEST_DATA = {
+ // Source IgnoreCase Offset Other Offset Length Result
+ new TestData("", true, -1, "", -1, -1, false),
+ new TestData("", true, 0, "", 0, 1, false),
+ new TestData("a", true, 0, "abc", 0, 0, true),
+ new TestData("a", true, 0, "abc", 0, 1, true),
+ new TestData("a", true, 0, null, 0, 0, NullPointerException.class),
+ new TestData(null, true, 0, null, 0, 0, NullPointerException.class),
+ new TestData(null, true, 0, "", 0, 0, NullPointerException.class),
+ new TestData("Abc", true, 0, "abc", 0, 3, true),
+ new TestData("Abc", false, 0, "abc", 0, 3, false),
+ new TestData("Abc", true, 1, "abc", 1, 2, true),
+ new TestData("Abc", false, 1, "abc", 1, 2, true),
+ new TestData("Abcd", true, 1, "abcD", 1, 2, true),
+ new TestData("Abcd", false, 1, "abcD", 1, 2, true),
+ };
+
+ private abstract static class RunTest {
+
+ abstract boolean invoke();
+
+ void run(final TestData data, final String id) {
+ if (data.throwable != null) {
+ assertThrows(data.throwable, this::invoke, id + " Expected " + data.throwable);
+ } else {
+ final boolean stringCheck = invoke();
+ assertEquals(data.expected, stringCheck, id + " Failed test " + data);
+ }
+ }
+
+ }
+
+ @Test
+ public void testRegionMatches() {
+ for (final TestData data : TEST_DATA) {
+ new RunTest() {
+ @Override
+ boolean invoke() {
+ return data.source.regionMatches(data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
+ }
+ }.run(data, "String");
+ new RunTest() {
+ @Override
+ boolean invoke() {
+ return CharSequenceUtils.regionMatches(data.source, data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
+ }
+ }.run(data, "CSString");
+ new RunTest() {
+ @Override
+ boolean invoke() {
+ return CharSequenceUtils.regionMatches(new StringBuilder(data.source), data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
+ }
+ }.run(data, "CSNonString");
+ }
+ }
+
+
+ @Test
+ public void testToCharArray() {
+ final StringBuilder builder = new StringBuilder("abcdefg");
+ final char[] expected = builder.toString().toCharArray();
+ assertArrayEquals(expected, CharSequenceUtils.toCharArray(builder));
+ assertArrayEquals(expected, CharSequenceUtils.toCharArray(builder.toString()));
+ assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, CharSequenceUtils.toCharArray(null));
+ }
+
+ static class WrapperString implements CharSequence {
+ private final CharSequence inner;
+
+ WrapperString(final CharSequence inner) {
+ this.inner = inner;
+ }
+
+ @Override
+ public int length() {
+ return inner.length();
+ }
+
+ @Override
+ public char charAt(final int index) {
+ return inner.charAt(index);
+ }
+
+ @Override
+ public CharSequence subSequence(final int start, final int end) {
+ return inner.subSequence(start, end);
+ }
+
+ @Override
+ public String toString() {
+ return inner.toString();
+ }
+
+ @Override
+ public IntStream chars() {
+ return inner.chars();
+ }
+
+ @Override
+ public IntStream codePoints() {
+ return inner.codePoints();
+ }
+ }
+
+ @Test
+ public void testNewLastIndexOf() {
+ testNewLastIndexOfSingle("808087847-1321060740-635567660180086727-925755305", "-1321060740-635567660", 21);
+ testNewLastIndexOfSingle("", "");
+ testNewLastIndexOfSingle("1", "");
+ testNewLastIndexOfSingle("", "1");
+ testNewLastIndexOfSingle("1", "1");
+ testNewLastIndexOfSingle("11", "1");
+ testNewLastIndexOfSingle("1", "11");
+
+ testNewLastIndexOfSingle("apache", "a");
+ testNewLastIndexOfSingle("apache", "p");
+ testNewLastIndexOfSingle("apache", "e");
+ testNewLastIndexOfSingle("apache", "x");
+ testNewLastIndexOfSingle("oraoraoraora", "r");
+ testNewLastIndexOfSingle("mudamudamudamuda", "d");
+ // There is a route through checkLaterThan1#checkLaterThan1
+ // which only gets touched if there is a two letter (or more) partial match
+ // (in this case "st") earlier in the searched string.
+ testNewLastIndexOfSingle("junk-ststarting", "starting");
+
+ final Random random = new Random();
+ final StringBuilder seg = new StringBuilder();
+ while (seg.length() <= CharSequenceUtils.TO_STRING_LIMIT) {
+ seg.append(random.nextInt());
+ }
+ StringBuilder original = new StringBuilder(seg);
+ testNewLastIndexOfSingle(original, seg);
+ for (int i = 0; i < 100; i++) {
+ if (random.nextDouble() < 0.5) {
+ original.append(random.nextInt() % 10);
+ } else {
+ original = new StringBuilder().append(String.valueOf(random.nextInt() % 100)).append(original);
+ }
+ testNewLastIndexOfSingle(original, seg);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("lastIndexWithStandardCharSequence")
+ public void testLastIndexOfWithDifferentCharSequences(final CharSequence cs, final CharSequence search, final int start,
+ final int expected) {
+ assertEquals(expected, CharSequenceUtils.lastIndexOf(cs, search, start));
+ }
+
+ static Stream<Arguments> lastIndexWithStandardCharSequence() {
+ return Stream.of(
+ arguments("abc", "b", 2, 1),
+ arguments(new StringBuilder("abc"), "b", 2, 1),
+ arguments(new StringBuffer("abc"), "b", 2, 1),
+ arguments("abc", new StringBuilder("b"), 2, 1),
+ arguments(new StringBuilder("abc"), new StringBuilder("b"), 2, 1),
+ arguments(new StringBuffer("abc"), new StringBuffer("b"), 2, 1),
+ arguments(new StringBuilder("abc"), new StringBuffer("b"), 2, 1)
+ );
+ }
+
+ private void testNewLastIndexOfSingle(final CharSequence a, final CharSequence b) {
+ final int maxa = Math.max(a.length(), b.length());
+ for (int i = -maxa - 10; i <= maxa + 10; i++) {
+ testNewLastIndexOfSingle(a, b, i);
+ }
+ testNewLastIndexOfSingle(a, b, Integer.MIN_VALUE);
+ testNewLastIndexOfSingle(a, b, Integer.MAX_VALUE);
+ }
+
+ private void testNewLastIndexOfSingle(final CharSequence a, final CharSequence b, final int start) {
+ testNewLastIndexOfSingleSingle(a, b, start);
+ testNewLastIndexOfSingleSingle(b, a, start);
+ }
+
+ private void testNewLastIndexOfSingleSingle(final CharSequence a, final CharSequence b, final int start) {
+ assertEquals(
+ a.toString().lastIndexOf(b.toString(), start),
+ CharSequenceUtils.lastIndexOf(new WrapperString(a.toString()), new WrapperString(b.toString()), start),
+ "testNewLastIndexOf fails! original : " + a + " seg : " + b + " start : " + start
+ );
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/CharSetTest.java b/src/test/java/org/apache/commons/lang3/CharSetTest.java
new file mode 100644
index 000000000..21f394a52
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharSetTest.java
@@ -0,0 +1,471 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Modifier;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.CharSet}.
+ */
+public class CharSetTest extends AbstractLangTest {
+
+ @Test
+ public void testClass() {
+ assertTrue(Modifier.isPublic(CharSet.class.getModifiers()));
+ assertFalse(Modifier.isFinal(CharSet.class.getModifiers()));
+ }
+
+ @Test
+ public void testGetInstance() {
+ assertSame(CharSet.EMPTY, CharSet.getInstance( (String) null));
+ assertSame(CharSet.EMPTY, CharSet.getInstance(""));
+ assertSame(CharSet.ASCII_ALPHA, CharSet.getInstance("a-zA-Z"));
+ assertSame(CharSet.ASCII_ALPHA, CharSet.getInstance("A-Za-z"));
+ assertSame(CharSet.ASCII_ALPHA_LOWER, CharSet.getInstance("a-z"));
+ assertSame(CharSet.ASCII_ALPHA_UPPER, CharSet.getInstance("A-Z"));
+ assertSame(CharSet.ASCII_NUMERIC, CharSet.getInstance("0-9"));
+ }
+
+ @Test
+ public void testGetInstance_Stringarray() {
+ assertNull(CharSet.getInstance((String[]) null));
+ assertEquals("[]", CharSet.getInstance(new String[0]).toString());
+ assertEquals("[]", CharSet.getInstance(new String[] {null}).toString());
+ assertEquals("[a-e]", CharSet.getInstance(new String[] {"a-e"}).toString());
+ }
+
+ @Test
+ public void testConstructor_String_simple() {
+ CharSet set;
+ CharRange[] array;
+
+ set = CharSet.getInstance((String) null);
+ array = set.getCharRanges();
+ assertEquals("[]", set.toString());
+ assertEquals(0, array.length);
+
+ set = CharSet.getInstance("");
+ array = set.getCharRanges();
+ assertEquals("[]", set.toString());
+ assertEquals(0, array.length);
+
+ set = CharSet.getInstance("a");
+ array = set.getCharRanges();
+ assertEquals("[a]", set.toString());
+ assertEquals(1, array.length);
+ assertEquals("a", array[0].toString());
+
+ set = CharSet.getInstance("^a");
+ array = set.getCharRanges();
+ assertEquals("[^a]", set.toString());
+ assertEquals(1, array.length);
+ assertEquals("^a", array[0].toString());
+
+ set = CharSet.getInstance("a-e");
+ array = set.getCharRanges();
+ assertEquals("[a-e]", set.toString());
+ assertEquals(1, array.length);
+ assertEquals("a-e", array[0].toString());
+
+ set = CharSet.getInstance("^a-e");
+ array = set.getCharRanges();
+ assertEquals("[^a-e]", set.toString());
+ assertEquals(1, array.length);
+ assertEquals("^a-e", array[0].toString());
+ }
+
+ @Test
+ public void testConstructor_String_combo() {
+ CharSet set;
+ CharRange[] array;
+
+ set = CharSet.getInstance("abc");
+ array = set.getCharRanges();
+ assertEquals(3, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('a')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('b')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('c')));
+
+ set = CharSet.getInstance("a-ce-f");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('a', 'c')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('e', 'f')));
+
+ set = CharSet.getInstance("ae-f");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('a')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('e', 'f')));
+
+ set = CharSet.getInstance("e-fa");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('a')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('e', 'f')));
+
+ set = CharSet.getInstance("ae-fm-pz");
+ array = set.getCharRanges();
+ assertEquals(4, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('a')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('e', 'f')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('m', 'p')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('z')));
+ }
+
+ @Test
+ public void testConstructor_String_comboNegated() {
+ CharSet set;
+ CharRange[] array;
+
+ set = CharSet.getInstance("^abc");
+ array = set.getCharRanges();
+ assertEquals(3, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('a')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('b')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('c')));
+
+ set = CharSet.getInstance("b^ac");
+ array = set.getCharRanges();
+ assertEquals(3, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('b')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('a')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('c')));
+
+ set = CharSet.getInstance("db^ac");
+ array = set.getCharRanges();
+ assertEquals(4, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('d')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('b')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('a')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('c')));
+
+ set = CharSet.getInstance("^b^a");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('b')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('a')));
+
+ set = CharSet.getInstance("b^a-c^z");
+ array = set.getCharRanges();
+ assertEquals(3, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNotIn('a', 'c')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('z')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('b')));
+ }
+
+ @Test
+ public void testConstructor_String_oddDash() {
+ CharSet set;
+ CharRange[] array;
+
+ set = CharSet.getInstance("-");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('-')));
+
+ set = CharSet.getInstance("--");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('-')));
+
+ set = CharSet.getInstance("---");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('-')));
+
+ set = CharSet.getInstance("----");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('-')));
+
+ set = CharSet.getInstance("-a");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('-')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('a')));
+
+ set = CharSet.getInstance("a-");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('a')));
+ assertTrue(ArrayUtils.contains(array, CharRange.is('-')));
+
+ set = CharSet.getInstance("a--");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('a', '-')));
+
+ set = CharSet.getInstance("--a");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('-', 'a')));
+ }
+
+ @Test
+ public void testConstructor_String_oddNegate() {
+ CharSet set;
+ CharRange[] array;
+ set = CharSet.getInstance("^");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('^'))); // "^"
+
+ set = CharSet.getInstance("^^");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('^'))); // "^^"
+
+ set = CharSet.getInstance("^^^");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('^'))); // "^^"
+ assertTrue(ArrayUtils.contains(array, CharRange.is('^'))); // "^"
+
+ set = CharSet.getInstance("^^^^");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('^'))); // "^^" x2
+
+ set = CharSet.getInstance("a^");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.is('a'))); // "a"
+ assertTrue(ArrayUtils.contains(array, CharRange.is('^'))); // "^"
+
+ set = CharSet.getInstance("^a-");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('a'))); // "^a"
+ assertTrue(ArrayUtils.contains(array, CharRange.is('-'))); // "-"
+
+ set = CharSet.getInstance("^^-c");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNotIn('^', 'c'))); // "^^-c"
+
+ set = CharSet.getInstance("^c-^");
+ array = set.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNotIn('c', '^'))); // "^c-^"
+
+ set = CharSet.getInstance("^c-^d");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNotIn('c', '^'))); // "^c-^"
+ assertTrue(ArrayUtils.contains(array, CharRange.is('d'))); // "d"
+
+ set = CharSet.getInstance("^^-");
+ array = set.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isNot('^'))); // "^^"
+ assertTrue(ArrayUtils.contains(array, CharRange.is('-'))); // "-"
+ }
+
+ @Test
+ public void testConstructor_String_oddCombinations() {
+ CharSet set;
+ CharRange[] array;
+
+ set = CharSet.getInstance("a-^c");
+ array = set.getCharRanges();
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('a', '^'))); // "a-^"
+ assertTrue(ArrayUtils.contains(array, CharRange.is('c'))); // "c"
+ assertFalse(set.contains('b'));
+ assertTrue(set.contains('^'));
+ assertTrue(set.contains('_')); // between ^ and a
+ assertTrue(set.contains('c'));
+
+ set = CharSet.getInstance("^a-^c");
+ array = set.getCharRanges();
+ assertTrue(ArrayUtils.contains(array, CharRange.isNotIn('a', '^'))); // "^a-^"
+ assertTrue(ArrayUtils.contains(array, CharRange.is('c'))); // "c"
+ assertTrue(set.contains('b'));
+ assertFalse(set.contains('^'));
+ assertFalse(set.contains('_')); // between ^ and a
+
+ set = CharSet.getInstance("a- ^-- "); //contains everything
+ array = set.getCharRanges();
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('a', ' '))); // "a- "
+ assertTrue(ArrayUtils.contains(array, CharRange.isNotIn('-', ' '))); // "^-- "
+ assertTrue(set.contains('#'));
+ assertTrue(set.contains('^'));
+ assertTrue(set.contains('a'));
+ assertTrue(set.contains('*'));
+ assertTrue(set.contains('A'));
+
+ set = CharSet.getInstance("^-b");
+ array = set.getCharRanges();
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('^', 'b'))); // "^-b"
+ assertTrue(set.contains('b'));
+ assertTrue(set.contains('_')); // between ^ and a
+ assertFalse(set.contains('A'));
+ assertTrue(set.contains('^'));
+
+ set = CharSet.getInstance("b-^");
+ array = set.getCharRanges();
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('^', 'b'))); // "b-^"
+ assertTrue(set.contains('b'));
+ assertTrue(set.contains('^'));
+ assertTrue(set.contains('a')); // between ^ and b
+ assertFalse(set.contains('c'));
+ }
+
+ @Test
+ public void testEquals_Object() {
+ final CharSet abc = CharSet.getInstance("abc");
+ final CharSet abc2 = CharSet.getInstance("abc");
+ final CharSet atoc = CharSet.getInstance("a-c");
+ final CharSet atoc2 = CharSet.getInstance("a-c");
+ final CharSet notatoc = CharSet.getInstance("^a-c");
+ final CharSet notatoc2 = CharSet.getInstance("^a-c");
+
+ assertNotEquals(null, abc);
+
+ assertEquals(abc, abc);
+ assertEquals(abc, abc2);
+ assertNotEquals(abc, atoc);
+ assertNotEquals(abc, notatoc);
+
+ assertNotEquals(atoc, abc);
+ assertEquals(atoc, atoc);
+ assertEquals(atoc, atoc2);
+ assertNotEquals(atoc, notatoc);
+
+ assertNotEquals(notatoc, abc);
+ assertNotEquals(notatoc, atoc);
+ assertEquals(notatoc, notatoc);
+ assertEquals(notatoc, notatoc2);
+ }
+
+ @Test
+ public void testHashCode() {
+ final CharSet abc = CharSet.getInstance("abc");
+ final CharSet abc2 = CharSet.getInstance("abc");
+ final CharSet atoc = CharSet.getInstance("a-c");
+ final CharSet atoc2 = CharSet.getInstance("a-c");
+ final CharSet notatoc = CharSet.getInstance("^a-c");
+ final CharSet notatoc2 = CharSet.getInstance("^a-c");
+
+ assertEquals(abc.hashCode(), abc.hashCode());
+ assertEquals(abc.hashCode(), abc2.hashCode());
+ assertEquals(atoc.hashCode(), atoc.hashCode());
+ assertEquals(atoc.hashCode(), atoc2.hashCode());
+ assertEquals(notatoc.hashCode(), notatoc.hashCode());
+ assertEquals(notatoc.hashCode(), notatoc2.hashCode());
+ }
+
+ @Test
+ public void testContains_Char() {
+ final CharSet btod = CharSet.getInstance("b-d");
+ final CharSet dtob = CharSet.getInstance("d-b");
+ final CharSet bcd = CharSet.getInstance("bcd");
+ final CharSet bd = CharSet.getInstance("bd");
+ final CharSet notbtod = CharSet.getInstance("^b-d");
+
+ assertFalse(btod.contains('a'));
+ assertTrue(btod.contains('b'));
+ assertTrue(btod.contains('c'));
+ assertTrue(btod.contains('d'));
+ assertFalse(btod.contains('e'));
+
+ assertFalse(bcd.contains('a'));
+ assertTrue(bcd.contains('b'));
+ assertTrue(bcd.contains('c'));
+ assertTrue(bcd.contains('d'));
+ assertFalse(bcd.contains('e'));
+
+ assertFalse(bd.contains('a'));
+ assertTrue(bd.contains('b'));
+ assertFalse(bd.contains('c'));
+ assertTrue(bd.contains('d'));
+ assertFalse(bd.contains('e'));
+
+ assertTrue(notbtod.contains('a'));
+ assertFalse(notbtod.contains('b'));
+ assertFalse(notbtod.contains('c'));
+ assertFalse(notbtod.contains('d'));
+ assertTrue(notbtod.contains('e'));
+
+ assertFalse(dtob.contains('a'));
+ assertTrue(dtob.contains('b'));
+ assertTrue(dtob.contains('c'));
+ assertTrue(dtob.contains('d'));
+ assertFalse(dtob.contains('e'));
+
+ final CharRange[] array = dtob.getCharRanges();
+ assertEquals("[b-d]", dtob.toString());
+ assertEquals(1, array.length);
+ }
+
+ @Test
+ public void testSerialization() {
+ CharSet set = CharSet.getInstance("a");
+ assertEquals(set, SerializationUtils.clone(set));
+ set = CharSet.getInstance("a-e");
+ assertEquals(set, SerializationUtils.clone(set));
+ set = CharSet.getInstance("be-f^a-z");
+ assertEquals(set, SerializationUtils.clone(set));
+ }
+
+ @Test
+ public void testStatics() {
+ CharRange[] array;
+
+ array = CharSet.EMPTY.getCharRanges();
+ assertEquals(0, array.length);
+
+ array = CharSet.ASCII_ALPHA.getCharRanges();
+ assertEquals(2, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('a', 'z')));
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('A', 'Z')));
+
+ array = CharSet.ASCII_ALPHA_LOWER.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('a', 'z')));
+
+ array = CharSet.ASCII_ALPHA_UPPER.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('A', 'Z')));
+
+ array = CharSet.ASCII_NUMERIC.getCharRanges();
+ assertEquals(1, array.length);
+ assertTrue(ArrayUtils.contains(array, CharRange.isIn('0', '9')));
+ }
+
+ @Test
+ public void testJavadocExamples() {
+ assertFalse(CharSet.getInstance("^a-c").contains('a'));
+ assertTrue(CharSet.getInstance("^a-c").contains('d'));
+ assertTrue(CharSet.getInstance("^^a-c").contains('a'));
+ assertFalse(CharSet.getInstance("^^a-c").contains('^'));
+ assertTrue(CharSet.getInstance("^a-cd-f").contains('d'));
+ assertTrue(CharSet.getInstance("a-c^").contains('^'));
+ assertTrue(CharSet.getInstance("^", "a-c").contains('^'));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/CharSetUtilsTest.java b/src/test/java/org/apache/commons/lang3/CharSetUtilsTest.java
new file mode 100644
index 000000000..b6b5e7925
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharSetUtilsTest.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.CharSetUtils}.
+ */
+public class CharSetUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new CharSetUtils());
+ final Constructor<?>[] cons = CharSetUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(CharSetUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(CharSetUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testSqueeze_StringString() {
+ assertNull(CharSetUtils.squeeze(null, (String) null));
+ assertNull(CharSetUtils.squeeze(null, ""));
+
+ assertEquals("", CharSetUtils.squeeze("", (String) null));
+ assertEquals("", CharSetUtils.squeeze("", ""));
+ assertEquals("", CharSetUtils.squeeze("", "a-e"));
+
+ assertEquals("hello", CharSetUtils.squeeze("hello", (String) null));
+ assertEquals("hello", CharSetUtils.squeeze("hello", ""));
+ assertEquals("hello", CharSetUtils.squeeze("hello", "a-e"));
+ assertEquals("helo", CharSetUtils.squeeze("hello", "l-p"));
+ assertEquals("heloo", CharSetUtils.squeeze("helloo", "l"));
+ assertEquals("hello", CharSetUtils.squeeze("helloo", "^l"));
+ }
+
+ @Test
+ public void testSqueeze_StringStringarray() {
+ assertNull(CharSetUtils.squeeze(null, (String[]) null));
+ assertNull(CharSetUtils.squeeze(null));
+ assertNull(CharSetUtils.squeeze(null, null));
+ assertNull(CharSetUtils.squeeze(null, "el"));
+
+ assertEquals("", CharSetUtils.squeeze("", (String[]) null));
+ assertEquals("", CharSetUtils.squeeze(""));
+ assertEquals("", CharSetUtils.squeeze("", null));
+ assertEquals("", CharSetUtils.squeeze("", "a-e"));
+
+ assertEquals("hello", CharSetUtils.squeeze("hello", (String[]) null));
+ assertEquals("hello", CharSetUtils.squeeze("hello"));
+ assertEquals("hello", CharSetUtils.squeeze("hello", null));
+ assertEquals("hello", CharSetUtils.squeeze("hello", "a-e"));
+
+ assertEquals("helo", CharSetUtils.squeeze("hello", "el"));
+ assertEquals("hello", CharSetUtils.squeeze("hello", "e"));
+ assertEquals("fofof", CharSetUtils.squeeze("fooffooff", "of"));
+ assertEquals("fof", CharSetUtils.squeeze("fooooff", "fo"));
+ }
+
+ @Test
+ public void testContainsAny_StringString() {
+ assertFalse(CharSetUtils.containsAny(null, (String) null));
+ assertFalse(CharSetUtils.containsAny(null, ""));
+
+ assertFalse(CharSetUtils.containsAny("", (String) null));
+ assertFalse(CharSetUtils.containsAny("", ""));
+ assertFalse(CharSetUtils.containsAny("", "a-e"));
+
+ assertFalse(CharSetUtils.containsAny("hello", (String) null));
+ assertFalse(CharSetUtils.containsAny("hello", ""));
+ assertTrue(CharSetUtils.containsAny("hello", "a-e"));
+ assertTrue(CharSetUtils.containsAny("hello", "l-p"));
+ }
+
+ @Test
+ public void testContainsAny_StringStringarray() {
+ assertFalse(CharSetUtils.containsAny(null, (String[]) null));
+ assertFalse(CharSetUtils.containsAny(null));
+ assertFalse(CharSetUtils.containsAny(null, null));
+ assertFalse(CharSetUtils.containsAny(null, "a-e"));
+
+ assertFalse(CharSetUtils.containsAny("", (String[]) null));
+ assertFalse(CharSetUtils.containsAny(""));
+ assertFalse(CharSetUtils.containsAny("", null));
+ assertFalse(CharSetUtils.containsAny("", "a-e"));
+
+ assertFalse(CharSetUtils.containsAny("hello", (String[]) null));
+ assertFalse(CharSetUtils.containsAny("hello"));
+ assertFalse(CharSetUtils.containsAny("hello", null));
+ assertTrue(CharSetUtils.containsAny("hello", "a-e"));
+
+ assertTrue(CharSetUtils.containsAny("hello", "el"));
+ assertFalse(CharSetUtils.containsAny("hello", "x"));
+ assertTrue(CharSetUtils.containsAny("hello", "e-i"));
+ assertTrue(CharSetUtils.containsAny("hello", "a-z"));
+ assertFalse(CharSetUtils.containsAny("hello", ""));
+ }
+
+ @Test
+ public void testCount_StringString() {
+ assertEquals(0, CharSetUtils.count(null, (String) null));
+ assertEquals(0, CharSetUtils.count(null, ""));
+
+ assertEquals(0, CharSetUtils.count("", (String) null));
+ assertEquals(0, CharSetUtils.count("", ""));
+ assertEquals(0, CharSetUtils.count("", "a-e"));
+
+ assertEquals(0, CharSetUtils.count("hello", (String) null));
+ assertEquals(0, CharSetUtils.count("hello", ""));
+ assertEquals(1, CharSetUtils.count("hello", "a-e"));
+ assertEquals(3, CharSetUtils.count("hello", "l-p"));
+ }
+
+ @Test
+ public void testCount_StringStringarray() {
+ assertEquals(0, CharSetUtils.count(null, (String[]) null));
+ assertEquals(0, CharSetUtils.count(null));
+ assertEquals(0, CharSetUtils.count(null, null));
+ assertEquals(0, CharSetUtils.count(null, "a-e"));
+
+ assertEquals(0, CharSetUtils.count("", (String[]) null));
+ assertEquals(0, CharSetUtils.count(""));
+ assertEquals(0, CharSetUtils.count("", null));
+ assertEquals(0, CharSetUtils.count("", "a-e"));
+
+ assertEquals(0, CharSetUtils.count("hello", (String[]) null));
+ assertEquals(0, CharSetUtils.count("hello"));
+ assertEquals(0, CharSetUtils.count("hello", null));
+ assertEquals(1, CharSetUtils.count("hello", "a-e"));
+
+ assertEquals(3, CharSetUtils.count("hello", "el"));
+ assertEquals(0, CharSetUtils.count("hello", "x"));
+ assertEquals(2, CharSetUtils.count("hello", "e-i"));
+ assertEquals(5, CharSetUtils.count("hello", "a-z"));
+ assertEquals(0, CharSetUtils.count("hello", ""));
+ }
+
+ @Test
+ public void testKeep_StringString() {
+ assertNull(CharSetUtils.keep(null, (String) null));
+ assertNull(CharSetUtils.keep(null, ""));
+
+ assertEquals("", CharSetUtils.keep("", (String) null));
+ assertEquals("", CharSetUtils.keep("", ""));
+ assertEquals("", CharSetUtils.keep("", "a-e"));
+
+ assertEquals("", CharSetUtils.keep("hello", (String) null));
+ assertEquals("", CharSetUtils.keep("hello", ""));
+ assertEquals("", CharSetUtils.keep("hello", "xyz"));
+ assertEquals("hello", CharSetUtils.keep("hello", "a-z"));
+ assertEquals("hello", CharSetUtils.keep("hello", "oleh"));
+ assertEquals("ell", CharSetUtils.keep("hello", "el"));
+ }
+
+ @Test
+ public void testKeep_StringStringarray() {
+ assertNull(CharSetUtils.keep(null, (String[]) null));
+ assertNull(CharSetUtils.keep(null));
+ assertNull(CharSetUtils.keep(null, null));
+ assertNull(CharSetUtils.keep(null, "a-e"));
+
+ assertEquals("", CharSetUtils.keep("", (String[]) null));
+ assertEquals("", CharSetUtils.keep(""));
+ assertEquals("", CharSetUtils.keep("", null));
+ assertEquals("", CharSetUtils.keep("", "a-e"));
+
+ assertEquals("", CharSetUtils.keep("hello", (String[]) null));
+ assertEquals("", CharSetUtils.keep("hello"));
+ assertEquals("", CharSetUtils.keep("hello", null));
+ assertEquals("e", CharSetUtils.keep("hello", "a-e"));
+
+ assertEquals("e", CharSetUtils.keep("hello", "a-e"));
+ assertEquals("ell", CharSetUtils.keep("hello", "el"));
+ assertEquals("hello", CharSetUtils.keep("hello", "elho"));
+ assertEquals("hello", CharSetUtils.keep("hello", "a-z"));
+ assertEquals("----", CharSetUtils.keep("----", "-"));
+ assertEquals("ll", CharSetUtils.keep("hello", "l"));
+ }
+
+ @Test
+ public void testDelete_StringString() {
+ assertNull(CharSetUtils.delete(null, (String) null));
+ assertNull(CharSetUtils.delete(null, ""));
+
+ assertEquals("", CharSetUtils.delete("", (String) null));
+ assertEquals("", CharSetUtils.delete("", ""));
+ assertEquals("", CharSetUtils.delete("", "a-e"));
+
+ assertEquals("hello", CharSetUtils.delete("hello", (String) null));
+ assertEquals("hello", CharSetUtils.delete("hello", ""));
+ assertEquals("hllo", CharSetUtils.delete("hello", "a-e"));
+ assertEquals("he", CharSetUtils.delete("hello", "l-p"));
+ assertEquals("hello", CharSetUtils.delete("hello", "z"));
+ }
+
+ @Test
+ public void testDelete_StringStringarray() {
+ assertNull(CharSetUtils.delete(null, (String[]) null));
+ assertNull(CharSetUtils.delete(null));
+ assertNull(CharSetUtils.delete(null, null));
+ assertNull(CharSetUtils.delete(null, "el"));
+
+ assertEquals("", CharSetUtils.delete("", (String[]) null));
+ assertEquals("", CharSetUtils.delete(""));
+ assertEquals("", CharSetUtils.delete("", null));
+ assertEquals("", CharSetUtils.delete("", "a-e"));
+
+ assertEquals("hello", CharSetUtils.delete("hello", (String[]) null));
+ assertEquals("hello", CharSetUtils.delete("hello"));
+ assertEquals("hello", CharSetUtils.delete("hello", null));
+ assertEquals("hello", CharSetUtils.delete("hello", "xyz"));
+
+ assertEquals("ho", CharSetUtils.delete("hello", "el"));
+ assertEquals("", CharSetUtils.delete("hello", "elho"));
+ assertEquals("hello", CharSetUtils.delete("hello", ""));
+ assertEquals("hello", CharSetUtils.delete("hello", ""));
+ assertEquals("", CharSetUtils.delete("hello", "a-z"));
+ assertEquals("", CharSetUtils.delete("----", "-"));
+ assertEquals("heo", CharSetUtils.delete("hello", "l"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/CharUtilsPerfRun.java b/src/test/java/org/apache/commons/lang3/CharUtilsPerfRun.java
new file mode 100644
index 000000000..1aa4d9879
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharUtilsPerfRun.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.text.NumberFormat;
+import java.util.Calendar;
+
+/**
+ * Tests the difference in performance between CharUtils and CharSet.
+ *
+ * Sample runs:
+
+Now: Thu Mar 18 14:29:48 PST 2004
+Sun Microsystems Inc. Java(TM) 2 Runtime Environment, Standard Edition 1.3.1_10-b03
+Sun Microsystems Inc. Java HotSpot(TM) Client VM 1.3.1_10-b03
+Windows XP 5.1 x86 pentium i486 i386
+Do nothing: 0 milliseconds.
+run_CharUtils_isAsciiNumeric: 4,545 milliseconds.
+run_inlined_CharUtils_isAsciiNumeric: 3,417 milliseconds.
+run_inlined_CharUtils_isAsciiNumeric: 85,679 milliseconds.
+
+
+Now: Thu Mar 18 14:24:51 PST 2004
+Sun Microsystems Inc. Java(TM) 2 Runtime Environment, Standard Edition 1.4.2_04-b05
+Sun Microsystems Inc. Java HotSpot(TM) Client VM 1.4.2_04-b05
+Windows XP 5.1 x86 pentium i486 i386
+Do nothing: 0 milliseconds.
+run_CharUtils_isAsciiNumeric: 2,578 milliseconds.
+run_inlined_CharUtils_isAsciiNumeric: 2,477 milliseconds.
+run_inlined_CharUtils_isAsciiNumeric: 114,429 milliseconds.
+
+Now: Thu Mar 18 14:27:55 PST 2004
+Sun Microsystems Inc. Java(TM) 2 Runtime Environment, Standard Edition 1.4.2_04-b05
+Sun Microsystems Inc. Java HotSpot(TM) Server VM 1.4.2_04-b05
+Windows XP 5.1 x86 pentium i486 i386
+Do nothing: 0 milliseconds.
+run_CharUtils_isAsciiNumeric: 630 milliseconds.
+run_inlined_CharUtils_isAsciiNumeric: 709 milliseconds.
+run_inlined_CharUtils_isAsciiNumeric: 84,420 milliseconds.
+
+
+ */
+public class CharUtilsPerfRun {
+ private static final String VERSION = "$Id$";
+
+ private static final int WARM_UP = 100;
+
+ private static final int COUNT = 5000;
+
+ private static final char[] CHAR_SAMPLES;
+
+ static {
+ CHAR_SAMPLES = new char[Character.MAX_VALUE];
+ for (char i = Character.MIN_VALUE; i < Character.MAX_VALUE; i++) {
+ CHAR_SAMPLES[i] = i;
+ }
+ }
+
+ public static void main(final String[] args) {
+ new CharUtilsPerfRun().run();
+ }
+
+ private void printSysInfo() {
+ System.out.println(VERSION);
+ System.out.println("Now: " + Calendar.getInstance().getTime());
+ System.out.println(System.getProperty("java.vendor")
+ + " "
+ + System.getProperty("java.runtime.name")
+ + " "
+ + System.getProperty("java.runtime.version"));
+ System.out.println(System.getProperty("java.vm.vendor")
+ + " "
+ + System.getProperty("java.vm.name")
+ + " "
+ + System.getProperty("java.vm.version"));
+ System.out.println(System.getProperty("os.name")
+ + " "
+ + System.getProperty("os.version")
+ + " "
+ + System.getProperty("os.arch")
+ + " "
+ + System.getProperty("sun.cpu.isalist"));
+ }
+
+ private void run() {
+ this.printSysInfo();
+ long startMillis;
+ startMillis = System.currentTimeMillis();
+ this.printlnTotal("Do nothing", startMillis);
+ run_CharUtils_isAsciiNumeric(WARM_UP);
+ startMillis = System.currentTimeMillis();
+ run_CharUtils_isAsciiNumeric(COUNT);
+ this.printlnTotal("run_CharUtils_isAsciiNumeric", startMillis);
+ run_inlined_CharUtils_isAsciiNumeric(WARM_UP);
+ startMillis = System.currentTimeMillis();
+ run_inlined_CharUtils_isAsciiNumeric(COUNT);
+ this.printlnTotal("run_inlined_CharUtils_isAsciiNumeric", startMillis);
+ run_CharSet(WARM_UP);
+ startMillis = System.currentTimeMillis();
+ run_CharSet(COUNT);
+ this.printlnTotal("run_CharSet", startMillis);
+ }
+
+ private int run_CharSet(final int loopCount) {
+ int t = 0;
+ for (int i = 0; i < loopCount; i++) {
+ for (final char ch : CHAR_SAMPLES) {
+ final boolean b = CharSet.ASCII_NUMERIC.contains(ch);
+ t += b ? 1 : 0;
+ }
+ }
+ return t;
+ }
+
+ private int run_CharUtils_isAsciiNumeric(final int loopCount) {
+ int t = 0;
+ for (int i = 0; i < loopCount; i++) {
+ for (final char ch : CHAR_SAMPLES) {
+ final boolean b = CharUtils.isAsciiNumeric(ch);
+ t += b ? 1 : 0;
+ }
+ }
+ return t;
+ }
+
+ private int run_inlined_CharUtils_isAsciiNumeric(final int loopCount) {
+ int t = 0;
+ for (int i = 0; i < loopCount; i++) {
+ for (final char ch : CHAR_SAMPLES) {
+ final boolean b = ch >= '0' && ch <= '9';
+ t += b ? 1 : 0;
+ }
+ }
+ return t;
+ }
+
+ private void printlnTotal(final String prefix, final long startMillis) {
+ final long totalMillis = System.currentTimeMillis() - startMillis;
+ System.out.println(prefix + ": " + NumberFormat.getInstance().format(totalMillis) + " milliseconds.");
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/CharUtilsTest.java b/src/test/java/org/apache/commons/lang3/CharUtilsTest.java
new file mode 100644
index 000000000..aec0351f5
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharUtilsTest.java
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.CharUtils}.
+ */
+public class CharUtilsTest extends AbstractLangTest {
+
+ private static final char CHAR_COPY = '\u00a9';
+ private static final Character CHARACTER_A = Character.valueOf('A');
+ private static final Character CHARACTER_B = Character.valueOf('B');
+
+ @Test
+ public void testCompare() {
+ assertTrue(CharUtils.compare('a', 'b') < 0);
+ assertEquals(0, CharUtils.compare('c', 'c'));
+ assertTrue(CharUtils.compare('c', 'a') > 0);
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new CharUtils());
+ final Constructor<?>[] cons = CharUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(CharUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(CharUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testIsAscii_char() {
+ assertTrue(CharUtils.isAscii('a'));
+ assertTrue(CharUtils.isAscii('A'));
+ assertTrue(CharUtils.isAscii('3'));
+ assertTrue(CharUtils.isAscii('-'));
+ assertTrue(CharUtils.isAscii('\n'));
+ assertFalse(CharUtils.isAscii(CHAR_COPY));
+
+ for (int i = 0; i < 255; i++) {
+ assertEquals(i < 128, CharUtils.isAscii((char) i));
+ }
+ }
+
+ @Test
+ public void testIsAsciiAlpha_char() {
+ assertTrue(CharUtils.isAsciiAlpha('a'));
+ assertTrue(CharUtils.isAsciiAlpha('A'));
+ assertFalse(CharUtils.isAsciiAlpha('3'));
+ assertFalse(CharUtils.isAsciiAlpha('-'));
+ assertFalse(CharUtils.isAsciiAlpha('\n'));
+ assertFalse(CharUtils.isAsciiAlpha(CHAR_COPY));
+
+ for (int i = 0; i < 196; i++) {
+ if ((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z')) {
+ assertTrue(CharUtils.isAsciiAlpha((char) i));
+ } else {
+ assertFalse(CharUtils.isAsciiAlpha((char) i));
+ }
+ }
+ }
+
+ @Test
+ public void testIsAsciiAlphaLower_char() {
+ assertTrue(CharUtils.isAsciiAlphaLower('a'));
+ assertFalse(CharUtils.isAsciiAlphaLower('A'));
+ assertFalse(CharUtils.isAsciiAlphaLower('3'));
+ assertFalse(CharUtils.isAsciiAlphaLower('-'));
+ assertFalse(CharUtils.isAsciiAlphaLower('\n'));
+ assertFalse(CharUtils.isAsciiAlphaLower(CHAR_COPY));
+
+ for (int i = 0; i < 196; i++) {
+ if (i >= 'a' && i <= 'z') {
+ assertTrue(CharUtils.isAsciiAlphaLower((char) i));
+ } else {
+ assertFalse(CharUtils.isAsciiAlphaLower((char) i));
+ }
+ }
+ }
+
+ @Test
+ public void testIsAsciiAlphanumeric_char() {
+ assertTrue(CharUtils.isAsciiAlphanumeric('a'));
+ assertTrue(CharUtils.isAsciiAlphanumeric('A'));
+ assertTrue(CharUtils.isAsciiAlphanumeric('3'));
+ assertFalse(CharUtils.isAsciiAlphanumeric('-'));
+ assertFalse(CharUtils.isAsciiAlphanumeric('\n'));
+ assertFalse(CharUtils.isAsciiAlphanumeric(CHAR_COPY));
+
+ for (int i = 0; i < 196; i++) {
+ if ((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9')) {
+ assertTrue(CharUtils.isAsciiAlphanumeric((char) i));
+ } else {
+ assertFalse(CharUtils.isAsciiAlphanumeric((char) i));
+ }
+ }
+ }
+
+ @Test
+ public void testIsAsciiAlphaUpper_char() {
+ assertFalse(CharUtils.isAsciiAlphaUpper('a'));
+ assertTrue(CharUtils.isAsciiAlphaUpper('A'));
+ assertFalse(CharUtils.isAsciiAlphaUpper('3'));
+ assertFalse(CharUtils.isAsciiAlphaUpper('-'));
+ assertFalse(CharUtils.isAsciiAlphaUpper('\n'));
+ assertFalse(CharUtils.isAsciiAlphaUpper(CHAR_COPY));
+
+ for (int i = 0; i < 196; i++) {
+ if (i >= 'A' && i <= 'Z') {
+ assertTrue(CharUtils.isAsciiAlphaUpper((char) i));
+ } else {
+ assertFalse(CharUtils.isAsciiAlphaUpper((char) i));
+ }
+ }
+ }
+
+ @Test
+ public void testIsAsciiControl_char() {
+ assertFalse(CharUtils.isAsciiControl('a'));
+ assertFalse(CharUtils.isAsciiControl('A'));
+ assertFalse(CharUtils.isAsciiControl('3'));
+ assertFalse(CharUtils.isAsciiControl('-'));
+ assertTrue(CharUtils.isAsciiControl('\n'));
+ assertFalse(CharUtils.isAsciiControl(CHAR_COPY));
+
+ for (int i = 0; i < 196; i++) {
+ if (i < 32 || i == 127) {
+ assertTrue(CharUtils.isAsciiControl((char) i));
+ } else {
+ assertFalse(CharUtils.isAsciiControl((char) i));
+ }
+ }
+ }
+
+ @Test
+ public void testIsAsciiNumeric_char() {
+ assertFalse(CharUtils.isAsciiNumeric('a'));
+ assertFalse(CharUtils.isAsciiNumeric('A'));
+ assertTrue(CharUtils.isAsciiNumeric('3'));
+ assertFalse(CharUtils.isAsciiNumeric('-'));
+ assertFalse(CharUtils.isAsciiNumeric('\n'));
+ assertFalse(CharUtils.isAsciiNumeric(CHAR_COPY));
+
+ for (int i = 0; i < 196; i++) {
+ if (i >= '0' && i <= '9') {
+ assertTrue(CharUtils.isAsciiNumeric((char) i));
+ } else {
+ assertFalse(CharUtils.isAsciiNumeric((char) i));
+ }
+ }
+ }
+
+ @Test
+ public void testIsAsciiPrintable_char() {
+ assertTrue(CharUtils.isAsciiPrintable('a'));
+ assertTrue(CharUtils.isAsciiPrintable('A'));
+ assertTrue(CharUtils.isAsciiPrintable('3'));
+ assertTrue(CharUtils.isAsciiPrintable('-'));
+ assertFalse(CharUtils.isAsciiPrintable('\n'));
+ assertFalse(CharUtils.isAsciiPrintable(CHAR_COPY));
+
+ for (int i = 0; i < 196; i++) {
+ if (i >= 32 && i <= 126) {
+ assertTrue(CharUtils.isAsciiPrintable((char) i));
+ } else {
+ assertFalse(CharUtils.isAsciiPrintable((char) i));
+ }
+ }
+ }
+
+ @Test
+ public void testToChar_Character() {
+ assertEquals('A', CharUtils.toChar(CHARACTER_A));
+ assertEquals('B', CharUtils.toChar(CHARACTER_B));
+ assertThrows(NullPointerException.class, () -> CharUtils.toChar((Character) null));
+ }
+
+ @Test
+ public void testToChar_Character_char() {
+ assertEquals('A', CharUtils.toChar(CHARACTER_A, 'X'));
+ assertEquals('B', CharUtils.toChar(CHARACTER_B, 'X'));
+ assertEquals('X', CharUtils.toChar((Character) null, 'X'));
+ }
+
+ @Test
+ public void testToChar_String() {
+ assertEquals('A', CharUtils.toChar("A"));
+ assertEquals('B', CharUtils.toChar("BA"));
+ assertThrows(NullPointerException.class, () -> CharUtils.toChar((String) null));
+ assertThrows(IllegalArgumentException.class, () -> CharUtils.toChar(""));
+ }
+
+ @Test
+ public void testToChar_String_char() {
+ assertEquals('A', CharUtils.toChar("A", 'X'));
+ assertEquals('B', CharUtils.toChar("BA", 'X'));
+ assertEquals('X', CharUtils.toChar("", 'X'));
+ assertEquals('X', CharUtils.toChar((String) null, 'X'));
+ }
+
+ @SuppressWarnings("deprecation") // intentional test of deprecated method
+ @Test
+ public void testToCharacterObject_char() {
+ assertEquals(Character.valueOf('a'), CharUtils.toCharacterObject('a'));
+ assertSame(CharUtils.toCharacterObject('a'), CharUtils.toCharacterObject('a'));
+
+ for (int i = 0; i < 128; i++) {
+ final Character ch = CharUtils.toCharacterObject((char) i);
+ final Character ch2 = CharUtils.toCharacterObject((char) i);
+ assertSame(ch, ch2);
+ assertEquals(i, ch.charValue());
+ }
+ for (int i = 128; i < 196; i++) {
+ final Character ch = CharUtils.toCharacterObject((char) i);
+ final Character ch2 = CharUtils.toCharacterObject((char) i);
+ assertEquals(ch, ch2);
+ assertNotSame(ch, ch2);
+ assertEquals(i, ch.charValue());
+ assertEquals(i, ch2.charValue());
+ }
+ assertSame(CharUtils.toCharacterObject("a"), CharUtils.toCharacterObject('a'));
+ }
+
+ @Test
+ public void testToCharacterObject_String() {
+ assertNull(CharUtils.toCharacterObject(null));
+ assertNull(CharUtils.toCharacterObject(""));
+ assertEquals(Character.valueOf('a'), CharUtils.toCharacterObject("a"));
+ assertEquals(Character.valueOf('a'), CharUtils.toCharacterObject("abc"));
+ assertSame(CharUtils.toCharacterObject("a"), CharUtils.toCharacterObject("a"));
+ }
+
+ @Test
+ public void testToIntValue_char() {
+ assertEquals(0, CharUtils.toIntValue('0'));
+ assertEquals(1, CharUtils.toIntValue('1'));
+ assertEquals(2, CharUtils.toIntValue('2'));
+ assertEquals(3, CharUtils.toIntValue('3'));
+ assertEquals(4, CharUtils.toIntValue('4'));
+ assertEquals(5, CharUtils.toIntValue('5'));
+ assertEquals(6, CharUtils.toIntValue('6'));
+ assertEquals(7, CharUtils.toIntValue('7'));
+ assertEquals(8, CharUtils.toIntValue('8'));
+ assertEquals(9, CharUtils.toIntValue('9'));
+ assertThrows(IllegalArgumentException.class, () -> CharUtils.toIntValue('a'));
+ }
+
+ @Test
+ public void testToIntValue_char_int() {
+ assertEquals(0, CharUtils.toIntValue('0', -1));
+ assertEquals(3, CharUtils.toIntValue('3', -1));
+ assertEquals(-1, CharUtils.toIntValue('a', -1));
+ }
+
+ @Test
+ public void testToIntValue_Character() {
+ assertEquals(0, CharUtils.toIntValue(Character.valueOf('0')));
+ assertEquals(3, CharUtils.toIntValue(Character.valueOf('3')));
+ assertThrows(NullPointerException.class, () -> CharUtils.toIntValue(null));
+ assertThrows(IllegalArgumentException.class, () -> CharUtils.toIntValue(CHARACTER_A));
+ }
+
+ @Test
+ public void testToIntValue_Character_int() {
+ assertEquals(0, CharUtils.toIntValue(Character.valueOf('0'), -1));
+ assertEquals(3, CharUtils.toIntValue(Character.valueOf('3'), -1));
+ assertEquals(-1, CharUtils.toIntValue(Character.valueOf('A'), -1));
+ assertEquals(-1, CharUtils.toIntValue(null, -1));
+ }
+
+ @Test
+ public void testToString_char() {
+ assertEquals("a", CharUtils.toString('a'));
+ assertSame(CharUtils.toString('a'), CharUtils.toString('a'));
+
+ for (int i = 0; i < 128; i++) {
+ final String str = CharUtils.toString((char) i);
+ final String str2 = CharUtils.toString((char) i);
+ assertSame(str, str2);
+ assertEquals(1, str.length());
+ assertEquals(i, str.charAt(0));
+ }
+ for (int i = 128; i < 196; i++) {
+ final String str = CharUtils.toString((char) i);
+ final String str2 = CharUtils.toString((char) i);
+ assertEquals(str, str2);
+ assertNotSame(str, str2);
+ assertEquals(1, str.length());
+ assertEquals(i, str.charAt(0));
+ assertEquals(1, str2.length());
+ assertEquals(i, str2.charAt(0));
+ }
+ }
+
+ @Test
+ public void testToString_Character() {
+ assertNull(CharUtils.toString(null));
+ assertEquals("A", CharUtils.toString(CHARACTER_A));
+ assertSame(CharUtils.toString(CHARACTER_A), CharUtils.toString(CHARACTER_A));
+ }
+
+ @Test
+ public void testToUnicodeEscaped_char() {
+ assertEquals("\\u0041", CharUtils.unicodeEscaped('A'));
+ assertEquals("\\u004c", CharUtils.unicodeEscaped('L'));
+
+ for (int i = 0; i < 196; i++) {
+ final String str = CharUtils.unicodeEscaped((char) i);
+ assertEquals(6, str.length());
+ final int val = Integer.parseInt(str.substring(2), 16);
+ assertEquals(i, val);
+ }
+ assertEquals("\\u0999", CharUtils.unicodeEscaped((char) 0x999));
+ assertEquals("\\u1001", CharUtils.unicodeEscaped((char) 0x1001));
+ }
+
+ @Test
+ public void testToUnicodeEscaped_Character() {
+ assertNull(CharUtils.unicodeEscaped(null));
+ assertEquals("\\u0041", CharUtils.unicodeEscaped(CHARACTER_A));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/CharsetsTest.java b/src/test/java/org/apache/commons/lang3/CharsetsTest.java
new file mode 100644
index 000000000..2f62536ab
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharsetsTest.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;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link Charsets}.
+ */
+public class CharsetsTest extends AbstractLangTest {
+
+ @Test
+ public void testToCharset_Charset() {
+ Assertions.assertEquals(Charset.defaultCharset(), Charsets.toCharset((Charset) null));
+ Assertions.assertEquals(Charset.defaultCharset(), Charsets.toCharset(Charset.defaultCharset()));
+ Assertions.assertEquals(StandardCharsets.UTF_8, Charsets.toCharset(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testToCharset_String() {
+ Assertions.assertEquals(Charset.defaultCharset(), Charsets.toCharset((String) null));
+ Assertions.assertEquals(Charset.defaultCharset(), Charsets.toCharset(Charset.defaultCharset().name()));
+ Assertions.assertEquals(StandardCharsets.UTF_8, Charsets.toCharset("UTF-8"));
+ }
+
+ @Test
+ public void testToCharsetName() {
+ Assertions.assertEquals(Charset.defaultCharset().name(), Charsets.toCharsetName((String) null));
+ Assertions.assertEquals("UTF-8", Charsets.toCharsetName("UTF-8"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/ClassLoaderUtilsTest.java b/src/test/java/org/apache/commons/lang3/ClassLoaderUtilsTest.java
new file mode 100644
index 000000000..ea56f50c8
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ClassLoaderUtilsTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.net.URL;
+import java.net.URLClassLoader;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link ClassLoaderUtils}.
+ */
+public class ClassLoaderUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testToString_ClassLoader() throws IOException {
+ final URL url = new URL("http://localhost");
+ try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { url })) {
+ @SuppressWarnings("resource")
+ final ClassLoader classLoader = urlClassLoader;
+ Assertions.assertEquals(String.format("%s[%s]", classLoader, url), ClassLoaderUtils.toString(classLoader));
+ }
+ }
+
+ @Test
+ public void testToString_URLClassLoader() throws IOException {
+ final URL url = new URL("http://localhost");
+ try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { url })) {
+ Assertions.assertEquals(String.format("%s[%s]", urlClassLoader, url),
+ ClassLoaderUtils.toString(urlClassLoader));
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ClassPathUtilsTest.java b/src/test/java/org/apache/commons/lang3/ClassPathUtilsTest.java
new file mode 100644
index 000000000..d69fe3e5c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ClassPathUtilsTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ */
+public class ClassPathUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new ClassPathUtils());
+ final Constructor<?>[] cons = ClassPathUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(ClassPathUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(ClassPathUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testPackageToPath() {
+ assertEquals("a", ClassPathUtils.packageToPath("a"));
+ assertEquals("a/b", ClassPathUtils.packageToPath("a.b"));
+ assertEquals("a/b/c", ClassPathUtils.packageToPath("a.b.c"));
+ }
+
+ @Test
+ public void testPathToPackage() {
+ assertEquals("a", ClassPathUtils.pathToPackage("a"));
+ assertEquals("a.b", ClassPathUtils.pathToPackage("a/b"));
+ assertEquals("a.b.c", ClassPathUtils.pathToPackage("a/b/c"));
+ }
+
+ @Test
+ public void testToFullyQualifiedNameNullClassString() {
+ assertThrows(NullPointerException.class,
+ () -> ClassPathUtils.toFullyQualifiedName((Class<?>) null, "Test.properties"));
+ }
+
+ @Test
+ public void testToFullyQualifiedNameClassNull() {
+ assertThrows(NullPointerException.class, () -> ClassPathUtils.toFullyQualifiedName(ClassPathUtils.class, null));
+ }
+
+ @Test
+ public void testToFullyQualifiedNameClassString() {
+ final String expected = "org.apache.commons.lang3.Test.properties";
+ final String actual = ClassPathUtils.toFullyQualifiedName(ClassPathUtils.class, "Test.properties");
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testToFullyQualifiedNameNullPackageString() {
+ assertThrows(NullPointerException.class,
+ () -> ClassPathUtils.toFullyQualifiedName((Package) null, "Test.properties"));
+ }
+
+ @Test
+ public void testToFullyQualifiedNamePackageNull() {
+ assertThrows(NullPointerException.class,
+ () -> ClassPathUtils.toFullyQualifiedName(ClassPathUtils.class.getPackage(), null));
+ }
+
+ @Test
+ public void testToFullyQualifiedNamePackageString() {
+ final String expected = "org.apache.commons.lang3.Test.properties";
+ final String actual = ClassPathUtils.toFullyQualifiedName(ClassPathUtils.class.getPackage(), "Test.properties");
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testToFullyQualifiedPathClassNullString() {
+ assertThrows(NullPointerException.class,
+ () -> ClassPathUtils.toFullyQualifiedPath((Class<?>) null, "Test.properties"));
+ }
+
+ @Test
+ public void testToFullyQualifiedPathClassNull() {
+ assertThrows(NullPointerException.class, () -> ClassPathUtils.toFullyQualifiedPath(ClassPathUtils.class, null));
+ }
+
+ @Test
+ public void testToFullyQualifiedPathClass() {
+ final String expected = "org/apache/commons/lang3/Test.properties";
+ final String actual = ClassPathUtils.toFullyQualifiedPath(ClassPathUtils.class, "Test.properties");
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testToFullyQualifiedPathPackageNullString() {
+ assertThrows(NullPointerException.class,
+ () -> ClassPathUtils.toFullyQualifiedPath((Package) null, "Test.properties"));
+ }
+
+ @Test
+ public void testToFullyQualifiedPathPackageNull() {
+ assertThrows(NullPointerException.class,
+ () -> ClassPathUtils.toFullyQualifiedPath(ClassPathUtils.class.getPackage(), null));
+ }
+
+ @Test
+ public void testToFullyQualifiedPathPackage() {
+ final String expected = "org/apache/commons/lang3/Test.properties";
+ final String actual = ClassPathUtils.toFullyQualifiedPath(ClassPathUtils.class.getPackage(), "Test.properties");
+
+ assertEquals(expected, actual);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java b/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java
new file mode 100644
index 000000000..0050753bd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java
@@ -0,0 +1,1523 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.commons.lang3.ClassUtils.Interfaces;
+import org.apache.commons.lang3.reflect.testbed.GenericConsumer;
+import org.apache.commons.lang3.reflect.testbed.GenericParent;
+import org.apache.commons.lang3.reflect.testbed.StringParameterizedChild;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.ClassUtils}.
+ */
+@SuppressWarnings("boxing") // JUnit4 does not support primitive equality testing apart from long
+public class ClassUtilsTest extends AbstractLangTest {
+
+ private static final String OBJECT_CANONICAL_NAME = "java.lang.Object";
+
+ private static class CX implements IB, IA, IE {
+ // empty
+ }
+
+ @SuppressWarnings("unused") // IB is redundant but what a test checks
+ private static class CY extends CX implements IB, IC {
+ // empty
+ }
+
+ private interface IA {
+ // empty
+ }
+
+ private interface IB {
+ // empty
+ }
+
+ private interface IC extends ID, IE {
+ // empty
+ }
+
+ private interface ID {
+ // empty
+ }
+
+ private interface IE extends IF {
+ // empty
+ }
+
+ private interface IF {
+ // empty
+ }
+
+ private static class Inner {
+ private class DeeplyNested {
+ // empty
+ }
+ }
+
+ private void assertGetClassReturnsClass(final Class<?> c) throws Exception {
+ assertEquals(c, ClassUtils.getClass(c.getName()));
+ }
+
+ private void assertGetClassThrowsClassNotFound(final String className) {
+ assertGetClassThrowsException(className, ClassNotFoundException.class);
+ }
+
+ private void assertGetClassThrowsException(final String className, final Class<? extends Exception> exceptionType) {
+ assertThrows(exceptionType, () -> ClassUtils.getClass(className),
+ "ClassUtils.getClass() should fail with an exception of type " + exceptionType.getName() + " when given class name \"" + className + "\".");
+ }
+
+ private void assertGetClassThrowsNullPointerException(final String className) {
+ assertGetClassThrowsException(className, NullPointerException.class);
+ }
+
+ @Test
+ public void test_convertClassesToClassNames_List() {
+ final List<Class<?>> list = new ArrayList<>();
+ List<String> result = ClassUtils.convertClassesToClassNames(list);
+ assertEquals(0, result.size());
+
+ list.add(String.class);
+ list.add(null);
+ list.add(Object.class);
+ result = ClassUtils.convertClassesToClassNames(list);
+ assertEquals(3, result.size());
+ assertEquals("java.lang.String", result.get(0));
+ assertNull(result.get(1));
+ assertEquals(OBJECT_CANONICAL_NAME, result.get(2));
+
+ @SuppressWarnings("unchecked") // test what happens when non-generic code adds wrong type of element
+ final List<Object> olist = (List<Object>) (List<?>) list;
+ olist.add(new Object());
+ assertThrows(ClassCastException.class, () -> ClassUtils.convertClassesToClassNames(list), "Should not have been able to convert list");
+ assertNull(ClassUtils.convertClassesToClassNames(null));
+ }
+
+ @Test
+ public void test_convertClassNamesToClasses_List() {
+ final List<String> list = new ArrayList<>();
+ List<Class<?>> result = ClassUtils.convertClassNamesToClasses(list);
+ assertEquals(0, result.size());
+
+ list.add("java.lang.String");
+ list.add("java.lang.xxx");
+ list.add(OBJECT_CANONICAL_NAME);
+ result = ClassUtils.convertClassNamesToClasses(list);
+ assertEquals(3, result.size());
+ assertEquals(String.class, result.get(0));
+ assertNull(result.get(1));
+ assertEquals(Object.class, result.get(2));
+
+ @SuppressWarnings("unchecked") // test what happens when non-generic code adds wrong type of element
+ final List<Object> olist = (List<Object>) (List<?>) list;
+ olist.add(new Object());
+ assertThrows(ClassCastException.class, () -> ClassUtils.convertClassNamesToClasses(list), "Should not have been able to convert list");
+ assertNull(ClassUtils.convertClassNamesToClasses(null));
+ }
+
+ @Test
+ public void test_getAbbreviatedName_Class() {
+ assertEquals("", ClassUtils.getAbbreviatedName((Class<?>) null, 1));
+ assertEquals("j.l.String", ClassUtils.getAbbreviatedName(String.class, 1));
+ assertEquals("j.l.String", ClassUtils.getAbbreviatedName(String.class, 5));
+ assertEquals("o.a.c.l.ClassUtils", ClassUtils.getAbbreviatedName(ClassUtils.class, 18));
+ assertEquals("j.lang.String", ClassUtils.getAbbreviatedName(String.class, 13));
+ assertEquals("j.lang.String", ClassUtils.getAbbreviatedName(String.class, 15));
+ assertEquals("java.lang.String", ClassUtils.getAbbreviatedName(String.class, 20));
+ }
+
+ @Test
+ @DisplayName("When the desired length is negative then exception is thrown")
+ public void test_getAbbreviatedName_Class_NegativeLen() {
+ assertThrows(IllegalArgumentException.class, () -> ClassUtils.getAbbreviatedName(String.class, -10));
+ }
+
+ @Test
+ @DisplayName("When the desired length is zero then exception is thrown")
+ public void test_getAbbreviatedName_Class_ZeroLen() {
+ assertThrows(IllegalArgumentException.class, () -> ClassUtils.getAbbreviatedName(String.class, 0));
+ }
+
+ @Test
+ public void test_getAbbreviatedName_String() {
+ assertEquals("", ClassUtils.getAbbreviatedName((String) null, 1));
+ assertEquals("", ClassUtils.getAbbreviatedName("", 1));
+ assertEquals("WithoutPackage", ClassUtils.getAbbreviatedName("WithoutPackage", 1));
+ assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", 1));
+ assertEquals("o.a.c.l.ClassUtils", ClassUtils.getAbbreviatedName("org.apache.commons.lang3.ClassUtils", 18));
+ assertEquals("org.apache.commons.lang3.ClassUtils",
+ ClassUtils.getAbbreviatedName("org.apache.commons.lang3.ClassUtils", "org.apache.commons.lang3.ClassUtils".length()));
+ assertEquals("o.a.c.l.ClassUtils", ClassUtils.getAbbreviatedName("o.a.c.l.ClassUtils", 18));
+ assertEquals("o..c.l.ClassUtils", ClassUtils.getAbbreviatedName("o..c.l.ClassUtils", 18));
+ assertEquals(".", ClassUtils.getAbbreviatedName(".", 18));
+ assertEquals(".", ClassUtils.getAbbreviatedName(".", 1));
+ assertEquals("..", ClassUtils.getAbbreviatedName("..", 1));
+ assertEquals("...", ClassUtils.getAbbreviatedName("...", 2));
+ assertEquals("...", ClassUtils.getAbbreviatedName("...", 3));
+ assertEquals("java.lang.String", ClassUtils.getAbbreviatedName("java.lang.String", Integer.MAX_VALUE));
+ assertEquals("j.lang.String", ClassUtils.getAbbreviatedName("java.lang.String", "j.lang.String".length()));
+ assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", "j.lang.String".length() - 1));
+ assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", "j.l.String".length()));
+ assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", "j.l.String".length() - 1));
+ }
+
+ /**
+ * Test that in case the required length is larger than the name and thus there is no need for any shortening then the
+ * returned string object is the same as the one passed as argument. Note, however, that this is tested as an internal
+ * implementation detail, but it is not a guaranteed feature of the implementation.
+ */
+ @Test
+ @DisplayName("When the length hint is longer than the actual length then the same String object is returned")
+ public void test_getAbbreviatedName_TooLongHint() {
+ final String className = "java.lang.String";
+ Assertions.assertSame(className, ClassUtils.getAbbreviatedName(className, className.length() + 1));
+ Assertions.assertSame(className, ClassUtils.getAbbreviatedName(className, className.length()));
+ }
+
+ @Test
+ public void test_getAllInterfaces_Class() {
+ final List<?> list = ClassUtils.getAllInterfaces(CY.class);
+ assertEquals(6, list.size());
+ assertEquals(IB.class, list.get(0));
+ assertEquals(IC.class, list.get(1));
+ assertEquals(ID.class, list.get(2));
+ assertEquals(IE.class, list.get(3));
+ assertEquals(IF.class, list.get(4));
+ assertEquals(IA.class, list.get(5));
+
+ assertNull(ClassUtils.getAllInterfaces(null));
+ }
+
+ @Test
+ public void test_getAllSuperclasses_Class() {
+ final List<?> list = ClassUtils.getAllSuperclasses(CY.class);
+ assertEquals(2, list.size());
+ assertEquals(CX.class, list.get(0));
+ assertEquals(Object.class, list.get(1));
+
+ assertNull(ClassUtils.getAllSuperclasses(null));
+ }
+
+ @Test
+ public void test_getCanonicalName_Class() {
+ assertEquals("org.apache.commons.lang3.ClassUtils", ClassUtils.getCanonicalName(ClassUtils.class));
+ assertEquals("java.util.Map.Entry", ClassUtils.getCanonicalName(Map.Entry.class));
+ assertEquals("", ClassUtils.getCanonicalName((Class<?>) null));
+
+ assertEquals("java.lang.String[]", ClassUtils.getCanonicalName(String[].class));
+ assertEquals("java.util.Map.Entry[]", ClassUtils.getCanonicalName(Map.Entry[].class));
+
+ // Primitives
+ assertEquals("boolean", ClassUtils.getCanonicalName(boolean.class));
+ assertEquals("byte", ClassUtils.getCanonicalName(byte.class));
+ assertEquals("char", ClassUtils.getCanonicalName(char.class));
+ assertEquals("short", ClassUtils.getCanonicalName(short.class));
+ assertEquals("int", ClassUtils.getCanonicalName(int.class));
+ assertEquals("long", ClassUtils.getCanonicalName(long.class));
+ assertEquals("float", ClassUtils.getCanonicalName(float.class));
+ assertEquals("double", ClassUtils.getCanonicalName(double.class));
+
+ // Primitive Arrays
+ assertEquals("boolean[]", ClassUtils.getCanonicalName(boolean[].class));
+ assertEquals("byte[]", ClassUtils.getCanonicalName(byte[].class));
+ assertEquals("char[]", ClassUtils.getCanonicalName(char[].class));
+ assertEquals("short[]", ClassUtils.getCanonicalName(short[].class));
+ assertEquals("int[]", ClassUtils.getCanonicalName(int[].class));
+ assertEquals("long[]", ClassUtils.getCanonicalName(long[].class));
+ assertEquals("float[]", ClassUtils.getCanonicalName(float[].class));
+ assertEquals("double[]", ClassUtils.getCanonicalName(double[].class));
+
+ // Arrays of arrays of ...
+ assertEquals("java.lang.String[][]", ClassUtils.getCanonicalName(String[][].class));
+ assertEquals("java.lang.String[][][]", ClassUtils.getCanonicalName(String[][][].class));
+ assertEquals("java.lang.String[][][][]", ClassUtils.getCanonicalName(String[][][][].class));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ assertEquals(StringUtils.EMPTY, ClassUtils.getCanonicalName(new Object() {
+ // empty
+ }.getClass()));
+ assertEquals(StringUtils.EMPTY, ClassUtils.getCanonicalName(Named.class));
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest.Inner", ClassUtils.getCanonicalName(Inner.class));
+ }
+
+ @Test
+ public void test_getCanonicalName_Class_String() {
+ assertEquals("org.apache.commons.lang3.ClassUtils", ClassUtils.getCanonicalName(ClassUtils.class, "X"));
+ assertEquals("java.util.Map.Entry", ClassUtils.getCanonicalName(Map.Entry.class, "X"));
+ assertEquals("X", ClassUtils.getCanonicalName((Class<?>) null, "X"));
+
+ assertEquals("java.lang.String[]", ClassUtils.getCanonicalName(String[].class, "X"));
+ assertEquals("java.util.Map.Entry[]", ClassUtils.getCanonicalName(Map.Entry[].class, "X"));
+
+ // Primitives
+ assertEquals("boolean", ClassUtils.getCanonicalName(boolean.class, "X"));
+ assertEquals("byte", ClassUtils.getCanonicalName(byte.class, "X"));
+ assertEquals("char", ClassUtils.getCanonicalName(char.class, "X"));
+ assertEquals("short", ClassUtils.getCanonicalName(short.class, "X"));
+ assertEquals("int", ClassUtils.getCanonicalName(int.class, "X"));
+ assertEquals("long", ClassUtils.getCanonicalName(long.class, "X"));
+ assertEquals("float", ClassUtils.getCanonicalName(float.class, "X"));
+ assertEquals("double", ClassUtils.getCanonicalName(double.class, "X"));
+
+ // Primitive Arrays
+ assertEquals("boolean[]", ClassUtils.getCanonicalName(boolean[].class, "X"));
+ assertEquals("byte[]", ClassUtils.getCanonicalName(byte[].class, "X"));
+ assertEquals("char[]", ClassUtils.getCanonicalName(char[].class, "X"));
+ assertEquals("short[]", ClassUtils.getCanonicalName(short[].class, "X"));
+ assertEquals("int[]", ClassUtils.getCanonicalName(int[].class, "X"));
+ assertEquals("long[]", ClassUtils.getCanonicalName(long[].class, "X"));
+ assertEquals("float[]", ClassUtils.getCanonicalName(float[].class, "X"));
+ assertEquals("double[]", ClassUtils.getCanonicalName(double[].class, "X"));
+
+ // Arrays of arrays of ...
+ assertEquals("java.lang.String[][]", ClassUtils.getCanonicalName(String[][].class, "X"));
+ assertEquals("java.lang.String[][][]", ClassUtils.getCanonicalName(String[][][].class, "X"));
+ assertEquals("java.lang.String[][][][]", ClassUtils.getCanonicalName(String[][][][].class, "X"));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ assertEquals("X", ClassUtils.getCanonicalName(new Object() {
+ // empty
+ }.getClass(), "X"));
+ assertEquals("X", ClassUtils.getCanonicalName(Named.class, "X"));
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest.Inner", ClassUtils.getCanonicalName(Inner.class, "X"));
+ assertEquals("X", ClassUtils.getCanonicalName((Object) null, "X"));
+ assertEquals(OBJECT_CANONICAL_NAME, ClassUtils.getCanonicalName(new Object()));
+ }
+
+ @Test
+ public void test_getClass() {
+ // assertEquals("org.apache.commons.lang3.ClassUtils", ClassUtils.getName(ClassLoader.class, "@"));
+ }
+
+ @Test
+ public void test_getName_Class() {
+ assertEquals("org.apache.commons.lang3.ClassUtils", ClassUtils.getName(ClassUtils.class));
+ assertEquals("java.util.Map$Entry", ClassUtils.getName(Map.Entry.class));
+ assertEquals("", ClassUtils.getName((Class<?>) null));
+
+ assertEquals("[Ljava.lang.String;", ClassUtils.getName(String[].class));
+ assertEquals("[Ljava.util.Map$Entry;", ClassUtils.getName(Map.Entry[].class));
+
+ // Primitives
+ assertEquals("boolean", ClassUtils.getName(boolean.class));
+ assertEquals("byte", ClassUtils.getName(byte.class));
+ assertEquals("char", ClassUtils.getName(char.class));
+ assertEquals("short", ClassUtils.getName(short.class));
+ assertEquals("int", ClassUtils.getName(int.class));
+ assertEquals("long", ClassUtils.getName(long.class));
+ assertEquals("float", ClassUtils.getName(float.class));
+ assertEquals("double", ClassUtils.getName(double.class));
+
+ // Primitive Arrays
+ assertEquals("[Z", ClassUtils.getName(boolean[].class));
+ assertEquals("[B", ClassUtils.getName(byte[].class));
+ assertEquals("[C", ClassUtils.getName(char[].class));
+ assertEquals("[S", ClassUtils.getName(short[].class));
+ assertEquals("[I", ClassUtils.getName(int[].class));
+ assertEquals("[J", ClassUtils.getName(long[].class));
+ assertEquals("[F", ClassUtils.getName(float[].class));
+ assertEquals("[D", ClassUtils.getName(double[].class));
+
+ // Arrays of arrays of ...
+ assertEquals("[[Ljava.lang.String;", ClassUtils.getName(String[][].class));
+ assertEquals("[[[Ljava.lang.String;", ClassUtils.getName(String[][][].class));
+ assertEquals("[[[[Ljava.lang.String;", ClassUtils.getName(String[][][][].class));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest$3", ClassUtils.getName(new Object() {
+ // empty
+ }.getClass()));
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest$3Named", ClassUtils.getName(Named.class));
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest$Inner", ClassUtils.getName(Inner.class));
+ assertEquals(OBJECT_CANONICAL_NAME, ClassUtils.getName(new Object()));
+ }
+
+ @Test
+ public void test_getName_Object() {
+ assertEquals("org.apache.commons.lang3.ClassUtils", ClassUtils.getName(new ClassUtils(), "<null>"));
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest$Inner", ClassUtils.getName(new Inner(), "<null>"));
+ assertEquals("java.lang.String", ClassUtils.getName("hello", "<null>"));
+ assertEquals("<null>", ClassUtils.getName(null, "<null>"));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest$4", ClassUtils.getName(new Object() {
+ // empty
+ }, "<null>"));
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest$4Named", ClassUtils.getName(new Named(), "<null>"));
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest$Inner", ClassUtils.getName(new Inner(), "<null>"));
+ }
+
+ @Test
+ public void test_getPackageCanonicalName_Class() {
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(ClassUtils.class));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(ClassUtils[].class));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(ClassUtils[][].class));
+ assertEquals("", ClassUtils.getPackageCanonicalName(int[].class));
+ assertEquals("", ClassUtils.getPackageCanonicalName(int[][].class));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(new Object() {
+ // empty
+ }.getClass()));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(Named.class));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(Inner.class));
+ assertEquals(StringUtils.EMPTY, ClassUtils.getPackageCanonicalName((Class<?>) null));
+ }
+
+ @Test
+ public void test_getPackageCanonicalName_Object() {
+ assertEquals("<null>", ClassUtils.getPackageCanonicalName(null, "<null>"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(new ClassUtils(), "<null>"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(new ClassUtils[0], "<null>"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(new ClassUtils[0][0], "<null>"));
+ assertEquals("", ClassUtils.getPackageCanonicalName(new int[0], "<null>"));
+ assertEquals("", ClassUtils.getPackageCanonicalName(new int[0][0], "<null>"));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(new Object() {
+ // empty
+ }, "<null>"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(new Named(), "<null>"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName(new Inner(), "<null>"));
+ }
+
+ @Test
+ public void test_getPackageCanonicalName_String() {
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName("org.apache.commons.lang3.ClassUtils"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName("[Lorg.apache.commons.lang3.ClassUtils;"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName("[[Lorg.apache.commons.lang3.ClassUtils;"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName("org.apache.commons.lang3.ClassUtils[]"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName("org.apache.commons.lang3.ClassUtils[][]"));
+ assertEquals("", ClassUtils.getPackageCanonicalName("[I"));
+ assertEquals("", ClassUtils.getPackageCanonicalName("[[I"));
+ assertEquals("", ClassUtils.getPackageCanonicalName("int[]"));
+ assertEquals("", ClassUtils.getPackageCanonicalName("int[][]"));
+
+ // Inner types
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName("org.apache.commons.lang3.ClassUtilsTest$6"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName("org.apache.commons.lang3.ClassUtilsTest$5Named"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageCanonicalName("org.apache.commons.lang3.ClassUtilsTest$Inner"));
+ }
+
+ @Test
+ public void test_getPackageName_Class() {
+ assertEquals("java.lang", ClassUtils.getPackageName(String.class));
+ assertEquals("java.util", ClassUtils.getPackageName(Map.Entry.class));
+ assertEquals("", ClassUtils.getPackageName((Class<?>) null));
+
+ // LANG-535
+ assertEquals("java.lang", ClassUtils.getPackageName(String[].class));
+
+ // Primitive Arrays
+ assertEquals("", ClassUtils.getPackageName(boolean[].class));
+ assertEquals("", ClassUtils.getPackageName(byte[].class));
+ assertEquals("", ClassUtils.getPackageName(char[].class));
+ assertEquals("", ClassUtils.getPackageName(short[].class));
+ assertEquals("", ClassUtils.getPackageName(int[].class));
+ assertEquals("", ClassUtils.getPackageName(long[].class));
+ assertEquals("", ClassUtils.getPackageName(float[].class));
+ assertEquals("", ClassUtils.getPackageName(double[].class));
+
+ // Arrays of arrays of ...
+ assertEquals("java.lang", ClassUtils.getPackageName(String[][].class));
+ assertEquals("java.lang", ClassUtils.getPackageName(String[][][].class));
+ assertEquals("java.lang", ClassUtils.getPackageName(String[][][][].class));
+
+ // On-the-fly types
+ class Named {
+ // empty
+ }
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageName(new Object() {
+ // empty
+ }.getClass()));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageName(Named.class));
+ }
+
+ @Test
+ public void test_getPackageName_Object() {
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageName(new ClassUtils(), "<null>"));
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageName(new Inner(), "<null>"));
+ assertEquals("<null>", ClassUtils.getPackageName(null, "<null>"));
+ }
+
+ @Test
+ public void test_getPackageName_String() {
+ assertEquals("org.apache.commons.lang3", ClassUtils.getPackageName(ClassUtils.class.getName()));
+ assertEquals("java.util", ClassUtils.getPackageName(Map.Entry.class.getName()));
+ assertEquals("", ClassUtils.getPackageName((String) null));
+ assertEquals("", ClassUtils.getPackageName(""));
+ }
+
+ @Test
+ public void test_getShortCanonicalName_Class() {
+ assertEquals("ClassUtils", ClassUtils.getShortCanonicalName(ClassUtils.class));
+ assertEquals("ClassUtils[]", ClassUtils.getShortCanonicalName(ClassUtils[].class));
+ assertEquals("ClassUtils[][]", ClassUtils.getShortCanonicalName(ClassUtils[][].class));
+ assertEquals("int[]", ClassUtils.getShortCanonicalName(int[].class));
+ assertEquals("int[][]", ClassUtils.getShortCanonicalName(int[][].class));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ assertEquals("", ClassUtils.getShortCanonicalName(new Object() {
+ // empty
+ }.getClass()));
+ // WARNING: this is fragile, implementation may change, naming is not guaranteed
+ assertEquals("", ClassUtils.getShortCanonicalName(Named.class));
+ assertEquals("Inner", ClassUtils.getShortCanonicalName(Inner.class));
+ assertEquals(StringUtils.EMPTY, ClassUtils.getShortCanonicalName((Class<?>) null));
+ }
+
+ @Test
+ public void test_getShortCanonicalName_Object() {
+ assertEquals("<null>", ClassUtils.getShortCanonicalName(null, "<null>"));
+ assertEquals("ClassUtils", ClassUtils.getShortCanonicalName(new ClassUtils(), "<null>"));
+ assertEquals("ClassUtils[]", ClassUtils.getShortCanonicalName(new ClassUtils[0], "<null>"));
+ assertEquals("ClassUtils[][]", ClassUtils.getShortCanonicalName(new ClassUtils[0][0], "<null>"));
+ assertEquals("int[]", ClassUtils.getShortCanonicalName(new int[0], "<null>"));
+ assertEquals("int[][]", ClassUtils.getShortCanonicalName(new int[0][0], "<null>"));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ assertEquals("", ClassUtils.getShortCanonicalName(new Object() {
+ // empty
+ }, "<null>"));
+ assertEquals("", ClassUtils.getShortCanonicalName(new Named(), "<null>"));
+ assertEquals("Inner", ClassUtils.getShortCanonicalName(new Inner(), "<null>"));
+ }
+
+ @Test
+ public void test_getShortCanonicalName_String() {
+ assertEquals("", ClassUtils.getShortCanonicalName((String) null));
+ assertEquals("Map.Entry", ClassUtils.getShortCanonicalName(java.util.Map.Entry.class.getName()));
+ assertEquals("Entry", ClassUtils.getShortCanonicalName(java.util.Map.Entry.class.getCanonicalName()));
+ assertEquals("ClassUtils", ClassUtils.getShortCanonicalName("org.apache.commons.lang3.ClassUtils"));
+ assertEquals("ClassUtils[]", ClassUtils.getShortCanonicalName("[Lorg.apache.commons.lang3.ClassUtils;"));
+ assertEquals("ClassUtils[][]", ClassUtils.getShortCanonicalName("[[Lorg.apache.commons.lang3.ClassUtils;"));
+ assertEquals("ClassUtils[]", ClassUtils.getShortCanonicalName("org.apache.commons.lang3.ClassUtils[]"));
+ assertEquals("ClassUtils[][]", ClassUtils.getShortCanonicalName("org.apache.commons.lang3.ClassUtils[][]"));
+ assertEquals("int[]", ClassUtils.getShortCanonicalName("[I"));
+ assertEquals("int[]", ClassUtils.getShortCanonicalName(int[].class.getCanonicalName()));
+ assertEquals("int[]", ClassUtils.getShortCanonicalName(int[].class.getName()));
+ assertEquals("int[][]", ClassUtils.getShortCanonicalName("[[I"));
+ assertEquals("int[]", ClassUtils.getShortCanonicalName("int[]"));
+ assertEquals("int[][]", ClassUtils.getShortCanonicalName("int[][]"));
+
+ // this is to demonstrate that the documentation and the naming of the methods
+ // uses the class name and canonical name totally mixed up, which cannot be
+ // fixed without backward compatibility break
+ assertEquals("int[]", int[].class.getCanonicalName());
+ assertEquals("[I", int[].class.getName());
+
+ // Inner types... the problem is that these are not canonical names, classes with this name do not even have canonical
+ // name
+ // WARNING: this is fragile, implementation may change, naming is not guaranteed
+ assertEquals("ClassUtilsTest.6", ClassUtils.getShortCanonicalName("org.apache.commons.lang3.ClassUtilsTest$6"));
+ // WARNING: this is fragile, implementation may change, naming is not guaranteed
+ assertEquals("ClassUtilsTest.5Named", ClassUtils.getShortCanonicalName("org.apache.commons.lang3.ClassUtilsTest$5Named"));
+ assertEquals("ClassUtilsTest.Inner", ClassUtils.getShortCanonicalName("org.apache.commons.lang3.ClassUtilsTest$Inner"));
+ // demonstrating what a canonical name is... it is a bigger issue to clean this up
+ assertEquals("org.apache.commons.lang3.ClassUtilsTest$10", new org.apache.commons.lang3.ClassUtilsTest() {
+ }.getClass().getName());
+ assertNull(new org.apache.commons.lang3.ClassUtilsTest() {
+ }.getClass().getCanonicalName());
+ }
+
+ @Test
+ public void test_getShortClassName_Class() {
+ assertEquals("ClassUtils", ClassUtils.getShortClassName(ClassUtils.class));
+ assertEquals("Map.Entry", ClassUtils.getShortClassName(Map.Entry.class));
+ assertEquals("", ClassUtils.getShortClassName((Class<?>) null));
+
+ // LANG-535
+ assertEquals("String[]", ClassUtils.getShortClassName(String[].class));
+ assertEquals("Map.Entry[]", ClassUtils.getShortClassName(Map.Entry[].class));
+
+ // Primitives
+ assertEquals("boolean", ClassUtils.getShortClassName(boolean.class));
+ assertEquals("byte", ClassUtils.getShortClassName(byte.class));
+ assertEquals("char", ClassUtils.getShortClassName(char.class));
+ assertEquals("short", ClassUtils.getShortClassName(short.class));
+ assertEquals("int", ClassUtils.getShortClassName(int.class));
+ assertEquals("long", ClassUtils.getShortClassName(long.class));
+ assertEquals("float", ClassUtils.getShortClassName(float.class));
+ assertEquals("double", ClassUtils.getShortClassName(double.class));
+
+ // Primitive Arrays
+ assertEquals("boolean[]", ClassUtils.getShortClassName(boolean[].class));
+ assertEquals("byte[]", ClassUtils.getShortClassName(byte[].class));
+ assertEquals("char[]", ClassUtils.getShortClassName(char[].class));
+ assertEquals("short[]", ClassUtils.getShortClassName(short[].class));
+ assertEquals("int[]", ClassUtils.getShortClassName(int[].class));
+ assertEquals("long[]", ClassUtils.getShortClassName(long[].class));
+ assertEquals("float[]", ClassUtils.getShortClassName(float[].class));
+ assertEquals("double[]", ClassUtils.getShortClassName(double[].class));
+
+ // Arrays of arrays of ...
+ assertEquals("String[][]", ClassUtils.getShortClassName(String[][].class));
+ assertEquals("String[][][]", ClassUtils.getShortClassName(String[][][].class));
+ assertEquals("String[][][][]", ClassUtils.getShortClassName(String[][][][].class));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ // WARNING: this is fragile, implementation may change, naming is not guaranteed
+ assertEquals("ClassUtilsTest.12", ClassUtils.getShortClassName(new Object() {
+ // empty
+ }.getClass()));
+ // WARNING: this is fragile, implementation may change, naming is not guaranteed
+ assertEquals("ClassUtilsTest.10Named", ClassUtils.getShortClassName(Named.class));
+ assertEquals("ClassUtilsTest.Inner", ClassUtils.getShortClassName(Inner.class));
+ }
+
+ @Test
+ public void test_getShortClassName_Object() {
+ assertEquals("ClassUtils", ClassUtils.getShortClassName(new ClassUtils(), "<null>"));
+ assertEquals("ClassUtilsTest.Inner", ClassUtils.getShortClassName(new Inner(), "<null>"));
+ assertEquals("String", ClassUtils.getShortClassName("hello", "<null>"));
+ assertEquals("<null>", ClassUtils.getShortClassName(null, "<null>"));
+
+ // Inner types
+ class Named {
+ // empty
+ }
+ // WARNING: this is fragile, implementation may change, naming is not guaranteed
+ assertEquals("ClassUtilsTest.13", ClassUtils.getShortClassName(new Object() {
+ // empty
+ }, "<null>"));
+ // WARNING: this is fragile, implementation may change, naming is not guaranteed
+ assertEquals("ClassUtilsTest.11Named", ClassUtils.getShortClassName(new Named(), "<null>"));
+ assertEquals("ClassUtilsTest.Inner", ClassUtils.getShortClassName(new Inner(), "<null>"));
+ }
+
+ @Test
+ public void test_getShortClassName_String() {
+ assertEquals("ClassUtils", ClassUtils.getShortClassName(ClassUtils.class.getName()));
+ assertEquals("Map.Entry", ClassUtils.getShortClassName(Map.Entry.class.getName()));
+ assertEquals("", ClassUtils.getShortClassName((String) null));
+ assertEquals("", ClassUtils.getShortClassName(""));
+ }
+
+ @Test
+ public void test_getSimpleName_Class() {
+ assertEquals("ClassUtils", ClassUtils.getSimpleName(ClassUtils.class));
+ assertEquals("Entry", ClassUtils.getSimpleName(Map.Entry.class));
+ assertEquals("", ClassUtils.getSimpleName(null));
+
+ // LANG-535
+ assertEquals("String[]", ClassUtils.getSimpleName(String[].class));
+ assertEquals("Entry[]", ClassUtils.getSimpleName(Map.Entry[].class));
+
+ // Primitives
+ assertEquals("boolean", ClassUtils.getSimpleName(boolean.class));
+ assertEquals("byte", ClassUtils.getSimpleName(byte.class));
+ assertEquals("char", ClassUtils.getSimpleName(char.class));
+ assertEquals("short", ClassUtils.getSimpleName(short.class));
+ assertEquals("int", ClassUtils.getSimpleName(int.class));
+ assertEquals("long", ClassUtils.getSimpleName(long.class));
+ assertEquals("float", ClassUtils.getSimpleName(float.class));
+ assertEquals("double", ClassUtils.getSimpleName(double.class));
+
+ // Primitive Arrays
+ assertEquals("boolean[]", ClassUtils.getSimpleName(boolean[].class));
+ assertEquals("byte[]", ClassUtils.getSimpleName(byte[].class));
+ assertEquals("char[]", ClassUtils.getSimpleName(char[].class));
+ assertEquals("short[]", ClassUtils.getSimpleName(short[].class));
+ assertEquals("int[]", ClassUtils.getSimpleName(int[].class));
+ assertEquals("long[]", ClassUtils.getSimpleName(long[].class));
+ assertEquals("float[]", ClassUtils.getSimpleName(float[].class));
+ assertEquals("double[]", ClassUtils.getSimpleName(double[].class));
+
+ // Arrays of arrays of ...
+ assertEquals("String[][]", ClassUtils.getSimpleName(String[][].class));
+ assertEquals("String[][][]", ClassUtils.getSimpleName(String[][][].class));
+ assertEquals("String[][][][]", ClassUtils.getSimpleName(String[][][][].class));
+
+ // On-the-fly types
+ class Named {
+ // empty
+ }
+ assertEquals("", ClassUtils.getSimpleName(new Object() {
+ // empty
+ }.getClass()));
+ assertEquals("Named", ClassUtils.getSimpleName(Named.class));
+ }
+
+ @Test
+ public void test_getSimpleName_Object() {
+ assertEquals("ClassUtils", ClassUtils.getSimpleName(new ClassUtils()));
+ assertEquals("Inner", ClassUtils.getSimpleName(new Inner()));
+ assertEquals("String", ClassUtils.getSimpleName("hello"));
+ assertEquals(StringUtils.EMPTY, ClassUtils.getSimpleName(null));
+ assertEquals(StringUtils.EMPTY, ClassUtils.getSimpleName(null));
+ }
+
+ @Test
+ public void test_getSimpleName_Object_String() {
+ assertEquals("ClassUtils", ClassUtils.getSimpleName(new ClassUtils(), "<null>"));
+ assertEquals("Inner", ClassUtils.getSimpleName(new Inner(), "<null>"));
+ assertEquals("String", ClassUtils.getSimpleName("hello", "<null>"));
+ assertEquals("<null>", ClassUtils.getSimpleName(null, "<null>"));
+ assertNull(ClassUtils.getSimpleName(null, null));
+ }
+
+ @Test
+ public void test_isAssignable() {
+ assertFalse(ClassUtils.isAssignable((Class<?>) null, null));
+ assertFalse(ClassUtils.isAssignable(String.class, null));
+
+ assertTrue(ClassUtils.isAssignable(null, Object.class));
+ assertTrue(ClassUtils.isAssignable(null, Integer.class));
+ assertFalse(ClassUtils.isAssignable(null, Integer.TYPE));
+ assertTrue(ClassUtils.isAssignable(String.class, Object.class));
+ assertTrue(ClassUtils.isAssignable(String.class, String.class));
+ assertFalse(ClassUtils.isAssignable(Object.class, String.class));
+
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Integer.class));
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Object.class));
+ assertTrue(ClassUtils.isAssignable(Integer.class, Integer.TYPE));
+ assertTrue(ClassUtils.isAssignable(Integer.class, Object.class));
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Integer.TYPE));
+ assertTrue(ClassUtils.isAssignable(Integer.class, Integer.class));
+ assertTrue(ClassUtils.isAssignable(Boolean.TYPE, Boolean.class));
+ assertTrue(ClassUtils.isAssignable(Boolean.TYPE, Object.class));
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Boolean.TYPE));
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Object.class));
+ assertTrue(ClassUtils.isAssignable(Boolean.TYPE, Boolean.TYPE));
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Boolean.class));
+ }
+
+ @Test
+ public void test_isAssignable_Autoboxing() {
+ assertFalse(ClassUtils.isAssignable((Class<?>) null, null, true));
+ assertFalse(ClassUtils.isAssignable(String.class, null, true));
+
+ assertTrue(ClassUtils.isAssignable(null, Object.class, true));
+ assertTrue(ClassUtils.isAssignable(null, Integer.class, true));
+ assertFalse(ClassUtils.isAssignable(null, Integer.TYPE, true));
+ assertTrue(ClassUtils.isAssignable(String.class, Object.class, true));
+ assertTrue(ClassUtils.isAssignable(String.class, String.class, true));
+ assertFalse(ClassUtils.isAssignable(Object.class, String.class, true));
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Integer.class, true));
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Object.class, true));
+ assertTrue(ClassUtils.isAssignable(Integer.class, Integer.TYPE, true));
+ assertTrue(ClassUtils.isAssignable(Integer.class, Object.class, true));
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Integer.TYPE, true));
+ assertTrue(ClassUtils.isAssignable(Integer.class, Integer.class, true));
+ assertTrue(ClassUtils.isAssignable(Boolean.TYPE, Boolean.class, true));
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Boolean.TYPE, true));
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Object.class, true));
+ assertTrue(ClassUtils.isAssignable(Boolean.TYPE, Boolean.TYPE, true));
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Boolean.class, true));
+ }
+
+ @Test
+ public void test_isAssignable_ClassArray_ClassArray() {
+ final Class<?>[] array2 = new Class[] {Object.class, Object.class};
+ final Class<?>[] array1 = new Class[] {Object.class};
+ final Class<?>[] array1s = new Class[] {String.class};
+ final Class<?>[] array0 = new Class[] {};
+ final Class<?>[] arrayPrimitives = {Integer.TYPE, Boolean.TYPE};
+ final Class<?>[] arrayWrappers = {Integer.class, Boolean.class};
+
+ assertFalse(ClassUtils.isAssignable(array1, array2));
+ assertFalse(ClassUtils.isAssignable(null, array2));
+ assertTrue(ClassUtils.isAssignable(null, array0));
+ assertTrue(ClassUtils.isAssignable(array0, array0));
+ assertTrue(ClassUtils.isAssignable(array0, (Class<?>[]) null)); // explicit cast to avoid warning
+ assertTrue(ClassUtils.isAssignable(null, (Class<?>[]) null)); // explicit cast to avoid warning
+
+ assertFalse(ClassUtils.isAssignable(array1, array1s));
+ assertTrue(ClassUtils.isAssignable(array1s, array1s));
+ assertTrue(ClassUtils.isAssignable(array1s, array1));
+
+ assertTrue(ClassUtils.isAssignable(arrayPrimitives, arrayWrappers));
+ assertTrue(ClassUtils.isAssignable(arrayWrappers, arrayPrimitives));
+ assertFalse(ClassUtils.isAssignable(arrayPrimitives, array1));
+ assertFalse(ClassUtils.isAssignable(arrayWrappers, array1));
+ assertTrue(ClassUtils.isAssignable(arrayPrimitives, array2));
+ assertTrue(ClassUtils.isAssignable(arrayWrappers, array2));
+ }
+
+ @Test
+ public void test_isAssignable_ClassArray_ClassArray_Autoboxing() {
+ final Class<?>[] array2 = new Class[] {Object.class, Object.class};
+ final Class<?>[] array1 = new Class[] {Object.class};
+ final Class<?>[] array1s = new Class[] {String.class};
+ final Class<?>[] array0 = new Class[] {};
+ final Class<?>[] arrayPrimitives = {Integer.TYPE, Boolean.TYPE};
+ final Class<?>[] arrayWrappers = {Integer.class, Boolean.class};
+
+ assertFalse(ClassUtils.isAssignable(array1, array2, true));
+ assertFalse(ClassUtils.isAssignable(null, array2, true));
+ assertTrue(ClassUtils.isAssignable(null, array0, true));
+ assertTrue(ClassUtils.isAssignable(array0, array0, true));
+ assertTrue(ClassUtils.isAssignable(array0, null, true));
+ assertTrue(ClassUtils.isAssignable((Class[]) null, null, true));
+
+ assertFalse(ClassUtils.isAssignable(array1, array1s, true));
+ assertTrue(ClassUtils.isAssignable(array1s, array1s, true));
+ assertTrue(ClassUtils.isAssignable(array1s, array1, true));
+
+ assertTrue(ClassUtils.isAssignable(arrayPrimitives, arrayWrappers, true));
+ assertTrue(ClassUtils.isAssignable(arrayWrappers, arrayPrimitives, true));
+ assertFalse(ClassUtils.isAssignable(arrayPrimitives, array1, true));
+ assertFalse(ClassUtils.isAssignable(arrayWrappers, array1, true));
+ assertTrue(ClassUtils.isAssignable(arrayPrimitives, array2, true));
+ assertTrue(ClassUtils.isAssignable(arrayWrappers, array2, true));
+ }
+
+ @Test
+ public void test_isAssignable_ClassArray_ClassArray_NoAutoboxing() {
+ final Class<?>[] array2 = new Class[] {Object.class, Object.class};
+ final Class<?>[] array1 = new Class[] {Object.class};
+ final Class<?>[] array1s = new Class[] {String.class};
+ final Class<?>[] array0 = new Class[] {};
+ final Class<?>[] arrayPrimitives = {Integer.TYPE, Boolean.TYPE};
+ final Class<?>[] arrayWrappers = {Integer.class, Boolean.class};
+
+ assertFalse(ClassUtils.isAssignable(array1, array2, false));
+ assertFalse(ClassUtils.isAssignable(null, array2, false));
+ assertTrue(ClassUtils.isAssignable(null, array0, false));
+ assertTrue(ClassUtils.isAssignable(array0, array0, false));
+ assertTrue(ClassUtils.isAssignable(array0, null, false));
+ assertTrue(ClassUtils.isAssignable((Class[]) null, null, false));
+
+ assertFalse(ClassUtils.isAssignable(array1, array1s, false));
+ assertTrue(ClassUtils.isAssignable(array1s, array1s, false));
+ assertTrue(ClassUtils.isAssignable(array1s, array1, false));
+
+ assertFalse(ClassUtils.isAssignable(arrayPrimitives, arrayWrappers, false));
+ assertFalse(ClassUtils.isAssignable(arrayWrappers, arrayPrimitives, false));
+ assertFalse(ClassUtils.isAssignable(arrayPrimitives, array1, false));
+ assertFalse(ClassUtils.isAssignable(arrayWrappers, array1, false));
+ assertTrue(ClassUtils.isAssignable(arrayWrappers, array2, false));
+ assertFalse(ClassUtils.isAssignable(arrayPrimitives, array2, false));
+ }
+
+ @Test
+ public void test_isAssignable_DefaultUnboxing_Widening() {
+ // test byte conversions
+ assertFalse(ClassUtils.isAssignable(Byte.class, Character.TYPE), "byte -> char");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Byte.TYPE), "byte -> byte");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Short.TYPE), "byte -> short");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Integer.TYPE), "byte -> int");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Long.TYPE), "byte -> long");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Float.TYPE), "byte -> float");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Double.TYPE), "byte -> double");
+ assertFalse(ClassUtils.isAssignable(Byte.class, Boolean.TYPE), "byte -> boolean");
+
+ // test short conversions
+ assertFalse(ClassUtils.isAssignable(Short.class, Character.TYPE), "short -> char");
+ assertFalse(ClassUtils.isAssignable(Short.class, Byte.TYPE), "short -> byte");
+ assertTrue(ClassUtils.isAssignable(Short.class, Short.TYPE), "short -> short");
+ assertTrue(ClassUtils.isAssignable(Short.class, Integer.TYPE), "short -> int");
+ assertTrue(ClassUtils.isAssignable(Short.class, Long.TYPE), "short -> long");
+ assertTrue(ClassUtils.isAssignable(Short.class, Float.TYPE), "short -> float");
+ assertTrue(ClassUtils.isAssignable(Short.class, Double.TYPE), "short -> double");
+ assertFalse(ClassUtils.isAssignable(Short.class, Boolean.TYPE), "short -> boolean");
+
+ // test char conversions
+ assertTrue(ClassUtils.isAssignable(Character.class, Character.TYPE), "char -> char");
+ assertFalse(ClassUtils.isAssignable(Character.class, Byte.TYPE), "char -> byte");
+ assertFalse(ClassUtils.isAssignable(Character.class, Short.TYPE), "char -> short");
+ assertTrue(ClassUtils.isAssignable(Character.class, Integer.TYPE), "char -> int");
+ assertTrue(ClassUtils.isAssignable(Character.class, Long.TYPE), "char -> long");
+ assertTrue(ClassUtils.isAssignable(Character.class, Float.TYPE), "char -> float");
+ assertTrue(ClassUtils.isAssignable(Character.class, Double.TYPE), "char -> double");
+ assertFalse(ClassUtils.isAssignable(Character.class, Boolean.TYPE), "char -> boolean");
+
+ // test int conversions
+ assertFalse(ClassUtils.isAssignable(Integer.class, Character.TYPE), "int -> char");
+ assertFalse(ClassUtils.isAssignable(Integer.class, Byte.TYPE), "int -> byte");
+ assertFalse(ClassUtils.isAssignable(Integer.class, Short.TYPE), "int -> short");
+ assertTrue(ClassUtils.isAssignable(Integer.class, Integer.TYPE), "int -> int");
+ assertTrue(ClassUtils.isAssignable(Integer.class, Long.TYPE), "int -> long");
+ assertTrue(ClassUtils.isAssignable(Integer.class, Float.TYPE), "int -> float");
+ assertTrue(ClassUtils.isAssignable(Integer.class, Double.TYPE), "int -> double");
+ assertFalse(ClassUtils.isAssignable(Integer.class, Boolean.TYPE), "int -> boolean");
+
+ // test long conversions
+ assertFalse(ClassUtils.isAssignable(Long.class, Character.TYPE), "long -> char");
+ assertFalse(ClassUtils.isAssignable(Long.class, Byte.TYPE), "long -> byte");
+ assertFalse(ClassUtils.isAssignable(Long.class, Short.TYPE), "long -> short");
+ assertFalse(ClassUtils.isAssignable(Long.class, Integer.TYPE), "long -> int");
+ assertTrue(ClassUtils.isAssignable(Long.class, Long.TYPE), "long -> long");
+ assertTrue(ClassUtils.isAssignable(Long.class, Float.TYPE), "long -> float");
+ assertTrue(ClassUtils.isAssignable(Long.class, Double.TYPE), "long -> double");
+ assertFalse(ClassUtils.isAssignable(Long.class, Boolean.TYPE), "long -> boolean");
+
+ // test float conversions
+ assertFalse(ClassUtils.isAssignable(Float.class, Character.TYPE), "float -> char");
+ assertFalse(ClassUtils.isAssignable(Float.class, Byte.TYPE), "float -> byte");
+ assertFalse(ClassUtils.isAssignable(Float.class, Short.TYPE), "float -> short");
+ assertFalse(ClassUtils.isAssignable(Float.class, Integer.TYPE), "float -> int");
+ assertFalse(ClassUtils.isAssignable(Float.class, Long.TYPE), "float -> long");
+ assertTrue(ClassUtils.isAssignable(Float.class, Float.TYPE), "float -> float");
+ assertTrue(ClassUtils.isAssignable(Float.class, Double.TYPE), "float -> double");
+ assertFalse(ClassUtils.isAssignable(Float.class, Boolean.TYPE), "float -> boolean");
+
+ // test double conversions
+ assertFalse(ClassUtils.isAssignable(Double.class, Character.TYPE), "double -> char");
+ assertFalse(ClassUtils.isAssignable(Double.class, Byte.TYPE), "double -> byte");
+ assertFalse(ClassUtils.isAssignable(Double.class, Short.TYPE), "double -> short");
+ assertFalse(ClassUtils.isAssignable(Double.class, Integer.TYPE), "double -> int");
+ assertFalse(ClassUtils.isAssignable(Double.class, Long.TYPE), "double -> long");
+ assertFalse(ClassUtils.isAssignable(Double.class, Float.TYPE), "double -> float");
+ assertTrue(ClassUtils.isAssignable(Double.class, Double.TYPE), "double -> double");
+ assertFalse(ClassUtils.isAssignable(Double.class, Boolean.TYPE), "double -> boolean");
+
+ // test boolean conversions
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Character.TYPE), "boolean -> char");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Byte.TYPE), "boolean -> byte");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Short.TYPE), "boolean -> short");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Integer.TYPE), "boolean -> int");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Long.TYPE), "boolean -> long");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Float.TYPE), "boolean -> float");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Double.TYPE), "boolean -> double");
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Boolean.TYPE), "boolean -> boolean");
+ }
+
+ @Test
+ public void test_isAssignable_NoAutoboxing() {
+ assertFalse(ClassUtils.isAssignable((Class<?>) null, null, false));
+ assertFalse(ClassUtils.isAssignable(String.class, null, false));
+
+ assertTrue(ClassUtils.isAssignable(null, Object.class, false));
+ assertTrue(ClassUtils.isAssignable(null, Integer.class, false));
+ assertFalse(ClassUtils.isAssignable(null, Integer.TYPE, false));
+ assertTrue(ClassUtils.isAssignable(String.class, Object.class, false));
+ assertTrue(ClassUtils.isAssignable(String.class, String.class, false));
+ assertFalse(ClassUtils.isAssignable(Object.class, String.class, false));
+ assertFalse(ClassUtils.isAssignable(Integer.TYPE, Integer.class, false));
+ assertFalse(ClassUtils.isAssignable(Integer.TYPE, Object.class, false));
+ assertFalse(ClassUtils.isAssignable(Integer.class, Integer.TYPE, false));
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Integer.TYPE, false));
+ assertTrue(ClassUtils.isAssignable(Integer.class, Integer.class, false));
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Boolean.class, false));
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Object.class, false));
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Boolean.TYPE, false));
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Object.class, false));
+ assertTrue(ClassUtils.isAssignable(Boolean.TYPE, Boolean.TYPE, false));
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Boolean.class, false));
+ }
+
+ @Test
+ public void test_isAssignable_Unboxing_Widening() {
+ // test byte conversions
+ assertFalse(ClassUtils.isAssignable(Byte.class, Character.TYPE, true), "byte -> char");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Byte.TYPE, true), "byte -> byte");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Short.TYPE, true), "byte -> short");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Integer.TYPE, true), "byte -> int");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Long.TYPE, true), "byte -> long");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Float.TYPE, true), "byte -> float");
+ assertTrue(ClassUtils.isAssignable(Byte.class, Double.TYPE, true), "byte -> double");
+ assertFalse(ClassUtils.isAssignable(Byte.class, Boolean.TYPE, true), "byte -> boolean");
+
+ // test short conversions
+ assertFalse(ClassUtils.isAssignable(Short.class, Character.TYPE, true), "short -> char");
+ assertFalse(ClassUtils.isAssignable(Short.class, Byte.TYPE, true), "short -> byte");
+ assertTrue(ClassUtils.isAssignable(Short.class, Short.TYPE, true), "short -> short");
+ assertTrue(ClassUtils.isAssignable(Short.class, Integer.TYPE, true), "short -> int");
+ assertTrue(ClassUtils.isAssignable(Short.class, Long.TYPE, true), "short -> long");
+ assertTrue(ClassUtils.isAssignable(Short.class, Float.TYPE, true), "short -> float");
+ assertTrue(ClassUtils.isAssignable(Short.class, Double.TYPE, true), "short -> double");
+ assertFalse(ClassUtils.isAssignable(Short.class, Boolean.TYPE, true), "short -> boolean");
+
+ // test char conversions
+ assertTrue(ClassUtils.isAssignable(Character.class, Character.TYPE, true), "char -> char");
+ assertFalse(ClassUtils.isAssignable(Character.class, Byte.TYPE, true), "char -> byte");
+ assertFalse(ClassUtils.isAssignable(Character.class, Short.TYPE, true), "char -> short");
+ assertTrue(ClassUtils.isAssignable(Character.class, Integer.TYPE, true), "char -> int");
+ assertTrue(ClassUtils.isAssignable(Character.class, Long.TYPE, true), "char -> long");
+ assertTrue(ClassUtils.isAssignable(Character.class, Float.TYPE, true), "char -> float");
+ assertTrue(ClassUtils.isAssignable(Character.class, Double.TYPE, true), "char -> double");
+ assertFalse(ClassUtils.isAssignable(Character.class, Boolean.TYPE, true), "char -> boolean");
+
+ // test int conversions
+ assertFalse(ClassUtils.isAssignable(Integer.class, Character.TYPE, true), "int -> char");
+ assertFalse(ClassUtils.isAssignable(Integer.class, Byte.TYPE, true), "int -> byte");
+ assertFalse(ClassUtils.isAssignable(Integer.class, Short.TYPE, true), "int -> short");
+ assertTrue(ClassUtils.isAssignable(Integer.class, Integer.TYPE, true), "int -> int");
+ assertTrue(ClassUtils.isAssignable(Integer.class, Long.TYPE, true), "int -> long");
+ assertTrue(ClassUtils.isAssignable(Integer.class, Float.TYPE, true), "int -> float");
+ assertTrue(ClassUtils.isAssignable(Integer.class, Double.TYPE, true), "int -> double");
+ assertFalse(ClassUtils.isAssignable(Integer.class, Boolean.TYPE, true), "int -> boolean");
+
+ // test long conversions
+ assertFalse(ClassUtils.isAssignable(Long.class, Character.TYPE, true), "long -> char");
+ assertFalse(ClassUtils.isAssignable(Long.class, Byte.TYPE, true), "long -> byte");
+ assertFalse(ClassUtils.isAssignable(Long.class, Short.TYPE, true), "long -> short");
+ assertFalse(ClassUtils.isAssignable(Long.class, Integer.TYPE, true), "long -> int");
+ assertTrue(ClassUtils.isAssignable(Long.class, Long.TYPE, true), "long -> long");
+ assertTrue(ClassUtils.isAssignable(Long.class, Float.TYPE, true), "long -> float");
+ assertTrue(ClassUtils.isAssignable(Long.class, Double.TYPE, true), "long -> double");
+ assertFalse(ClassUtils.isAssignable(Long.class, Boolean.TYPE, true), "long -> boolean");
+
+ // test float conversions
+ assertFalse(ClassUtils.isAssignable(Float.class, Character.TYPE, true), "float -> char");
+ assertFalse(ClassUtils.isAssignable(Float.class, Byte.TYPE, true), "float -> byte");
+ assertFalse(ClassUtils.isAssignable(Float.class, Short.TYPE, true), "float -> short");
+ assertFalse(ClassUtils.isAssignable(Float.class, Integer.TYPE, true), "float -> int");
+ assertFalse(ClassUtils.isAssignable(Float.class, Long.TYPE, true), "float -> long");
+ assertTrue(ClassUtils.isAssignable(Float.class, Float.TYPE, true), "float -> float");
+ assertTrue(ClassUtils.isAssignable(Float.class, Double.TYPE, true), "float -> double");
+ assertFalse(ClassUtils.isAssignable(Float.class, Boolean.TYPE, true), "float -> boolean");
+
+ // test double conversions
+ assertFalse(ClassUtils.isAssignable(Double.class, Character.TYPE, true), "double -> char");
+ assertFalse(ClassUtils.isAssignable(Double.class, Byte.TYPE, true), "double -> byte");
+ assertFalse(ClassUtils.isAssignable(Double.class, Short.TYPE, true), "double -> short");
+ assertFalse(ClassUtils.isAssignable(Double.class, Integer.TYPE, true), "double -> int");
+ assertFalse(ClassUtils.isAssignable(Double.class, Long.TYPE, true), "double -> long");
+ assertFalse(ClassUtils.isAssignable(Double.class, Float.TYPE, true), "double -> float");
+ assertTrue(ClassUtils.isAssignable(Double.class, Double.TYPE, true), "double -> double");
+ assertFalse(ClassUtils.isAssignable(Double.class, Boolean.TYPE, true), "double -> boolean");
+
+ // test boolean conversions
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Character.TYPE, true), "boolean -> char");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Byte.TYPE, true), "boolean -> byte");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Short.TYPE, true), "boolean -> short");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Integer.TYPE, true), "boolean -> int");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Long.TYPE, true), "boolean -> long");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Float.TYPE, true), "boolean -> float");
+ assertFalse(ClassUtils.isAssignable(Boolean.class, Double.TYPE, true), "boolean -> double");
+ assertTrue(ClassUtils.isAssignable(Boolean.class, Boolean.TYPE, true), "boolean -> boolean");
+ }
+
+ @Test
+ public void test_isAssignable_Widening() {
+ // test byte conversions
+ assertFalse(ClassUtils.isAssignable(Byte.TYPE, Character.TYPE), "byte -> char");
+ assertTrue(ClassUtils.isAssignable(Byte.TYPE, Byte.TYPE), "byte -> byte");
+ assertTrue(ClassUtils.isAssignable(Byte.TYPE, Short.TYPE), "byte -> short");
+ assertTrue(ClassUtils.isAssignable(Byte.TYPE, Integer.TYPE), "byte -> int");
+ assertTrue(ClassUtils.isAssignable(Byte.TYPE, Long.TYPE), "byte -> long");
+ assertTrue(ClassUtils.isAssignable(Byte.TYPE, Float.TYPE), "byte -> float");
+ assertTrue(ClassUtils.isAssignable(Byte.TYPE, Double.TYPE), "byte -> double");
+ assertFalse(ClassUtils.isAssignable(Byte.TYPE, Boolean.TYPE), "byte -> boolean");
+
+ // test short conversions
+ assertFalse(ClassUtils.isAssignable(Short.TYPE, Character.TYPE), "short -> char");
+ assertFalse(ClassUtils.isAssignable(Short.TYPE, Byte.TYPE), "short -> byte");
+ assertTrue(ClassUtils.isAssignable(Short.TYPE, Short.TYPE), "short -> short");
+ assertTrue(ClassUtils.isAssignable(Short.TYPE, Integer.TYPE), "short -> int");
+ assertTrue(ClassUtils.isAssignable(Short.TYPE, Long.TYPE), "short -> long");
+ assertTrue(ClassUtils.isAssignable(Short.TYPE, Float.TYPE), "short -> float");
+ assertTrue(ClassUtils.isAssignable(Short.TYPE, Double.TYPE), "short -> double");
+ assertFalse(ClassUtils.isAssignable(Short.TYPE, Boolean.TYPE), "short -> boolean");
+
+ // test char conversions
+ assertTrue(ClassUtils.isAssignable(Character.TYPE, Character.TYPE), "char -> char");
+ assertFalse(ClassUtils.isAssignable(Character.TYPE, Byte.TYPE), "char -> byte");
+ assertFalse(ClassUtils.isAssignable(Character.TYPE, Short.TYPE), "char -> short");
+ assertTrue(ClassUtils.isAssignable(Character.TYPE, Integer.TYPE), "char -> int");
+ assertTrue(ClassUtils.isAssignable(Character.TYPE, Long.TYPE), "char -> long");
+ assertTrue(ClassUtils.isAssignable(Character.TYPE, Float.TYPE), "char -> float");
+ assertTrue(ClassUtils.isAssignable(Character.TYPE, Double.TYPE), "char -> double");
+ assertFalse(ClassUtils.isAssignable(Character.TYPE, Boolean.TYPE), "char -> boolean");
+
+ // test int conversions
+ assertFalse(ClassUtils.isAssignable(Integer.TYPE, Character.TYPE), "int -> char");
+ assertFalse(ClassUtils.isAssignable(Integer.TYPE, Byte.TYPE), "int -> byte");
+ assertFalse(ClassUtils.isAssignable(Integer.TYPE, Short.TYPE), "int -> short");
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Integer.TYPE), "int -> int");
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Long.TYPE), "int -> long");
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Float.TYPE), "int -> float");
+ assertTrue(ClassUtils.isAssignable(Integer.TYPE, Double.TYPE), "int -> double");
+ assertFalse(ClassUtils.isAssignable(Integer.TYPE, Boolean.TYPE), "int -> boolean");
+
+ // test long conversions
+ assertFalse(ClassUtils.isAssignable(Long.TYPE, Character.TYPE), "long -> char");
+ assertFalse(ClassUtils.isAssignable(Long.TYPE, Byte.TYPE), "long -> byte");
+ assertFalse(ClassUtils.isAssignable(Long.TYPE, Short.TYPE), "long -> short");
+ assertFalse(ClassUtils.isAssignable(Long.TYPE, Integer.TYPE), "long -> int");
+ assertTrue(ClassUtils.isAssignable(Long.TYPE, Long.TYPE), "long -> long");
+ assertTrue(ClassUtils.isAssignable(Long.TYPE, Float.TYPE), "long -> float");
+ assertTrue(ClassUtils.isAssignable(Long.TYPE, Double.TYPE), "long -> double");
+ assertFalse(ClassUtils.isAssignable(Long.TYPE, Boolean.TYPE), "long -> boolean");
+
+ // test float conversions
+ assertFalse(ClassUtils.isAssignable(Float.TYPE, Character.TYPE), "float -> char");
+ assertFalse(ClassUtils.isAssignable(Float.TYPE, Byte.TYPE), "float -> byte");
+ assertFalse(ClassUtils.isAssignable(Float.TYPE, Short.TYPE), "float -> short");
+ assertFalse(ClassUtils.isAssignable(Float.TYPE, Integer.TYPE), "float -> int");
+ assertFalse(ClassUtils.isAssignable(Float.TYPE, Long.TYPE), "float -> long");
+ assertTrue(ClassUtils.isAssignable(Float.TYPE, Float.TYPE), "float -> float");
+ assertTrue(ClassUtils.isAssignable(Float.TYPE, Double.TYPE), "float -> double");
+ assertFalse(ClassUtils.isAssignable(Float.TYPE, Boolean.TYPE), "float -> boolean");
+
+ // test double conversions
+ assertFalse(ClassUtils.isAssignable(Double.TYPE, Character.TYPE), "double -> char");
+ assertFalse(ClassUtils.isAssignable(Double.TYPE, Byte.TYPE), "double -> byte");
+ assertFalse(ClassUtils.isAssignable(Double.TYPE, Short.TYPE), "double -> short");
+ assertFalse(ClassUtils.isAssignable(Double.TYPE, Integer.TYPE), "double -> int");
+ assertFalse(ClassUtils.isAssignable(Double.TYPE, Long.TYPE), "double -> long");
+ assertFalse(ClassUtils.isAssignable(Double.TYPE, Float.TYPE), "double -> float");
+ assertTrue(ClassUtils.isAssignable(Double.TYPE, Double.TYPE), "double -> double");
+ assertFalse(ClassUtils.isAssignable(Double.TYPE, Boolean.TYPE), "double -> boolean");
+
+ // test boolean conversions
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Character.TYPE), "boolean -> char");
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Byte.TYPE), "boolean -> byte");
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Short.TYPE), "boolean -> short");
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Integer.TYPE), "boolean -> int");
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Long.TYPE), "boolean -> long");
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Float.TYPE), "boolean -> float");
+ assertFalse(ClassUtils.isAssignable(Boolean.TYPE, Double.TYPE), "boolean -> double");
+ assertTrue(ClassUtils.isAssignable(Boolean.TYPE, Boolean.TYPE), "boolean -> boolean");
+ }
+
+ @Test
+ public void test_isInnerClass_Class() {
+ assertTrue(ClassUtils.isInnerClass(Inner.class));
+ assertTrue(ClassUtils.isInnerClass(Map.Entry.class));
+ assertTrue(ClassUtils.isInnerClass(new Cloneable() {
+ // empty
+ }.getClass()));
+ assertFalse(ClassUtils.isInnerClass(this.getClass()));
+ assertFalse(ClassUtils.isInnerClass(String.class));
+ assertFalse(ClassUtils.isInnerClass(null));
+ }
+
+ @Test
+ public void testComparable() {
+ final TreeMap<Class<?>, String> map = new TreeMap<>(ClassUtils.comparator());
+ map.put(String.class, "lastEntry");
+ map.toString();
+ map.put(Character.class, "firstEntry");
+ map.toString();
+ assertEquals("firstEntry", map.firstEntry().getValue());
+ assertEquals(Character.class, map.firstEntry().getKey());
+ //
+ assertEquals("lastEntry", map.lastEntry().getValue());
+ assertEquals(String.class, map.lastEntry().getKey());
+ //
+ map.put(null, "null");
+ map.toString();
+ assertEquals("null", map.get(null));
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new ClassUtils());
+ final Constructor<?>[] cons = ClassUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(ClassUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(ClassUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testGetClassByNormalNameArrays() throws ClassNotFoundException {
+ assertEquals(int[].class, ClassUtils.getClass("int[]"));
+ assertEquals(long[].class, ClassUtils.getClass("long[]"));
+ assertEquals(short[].class, ClassUtils.getClass("short[]"));
+ assertEquals(byte[].class, ClassUtils.getClass("byte[]"));
+ assertEquals(char[].class, ClassUtils.getClass("char[]"));
+ assertEquals(float[].class, ClassUtils.getClass("float[]"));
+ assertEquals(double[].class, ClassUtils.getClass("double[]"));
+ assertEquals(boolean[].class, ClassUtils.getClass("boolean[]"));
+ assertEquals(String[].class, ClassUtils.getClass("java.lang.String[]"));
+ assertEquals(java.util.Map.Entry[].class, ClassUtils.getClass("java.util.Map.Entry[]"));
+ assertEquals(java.util.Map.Entry[].class, ClassUtils.getClass("java.util.Map$Entry[]"));
+ assertEquals(java.util.Map.Entry[].class, ClassUtils.getClass("[Ljava.util.Map.Entry;"));
+ assertEquals(java.util.Map.Entry[].class, ClassUtils.getClass("[Ljava.util.Map$Entry;"));
+ }
+
+ @Test
+ public void testGetClassByNormalNameArrays2D() throws ClassNotFoundException {
+ assertEquals(int[][].class, ClassUtils.getClass("int[][]"));
+ assertEquals(long[][].class, ClassUtils.getClass("long[][]"));
+ assertEquals(short[][].class, ClassUtils.getClass("short[][]"));
+ assertEquals(byte[][].class, ClassUtils.getClass("byte[][]"));
+ assertEquals(char[][].class, ClassUtils.getClass("char[][]"));
+ assertEquals(float[][].class, ClassUtils.getClass("float[][]"));
+ assertEquals(double[][].class, ClassUtils.getClass("double[][]"));
+ assertEquals(boolean[][].class, ClassUtils.getClass("boolean[][]"));
+ assertEquals(String[][].class, ClassUtils.getClass("java.lang.String[][]"));
+ }
+
+ @Test
+ public void testGetClassClassNotFound() throws Exception {
+ assertGetClassThrowsClassNotFound("bool");
+ assertGetClassThrowsClassNotFound("bool[]");
+ assertGetClassThrowsClassNotFound("integer[]");
+ }
+
+ @Test
+ public void testGetClassInvalidArguments() throws Exception {
+ assertGetClassThrowsNullPointerException(null);
+ assertGetClassThrowsClassNotFound("[][][]");
+ assertGetClassThrowsClassNotFound("[[]");
+ assertGetClassThrowsClassNotFound("[");
+ assertGetClassThrowsClassNotFound("java.lang.String][");
+ assertGetClassThrowsClassNotFound(".hello.world");
+ assertGetClassThrowsClassNotFound("hello..world");
+ }
+
+ @Test
+ public void testGetClassRawPrimitives() throws ClassNotFoundException {
+ assertEquals(int.class, ClassUtils.getClass("int"));
+ assertEquals(long.class, ClassUtils.getClass("long"));
+ assertEquals(short.class, ClassUtils.getClass("short"));
+ assertEquals(byte.class, ClassUtils.getClass("byte"));
+ assertEquals(char.class, ClassUtils.getClass("char"));
+ assertEquals(float.class, ClassUtils.getClass("float"));
+ assertEquals(double.class, ClassUtils.getClass("double"));
+ assertEquals(boolean.class, ClassUtils.getClass("boolean"));
+ assertEquals(void.class, ClassUtils.getClass("void"));
+ }
+
+ @Test
+ public void testGetClassWithArrayClasses() throws Exception {
+ assertGetClassReturnsClass(String[].class);
+ assertGetClassReturnsClass(int[].class);
+ assertGetClassReturnsClass(long[].class);
+ assertGetClassReturnsClass(short[].class);
+ assertGetClassReturnsClass(byte[].class);
+ assertGetClassReturnsClass(char[].class);
+ assertGetClassReturnsClass(float[].class);
+ assertGetClassReturnsClass(double[].class);
+ assertGetClassReturnsClass(boolean[].class);
+ }
+
+ @Test
+ public void testGetClassWithArrayClasses2D() throws Exception {
+ assertGetClassReturnsClass(String[][].class);
+ assertGetClassReturnsClass(int[][].class);
+ assertGetClassReturnsClass(long[][].class);
+ assertGetClassReturnsClass(short[][].class);
+ assertGetClassReturnsClass(byte[][].class);
+ assertGetClassReturnsClass(char[][].class);
+ assertGetClassReturnsClass(float[][].class);
+ assertGetClassReturnsClass(double[][].class);
+ assertGetClassReturnsClass(boolean[][].class);
+ }
+
+ @Test
+ public void testGetComponentType() {
+ final CX[] newArray = {};
+ @SuppressWarnings("unchecked")
+ final Class<CX[]> classCxArray = (Class<CX[]>) newArray.getClass();
+ // No type-cast required.
+ final Class<CX> componentType = ClassUtils.getComponentType(classCxArray);
+ assertEquals(CX.class, componentType);
+ assertNull(ClassUtils.getComponentType(null));
+ }
+
+ @Test
+ public void testGetInnerClass() throws ClassNotFoundException {
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
+ //
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested", true));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested", true));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested", true));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested", true));
+ //
+ final ClassLoader classLoader = Inner.DeeplyNested.class.getClassLoader();
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
+ //
+ }
+
+ @Test
+ public void testGetPublicMethod() throws Exception {
+ // Tests with Collections$UnmodifiableSet
+ final Set<?> set = Collections.unmodifiableSet(new HashSet<>());
+ final Method isEmptyMethod = ClassUtils.getPublicMethod(set.getClass(), "isEmpty");
+ assertTrue(Modifier.isPublic(isEmptyMethod.getDeclaringClass().getModifiers()));
+ assertTrue((Boolean) isEmptyMethod.invoke(set));
+
+ // Tests with a public Class
+ final Method toStringMethod = ClassUtils.getPublicMethod(Object.class, "toString");
+ assertEquals(Object.class.getMethod("toString"), toStringMethod);
+ }
+
+ @Test
+ public void testHierarchyExcludingInterfaces() {
+ final Iterator<Class<?>> iter = ClassUtils.hierarchy(StringParameterizedChild.class).iterator();
+ assertEquals(StringParameterizedChild.class, iter.next());
+ assertEquals(GenericParent.class, iter.next());
+ assertEquals(Object.class, iter.next());
+ assertFalse(iter.hasNext());
+ }
+
+ @Test
+ public void testHierarchyIncludingInterfaces() {
+ final Iterator<Class<?>> iter = ClassUtils.hierarchy(StringParameterizedChild.class, Interfaces.INCLUDE).iterator();
+ assertEquals(StringParameterizedChild.class, iter.next());
+ assertEquals(GenericParent.class, iter.next());
+ assertEquals(GenericConsumer.class, iter.next());
+ assertEquals(Object.class, iter.next());
+ assertFalse(iter.hasNext());
+ }
+
+ @Test
+ public void testIsPrimitiveOrWrapper() {
+
+ // test primitive wrapper classes
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Boolean.class), "Boolean.class");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Byte.class), "Byte.class");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Character.class), "Character.class");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Short.class), "Short.class");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Integer.class), "Integer.class");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Long.class), "Long.class");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Double.class), "Double.class");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Float.class), "Float.class");
+
+ // test primitive classes
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Boolean.TYPE), "boolean");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Byte.TYPE), "byte");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Character.TYPE), "char");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Short.TYPE), "short");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Integer.TYPE), "int");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Long.TYPE), "long");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Double.TYPE), "double");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Float.TYPE), "float");
+ assertTrue(ClassUtils.isPrimitiveOrWrapper(Void.TYPE), "Void.TYPE");
+
+ // others
+ assertFalse(ClassUtils.isPrimitiveOrWrapper(null), "null");
+ assertFalse(ClassUtils.isPrimitiveOrWrapper(Void.class), "Void.class");
+ assertFalse(ClassUtils.isPrimitiveOrWrapper(String.class), "String.class");
+ assertFalse(ClassUtils.isPrimitiveOrWrapper(this.getClass()), "this.getClass()");
+ }
+
+ @Test
+ public void testIsPrimitiveWrapper() {
+
+ // test primitive wrapper classes
+ assertTrue(ClassUtils.isPrimitiveWrapper(Boolean.class), "Boolean.class");
+ assertTrue(ClassUtils.isPrimitiveWrapper(Byte.class), "Byte.class");
+ assertTrue(ClassUtils.isPrimitiveWrapper(Character.class), "Character.class");
+ assertTrue(ClassUtils.isPrimitiveWrapper(Short.class), "Short.class");
+ assertTrue(ClassUtils.isPrimitiveWrapper(Integer.class), "Integer.class");
+ assertTrue(ClassUtils.isPrimitiveWrapper(Long.class), "Long.class");
+ assertTrue(ClassUtils.isPrimitiveWrapper(Double.class), "Double.class");
+ assertTrue(ClassUtils.isPrimitiveWrapper(Float.class), "Float.class");
+
+ // test primitive classes
+ assertFalse(ClassUtils.isPrimitiveWrapper(Boolean.TYPE), "boolean");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Byte.TYPE), "byte");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Character.TYPE), "char");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Short.TYPE), "short");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Integer.TYPE), "int");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Long.TYPE), "long");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Double.TYPE), "double");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Float.TYPE), "float");
+
+ // others
+ assertFalse(ClassUtils.isPrimitiveWrapper(null), "null");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Void.class), "Void.class");
+ assertFalse(ClassUtils.isPrimitiveWrapper(Void.TYPE), "Void.TYPE");
+ assertFalse(ClassUtils.isPrimitiveWrapper(String.class), "String.class");
+ assertFalse(ClassUtils.isPrimitiveWrapper(this.getClass()), "this.getClass()");
+ }
+
+ @Test
+ public void testPrimitivesToWrappers() {
+ // test null
+// assertNull("null -> null", ClassUtils.primitivesToWrappers(null)); // generates warning
+ assertNull(ClassUtils.primitivesToWrappers((Class<?>[]) null), "null -> null"); // equivalent cast to avoid warning
+ // Other possible casts for null
+ assertArrayEquals(ArrayUtils.EMPTY_CLASS_ARRAY, ClassUtils.primitivesToWrappers(), "empty -> empty");
+ final Class<?>[] castNull = ClassUtils.primitivesToWrappers((Class<?>) null); // == new Class<?>[]{null}
+ assertArrayEquals(new Class<?>[] {null}, castNull, "(Class<?>) null -> [null]");
+ // test empty array is returned unchanged
+ assertArrayEquals(ArrayUtils.EMPTY_CLASS_ARRAY, ClassUtils.primitivesToWrappers(ArrayUtils.EMPTY_CLASS_ARRAY), "empty -> empty");
+
+ // test an array of various classes
+ final Class<?>[] primitives = new Class[] {Boolean.TYPE, Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Double.TYPE, Float.TYPE,
+ String.class, ClassUtils.class};
+ final Class<?>[] wrappers = ClassUtils.primitivesToWrappers(primitives);
+
+ for (int i = 0; i < primitives.length; i++) {
+ // test each returned wrapper
+ final Class<?> primitive = primitives[i];
+ final Class<?> expectedWrapper = ClassUtils.primitiveToWrapper(primitive);
+
+ assertEquals(expectedWrapper, wrappers[i], primitive + " -> " + expectedWrapper);
+ }
+
+ // test an array of no primitive classes
+ final Class<?>[] noPrimitives = new Class[] {String.class, ClassUtils.class, Void.TYPE};
+ // This used to return the exact same array, but no longer does.
+ assertNotSame(noPrimitives, ClassUtils.primitivesToWrappers(noPrimitives), "unmodified");
+ }
+
+ @Test
+ public void testPrimitiveToWrapper() {
+
+ // test primitive classes
+ assertEquals(Boolean.class, ClassUtils.primitiveToWrapper(Boolean.TYPE), "boolean -> Boolean.class");
+ assertEquals(Byte.class, ClassUtils.primitiveToWrapper(Byte.TYPE), "byte -> Byte.class");
+ assertEquals(Character.class, ClassUtils.primitiveToWrapper(Character.TYPE), "char -> Character.class");
+ assertEquals(Short.class, ClassUtils.primitiveToWrapper(Short.TYPE), "short -> Short.class");
+ assertEquals(Integer.class, ClassUtils.primitiveToWrapper(Integer.TYPE), "int -> Integer.class");
+ assertEquals(Long.class, ClassUtils.primitiveToWrapper(Long.TYPE), "long -> Long.class");
+ assertEquals(Double.class, ClassUtils.primitiveToWrapper(Double.TYPE), "double -> Double.class");
+ assertEquals(Float.class, ClassUtils.primitiveToWrapper(Float.TYPE), "float -> Float.class");
+
+ // test a few other classes
+ assertEquals(String.class, ClassUtils.primitiveToWrapper(String.class), "String.class -> String.class");
+ assertEquals(ClassUtils.class, ClassUtils.primitiveToWrapper(ClassUtils.class), "ClassUtils.class -> ClassUtils.class");
+ assertEquals(Void.TYPE, ClassUtils.primitiveToWrapper(Void.TYPE), "Void.TYPE -> Void.TYPE");
+
+ // test null
+ assertNull(ClassUtils.primitiveToWrapper(null), "null -> null");
+ }
+
+ // Show the Java bug: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4071957
+ // We may have to delete this if a JDK fixes the bug.
+ @Test
+ public void testShowJavaBug() throws Exception {
+ // Tests with Collections$UnmodifiableSet
+ final Set<?> set = Collections.unmodifiableSet(new HashSet<>());
+ final Method isEmptyMethod = set.getClass().getMethod("isEmpty");
+ assertThrows(IllegalAccessException.class, () -> isEmptyMethod.invoke(set));
+ }
+
+ @Test
+ public void testToClass_object() {
+// assertNull(ClassUtils.toClass(null)); // generates warning
+ assertNull(ClassUtils.toClass((Object[]) null)); // equivalent explicit cast
+
+ // Additional varargs tests
+ assertArrayEquals(ArrayUtils.EMPTY_CLASS_ARRAY, ClassUtils.toClass(), "empty -> empty");
+ final Class<?>[] castNull = ClassUtils.toClass((Object) null); // == new Object[]{null}
+ assertArrayEquals(new Object[] {null}, castNull, "(Object) null -> [null]");
+
+ assertSame(ArrayUtils.EMPTY_CLASS_ARRAY, ClassUtils.toClass(ArrayUtils.EMPTY_OBJECT_ARRAY));
+
+ assertArrayEquals(new Class[] {String.class, Integer.class, Double.class}, ClassUtils.toClass("Test", Integer.valueOf(1), Double.valueOf(99d)));
+
+ assertArrayEquals(new Class[] {String.class, null, Double.class}, ClassUtils.toClass("Test", null, Double.valueOf(99d)));
+ }
+
+ @Test
+ public void testWithInterleavingWhitespace() throws ClassNotFoundException {
+ assertEquals(int[].class, ClassUtils.getClass(" int [ ] "));
+ assertEquals(long[].class, ClassUtils.getClass("\rlong\t[\n]\r"));
+ assertEquals(short[].class, ClassUtils.getClass("\tshort \t\t[]"));
+ assertEquals(byte[].class, ClassUtils.getClass("byte[\t\t\n\r] "));
+ }
+
+ @Test
+ public void testWrappersToPrimitives() {
+ // an array with classes to test
+ final Class<?>[] classes = {Boolean.class, Byte.class, Character.class, Short.class, Integer.class, Long.class, Float.class, Double.class, String.class,
+ ClassUtils.class, null};
+
+ final Class<?>[] primitives = ClassUtils.wrappersToPrimitives(classes);
+ // now test the result
+ assertEquals(classes.length, primitives.length, "Wrong length of result array");
+ for (int i = 0; i < classes.length; i++) {
+ final Class<?> expectedPrimitive = ClassUtils.wrapperToPrimitive(classes[i]);
+ assertEquals(expectedPrimitive, primitives[i], classes[i] + " -> " + expectedPrimitive);
+ }
+ }
+
+ @Test
+ public void testWrappersToPrimitivesEmpty() {
+ final Class<?>[] empty = new Class[0];
+ assertArrayEquals(empty, ClassUtils.wrappersToPrimitives(empty), "Wrong result for empty input");
+ }
+
+ @Test
+ public void testWrappersToPrimitivesNull() {
+// assertNull("Wrong result for null input", ClassUtils.wrappersToPrimitives(null)); // generates warning
+ assertNull(ClassUtils.wrappersToPrimitives((Class<?>[]) null), "Wrong result for null input"); // equivalent cast
+ // Other possible casts for null
+ assertArrayEquals(ArrayUtils.EMPTY_CLASS_ARRAY, ClassUtils.wrappersToPrimitives(), "empty -> empty");
+ final Class<?>[] castNull = ClassUtils.wrappersToPrimitives((Class<?>) null); // == new Class<?>[]{null}
+ assertArrayEquals(new Class<?>[] {null}, castNull, "(Class<?>) null -> [null]");
+ }
+
+ @Test
+ public void testWrapperToPrimitive() {
+ // an array with classes to convert
+ final Class<?>[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE};
+ for (final Class<?> primitive : primitives) {
+ final Class<?> wrapperCls = ClassUtils.primitiveToWrapper(primitive);
+ assertFalse(wrapperCls.isPrimitive(), "Still primitive");
+ assertEquals(primitive, ClassUtils.wrapperToPrimitive(wrapperCls), wrapperCls + " -> " + primitive);
+ }
+ }
+
+ @Test
+ public void testWrapperToPrimitiveNoWrapper() {
+ assertNull(ClassUtils.wrapperToPrimitive(String.class), "Wrong result for non wrapper class");
+ }
+
+ @Test
+ public void testWrapperToPrimitiveNull() {
+ assertNull(ClassUtils.wrapperToPrimitive(null), "Wrong result for null class");
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ConversionTest.java b/src/test/java/org/apache/commons/lang3/ConversionTest.java
new file mode 100644
index 000000000..c10573918
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ConversionTest.java
@@ -0,0 +1,1766 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Arrays;
+import java.util.SplittableRandom;
+import java.util.UUID;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+
+/**
+ * Unit tests {@link Conversion}.
+ */
+public class ConversionTest extends AbstractLangTest {
+
+ /**
+ * Tests {@link Conversion#hexDigitToInt(char)}.
+ */
+ @Test
+ public void testHexDigitToInt() {
+ assertEquals(0, Conversion.hexDigitToInt('0'));
+ assertEquals(1, Conversion.hexDigitToInt('1'));
+ assertEquals(2, Conversion.hexDigitToInt('2'));
+ assertEquals(3, Conversion.hexDigitToInt('3'));
+ assertEquals(4, Conversion.hexDigitToInt('4'));
+ assertEquals(5, Conversion.hexDigitToInt('5'));
+ assertEquals(6, Conversion.hexDigitToInt('6'));
+ assertEquals(7, Conversion.hexDigitToInt('7'));
+ assertEquals(8, Conversion.hexDigitToInt('8'));
+ assertEquals(9, Conversion.hexDigitToInt('9'));
+ assertEquals(10, Conversion.hexDigitToInt('A'));
+ assertEquals(10, Conversion.hexDigitToInt('a'));
+ assertEquals(11, Conversion.hexDigitToInt('B'));
+ assertEquals(11, Conversion.hexDigitToInt('b'));
+ assertEquals(12, Conversion.hexDigitToInt('C'));
+ assertEquals(12, Conversion.hexDigitToInt('c'));
+ assertEquals(13, Conversion.hexDigitToInt('D'));
+ assertEquals(13, Conversion.hexDigitToInt('d'));
+ assertEquals(14, Conversion.hexDigitToInt('E'));
+ assertEquals(14, Conversion.hexDigitToInt('e'));
+ assertEquals(15, Conversion.hexDigitToInt('F'));
+ assertEquals(15, Conversion.hexDigitToInt('f'));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.hexDigitToInt('G'));
+ }
+
+ /**
+ * Tests {@link Conversion#hexDigitMsb0ToInt(char)}.
+ */
+ @Test
+ public void testHexDigitMsb0ToInt() {
+ assertEquals(0x0, Conversion.hexDigitMsb0ToInt('0'));
+ assertEquals(0x8, Conversion.hexDigitMsb0ToInt('1'));
+ assertEquals(0x4, Conversion.hexDigitMsb0ToInt('2'));
+ assertEquals(0xC, Conversion.hexDigitMsb0ToInt('3'));
+ assertEquals(0x2, Conversion.hexDigitMsb0ToInt('4'));
+ assertEquals(0xA, Conversion.hexDigitMsb0ToInt('5'));
+ assertEquals(0x6, Conversion.hexDigitMsb0ToInt('6'));
+ assertEquals(0xE, Conversion.hexDigitMsb0ToInt('7'));
+ assertEquals(0x1, Conversion.hexDigitMsb0ToInt('8'));
+ assertEquals(0x9, Conversion.hexDigitMsb0ToInt('9'));
+ assertEquals(0x5, Conversion.hexDigitMsb0ToInt('A'));
+ assertEquals(0x5, Conversion.hexDigitMsb0ToInt('a'));
+ assertEquals(0xD, Conversion.hexDigitMsb0ToInt('B'));
+ assertEquals(0xD, Conversion.hexDigitMsb0ToInt('b'));
+ assertEquals(0x3, Conversion.hexDigitMsb0ToInt('C'));
+ assertEquals(0x3, Conversion.hexDigitMsb0ToInt('c'));
+ assertEquals(0xB, Conversion.hexDigitMsb0ToInt('D'));
+ assertEquals(0xB, Conversion.hexDigitMsb0ToInt('d'));
+ assertEquals(0x7, Conversion.hexDigitMsb0ToInt('E'));
+ assertEquals(0x7, Conversion.hexDigitMsb0ToInt('e'));
+ assertEquals(0xF, Conversion.hexDigitMsb0ToInt('F'));
+ assertEquals(0xF, Conversion.hexDigitMsb0ToInt('f'));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.hexDigitMsb0ToInt('G'));
+ }
+
+ /**
+ * Tests {@link Conversion#hexDigitToBinary(char)}.
+ */
+ @Test
+ public void testHexDigitToBinary() {
+ assertArrayEquals(
+ new boolean[]{false, false, false, false}, Conversion.hexDigitToBinary('0'));
+ assertArrayEquals(
+ new boolean[]{true, false, false, false}, Conversion.hexDigitToBinary('1'));
+ assertArrayEquals(
+ new boolean[]{false, true, false, false}, Conversion.hexDigitToBinary('2'));
+ assertArrayEquals(
+ new boolean[]{true, true, false, false}, Conversion.hexDigitToBinary('3'));
+ assertArrayEquals(
+ new boolean[]{false, false, true, false}, Conversion.hexDigitToBinary('4'));
+ assertArrayEquals(
+ new boolean[]{true, false, true, false}, Conversion.hexDigitToBinary('5'));
+ assertArrayEquals(
+ new boolean[]{false, true, true, false}, Conversion.hexDigitToBinary('6'));
+ assertArrayEquals(
+ new boolean[]{true, true, true, false}, Conversion.hexDigitToBinary('7'));
+ assertArrayEquals(
+ new boolean[]{false, false, false, true}, Conversion.hexDigitToBinary('8'));
+ assertArrayEquals(
+ new boolean[]{true, false, false, true}, Conversion.hexDigitToBinary('9'));
+ assertArrayEquals(
+ new boolean[]{false, true, false, true}, Conversion.hexDigitToBinary('A'));
+ assertArrayEquals(
+ new boolean[]{false, true, false, true}, Conversion.hexDigitToBinary('a'));
+ assertArrayEquals(
+ new boolean[]{true, true, false, true}, Conversion.hexDigitToBinary('B'));
+ assertArrayEquals(
+ new boolean[]{true, true, false, true}, Conversion.hexDigitToBinary('b'));
+ assertArrayEquals(
+ new boolean[]{false, false, true, true}, Conversion.hexDigitToBinary('C'));
+ assertArrayEquals(
+ new boolean[]{false, false, true, true}, Conversion.hexDigitToBinary('c'));
+ assertArrayEquals(
+ new boolean[]{true, false, true, true}, Conversion.hexDigitToBinary('D'));
+ assertArrayEquals(
+ new boolean[]{true, false, true, true}, Conversion.hexDigitToBinary('d'));
+ assertArrayEquals(
+ new boolean[]{false, true, true, true}, Conversion.hexDigitToBinary('E'));
+ assertArrayEquals(
+ new boolean[]{false, true, true, true}, Conversion.hexDigitToBinary('e'));
+ assertArrayEquals(
+ new boolean[]{true, true, true, true}, Conversion.hexDigitToBinary('F'));
+ assertArrayEquals(
+ new boolean[]{true, true, true, true}, Conversion.hexDigitToBinary('f'));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.hexDigitToBinary('G'));
+ }
+
+ /**
+ * Tests {@link Conversion#hexDigitMsb0ToBinary(char)}.
+ */
+ @Test
+ public void testHexDigitMsb0ToBinary() {
+ assertArrayEquals(
+ new boolean[]{false, false, false, false}, Conversion.hexDigitMsb0ToBinary('0'));
+ assertArrayEquals(
+ new boolean[]{false, false, false, true}, Conversion.hexDigitMsb0ToBinary('1'));
+ assertArrayEquals(
+ new boolean[]{false, false, true, false}, Conversion.hexDigitMsb0ToBinary('2'));
+ assertArrayEquals(
+ new boolean[]{false, false, true, true}, Conversion.hexDigitMsb0ToBinary('3'));
+ assertArrayEquals(
+ new boolean[]{false, true, false, false}, Conversion.hexDigitMsb0ToBinary('4'));
+ assertArrayEquals(
+ new boolean[]{false, true, false, true}, Conversion.hexDigitMsb0ToBinary('5'));
+ assertArrayEquals(
+ new boolean[]{false, true, true, false}, Conversion.hexDigitMsb0ToBinary('6'));
+ assertArrayEquals(
+ new boolean[]{false, true, true, true}, Conversion.hexDigitMsb0ToBinary('7'));
+ assertArrayEquals(
+ new boolean[]{true, false, false, false}, Conversion.hexDigitMsb0ToBinary('8'));
+ assertArrayEquals(
+ new boolean[]{true, false, false, true}, Conversion.hexDigitMsb0ToBinary('9'));
+ assertArrayEquals(
+ new boolean[]{true, false, true, false}, Conversion.hexDigitMsb0ToBinary('A'));
+ assertArrayEquals(
+ new boolean[]{true, false, true, false}, Conversion.hexDigitMsb0ToBinary('a'));
+ assertArrayEquals(
+ new boolean[]{true, false, true, true}, Conversion.hexDigitMsb0ToBinary('B'));
+ assertArrayEquals(
+ new boolean[]{true, false, true, true}, Conversion.hexDigitMsb0ToBinary('b'));
+ assertArrayEquals(
+ new boolean[]{true, true, false, false}, Conversion.hexDigitMsb0ToBinary('C'));
+ assertArrayEquals(
+ new boolean[]{true, true, false, false}, Conversion.hexDigitMsb0ToBinary('c'));
+ assertArrayEquals(
+ new boolean[]{true, true, false, true}, Conversion.hexDigitMsb0ToBinary('D'));
+ assertArrayEquals(
+ new boolean[]{true, true, false, true}, Conversion.hexDigitMsb0ToBinary('d'));
+ assertArrayEquals(
+ new boolean[]{true, true, true, false}, Conversion.hexDigitMsb0ToBinary('E'));
+ assertArrayEquals(
+ new boolean[]{true, true, true, false}, Conversion.hexDigitMsb0ToBinary('e'));
+ assertArrayEquals(
+ new boolean[]{true, true, true, true}, Conversion.hexDigitMsb0ToBinary('F'));
+ assertArrayEquals(
+ new boolean[]{true, true, true, true}, Conversion.hexDigitMsb0ToBinary('f'));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.hexDigitMsb0ToBinary('G'));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToHexDigit(boolean[])}.
+ */
+ @Test
+ public void testBinaryToHexDigit() {
+ assertEquals(
+ '0', Conversion.binaryToHexDigit(new boolean[]{false, false, false, false}));
+ assertEquals('1', Conversion.binaryToHexDigit(new boolean[]{true, false, false, false}));
+ assertEquals('2', Conversion.binaryToHexDigit(new boolean[]{false, true, false, false}));
+ assertEquals('3', Conversion.binaryToHexDigit(new boolean[]{true, true, false, false}));
+ assertEquals('4', Conversion.binaryToHexDigit(new boolean[]{false, false, true, false}));
+ assertEquals('5', Conversion.binaryToHexDigit(new boolean[]{true, false, true, false}));
+ assertEquals('6', Conversion.binaryToHexDigit(new boolean[]{false, true, true, false}));
+ assertEquals('7', Conversion.binaryToHexDigit(new boolean[]{true, true, true, false}));
+ assertEquals('8', Conversion.binaryToHexDigit(new boolean[]{false, false, false, true}));
+ assertEquals('9', Conversion.binaryToHexDigit(new boolean[]{true, false, false, true}));
+ assertEquals('a', Conversion.binaryToHexDigit(new boolean[]{false, true, false, true}));
+ assertEquals('b', Conversion.binaryToHexDigit(new boolean[]{true, true, false, true}));
+ assertEquals('c', Conversion.binaryToHexDigit(new boolean[]{false, false, true, true}));
+ assertEquals('d', Conversion.binaryToHexDigit(new boolean[]{true, false, true, true}));
+ assertEquals('e', Conversion.binaryToHexDigit(new boolean[]{false, true, true, true}));
+ assertEquals('f', Conversion.binaryToHexDigit(new boolean[]{true, true, true, true}));
+ assertEquals('1', Conversion.binaryToHexDigit(new boolean[]{true}));
+ assertEquals(
+ 'f', Conversion.binaryToHexDigit(new boolean[]{true, true, true, true, true}));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.binaryToHexDigit(new boolean[]{}));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryBeMsb0ToHexDigit(boolean[], int)}.
+ */
+ @Test
+ public void testBinaryToHexDigit_2args() {
+ final boolean[] shortArray = {false, true, true};
+ assertEquals('6', Conversion.binaryToHexDigit(shortArray, 0));
+ assertEquals('3', Conversion.binaryToHexDigit(shortArray, 1));
+ assertEquals('1', Conversion.binaryToHexDigit(shortArray, 2));
+ final boolean[] longArray = {true, false, true, false, false, true, true};
+ assertEquals('5', Conversion.binaryToHexDigit(longArray, 0));
+ assertEquals('2', Conversion.binaryToHexDigit(longArray, 1));
+ assertEquals('9', Conversion.binaryToHexDigit(longArray, 2));
+ assertEquals('c', Conversion.binaryToHexDigit(longArray, 3));
+ assertEquals('6', Conversion.binaryToHexDigit(longArray, 4));
+ assertEquals('3', Conversion.binaryToHexDigit(longArray, 5));
+ assertEquals('1', Conversion.binaryToHexDigit(longArray, 6));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToHexDigitMsb0_4bits(boolean[])}.
+ */
+ @Test
+ public void testBinaryToHexDigitMsb0_bits() {
+ assertEquals(
+ '0',
+ Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{false, false, false, false}));
+ assertEquals(
+ '1',
+ Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{false, false, false, true}));
+ assertEquals(
+ '2',
+ Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{false, false, true, false}));
+ assertEquals(
+ '3', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{false, false, true, true}));
+ assertEquals(
+ '4',
+ Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{false, true, false, false}));
+ assertEquals(
+ '5', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{false, true, false, true}));
+ assertEquals(
+ '6', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{false, true, true, false}));
+ assertEquals(
+ '7', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{false, true, true, true}));
+ assertEquals(
+ '8',
+ Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{true, false, false, false}));
+ assertEquals(
+ '9', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{true, false, false, true}));
+ assertEquals(
+ 'a', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{true, false, true, false}));
+ assertEquals(
+ 'b', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{true, false, true, true}));
+ assertEquals(
+ 'c', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{true, true, false, false}));
+ assertEquals(
+ 'd', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{true, true, false, true}));
+ assertEquals(
+ 'e', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{true, true, true, false}));
+ assertEquals(
+ 'f', Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{true, true, true, true}));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.binaryToHexDigitMsb0_4bits(new boolean[]{}));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToHexDigitMsb0_4bits(boolean[], int)}.
+ */
+ @Test
+ public void testBinaryToHexDigitMsb0_4bits_2args() {
+ // boolean[] shortArray = new boolean[]{true, true, false};
+ // assertEquals('6', Conversion.BinaryToHexDigitMsb0(shortArray, 0));
+ // assertEquals('3', Conversion.BinaryToHexDigitMsb0(shortArray, 1));
+ // assertEquals('1', Conversion.BinaryToHexDigitMsb0(shortArray, 2));
+ final boolean[] shortArray = {true, true, false, true};
+ assertEquals('d', Conversion.binaryToHexDigitMsb0_4bits(shortArray, 0));
+ final boolean[] longArray = {true, false, true, false, false, true, true};
+ assertEquals('a', Conversion.binaryToHexDigitMsb0_4bits(longArray, 0));
+ assertEquals('4', Conversion.binaryToHexDigitMsb0_4bits(longArray, 1));
+ assertEquals('9', Conversion.binaryToHexDigitMsb0_4bits(longArray, 2));
+ assertEquals('3', Conversion.binaryToHexDigitMsb0_4bits(longArray, 3));
+ // assertEquals('6', Conversion.BinaryToHexDigitMsb0(longArray, 4));
+ // assertEquals('3', Conversion.BinaryToHexDigitMsb0(longArray, 5));
+ // assertEquals('1', Conversion.BinaryToHexDigitMsb0(longArray, 6));
+ final boolean[] maxLengthArray = {
+ true, false, true, false, false, true, true, true};
+ assertEquals('a', Conversion.binaryToHexDigitMsb0_4bits(maxLengthArray, 0));
+ assertEquals('4', Conversion.binaryToHexDigitMsb0_4bits(maxLengthArray, 1));
+ assertEquals('9', Conversion.binaryToHexDigitMsb0_4bits(maxLengthArray, 2));
+ assertEquals('3', Conversion.binaryToHexDigitMsb0_4bits(maxLengthArray, 3));
+ assertEquals('7', Conversion.binaryToHexDigitMsb0_4bits(maxLengthArray, 4));
+ // assertEquals('7', Conversion.BinaryToHexDigitMsb0(longArray, 5));
+ // assertEquals('3', Conversion.BinaryToHexDigitMsb0(longArray, 6));
+ // assertEquals('1', Conversion.BinaryToHexDigitMsb0(longArray, 7));
+ final boolean[] javaDocCheck = {
+ true, false, false, true, true, false, true, false};
+ assertEquals('d', Conversion.binaryToHexDigitMsb0_4bits(javaDocCheck, 3));
+
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToHexDigit(boolean[])}.
+ */
+ @Test
+ public void testBinaryBeMsb0ToHexDigit() {
+ assertEquals(
+ '0', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{false, false, false, false}));
+ assertEquals(
+ '1', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{false, false, false, true}));
+ assertEquals(
+ '2', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{false, false, true, false}));
+ assertEquals(
+ '3', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{false, false, true, true}));
+ assertEquals(
+ '4', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{false, true, false, false}));
+ assertEquals(
+ '5', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{false, true, false, true}));
+ assertEquals(
+ '6', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{false, true, true, false}));
+ assertEquals(
+ '7', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{false, true, true, true}));
+ assertEquals(
+ '8', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{true, false, false, false}));
+ assertEquals(
+ '9', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{true, false, false, true}));
+ assertEquals(
+ 'a', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{true, false, true, false}));
+ assertEquals(
+ 'b', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{true, false, true, true}));
+ assertEquals(
+ 'c', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{true, true, false, false}));
+ assertEquals(
+ 'd', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{true, true, false, true}));
+ assertEquals(
+ 'e', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{true, true, true, false}));
+ assertEquals(
+ 'f', Conversion.binaryBeMsb0ToHexDigit(new boolean[]{true, true, true, true}));
+ assertEquals(
+ '4',
+ Conversion.binaryBeMsb0ToHexDigit(new boolean[]{
+ true, false, false, false, false, false, false, false, false, false, false,
+ false, false, true, false, false}));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.binaryBeMsb0ToHexDigit(new boolean[]{}));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToHexDigit(boolean[], int)}.
+ */
+ @Test
+ public void testBinaryBeMsb0ToHexDigit_2args() {
+ assertEquals(
+ '5',
+ Conversion.binaryBeMsb0ToHexDigit(new boolean[]{
+ true, false, false, false, false, false, false, false, false, false, false,
+ true, false, true, false, false}, 2));
+
+ final boolean[] shortArray = {true, true, false};
+ assertEquals('6', Conversion.binaryBeMsb0ToHexDigit(shortArray, 0));
+ assertEquals('3', Conversion.binaryBeMsb0ToHexDigit(shortArray, 1));
+ assertEquals('1', Conversion.binaryBeMsb0ToHexDigit(shortArray, 2));
+ final boolean[] shortArray2 = {true, true, true, false, false, true, false, true};
+ assertEquals('5', Conversion.binaryBeMsb0ToHexDigit(shortArray2, 0));
+ assertEquals('2', Conversion.binaryBeMsb0ToHexDigit(shortArray2, 1));
+ assertEquals('9', Conversion.binaryBeMsb0ToHexDigit(shortArray2, 2));
+ assertEquals('c', Conversion.binaryBeMsb0ToHexDigit(shortArray2, 3));
+ assertEquals('e', Conversion.binaryBeMsb0ToHexDigit(shortArray2, 4));
+ assertEquals('7', Conversion.binaryBeMsb0ToHexDigit(shortArray2, 5));
+ assertEquals('3', Conversion.binaryBeMsb0ToHexDigit(shortArray2, 6));
+ assertEquals('1', Conversion.binaryBeMsb0ToHexDigit(shortArray2, 7));
+ final boolean[] multiBytesArray = {
+ true, true, false, false, true, false, true, false, true, true, true, false, false,
+ true, false, true};
+ assertEquals('5', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 0));
+ assertEquals('2', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 1));
+ assertEquals('9', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 2));
+ assertEquals('c', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 3));
+ assertEquals('e', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 4));
+ assertEquals('7', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 5));
+ assertEquals('b', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 6));
+ assertEquals('5', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 7));
+
+ assertEquals('a', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 8));
+ assertEquals('5', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 9));
+ assertEquals('2', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 10));
+ assertEquals('9', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 11));
+ assertEquals('c', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 12));
+ assertEquals('6', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 13));
+ assertEquals('3', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 14));
+ assertEquals('1', Conversion.binaryBeMsb0ToHexDigit(multiBytesArray, 15));
+
+ }
+
+ @Test
+ public void testBinaryToHexDigitReverse() {
+ final SplittableRandom rng = new SplittableRandom();
+ final boolean[] x = new boolean[8];
+ for (int i = 0; i < 100; i++) {
+ Conversion.longToBinary(rng.nextLong(), 0, x, 0, 8);
+ for (int j = 1; j <= 8; j++) {
+ final boolean[] a = Arrays.copyOf(x, j);
+ final boolean[] b = a.clone();
+ ArrayUtils.reverse(b);
+ for (int k = 0; k < j; k++) {
+ assertEquals(Conversion.binaryToHexDigit(a, k),
+ Conversion.binaryBeMsb0ToHexDigit(b, k));
+ }
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 8, 99})
+ public void binaryBeMsb0ToHexDigitPosOutsideArray(final int index) {
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> Conversion.binaryBeMsb0ToHexDigit(new boolean[8], index));
+ }
+
+ /**
+ * Tests {@link Conversion#intToHexDigit(int)}.
+ */
+ @Test
+ public void testIntToHexDigit() {
+ assertEquals('0', Conversion.intToHexDigit(0));
+ assertEquals('1', Conversion.intToHexDigit(1));
+ assertEquals('2', Conversion.intToHexDigit(2));
+ assertEquals('3', Conversion.intToHexDigit(3));
+ assertEquals('4', Conversion.intToHexDigit(4));
+ assertEquals('5', Conversion.intToHexDigit(5));
+ assertEquals('6', Conversion.intToHexDigit(6));
+ assertEquals('7', Conversion.intToHexDigit(7));
+ assertEquals('8', Conversion.intToHexDigit(8));
+ assertEquals('9', Conversion.intToHexDigit(9));
+ assertEquals('a', Conversion.intToHexDigit(10));
+ assertEquals('b', Conversion.intToHexDigit(11));
+ assertEquals('c', Conversion.intToHexDigit(12));
+ assertEquals('d', Conversion.intToHexDigit(13));
+ assertEquals('e', Conversion.intToHexDigit(14));
+ assertEquals('f', Conversion.intToHexDigit(15));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.intToHexDigit(16));
+ }
+
+ /**
+ * Tests {@link Conversion#intToHexDigitMsb0(int)}.
+ */
+ @Test
+ public void testIntToHexDigitMsb0() {
+ assertEquals('0', Conversion.intToHexDigitMsb0(0));
+ assertEquals('8', Conversion.intToHexDigitMsb0(1));
+ assertEquals('4', Conversion.intToHexDigitMsb0(2));
+ assertEquals('c', Conversion.intToHexDigitMsb0(3));
+ assertEquals('2', Conversion.intToHexDigitMsb0(4));
+ assertEquals('a', Conversion.intToHexDigitMsb0(5));
+ assertEquals('6', Conversion.intToHexDigitMsb0(6));
+ assertEquals('e', Conversion.intToHexDigitMsb0(7));
+ assertEquals('1', Conversion.intToHexDigitMsb0(8));
+ assertEquals('9', Conversion.intToHexDigitMsb0(9));
+ assertEquals('5', Conversion.intToHexDigitMsb0(10));
+ assertEquals('d', Conversion.intToHexDigitMsb0(11));
+ assertEquals('3', Conversion.intToHexDigitMsb0(12));
+ assertEquals('b', Conversion.intToHexDigitMsb0(13));
+ assertEquals('7', Conversion.intToHexDigitMsb0(14));
+ assertEquals('f', Conversion.intToHexDigitMsb0(15));
+ assertThrows(IllegalArgumentException.class, () -> Conversion.intToHexDigitMsb0(16));
+ }
+
+ static String dbgPrint(final boolean[] src) {
+ final StringBuilder sb = new StringBuilder();
+ for (final boolean e : src) {
+ if (e) {
+ sb.append("1, ");
+ } else {
+ sb.append("0, ");
+ }
+ }
+ final String out = sb.toString();
+ return out.substring(0, out.length() - 1);
+ }
+
+ /**
+ * Tests {@link Conversion#intArrayToLong(int[], int, long, int, int)}.
+ */
+ @Test
+ public void testIntArrayToLong() {
+ final int[] src = {0xCDF1F0C1, 0x0F123456, 0x78000000};
+ assertEquals(0x0000000000000000L, Conversion.intArrayToLong(src, 0, 0L, 0, 0));
+ assertEquals(0x0000000000000000L, Conversion.intArrayToLong(src, 1, 0L, 0, 0));
+ assertEquals(0x00000000CDF1F0C1L, Conversion.intArrayToLong(src, 0, 0L, 0, 1));
+ assertEquals(0x0F123456CDF1F0C1L, Conversion.intArrayToLong(src, 0, 0L, 0, 2));
+ assertEquals(0x000000000F123456L, Conversion.intArrayToLong(src, 1, 0L, 0, 1));
+ assertEquals(
+ 0x123456789ABCDEF0L, Conversion.intArrayToLong(src, 0, 0x123456789ABCDEF0L, 0, 0));
+ assertEquals(
+ 0x1234567878000000L, Conversion.intArrayToLong(src, 2, 0x123456789ABCDEF0L, 0, 1));
+ // assertEquals(0x0F12345678000000L, Conversion.intsToLong(src, 1, 0x123456789ABCDEF0L, 32, 2));
+ }
+
+ /**
+ * Tests {@link Conversion#shortArrayToLong(short[], int, long, int, int)}.
+ */
+ @Test
+ public void testShortArrayToLong() {
+ final short[] src = {
+ (short) 0xCDF1, (short) 0xF0C1, (short) 0x0F12, (short) 0x3456, (short) 0x7800};
+ assertEquals(0x0000000000000000L, Conversion.shortArrayToLong(src, 0, 0L, 0, 0));
+ assertEquals(0x000000000000CDF1L, Conversion.shortArrayToLong(src, 0, 0L, 0, 1));
+ assertEquals(0x00000000F0C1CDF1L, Conversion.shortArrayToLong(src, 0, 0L, 0, 2));
+ assertEquals(0x780034560F12F0C1L, Conversion.shortArrayToLong(src, 1, 0L, 0, 4));
+ assertEquals(
+ 0x123456789ABCDEF0L, Conversion.shortArrayToLong(src, 0, 0x123456789ABCDEF0L, 0, 0));
+ assertEquals(
+ 0x123456CDF1BCDEF0L,
+ Conversion.shortArrayToLong(src, 0, 0x123456789ABCDEF0L, 24, 1));
+ assertEquals(
+ 0x123478003456DEF0L,
+ Conversion.shortArrayToLong(src, 3, 0x123456789ABCDEF0L, 16, 2));
+ }
+
+ /**
+ * Tests {@link Conversion#byteArrayToLong(byte[], int, long, int, int)}.
+ */
+ @Test
+ public void testByteArrayToLong() {
+ final byte[] src = {
+ (byte) 0xCD, (byte) 0xF1, (byte) 0xF0, (byte) 0xC1, (byte) 0x0F, (byte) 0x12, (byte) 0x34,
+ (byte) 0x56, (byte) 0x78};
+ assertEquals(0x0000000000000000L, Conversion.byteArrayToLong(src, 0, 0L, 0, 0));
+ assertEquals(0x00000000000000CDL, Conversion.byteArrayToLong(src, 0, 0L, 0, 1));
+ assertEquals(0x00000000C1F0F1CDL, Conversion.byteArrayToLong(src, 0, 0L, 0, 4));
+ assertEquals(0x000000000FC1F0F1L, Conversion.byteArrayToLong(src, 1, 0L, 0, 4));
+ assertEquals(
+ 0x123456789ABCDEF0L, Conversion.byteArrayToLong(src, 0, 0x123456789ABCDEF0L, 0, 0));
+ assertEquals(
+ 0x12345678CDBCDEF0L, Conversion.byteArrayToLong(src, 0, 0x123456789ABCDEF0L, 24, 1));
+ assertEquals(
+ 0x123456789A7856F0L, Conversion.byteArrayToLong(src, 7, 0x123456789ABCDEF0L, 8, 2));
+ }
+
+ /**
+ * Tests {@link Conversion#shortArrayToInt(short[], int, int, int, int)}.
+ */
+ @Test
+ public void testShortArrayToInt() {
+ final short[] src = {
+ (short) 0xCDF1, (short) 0xF0C1, (short) 0x0F12, (short) 0x3456, (short) 0x7800};
+ assertEquals(0x00000000, Conversion.shortArrayToInt(src, 0, 0, 0, 0));
+ assertEquals(0x0000CDF1, Conversion.shortArrayToInt(src, 0, 0, 0, 1));
+ assertEquals(0xF0C1CDF1, Conversion.shortArrayToInt(src, 0, 0, 0, 2));
+ assertEquals(0x0F12F0C1, Conversion.shortArrayToInt(src, 1, 0, 0, 2));
+ assertEquals(0x12345678, Conversion.shortArrayToInt(src, 0, 0x12345678, 0, 0));
+ assertEquals(0xCDF15678, Conversion.shortArrayToInt(src, 0, 0x12345678, 16, 1));
+ // assertEquals(0x34567800, Conversion.ShortArrayToInt(src, 3, 0x12345678, 16, 2));
+ }
+
+ /**
+ * Tests {@link Conversion#byteArrayToInt(byte[], int, int, int, int)}.
+ */
+ @Test
+ public void testByteArrayToInt() {
+ final byte[] src = {
+ (byte) 0xCD, (byte) 0xF1, (byte) 0xF0, (byte) 0xC1, (byte) 0x0F, (byte) 0x12, (byte) 0x34,
+ (byte) 0x56, (byte) 0x78};
+ assertEquals(0x00000000, Conversion.byteArrayToInt(src, 0, 0, 0, 0));
+ assertEquals(0x000000CD, Conversion.byteArrayToInt(src, 0, 0, 0, 1));
+ assertEquals(0xC1F0F1CD, Conversion.byteArrayToInt(src, 0, 0, 0, 4));
+ assertEquals(0x0FC1F0F1, Conversion.byteArrayToInt(src, 1, 0, 0, 4));
+ assertEquals(0x12345678, Conversion.byteArrayToInt(src, 0, 0x12345678, 0, 0));
+ assertEquals(0xCD345678, Conversion.byteArrayToInt(src, 0, 0x12345678, 24, 1));
+ // assertEquals(0x56341278, Conversion.ByteArrayToInt(src, 5, 0x01234567, 8, 4));
+ }
+
+ /**
+ * Tests {@link Conversion#byteArrayToShort(byte[], int, short, int, int)}.
+ */
+ @Test
+ public void testByteArrayToShort() {
+ final byte[] src = {
+ (byte) 0xCD, (byte) 0xF1, (byte) 0xF0, (byte) 0xC1, (byte) 0x0F, (byte) 0x12, (byte) 0x34,
+ (byte) 0x56, (byte) 0x78};
+ assertEquals((short) 0x0000, Conversion.byteArrayToShort(src, 0, (short) 0, 0, 0));
+ assertEquals((short) 0x00CD, Conversion.byteArrayToShort(src, 0, (short) 0, 0, 1));
+ assertEquals((short) 0xF1CD, Conversion.byteArrayToShort(src, 0, (short) 0, 0, 2));
+ assertEquals((short) 0xF0F1, Conversion.byteArrayToShort(src, 1, (short) 0, 0, 2));
+ assertEquals((short) 0x1234, Conversion.byteArrayToShort(src, 0, (short) 0x1234, 0, 0));
+ assertEquals((short) 0xCD34, Conversion.byteArrayToShort(src, 0, (short) 0x1234, 8, 1));
+ // assertEquals((short) 0x5678, Conversion.ByteArrayToShort(src, 7, (short) 0x0123, 8,
+ // 2));
+ }
+
+ /**
+ * Tests {@link Conversion#hexToLong(String, int, long, int, int)}.
+ */
+ @Test
+ public void testHexToLong() {
+ final String src = "CDF1F0C10F12345678";
+ assertEquals(0x0000000000000000L, Conversion.hexToLong(src, 0, 0L, 0, 0));
+ assertEquals(0x000000000000000CL, Conversion.hexToLong(src, 0, 0L, 0, 1));
+ assertEquals(0x000000001C0F1FDCL, Conversion.hexToLong(src, 0, 0L, 0, 8));
+ assertEquals(0x0000000001C0F1FDL, Conversion.hexToLong(src, 1, 0L, 0, 8));
+ assertEquals(
+ 0x123456798ABCDEF0L, Conversion.hexToLong(src, 0, 0x123456798ABCDEF0L, 0, 0));
+ assertEquals(
+ 0x1234567876BCDEF0L, Conversion.hexToLong(src, 15, 0x123456798ABCDEF0L, 24, 3));
+ }
+
+ /**
+ * Tests {@link Conversion#hexToInt(String, int, int, int, int)}.
+ */
+ @Test
+ public void testHexToInt() {
+ final String src = "CDF1F0C10F12345678";
+ assertEquals(0x00000000, Conversion.hexToInt(src, 0, 0, 0, 0));
+ assertEquals(0x0000000C, Conversion.hexToInt(src, 0, 0, 0, 1));
+ assertEquals(0x1C0F1FDC, Conversion.hexToInt(src, 0, 0, 0, 8));
+ assertEquals(0x01C0F1FD, Conversion.hexToInt(src, 1, 0, 0, 8));
+ assertEquals(0x12345679, Conversion.hexToInt(src, 0, 0x12345679, 0, 0));
+ assertEquals(0x87645679, Conversion.hexToInt(src, 15, 0x12345679, 20, 3));
+ }
+
+ /**
+ * Tests {@link Conversion#hexToShort(String, int, short, int, int)}.
+ */
+ @Test
+ public void testHexToShort() {
+ final String src = "CDF1F0C10F12345678";
+ assertEquals((short) 0x0000, Conversion.hexToShort(src, 0, (short) 0, 0, 0));
+ assertEquals((short) 0x000C, Conversion.hexToShort(src, 0, (short) 0, 0, 1));
+ assertEquals((short) 0x1FDC, Conversion.hexToShort(src, 0, (short) 0, 0, 4));
+ assertEquals((short) 0xF1FD, Conversion.hexToShort(src, 1, (short) 0, 0, 4));
+ assertEquals((short) 0x1234, Conversion.hexToShort(src, 0, (short) 0x1234, 0, 0));
+ assertEquals((short) 0x8764, Conversion.hexToShort(src, 15, (short) 0x1234, 4, 3));
+ }
+
+ /**
+ * Tests {@link Conversion#hexToByte(String, int, byte, int, int)}.
+ */
+ @Test
+ public void testHexToByte() {
+ final String src = "CDF1F0C10F12345678";
+ assertEquals((byte) 0x00, Conversion.hexToByte(src, 0, (byte) 0, 0, 0));
+ assertEquals((byte) 0x0C, Conversion.hexToByte(src, 0, (byte) 0, 0, 1));
+ assertEquals((byte) 0xDC, Conversion.hexToByte(src, 0, (byte) 0, 0, 2));
+ assertEquals((byte) 0xFD, Conversion.hexToByte(src, 1, (byte) 0, 0, 2));
+ assertEquals((byte) 0x34, Conversion.hexToByte(src, 0, (byte) 0x34, 0, 0));
+ assertEquals((byte) 0x84, Conversion.hexToByte(src, 17, (byte) 0x34, 4, 1));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToLong(boolean[], int, long, int, int)}.
+ */
+ @Test
+ public void testBinaryToLong() {
+ final boolean[] src = {
+ false, false, true, true, true, false, true, true, true, true, true, true, true,
+ false, false, false, true, true, true, true, false, false, false, false, false,
+ false, true, true, true, false, false, false, false, false, false, false, true,
+ true, true, true, true, false, false, false, false, true, false, false, true, true,
+ false, false, false, false, true, false, true, false, true, false, false, true,
+ true, false, true, true, true, false, false, false, false, true};
+ // conversion of "CDF1F0C10F12345678" by HexToBinary
+ assertEquals(0x0000000000000000L, Conversion.binaryToLong(src, 0, 0L, 0, 0));
+ assertEquals(0x000000000000000CL, Conversion.binaryToLong(src, 0, 0L, 0, 1 * 4));
+ assertEquals(0x000000001C0F1FDCL, Conversion.binaryToLong(src, 0, 0L, 0, 8 * 4));
+ assertEquals(0x0000000001C0F1FDL, Conversion.binaryToLong(src, 1 * 4, 0L, 0, 8 * 4));
+ assertEquals(
+ 0x123456798ABCDEF0L, Conversion.binaryToLong(src, 0, 0x123456798ABCDEF0L, 0, 0));
+ assertEquals(
+ 0x1234567876BCDEF0L,
+ Conversion.binaryToLong(src, 15 * 4, 0x123456798ABCDEF0L, 24, 3 * 4));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToInt(boolean[], int, int, int, int)}.
+ */
+ @Test
+ public void testBinaryToInt() {
+ final boolean[] src = {
+ false, false, true, true, true, false, true, true, true, true, true, true, true,
+ false, false, false, true, true, true, true, false, false, false, false, false,
+ false, true, true, true, false, false, false, false, false, false, false, true,
+ true, true, true, true, false, false, false, false, true, false, false, true, true,
+ false, false, false, false, true, false, true, false, true, false, false, true,
+ true, false, true, true, true, false, false, false, false, true};
+ // conversion of "CDF1F0C10F12345678" by HexToBinary
+ assertEquals(0x00000000, Conversion.binaryToInt(src, 0 * 4, 0, 0, 0 * 4));
+ assertEquals(0x0000000C, Conversion.binaryToInt(src, 0 * 4, 0, 0, 1 * 4));
+ assertEquals(0x1C0F1FDC, Conversion.binaryToInt(src, 0 * 4, 0, 0, 8 * 4));
+ assertEquals(0x01C0F1FD, Conversion.binaryToInt(src, 1 * 4, 0, 0, 8 * 4));
+ assertEquals(0x12345679, Conversion.binaryToInt(src, 0 * 4, 0x12345679, 0, 0 * 4));
+ assertEquals(0x87645679, Conversion.binaryToInt(src, 15 * 4, 0x12345679, 20, 3 * 4));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToShort(boolean[], int, short, int, int)}.
+ */
+ @Test
+ public void testBinaryToShort() {
+ final boolean[] src = {
+ false, false, true, true, true, false, true, true, true, true, true, true, true,
+ false, false, false, true, true, true, true, false, false, false, false, false,
+ false, true, true, true, false, false, false, false, false, false, false, true,
+ true, true, true, true, false, false, false, false, true, false, false, true, true,
+ false, false, false, false, true, false, true, false, true, false, false, true,
+ true, false, true, true, true, false, false, false, false, true};
+ // conversion of "CDF1F0C10F12345678" by HexToBinary
+ assertEquals((short) 0x0000, Conversion.binaryToShort(src, 0 * 4, (short) 0, 0, 0 * 4));
+ assertEquals((short) 0x000C, Conversion.binaryToShort(src, 0 * 4, (short) 0, 0, 1 * 4));
+ assertEquals((short) 0x1FDC, Conversion.binaryToShort(src, 0 * 4, (short) 0, 0, 4 * 4));
+ assertEquals((short) 0xF1FD, Conversion.binaryToShort(src, 1 * 4, (short) 0, 0, 4 * 4));
+ assertEquals(
+ (short) 0x1234, Conversion.binaryToShort(src, 0 * 4, (short) 0x1234, 0, 0 * 4));
+ assertEquals(
+ (short) 0x8764, Conversion.binaryToShort(src, 15 * 4, (short) 0x1234, 4, 3 * 4));
+ }
+
+ /**
+ * Tests {@link Conversion#binaryToByte(boolean[], int, byte, int, int)}.
+ */
+ @Test
+ public void testBinaryToByte() {
+ final boolean[] src = {
+ false, false, true, true, true, false, true, true, true, true, true, true, true,
+ false, false, false, true, true, true, true, false, false, false, false, false,
+ false, true, true, true, false, false, false, false, false, false, false, true,
+ true, true, true, true, false, false, false, false, true, false, false, true, true,
+ false, false, false, false, true, false, true, false, true, false, false, true,
+ true, false, true, true, true, false, false, false, false, true};
+ // conversion of "CDF1F0C10F12345678" by HexToBinary
+ assertEquals((byte) 0x00, Conversion.binaryToByte(src, 0 * 4, (byte) 0, 0, 0 * 4));
+ assertEquals((byte) 0x0C, Conversion.binaryToByte(src, 0 * 4, (byte) 0, 0, 1 * 4));
+ assertEquals((byte) 0xDC, Conversion.binaryToByte(src, 0 * 4, (byte) 0, 0, 2 * 4));
+ assertEquals((byte) 0xFD, Conversion.binaryToByte(src, 1 * 4, (byte) 0, 0, 2 * 4));
+ assertEquals((byte) 0x34, Conversion.binaryToByte(src, 0 * 4, (byte) 0x34, 0, 0 * 4));
+ assertEquals((byte) 0x84, Conversion.binaryToByte(src, 17 * 4, (byte) 0x34, 4, 1 * 4));
+ }
+
+ /**
+ * Tests {@link Conversion#longToIntArray(long, int, int[], int, int)}.
+ */
+ @Test
+ public void testLongToIntArray() {
+ assertArrayEquals(
+ new int[]{}, Conversion.longToIntArray(0x0000000000000000L, 0, new int[]{}, 0, 0));
+ assertArrayEquals(
+ new int[]{}, Conversion.longToIntArray(0x0000000000000000L, 100, new int[]{}, 0, 0));
+ assertArrayEquals(
+ new int[]{}, Conversion.longToIntArray(0x0000000000000000L, 0, new int[]{}, 100, 0));
+ assertArrayEquals(
+ new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 0, new int[]{-1, -1, -1, -1}, 0, 0));
+ assertArrayEquals(
+ new int[]{0x90ABCDEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 0, new int[]{-1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new int[]{0x90ABCDEF, 0x12345678, 0xFFFFFFFF, 0xFFFFFFFF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 0, new int[]{-1, -1, -1, -1}, 0, 2));
+ // assertArrayEquals(new
+ // int[]{0x90ABCDEF, 0x12345678, 0x90ABCDEF, 0x12345678}, Conversion.longToIntArray(0x1234567890ABCDEFL,
+ // 0, new int[]{-1, -1, -1, -1}, 0, 4));//rejected by assertion
+ // assertArrayEquals(new
+ // int[]{0xFFFFFFFF, 0x90ABCDEF, 0x12345678, 0x90ABCDEF}, Conversion.longToIntArray(0x1234567890ABCDEFL,
+ // 0, new int[]{-1, -1, -1, -1}, 1, 3));
+ assertArrayEquals(
+ new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0x90ABCDEF, 0x12345678},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 0, new int[]{-1, -1, -1, -1}, 2, 2));
+ assertArrayEquals(
+ new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0x90ABCDEF, 0xFFFFFFFF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 0, new int[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x90ABCDEF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 0, new int[]{-1, -1, -1, -1}, 3, 1));
+ assertArrayEquals(
+ new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0x4855E6F7, 0xFFFFFFFF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 1, new int[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0x242AF37B, 0xFFFFFFFF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 2, new int[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0x121579BD, 0xFFFFFFFF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 3, new int[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0x890ABCDE, 0xFFFFFFFF},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 4, new int[]{-1, -1, -1, -1}, 2, 1));
+ // assertArrayEquals(new
+ // int[]{0x4855E6F7, 0x091A2B3C, 0x4855E6F7, 0x091A2B3C}, Conversion.longToIntArray(0x1234567890ABCDEFL,
+ // 1, new int[]{-1, -1, -1, -1}, 0, 4));//rejected by assertion
+ assertArrayEquals(
+ new int[]{0x091A2B3C},
+ Conversion.longToIntArray(0x1234567890ABCDEFL, 33, new int[]{0}, 0, 1));
+ }
+
+ /**
+ * Tests {@link Conversion#longToShortArray(long, int, short[], int, int)}.
+ */
+ @Test
+ public void testLongToShortArray() {
+ assertArrayEquals(
+ new short[]{},
+ Conversion.longToShortArray(0x0000000000000000L, 0, new short[]{}, 0, 0));
+ assertArrayEquals(
+ new short[]{},
+ Conversion.longToShortArray(0x0000000000000000L, 100, new short[]{}, 0, 0));
+ assertArrayEquals(
+ new short[]{},
+ Conversion.longToShortArray(0x0000000000000000L, 0, new short[]{}, 100, 0));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xFFFF, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 0, 0));
+ assertArrayEquals(
+ new short[]{(short) 0xCDEF, (short) 0xFFFF, (short) 0xFFFF, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xCDEF, (short) 0x90AB, (short) 0xFFFF, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 0, 2));
+ assertArrayEquals(
+ new short[]{(short) 0xCDEF, (short) 0x90AB, (short) 0x5678, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 0, 3));
+ assertArrayEquals(
+ new short[]{(short) 0xCDEF, (short) 0x90AB, (short) 0x5678, (short) 0x1234},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 0, 4));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xCDEF, (short) 0x90AB, (short) 0x5678},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 1, 3));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xCDEF, (short) 0x90AB},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 2, 2));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xCDEF, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xFFFF, (short) 0xCDEF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 0, new short[]{-1, -1, -1, -1}, 3, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xE6F7, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 1, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xF37B, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 2, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0x79BD, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 3, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xBCDE, (short) 0xFFFF},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 4, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xE6F7, (short) 0x4855, (short) 0x2B3C, (short) 0x091A},
+ Conversion.longToShortArray(
+ 0x1234567890ABCDEFL, 1, new short[]{-1, -1, -1, -1}, 0, 4));
+ assertArrayEquals(
+ new short[]{(short) 0x2B3C},
+ Conversion.longToShortArray(0x1234567890ABCDEFL, 33, new short[]{0}, 0, 1));
+ }
+
+ /**
+ * Tests {@link Conversion#intToShortArray(int, int, short[], int, int)}.
+ */
+ @Test
+ public void testIntToShortArray() {
+ assertArrayEquals(
+ new short[]{}, Conversion.intToShortArray(0x00000000, 0, new short[]{}, 0, 0));
+ assertArrayEquals(
+ new short[]{}, Conversion.intToShortArray(0x00000000, 100, new short[]{}, 0, 0));
+ assertArrayEquals(
+ new short[]{}, Conversion.intToShortArray(0x00000000, 0, new short[]{}, 100, 0));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xFFFF, (short) 0xFFFF},
+ Conversion.intToShortArray(0x12345678, 0, new short[]{-1, -1, -1, -1}, 0, 0));
+ assertArrayEquals(
+ new short[]{(short) 0x5678, (short) 0xFFFF, (short) 0xFFFF, (short) 0xFFFF},
+ Conversion.intToShortArray(0x12345678, 0, new short[]{-1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new short[]{(short) 0x5678, (short) 0x1234, (short) 0xFFFF, (short) 0xFFFF},
+ Conversion.intToShortArray(0x12345678, 0, new short[]{-1, -1, -1, -1}, 0, 2));
+ // assertArrayEquals(new
+ // short[]{(short) 0x5678, (short) 0x1234, (short) 0x5678, (short) 0xFFFF}, Conversion.intToShortArray(0x12345678,
+ // 0, new short[]{-1, -1, -1, -1}, 0, 3));//rejected by assertion
+ // assertArrayEquals(new
+ // short[]{(short) 0x5678, (short) 0x1234, (short) 0x5678, (short) 0x1234}, Conversion.intToShortArray(0x12345678,
+ // 0, new short[]{-1, -1, -1, -1}, 0, 4));
+ // assertArrayEquals(new
+ // short[]{(short) 0xFFFF, (short) 0x5678, (short) 0x1234, (short) 0x5678}, Conversion.intToShortArray(0x12345678,
+ // 0, new short[]{-1, -1, -1, -1}, 1, 3));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0x5678, (short) 0x1234},
+ Conversion.intToShortArray(0x12345678, 0, new short[]{-1, -1, -1, -1}, 2, 2));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0x5678, (short) 0xFFFF},
+ Conversion.intToShortArray(0x12345678, 0, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0xFFFF, (short) 0x5678},
+ Conversion.intToShortArray(0x12345678, 0, new short[]{-1, -1, -1, -1}, 3, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0x2B3C, (short) 0xFFFF},
+ Conversion.intToShortArray(0x12345678, 1, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0x159E, (short) 0xFFFF},
+ Conversion.intToShortArray(0x12345678, 2, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0x8ACF, (short) 0xFFFF},
+ Conversion.intToShortArray(0x12345678, 3, new short[]{-1, -1, -1, -1}, 2, 1));
+ assertArrayEquals(
+ new short[]{(short) 0xFFFF, (short) 0xFFFF, (short) 0x4567, (short) 0xFFFF},
+ Conversion.intToShortArray(0x12345678, 4, new short[]{-1, -1, -1, -1}, 2, 1));
+ // assertArrayEquals(new
+ // short[]{(short) 0xE6F7, (short) 0x4855, (short) 0x2B3C, (short) 0x091A}, Conversion.intToShortArray(0x12345678,
+ // 1, new short[]{-1, -1, -1, -1}, 0, 4));//rejected by assertion
+ // assertArrayEquals(new
+ // short[]{(short) 0x2B3C}, Conversion.intToShortArray(0x12345678, 33, new
+ // short[]{0}, 0, 1));//rejected by assertion
+ assertArrayEquals(
+ new short[]{(short) 0x091A},
+ Conversion.intToShortArray(0x12345678, 17, new short[]{0}, 0, 1));
+ }
+
+ /**
+ * Tests {@link Conversion#longToByteArray(long, int, byte[], int, int)}.
+ */
+ @Test
+ public void testLongToByteArray() {
+ assertArrayEquals(
+ new byte[]{},
+ Conversion.longToByteArray(0x0000000000000000L, 0, new byte[]{}, 0, 0));
+ assertArrayEquals(
+ new byte[]{},
+ Conversion.longToByteArray(0x0000000000000000L, 100, new byte[]{}, 0, 0));
+ assertArrayEquals(
+ new byte[]{},
+ Conversion.longToByteArray(0x0000000000000000L, 0, new byte[]{}, 100, 0));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 0));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xCD, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 2));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xCD, (byte) 0xAB, (byte) 0x90, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 4));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xCD, (byte) 0xAB, (byte) 0x90, (byte) 0x78, (byte) 0x56,
+ (byte) 0x34, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 7));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xCD, (byte) 0xAB, (byte) 0x90, (byte) 0x78, (byte) 0x56,
+ (byte) 0x34, (byte) 0x12, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 8));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xCD, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 2));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xCD, (byte) 0xAB,
+ (byte) 0x90, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 4));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xCD, (byte) 0xAB,
+ (byte) 0x90, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 7));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xCD, (byte) 0xAB,
+ (byte) 0x90, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 8));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xF7, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 1, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0x7B, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 2, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0x6F, (byte) 0x5E, (byte) 0x85,
+ (byte) 0xC4, (byte) 0xB3, (byte) 0xA2, (byte) 0x91, (byte) 0x00},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 5, new byte[]{
+ -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 8));
+ // assertArrayEquals(new
+ // byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0x5E, (byte) 0x85, (byte) 0xC4, (byte) 0xB3, (byte) 0xA2, (byte) 0x91, (byte) 0x00, (byte) 0x00}, Conversion.longToByteArray(0x1234567890ABCDEFL, 13, new
+ // byte[]{-1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 8));//rejected by assertion
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0x5E, (byte) 0x85, (byte) 0xC4,
+ (byte) 0xB3, (byte) 0xA2, (byte) 0x91, (byte) 0x00, (byte) 0xFF},
+ Conversion.longToByteArray(0x1234567890ABCDEFL, 13, new byte[]{
+ -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 7));
+ }
+
+ /**
+ * Tests {@link Conversion#intToByteArray(int, int, byte[], int, int)}.
+ */
+ @Test
+ public void testIntToByteArray() {
+ assertArrayEquals(
+ new byte[]{}, Conversion.intToByteArray(0x00000000, 0, new byte[]{}, 0, 0));
+ assertArrayEquals(
+ new byte[]{}, Conversion.intToByteArray(0x00000000, 100, new byte[]{}, 0, 0));
+ assertArrayEquals(
+ new byte[]{}, Conversion.intToByteArray(0x00000000, 0, new byte[]{}, 100, 0));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 0));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xCD, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 2));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xCD, (byte) 0xAB, (byte) 0x90, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 4));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xCD, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 2));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xCD, (byte) 0xAB,
+ (byte) 0x90, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 4));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xF7, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 1, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0x7B, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 2, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0x6F, (byte) 0x5E, (byte) 0x85,
+ (byte) 0xFC, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 5, new byte[]{
+ -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 4));
+ // assertArrayEquals(new
+ // byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0x5E, (byte) 0x85, (byte) 0xFC, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}, Conversion.intToByteArray(0x90ABCDEF, 13, new
+ // byte[]{-1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 4));//rejected by assertion
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0x5E, (byte) 0x85, (byte) 0xFC,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
+ Conversion.intToByteArray(0x90ABCDEF, 13, new byte[]{
+ -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 3, 3));
+ }
+
+ /**
+ * Tests {@link Conversion#shortToByteArray(short, int, byte[], int, int)}.
+ */
+ @Test
+ public void testShortToByteArray() {
+ assertArrayEquals(
+ new byte[]{}, Conversion.shortToByteArray((short) 0x0000, 0, new byte[]{}, 0, 0));
+ assertArrayEquals(
+ new byte[]{}, Conversion.shortToByteArray((short) 0x0000, 100, new byte[]{}, 0, 0));
+ assertArrayEquals(
+ new byte[]{}, Conversion.shortToByteArray((short) 0x0000, 0, new byte[]{}, 100, 0));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1}, 0, 0));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xEF, (byte) 0xCD, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1}, 0, 2));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1}, 3, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, (byte) 0xCD, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 0, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1}, 3, 2));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xF7, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 1, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0x7B, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 2, new byte[]{
+ -1, -1, -1, -1, -1, -1, -1}, 0, 1));
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0x6F, (byte) 0xFE, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 5, new byte[]{
+ -1, 0, -1, -1, -1, -1, -1}, 3, 2));
+ // assertArrayEquals(new
+ // byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0x5E, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 13, new
+ // byte[]{-1, 0, -1, -1, -1, -1, -1}, 3, 2));//rejected by assertion
+ assertArrayEquals(
+ new byte[]{
+ (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFE, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF}, Conversion.shortToByteArray((short) 0xCDEF, 13, new byte[]{
+ -1, 0, -1, -1, -1, -1, -1}, 3, 1));
+ }
+
+ /**
+ * Tests {@link Conversion#longToHex(long, int, String, int, int)}.
+ */
+ @Test
+ public void testLongToHex() {
+ assertEquals("", Conversion.longToHex(0x0000000000000000L, 0, "", 0, 0));
+ assertEquals("", Conversion.longToHex(0x0000000000000000L, 100, "", 0, 0));
+ assertEquals("", Conversion.longToHex(0x0000000000000000L, 0, "", 100, 0));
+ assertEquals(
+ "ffffffffffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 0, 0));
+ assertEquals(
+ "3fffffffffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDE3L, 0, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "feffffffffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 0, 2));
+ assertEquals(
+ "fedcffffffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 0, 4));
+ assertEquals(
+ "fedcba098765432fffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 0, 15));
+ assertEquals(
+ "fedcba0987654321ffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 0, 16));
+ assertEquals(
+ "fff3ffffffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDE3L, 0, "ffffffffffffffffffffffff", 3, 1));
+ assertEquals(
+ "ffffefffffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 3, 2));
+ assertEquals(
+ "ffffedcfffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 3, 4));
+ assertEquals(
+ "ffffedcba098765432ffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 3, 15));
+ assertEquals(
+ "ffffedcba0987654321fffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 0, "ffffffffffffffffffffffff", 3, 16));
+ assertEquals(
+ "7fffffffffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 1, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "bfffffffffffffffffffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 2, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "fffdb975121fca86420fffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 3, "ffffffffffffffffffffffff", 3, 16));
+ // assertEquals("ffffffffffffffffffffffff", Conversion.longToHex(0x1234567890ABCDEFL, 4, "ffffffffffffffffffffffff", 3, 16));//rejected
+ // by assertion
+ assertEquals(
+ "fffedcba0987654321ffffff",
+ Conversion.longToHex(0x1234567890ABCDEFL, 4, "ffffffffffffffffffffffff", 3, 15));
+ assertEquals(
+ "fedcba0987654321", Conversion.longToHex(0x1234567890ABCDEFL, 0, "", 0, 16));
+ assertThrows(StringIndexOutOfBoundsException.class, () -> Conversion.longToHex(0x1234567890ABCDEFL, 0, "", 1, 8));
+ }
+
+ /**
+ * Tests {@link Conversion#intToHex(int, int, String, int, int)}.
+ */
+ @Test
+ public void testIntToHex() {
+ assertEquals("", Conversion.intToHex(0x00000000, 0, "", 0, 0));
+ assertEquals("", Conversion.intToHex(0x00000000, 100, "", 0, 0));
+ assertEquals("", Conversion.intToHex(0x00000000, 0, "", 100, 0));
+ assertEquals(
+ "ffffffffffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 0, 0));
+ assertEquals(
+ "3fffffffffffffffffffffff",
+ Conversion.intToHex(0x90ABCDE3, 0, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "feffffffffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 0, 2));
+ assertEquals(
+ "fedcffffffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 0, 4));
+ assertEquals(
+ "fedcba0fffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 0, 7));
+ assertEquals(
+ "fedcba09ffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 0, 8));
+ assertEquals(
+ "fff3ffffffffffffffffffff",
+ Conversion.intToHex(0x90ABCDE3, 0, "ffffffffffffffffffffffff", 3, 1));
+ assertEquals(
+ "ffffefffffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 3, 2));
+ assertEquals(
+ "ffffedcfffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 3, 4));
+ assertEquals(
+ "ffffedcba0ffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 3, 7));
+ assertEquals(
+ "ffffedcba09fffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 0, "ffffffffffffffffffffffff", 3, 8));
+ assertEquals(
+ "7fffffffffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 1, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "bfffffffffffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 2, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "fffdb97512ffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 3, "ffffffffffffffffffffffff", 3, 8));
+ // assertEquals("ffffffffffffffffffffffff", Conversion.intToHex(0x90ABCDEF,
+ // 4, "ffffffffffffffffffffffff", 3, 8));//rejected by assertion
+ assertEquals(
+ "fffedcba09ffffffffffffff",
+ Conversion.intToHex(0x90ABCDEF, 4, "ffffffffffffffffffffffff", 3, 7));
+ assertEquals("fedcba09", Conversion.intToHex(0x90ABCDEF, 0, "", 0, 8));
+ assertThrows(StringIndexOutOfBoundsException.class, () -> Conversion.intToHex(0x90ABCDEF, 0, "", 1, 8));
+ }
+
+ /**
+ * Tests {@link Conversion#shortToHex(short, int, String, int, int)}.
+ */
+ @Test
+ public void testShortToHex() {
+ assertEquals("", Conversion.shortToHex((short) 0x0000, 0, "", 0, 0));
+ assertEquals("", Conversion.shortToHex((short) 0x0000, 100, "", 0, 0));
+ assertEquals("", Conversion.shortToHex((short) 0x0000, 0, "", 100, 0));
+ assertEquals(
+ "ffffffffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 0, "ffffffffffffffffffffffff", 0, 0));
+ assertEquals(
+ "3fffffffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDE3, 0, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "feffffffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 0, "ffffffffffffffffffffffff", 0, 2));
+ assertEquals(
+ "fedfffffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 0, "ffffffffffffffffffffffff", 0, 3));
+ assertEquals(
+ "fedcffffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 0, "ffffffffffffffffffffffff", 0, 4));
+ assertEquals(
+ "fff3ffffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDE3, 0, "ffffffffffffffffffffffff", 3, 1));
+ assertEquals(
+ "ffffefffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 0, "ffffffffffffffffffffffff", 3, 2));
+ assertEquals(
+ "7fffffffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 1, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "bfffffffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 2, "ffffffffffffffffffffffff", 0, 1));
+ assertEquals(
+ "fffdb9ffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 3, "ffffffffffffffffffffffff", 3, 4));
+ // assertEquals("ffffffffffffffffffffffff", Conversion.shortToHex((short) 0xCDEF,
+ // 4, "ffffffffffffffffffffffff", 3, 4));//rejected by assertion
+ assertEquals(
+ "fffedcffffffffffffffffff",
+ Conversion.shortToHex((short) 0xCDEF, 4, "ffffffffffffffffffffffff", 3, 3));
+ assertEquals("fedc", Conversion.shortToHex((short) 0xCDEF, 0, "", 0, 4));
+ assertThrows(StringIndexOutOfBoundsException.class, () -> Conversion.shortToHex((short) 0xCDEF, 0, "", 1, 4));
+ }
+
+ /**
+ * Tests {@link Conversion#byteToHex(byte, int, String, int, int)}.
+ */
+ @Test
+ public void testByteToHex() {
+ assertEquals("", Conversion.byteToHex((byte) 0x00, 0, "", 0, 0));
+ assertEquals("", Conversion.byteToHex((byte) 0x00, 100, "", 0, 0));
+ assertEquals("", Conversion.byteToHex((byte) 0x00, 0, "", 100, 0));
+ assertEquals("00000", Conversion.byteToHex((byte) 0xEF, 0, "00000", 0, 0));
+ assertEquals("f0000", Conversion.byteToHex((byte) 0xEF, 0, "00000", 0, 1));
+ assertEquals("fe000", Conversion.byteToHex((byte) 0xEF, 0, "00000", 0, 2));
+ assertEquals("000f0", Conversion.byteToHex((byte) 0xEF, 0, "00000", 3, 1));
+ assertEquals("000fe", Conversion.byteToHex((byte) 0xEF, 0, "00000", 3, 2));
+ assertEquals("70000", Conversion.byteToHex((byte) 0xEF, 1, "00000", 0, 1));
+ assertEquals("b0000", Conversion.byteToHex((byte) 0xEF, 2, "00000", 0, 1));
+ assertEquals("000df", Conversion.byteToHex((byte) 0xEF, 3, "00000", 3, 2));
+ // assertEquals("00000", Conversion.byteToHex((byte) 0xEF, 4, "00000", 3, 2));//rejected by
+ // assertion
+ assertEquals("000e0", Conversion.byteToHex((byte) 0xEF, 4, "00000", 3, 1));
+ assertEquals("fe", Conversion.byteToHex((byte) 0xEF, 0, "", 0, 2));
+ assertThrows(StringIndexOutOfBoundsException.class, () -> Conversion.byteToHex((byte) 0xEF, 0, "", 1, 2));
+ }
+
+ /**
+ * Tests {@link Conversion#longToBinary(long, int, boolean[], int, int)}.
+ */
+ @Test
+ public void testLongToBinary() {
+ assertArrayEquals(
+ new boolean[]{},
+ Conversion.longToBinary(0x0000000000000000L, 0, new boolean[]{}, 0, 0));
+ assertArrayEquals(
+ new boolean[]{},
+ Conversion.longToBinary(0x0000000000000000L, 100, new boolean[]{}, 0, 0));
+ assertArrayEquals(
+ new boolean[]{},
+ Conversion.longToBinary(0x0000000000000000L, 0, new boolean[]{}, 100, 0));
+ assertArrayEquals(
+ new boolean[69],
+ Conversion.longToBinary(0x1234567890ABCDEFL, 0, new boolean[69], 0, 0));
+
+ assertArrayEquals(
+ new boolean[]{
+ true, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 0, new boolean[69], 0, 1));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 0, new boolean[69], 0, 2));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 0, new boolean[69], 0, 3));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, true, false, true, true, true, true, false, true, true,
+ false, false, true, true, true, true, false, true, false, true, false, true,
+ false, false, false, false, true, false, false, true, false, false, false,
+ true, true, true, true, false, false, true, true, false, true, false, true,
+ false, false, false, true, false, true, true, false, false, false, true, false,
+ false, true, false, false, false, false, false, false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 0, new boolean[69], 0, 63));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, true, false, true, true, true, true, false, true, true,
+ false, false, true, true, true, true, false, true, false, true, false, true,
+ false, false, false, false, true, false, false, true, false, false, false,
+ true, true, true, true, false, false, true, true, false, true, false, true,
+ false, false, false, true, false, true, true, false, false, false, true, false,
+ false, true, false, false, false, false, false, false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 0, new boolean[69], 0, 64));
+ assertArrayEquals(
+ new boolean[]{
+ false, false, true, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 0, new boolean[69], 2, 1));
+ assertArrayEquals(
+ new boolean[]{
+ false, false, true, true, true, true, false, true, true, true, true, false,
+ true, true, false, false, true, true, true, true, false, true, false, true,
+ false, true, false, false, false, false, true, false, false, true, false,
+ false, false, true, true, true, true, false, false, true, true, false, true,
+ false, true, false, false, false, true, false, true, true, false, false, false,
+ true, false, false, true, false, false, false, false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 0, new boolean[69], 2, 64));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, false, true, true, true, true, false, true, true, false,
+ false, true, true, true, true, false, true, false, true, false, true, false,
+ false, false, false, true, false, false, true, false, false, false, true, true,
+ true, true, false, false, true, true, false, true, false, true, false, false,
+ false, true, false, true, true, false, false, false, true, false, false, true,
+ false, false, false, false, false, false, false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 1, new boolean[69], 0, 63));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, false, true, true, true, true, false, true, true, false, false,
+ true, true, true, true, false, true, false, true, false, true, false, false,
+ false, false, true, false, false, true, false, false, false, true, true, true,
+ true, false, false, true, true, false, true, false, true, false, false, false,
+ true, false, true, true, false, false, false, true, false, false, true, false,
+ false, false, false, false, false, false, false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 2, new boolean[69], 0, 62));
+
+ // assertArrayEquals(new boolean[]{false, false, false, true, true, false, true, true,
+ // true, true, false, true, true, false, false, true, true, true, true, false, true,
+ // false, true, false, true, false, false, false, false, true, false, false, true,
+ // false, false, false, true, true, true, true, false, false, true, true, false, true,
+ // false, true, false, false, false, true, false, true, true, false, false, false, true,
+ // false, false, true, false, false, false
+ // , false, false, false, false}, Conversion.longToBinary(0x1234567890ABCDEFL, 2, new
+ // boolean[69], 3, 63));//rejected by assertion
+ assertArrayEquals(
+ new boolean[]{
+ false, false, false, true, true, false, true, true, true, true, false, true,
+ true, false, false, true, true, true, true, false, true, false, true, false,
+ true, false, false, false, false, true, false, false, true, false, false,
+ false, true, true, true, true, false, false, true, true, false, true, false,
+ true, false, false, false, true, false, true, true, false, false, false, true,
+ false, false, true, false, false, false, false, false, false, false},
+ Conversion.longToBinary(0x1234567890ABCDEFL, 2, new boolean[69], 3, 62));
+ }
+
+ /**
+ * Tests {@link Conversion#intToBinary(int, int, boolean[], int, int)}.
+ */
+ @Test
+ public void testIntToBinary() {
+ assertArrayEquals(
+ new boolean[]{}, Conversion.intToBinary(0x00000000, 0, new boolean[]{}, 0, 0));
+ assertArrayEquals(
+ new boolean[]{}, Conversion.intToBinary(0x00000000, 100, new boolean[]{}, 0, 0));
+ assertArrayEquals(
+ new boolean[]{}, Conversion.intToBinary(0x00000000, 0, new boolean[]{}, 100, 0));
+ assertArrayEquals(
+ new boolean[69], Conversion.intToBinary(0x90ABCDEF, 0, new boolean[69], 0, 0));
+ assertArrayEquals(new boolean[]{
+ true, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false,
+ false}, Conversion.intToBinary(0x90ABCDEF, 0, new boolean[37], 0, 1));
+ assertArrayEquals(new boolean[]{
+ true, true, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false,
+ false}, Conversion.intToBinary(0x90ABCDEF, 0, new boolean[37], 0, 2));
+ assertArrayEquals(new boolean[]{
+ true, true, true, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false,
+ false}, Conversion.intToBinary(0x90ABCDEF, 0, new boolean[37], 0, 3));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, true, false, true, true, true, true, false, true, true,
+ false, false, true, true, true, true, false, true, false, true, false, true,
+ false, false, false, false, true, false, false, false, false, false, false,
+ false, false}, Conversion.intToBinary(0x90ABCDEF, 0, new boolean[37], 0, 31));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, true, false, true, true, true, true, false, true, true,
+ false, false, true, true, true, true, false, true, false, true, false, true,
+ false, false, false, false, true, false, false, true, false, false, false,
+ false, false}, Conversion.intToBinary(0x90ABCDEF, 0, new boolean[37], 0, 32));
+ assertArrayEquals(new boolean[]{
+ false, false, true, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false,
+ false}, Conversion.intToBinary(0x90ABCDEF, 0, new boolean[37], 2, 1));
+ assertArrayEquals(
+ new boolean[]{
+ false, false, true, true, true, true, false, true, true, true, true, false,
+ true, true, false, false, true, true, true, true, false, true, false, true,
+ false, true, false, false, false, false, true, false, false, true, false,
+ false, false}, Conversion.intToBinary(0x90ABCDEF, 0, new boolean[37], 2, 32));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, false, true, true, true, true, false, true, true, false,
+ false, true, true, true, true, false, true, false, true, false, true, false,
+ false, false, false, true, false, false, true, false, false, false, false,
+ false, false}, Conversion.intToBinary(0x90ABCDEF, 1, new boolean[37], 0, 31));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, false, true, true, true, true, false, true, true, false, false,
+ true, true, true, true, false, true, false, true, false, true, false, false,
+ false, false, true, false, false, true, false, false, false, false, false,
+ false, false}, Conversion.intToBinary(0x90ABCDEF, 2, new boolean[37], 0, 30));
+ // assertArrayEquals(new boolean[]{false, false, false, true, true, false, true,
+ // true,
+ // true, true, false, true, true, false, false, true, true, true, true, false, true,
+ // false, true, false, true, false, false, false, false, true, false, false, false,
+ // false, false, false, false}, Conversion.intToBinary(0x90ABCDEF, 2, new boolean[37],
+ // 3, 31));//rejected by assertion
+ assertArrayEquals(
+ new boolean[]{
+ false, false, false, true, true, false, true, true, true, true, false, true,
+ true, false, false, true, true, true, true, false, true, false, true, false,
+ true, false, false, false, false, true, false, false, true, false, false,
+ false, false}, Conversion.intToBinary(0x90ABCDEF, 2, new boolean[37], 3, 30));
+ }
+
+ /**
+ * Tests {@link Conversion#shortToBinary(short, int, boolean[], int, int)}.
+ */
+ @Test
+ public void testShortToBinary() {
+ assertArrayEquals(
+ new boolean[]{}, Conversion.shortToBinary((short) 0x0000, 0, new boolean[]{}, 0, 0));
+ assertArrayEquals(
+ new boolean[]{},
+ Conversion.shortToBinary((short) 0x0000, 100, new boolean[]{}, 0, 0));
+ assertArrayEquals(
+ new boolean[]{},
+ Conversion.shortToBinary((short) 0x0000, 0, new boolean[]{}, 100, 0));
+ assertArrayEquals(
+ new boolean[69], Conversion.shortToBinary((short) 0xCDEF, 0, new boolean[69], 0, 0));
+ assertArrayEquals(
+ new boolean[]{
+ true, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 0, new boolean[21], 0, 1));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 0, new boolean[21], 0, 2));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 0, new boolean[21], 0, 3));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, true, false, true, true, true, true, false, true, true,
+ false, false, true, false, false, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 0, new boolean[21], 0, 15));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, true, false, true, true, true, true, false, true, true,
+ false, false, true, true, false, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 0, new boolean[21], 0, 16));
+ assertArrayEquals(
+ new boolean[]{
+ false, false, true, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 0, new boolean[21], 2, 1));
+ assertArrayEquals(
+ new boolean[]{
+ false, false, true, true, true, true, false, true, true, true, true, false,
+ true, true, false, false, true, true, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 0, new boolean[21], 2, 16));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, true, false, true, true, true, true, false, true, true, false,
+ false, true, true, false, false, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 1, new boolean[21], 0, 15));
+ assertArrayEquals(
+ new boolean[]{
+ true, true, false, true, true, true, true, false, true, true, false, false,
+ true, true, false, false, false, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 2, new boolean[21], 0, 14));
+ // assertArrayEquals(new boolean[]{false, false, false, true, true, false, true, true,
+ // true, true, false, true, true, false, false, true, false, false, false, false,
+ // false}, Conversion.shortToBinary((short) 0xCDEF, 2, new boolean[21],
+ // 3, 15));//rejected by
+ // assertion
+ assertArrayEquals(
+ new boolean[]{
+ false, false, false, true, true, false, true, true, true, true, false, true,
+ true, false, false, true, true, false, false, false, false},
+ Conversion.shortToBinary((short) 0xCDEF, 2, new boolean[21], 3, 14));
+ }
+
+ /**
+ * Tests {@link Conversion#byteToBinary(byte, int, boolean[], int, int)}.
+ */
+ @Test
+ public void testByteToBinary() {
+ assertArrayEquals(
+ new boolean[]{}, Conversion.byteToBinary((byte) 0x00, 0, new boolean[]{}, 0, 0));
+ assertArrayEquals(
+ new boolean[]{}, Conversion.byteToBinary((byte) 0x00, 100, new boolean[]{}, 0, 0));
+ assertArrayEquals(
+ new boolean[]{}, Conversion.byteToBinary((byte) 0x00, 0, new boolean[]{}, 100, 0));
+ assertArrayEquals(
+ new boolean[69], Conversion.byteToBinary((byte) 0xEF, 0, new boolean[69], 0, 0));
+ assertArrayEquals(new boolean[]{
+ true, false, false, false, false, false, false, false, false, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 0, new boolean[13], 0, 1));
+ assertArrayEquals(new boolean[]{
+ true, false, false, false, false, false, false, false, false, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 0, new boolean[13], 0, 2));
+ assertArrayEquals(new boolean[]{
+ true, false, true, false, false, false, false, false, false, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 0, new boolean[13], 0, 3));
+ assertArrayEquals(new boolean[]{
+ true, false, true, false, true, false, false, false, false, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 0, new boolean[13], 0, 7));
+ assertArrayEquals(new boolean[]{
+ true, false, true, false, true, false, false, true, false, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 0, new boolean[13], 0, 8));
+ assertArrayEquals(new boolean[]{
+ false, false, true, false, false, false, false, false, false, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 0, new boolean[13], 2, 1));
+ assertArrayEquals(new boolean[]{
+ false, false, true, false, true, false, true, false, false, true, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 0, new boolean[13], 2, 8));
+ assertArrayEquals(new boolean[]{
+ false, true, false, true, false, false, true, false, false, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 1, new boolean[13], 0, 7));
+ assertArrayEquals(new boolean[]{
+ true, false, true, false, false, true, false, false, false, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 2, new boolean[13], 0, 6));
+ // assertArrayEquals(new boolean[]{false, false, false, true, true, false, true, true,
+ // false, false, false, false, false}, Conversion.byteToBinary((byte) 0x95, 2, new
+ // boolean[13], 3, 7));//rejected by assertion
+ assertArrayEquals(new boolean[]{
+ false, false, false, true, false, true, false, false, true, false, false, false,
+ false}, Conversion.byteToBinary((byte) 0x95, 2, new boolean[13], 3, 6));
+ }
+
+ /**
+ * Tests {@link Conversion#uuidToByteArray(UUID, byte[], int, int)}.
+ */
+ @Test
+ public void testUuidToByteArray() {
+ assertArrayEquals(new byte[]{
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff}, Conversion.uuidToByteArray(new UUID(
+ 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL), new byte[16], 0, 16));
+ assertArrayEquals(new byte[]{
+ (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0xee,
+ (byte) 0xff, (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44, (byte) 0x55,
+ (byte) 0x66, (byte) 0x77}, Conversion.uuidToByteArray(new UUID(
+ 0xFFEEDDCCBBAA9988L, 0x7766554433221100L), new byte[16], 0, 16));
+ assertArrayEquals(new byte[]{
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x88, (byte) 0x99, (byte) 0xaa,
+ (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00}, Conversion.uuidToByteArray(new UUID(
+ 0xFFEEDDCCBBAA9988L, 0x7766554433221100L), new byte[16], 4, 8));
+ assertArrayEquals(new byte[]{
+ (byte) 0x00, (byte) 0x00, (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc,
+ (byte) 0xdd, (byte) 0xee, (byte) 0xff, (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+ (byte) 0x00, (byte) 0x00}, Conversion.uuidToByteArray(new UUID(
+ 0xFFEEDDCCBBAA9988L, 0x7766554433221100L), new byte[16], 2, 12));
+ }
+
+ /**
+ * Tests {@link Conversion#byteArrayToUuid(byte[], int)}.
+ */
+ @Test
+ public void testByteArrayToUuid() {
+ assertEquals(
+ new UUID(0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL),
+ Conversion.byteArrayToUuid(new byte[]{
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}, 0));
+ assertEquals(
+ new UUID(0xFFEEDDCCBBAA9988L, 0x7766554433221100L),
+ Conversion.byteArrayToUuid(new byte[]{
+ (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+ (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77}, 0));
+ assertEquals(
+ new UUID(0xFFEEDDCCBBAA9988L, 0x7766554433221100L),
+ Conversion.byteArrayToUuid(new byte[]{
+ 0, 0, (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+ (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77}, 2));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/DoubleRangeTest.java b/src/test/java/org/apache/commons/lang3/DoubleRangeTest.java
new file mode 100644
index 000000000..75b9ab66a
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/DoubleRangeTest.java
@@ -0,0 +1,412 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Comparator;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link DoubleRange}.
+ */
+@SuppressWarnings("boxing")
+public class DoubleRangeTest extends AbstractLangTest {
+
+ private static DoubleRange of(final double min, final double max) {
+ return DoubleRange.of(min, max);
+ }
+
+ private static DoubleRange of(final Double min, final Double max) {
+ return DoubleRange.of(min, max);
+ }
+
+ private DoubleRange range1;
+
+ private DoubleRange range2;
+
+ private DoubleRange range3;
+
+ private DoubleRange rangeFull;
+
+ @BeforeEach
+ public void setUp() {
+ range1 = of(10, 20);
+ range2 = of(10, 20);
+ range3 = of(-2, -1);
+ rangeFull = of(Double.MIN_VALUE, Double.MAX_VALUE);
+ }
+
+ @Test
+ public void testContainsInt() {
+ assertFalse(range1.contains(null));
+
+ assertTrue(rangeFull.contains(Double.MIN_VALUE));
+ assertTrue(rangeFull.contains(Double.MAX_VALUE));
+
+ assertFalse(range1.contains(5d));
+ assertTrue(range1.contains(10d));
+ assertTrue(range1.contains(15d));
+ assertTrue(range1.contains(20d));
+ assertFalse(range1.contains(25d));
+ }
+
+ @Test
+ public void testContainsRange() {
+
+ // null handling
+ assertFalse(range1.containsRange(null));
+
+ // easy inside range
+ assertTrue(range1.containsRange(Range.of(12d, 18d)));
+ assertTrue(range1.containsRange(of(12, 18)));
+
+ // outside range on each side
+ assertFalse(range1.containsRange(Range.of(32d, 45d)));
+ assertFalse(range1.containsRange(of(32, 45)));
+ assertFalse(range1.containsRange(Range.of(2d, 8d)));
+ assertFalse(range1.containsRange(of(2, 8)));
+
+ // equals range
+ assertTrue(range1.containsRange(Range.of(10d, 20d)));
+ assertTrue(range1.containsRange(of(10, 20)));
+
+ // overlaps
+ assertFalse(range1.containsRange(Range.of(9d, 14d)));
+ assertFalse(range1.containsRange(of(9, 14)));
+ assertFalse(range1.containsRange(Range.of(16d, 21d)));
+ assertFalse(range1.containsRange(of(16, 21)));
+
+ // touches lower boundary
+ assertTrue(range1.containsRange(Range.of(10d, 19d)));
+ assertTrue(range1.containsRange(of(10, 19)));
+ assertFalse(range1.containsRange(Range.of(10d, 21d)));
+ assertFalse(range1.containsRange(of(10, 21)));
+
+ // touches upper boundary
+ assertTrue(range1.containsRange(Range.of(11d, 20d)));
+ assertTrue(range1.containsRange(of(11, 20)));
+ assertFalse(range1.containsRange(Range.of(9d, 20d)));
+ assertFalse(range1.containsRange(of(9, 20)));
+
+ // negative
+ assertFalse(range1.containsRange(Range.of(-11d, -18d)));
+ assertFalse(range1.containsRange(of(-11, -18)));
+ }
+
+ @Test
+ public void testElementCompareTo() {
+ assertThrows(NullPointerException.class, () -> range1.elementCompareTo(null));
+
+ assertEquals(-1, range1.elementCompareTo(5d));
+ assertEquals(0, range1.elementCompareTo(10d));
+ assertEquals(0, range1.elementCompareTo(15d));
+ assertEquals(0, range1.elementCompareTo(20d));
+ assertEquals(1, range1.elementCompareTo(25d));
+ }
+
+ @Test
+ public void testEqualsObject() {
+ assertEquals(range1, range1);
+ assertEquals(range1, range2);
+ assertEquals(range2, range2);
+ assertEquals(range1, range1);
+ assertEquals(range2, range2);
+ assertEquals(range3, range3);
+ assertNotEquals(range2, range3);
+ assertNotEquals(null, range2);
+ assertNotEquals("Ni!", range2);
+ }
+
+ @Test
+ public void testFit() {
+ assertEquals(range1.getMinimum(), range1.fit(Double.MIN_VALUE));
+ assertEquals(range1.getMinimum(), range1.fit(range1.getMinimum()));
+ assertEquals(range1.getMaximum(), range1.fit(Double.MAX_VALUE));
+ assertEquals(range1.getMaximum(), range1.fit(range1.getMaximum()));
+ assertEquals(15, range1.fit(15d));
+ }
+
+ @Test
+ public void testFitNull() {
+ assertThrows(NullPointerException.class, () -> {
+ range1.fit(null);
+ });
+ }
+
+ @Test
+ public void testGetMaximum() {
+ assertEquals(20d, range1.getMaximum());
+ }
+
+ @Test
+ public void testGetMinimum() {
+ assertEquals(10d, range1.getMinimum());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(range1.hashCode(), range2.hashCode());
+ assertNotEquals(range1.hashCode(), range3.hashCode());
+
+ assertEquals(range1.hashCode(), range1.hashCode());
+ assertTrue(range1.hashCode() != 0);
+ }
+
+ @Test
+ public void testIntersectionWith() {
+ assertSame(range1, range1.intersectionWith(range1));
+
+ assertEquals(Range.of(10d, 15d), range1.intersectionWith(Range.of(5d, 15d)));
+ }
+
+ @Test
+ public void testIntersectionWithNonOverlapping() {
+ assertThrows(IllegalArgumentException.class, () -> range1.intersectionWith(Range.of(0d, 9d)));
+ }
+
+ @Test
+ public void testIntersectionWithNull() {
+ assertThrows(IllegalArgumentException.class, () -> range1.intersectionWith(null));
+ }
+
+ @Test
+ public void testIsAfter() {
+ assertFalse(range1.isAfter(null));
+
+ assertTrue(range1.isAfter(5d));
+ assertFalse(range1.isAfter(10d));
+ assertFalse(range1.isAfter(15d));
+ assertFalse(range1.isAfter(20d));
+ assertFalse(range1.isAfter(25d));
+ }
+
+ @Test
+ public void testIsAfterRange() {
+ assertFalse(range1.isAfterRange(null));
+
+ assertTrue(range1.isAfterRange(Range.of(5d, 9d)));
+
+ assertFalse(range1.isAfterRange(Range.of(5d, 10d)));
+ assertFalse(range1.isAfterRange(Range.of(5d, 20d)));
+ assertFalse(range1.isAfterRange(Range.of(5d, 25d)));
+ assertFalse(range1.isAfterRange(Range.of(15d, 25d)));
+
+ assertFalse(range1.isAfterRange(Range.of(21d, 25d)));
+
+ assertFalse(range1.isAfterRange(Range.of(10d, 20d)));
+ }
+
+ @Test
+ public void testIsBefore() {
+ assertFalse(range1.isBefore(null));
+
+ assertFalse(range1.isBefore(5d));
+ assertFalse(range1.isBefore(10d));
+ assertFalse(range1.isBefore(15d));
+ assertFalse(range1.isBefore(20d));
+ assertTrue(range1.isBefore(25d));
+ }
+
+ @Test
+ public void testIsBeforeIntegerRange() {
+ assertFalse(range1.isBeforeRange(null));
+
+ assertFalse(range1.isBeforeRange(of(5, 9)));
+
+ assertFalse(range1.isBeforeRange(of(5, 10)));
+ assertFalse(range1.isBeforeRange(of(5, 20)));
+ assertFalse(range1.isBeforeRange(of(5, 25)));
+ assertFalse(range1.isBeforeRange(of(15, 25)));
+
+ assertTrue(range1.isBeforeRange(of(21, 25)));
+
+ assertFalse(range1.isBeforeRange(of(10, 20)));
+ }
+
+ @Test
+ public void testIsBeforeRange() {
+ assertFalse(range1.isBeforeRange(null));
+
+ assertFalse(range1.isBeforeRange(Range.of(5d, 9d)));
+
+ assertFalse(range1.isBeforeRange(Range.of(5d, 10d)));
+ assertFalse(range1.isBeforeRange(Range.of(5d, 20d)));
+ assertFalse(range1.isBeforeRange(Range.of(5d, 25d)));
+ assertFalse(range1.isBeforeRange(Range.of(15d, 25d)));
+
+ assertTrue(range1.isBeforeRange(Range.of(21d, 25d)));
+
+ assertFalse(range1.isBeforeRange(Range.of(10d, 20d)));
+ }
+
+ @Test
+ public void testIsEndedBy() {
+ assertFalse(range1.isEndedBy(null));
+
+ assertFalse(range1.isEndedBy(5d));
+ assertFalse(range1.isEndedBy(10d));
+ assertFalse(range1.isEndedBy(15d));
+ assertTrue(range1.isEndedBy(20d));
+ assertFalse(range1.isEndedBy(25d));
+ }
+
+ @Test
+ public void testIsOverlappedByIntegerRange() {
+
+ // null handling
+ assertFalse(range1.isOverlappedBy(null));
+
+ // easy inside range
+ assertTrue(range1.isOverlappedBy(of(12, 18)));
+
+ // outside range on each side
+ assertFalse(range1.isOverlappedBy(of(32, 45)));
+ assertFalse(range1.isOverlappedBy(of(2, 8)));
+
+ // equals range
+ assertTrue(range1.isOverlappedBy(of(10, 20)));
+
+ // overlaps
+ assertTrue(range1.isOverlappedBy(of(9, 14)));
+ assertTrue(range1.isOverlappedBy(of(16, 21)));
+
+ // touches lower boundary
+ assertTrue(range1.isOverlappedBy(of(10, 19)));
+ assertTrue(range1.isOverlappedBy(of(10, 21)));
+
+ // touches upper boundary
+ assertTrue(range1.isOverlappedBy(of(11, 20)));
+ assertTrue(range1.isOverlappedBy(of(9, 20)));
+
+ // negative
+ assertFalse(range1.isOverlappedBy(of(-11, -18)));
+
+ // outside range whole range
+ assertTrue(range1.isOverlappedBy(of(9, 21)));
+ }
+
+ @Test
+ public void testIsOverlappedByRange() {
+
+ // null handling
+ assertFalse(range1.isOverlappedBy(null));
+
+ // easy inside range
+ assertTrue(range1.isOverlappedBy(Range.of(12d, 18d)));
+
+ // outside range on each side
+ assertFalse(range1.isOverlappedBy(Range.of(32d, 45d)));
+ assertFalse(range1.isOverlappedBy(Range.of(2d, 8d)));
+
+ // equals range
+ assertTrue(range1.isOverlappedBy(Range.of(10d, 20d)));
+
+ // overlaps
+ assertTrue(range1.isOverlappedBy(Range.of(9d, 14d)));
+ assertTrue(range1.isOverlappedBy(Range.of(16d, 21d)));
+
+ // touches lower boundary
+ assertTrue(range1.isOverlappedBy(Range.of(10d, 19d)));
+ assertTrue(range1.isOverlappedBy(Range.of(10d, 21d)));
+
+ // touches upper boundary
+ assertTrue(range1.isOverlappedBy(Range.of(11d, 20d)));
+ assertTrue(range1.isOverlappedBy(Range.of(9d, 20d)));
+
+ // negative
+ assertFalse(range1.isOverlappedBy(Range.of(-11d, -18d)));
+
+ // outside range whole range
+ assertTrue(range1.isOverlappedBy(Range.of(9d, 21d)));
+ }
+
+ @Test
+ public void testIsStartedBy() {
+ assertFalse(range1.isStartedBy(null));
+
+ assertFalse(range1.isStartedBy(5d));
+ assertTrue(range1.isStartedBy(10d));
+ assertFalse(range1.isStartedBy(15d));
+ assertFalse(range1.isStartedBy(20d));
+ assertFalse(range1.isStartedBy(25d));
+ }
+
+ @Test
+ public void testIsWithCompareRange() {
+ // all integers are equal
+ final Comparator<Integer> c = (o1, o2) -> 0;
+ Range<Integer> ri = Range.is(10);
+ assertFalse(ri.contains(null), "should not contain null");
+ assertTrue(ri.contains(10), "should contain 10");
+ assertFalse(ri.contains(11), "should not contain 11");
+ ri = Range.is(10, c);
+ assertFalse(ri.contains(null), "should not contain null");
+ assertTrue(ri.contains(10), "should contain 10");
+ assertTrue(ri.contains(11), "should contain 11");
+ }
+
+ @Test
+ public void testOfWithContains() {
+ // all integers are equal
+ final DoubleRange rb = of(-10, 20);
+ assertFalse(rb.contains(null), "should not contain null");
+ assertTrue(rb.contains(10d), "should contain 10");
+ assertTrue(rb.contains(-10d), "should contain -10");
+ assertFalse(rb.contains(21d), "should not contain 21");
+ assertFalse(rb.contains(-11d), "should not contain -11");
+
+ assertThrows(NullPointerException.class, () -> of(null, null));
+ }
+
+ @Test
+ public void testRangeOfChars() {
+ final DoubleRange chars = of('a', 'z');
+ assertTrue(chars.contains((double) 'b'));
+ assertFalse(chars.contains((double) 'B'));
+ }
+
+ @Test
+ public void testSerializing() {
+ SerializationUtils.clone(range1);
+ }
+
+ @Test
+ public void testToString() {
+ assertNotNull(range1.toString());
+
+ final String str = range1.toString();
+ assertEquals("[10.0..20.0]", str);
+ assertEquals("[-20.0..-10.0]", Range.of(-20d, -10d).toString());
+ assertEquals("[-20.0..-10.0]", DoubleRange.of(-20d, -10d).toString());
+ }
+
+ @Test
+ public void testToStringFormat() {
+ final String str = range1.toString("From %1$s to %2$s");
+ assertEquals("From 10.0 to 20.0", str);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/EnumUtilsTest.java b/src/test/java/org/apache/commons/lang3/EnumUtilsTest.java
new file mode 100644
index 000000000..9f8da63a3
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/EnumUtilsTest.java
@@ -0,0 +1,615 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+enum Enum64 {
+ A00, A01, A02, A03, A04, A05, A06, A07, A08, A09, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22,
+ A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45,
+ A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56, A57, A58, A59, A60, A61, A62, A63
+}
+
+/**
+ *
+ */
+public class EnumUtilsTest extends AbstractLangTest {
+
+ private void assertArrayEquals(final long[] actual, final long... expected) {
+ Assertions.assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void test_generateBitVector() {
+ assertEquals(0L, EnumUtils.generateBitVector(Traffic.class, EnumSet.noneOf(Traffic.class)));
+ assertEquals(1L, EnumUtils.generateBitVector(Traffic.class, EnumSet.of(Traffic.RED)));
+ assertEquals(2L, EnumUtils.generateBitVector(Traffic.class, EnumSet.of(Traffic.AMBER)));
+ assertEquals(4L, EnumUtils.generateBitVector(Traffic.class, EnumSet.of(Traffic.GREEN)));
+ assertEquals(3L, EnumUtils.generateBitVector(Traffic.class, EnumSet.of(Traffic.RED, Traffic.AMBER)));
+ assertEquals(5L, EnumUtils.generateBitVector(Traffic.class, EnumSet.of(Traffic.RED, Traffic.GREEN)));
+ assertEquals(6L, EnumUtils.generateBitVector(Traffic.class, EnumSet.of(Traffic.AMBER, Traffic.GREEN)));
+ assertEquals(7L,
+ EnumUtils.generateBitVector(Traffic.class, EnumSet.of(Traffic.RED, Traffic.AMBER, Traffic.GREEN)));
+
+ // 64 values Enum (to test whether no int<->long jdk conversion issue exists)
+ assertEquals((1L << 31), EnumUtils.generateBitVector(Enum64.class, EnumSet.of(Enum64.A31)));
+ assertEquals((1L << 32), EnumUtils.generateBitVector(Enum64.class, EnumSet.of(Enum64.A32)));
+ assertEquals((1L << 63), EnumUtils.generateBitVector(Enum64.class, EnumSet.of(Enum64.A63)));
+ assertEquals(Long.MIN_VALUE, EnumUtils.generateBitVector(Enum64.class, EnumSet.of(Enum64.A63)));
+ }
+
+ @Test
+ public void test_generateBitVector_longClass() {
+ assertThrows(IllegalArgumentException.class,
+ () -> EnumUtils.generateBitVector(TooMany.class, EnumSet.of(TooMany.A1)));
+ }
+
+ @Test
+ public void test_generateBitVector_longClassWithArray() {
+ assertThrows(IllegalArgumentException.class, () -> EnumUtils.generateBitVector(TooMany.class, TooMany.A1));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void test_generateBitVector_nonEnumClass() {
+ @SuppressWarnings("rawtypes")
+ final Class rawType = Object.class;
+ @SuppressWarnings("rawtypes")
+ final List rawList = new ArrayList();
+ assertThrows(IllegalArgumentException.class, () -> EnumUtils.generateBitVector(rawType, rawList));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void test_generateBitVector_nonEnumClassWithArray() {
+ @SuppressWarnings("rawtypes")
+ final Class rawType = Object.class;
+ assertThrows(IllegalArgumentException.class, () -> EnumUtils.generateBitVector(rawType));
+ }
+
+ @Test
+ public void test_generateBitVector_nullArray() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.generateBitVector(Traffic.class, (Traffic[]) null));
+ }
+
+ @Test
+ public void test_generateBitVector_nullArrayElement() {
+ assertThrows(IllegalArgumentException.class,
+ () -> EnumUtils.generateBitVector(Traffic.class, Traffic.RED, null));
+ }
+
+ @Test
+ public void test_generateBitVector_nullClass() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.generateBitVector(null, EnumSet.of(Traffic.RED)));
+ }
+
+ @Test
+ public void test_generateBitVector_nullClassWithArray() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.generateBitVector(null, Traffic.RED));
+ }
+
+ @Test
+ public void test_generateBitVector_nullElement() {
+ assertThrows(NullPointerException.class,
+ () -> EnumUtils.generateBitVector(Traffic.class, Arrays.asList(Traffic.RED, null)));
+ }
+
+ @Test
+ public void test_generateBitVector_nullIterable() {
+ assertThrows(NullPointerException.class,
+ () -> EnumUtils.generateBitVector(Traffic.class, (Iterable<Traffic>) null));
+ }
+
+ @Test
+ public void test_generateBitVectorFromArray() {
+ assertEquals(0L, EnumUtils.generateBitVector(Traffic.class));
+ assertEquals(1L, EnumUtils.generateBitVector(Traffic.class, Traffic.RED));
+ assertEquals(2L, EnumUtils.generateBitVector(Traffic.class, Traffic.AMBER));
+ assertEquals(4L, EnumUtils.generateBitVector(Traffic.class, Traffic.GREEN));
+ assertEquals(3L, EnumUtils.generateBitVector(Traffic.class, Traffic.RED, Traffic.AMBER));
+ assertEquals(5L, EnumUtils.generateBitVector(Traffic.class, Traffic.RED, Traffic.GREEN));
+ assertEquals(6L, EnumUtils.generateBitVector(Traffic.class, Traffic.AMBER, Traffic.GREEN));
+ assertEquals(7L, EnumUtils.generateBitVector(Traffic.class, Traffic.RED, Traffic.AMBER, Traffic.GREEN));
+ // gracefully handles duplicates:
+ assertEquals(7L,
+ EnumUtils.generateBitVector(Traffic.class, Traffic.RED, Traffic.AMBER, Traffic.GREEN, Traffic.GREEN));
+
+ // 64 values Enum (to test whether no int<->long jdk conversion issue exists)
+ assertEquals((1L << 31), EnumUtils.generateBitVector(Enum64.class, Enum64.A31));
+ assertEquals((1L << 32), EnumUtils.generateBitVector(Enum64.class, Enum64.A32));
+ assertEquals((1L << 63), EnumUtils.generateBitVector(Enum64.class, Enum64.A63));
+ assertEquals(Long.MIN_VALUE, EnumUtils.generateBitVector(Enum64.class, Enum64.A63));
+ }
+
+ @Test
+ public void test_generateBitVectors() {
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, EnumSet.noneOf(Traffic.class)), 0L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, EnumSet.of(Traffic.RED)), 1L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, EnumSet.of(Traffic.AMBER)), 2L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, EnumSet.of(Traffic.GREEN)), 4L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, EnumSet.of(Traffic.RED, Traffic.AMBER)), 3L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, EnumSet.of(Traffic.RED, Traffic.GREEN)), 5L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, EnumSet.of(Traffic.AMBER, Traffic.GREEN)), 6L);
+ assertArrayEquals(
+ EnumUtils.generateBitVectors(Traffic.class, EnumSet.of(Traffic.RED, Traffic.AMBER, Traffic.GREEN)), 7L);
+
+ // 64 values Enum (to test whether no int<->long jdk conversion issue exists)
+ assertArrayEquals(EnumUtils.generateBitVectors(Enum64.class, EnumSet.of(Enum64.A31)), (1L << 31));
+ assertArrayEquals(EnumUtils.generateBitVectors(Enum64.class, EnumSet.of(Enum64.A32)), (1L << 32));
+ assertArrayEquals(EnumUtils.generateBitVectors(Enum64.class, EnumSet.of(Enum64.A63)), (1L << 63));
+ assertArrayEquals(EnumUtils.generateBitVectors(Enum64.class, EnumSet.of(Enum64.A63)), Long.MIN_VALUE);
+
+ // More than 64 values Enum
+ assertArrayEquals(EnumUtils.generateBitVectors(TooMany.class, EnumSet.of(TooMany.M2)), 1L, 0L);
+ assertArrayEquals(EnumUtils.generateBitVectors(TooMany.class, EnumSet.of(TooMany.L2, TooMany.M2)), 1L,
+ (1L << 63));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void test_generateBitVectors_nonEnumClass() {
+ @SuppressWarnings("rawtypes")
+ final Class rawType = Object.class;
+ @SuppressWarnings("rawtypes")
+ final List rawList = new ArrayList();
+ assertThrows(IllegalArgumentException.class, () -> EnumUtils.generateBitVectors(rawType, rawList));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void test_generateBitVectors_nonEnumClassWithArray() {
+ @SuppressWarnings("rawtypes")
+ final Class rawType = Object.class;
+ assertThrows(IllegalArgumentException.class, () -> EnumUtils.generateBitVectors(rawType));
+ }
+
+ @Test
+ public void test_generateBitVectors_nullArray() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.generateBitVectors(Traffic.class, (Traffic[]) null));
+ }
+
+ @Test
+ public void test_generateBitVectors_nullArrayElement() {
+ assertThrows(IllegalArgumentException.class,
+ () -> EnumUtils.generateBitVectors(Traffic.class, Traffic.RED, null));
+ }
+
+ @Test
+ public void test_generateBitVectors_nullClass() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.generateBitVectors(null, EnumSet.of(Traffic.RED)));
+ }
+
+ @Test
+ public void test_generateBitVectors_nullClassWithArray() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.generateBitVectors(null, Traffic.RED));
+ }
+
+ @Test
+ public void test_generateBitVectors_nullElement() {
+ assertThrows(NullPointerException.class,
+ () -> EnumUtils.generateBitVectors(Traffic.class, Arrays.asList(Traffic.RED, null)));
+ }
+
+ @Test
+ public void test_generateBitVectors_nullIterable() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.generateBitVectors(null, (Iterable<Traffic>) null));
+ }
+
+ @Test
+ public void test_generateBitVectorsFromArray() {
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class), 0L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, Traffic.RED), 1L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, Traffic.AMBER), 2L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, Traffic.GREEN), 4L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, Traffic.RED, Traffic.AMBER), 3L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, Traffic.RED, Traffic.GREEN), 5L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, Traffic.AMBER, Traffic.GREEN), 6L);
+ assertArrayEquals(EnumUtils.generateBitVectors(Traffic.class, Traffic.RED, Traffic.AMBER, Traffic.GREEN), 7L);
+ // gracefully handles duplicates:
+ assertArrayEquals(
+ EnumUtils.generateBitVectors(Traffic.class, Traffic.RED, Traffic.AMBER, Traffic.GREEN, Traffic.GREEN), 7L);
+
+ // 64 values Enum (to test whether no int<->long jdk conversion issue exists)
+ assertArrayEquals(EnumUtils.generateBitVectors(Enum64.class, Enum64.A31), (1L << 31));
+ assertArrayEquals(EnumUtils.generateBitVectors(Enum64.class, Enum64.A32), (1L << 32));
+ assertArrayEquals(EnumUtils.generateBitVectors(Enum64.class, Enum64.A63), (1L << 63));
+ assertArrayEquals(EnumUtils.generateBitVectors(Enum64.class, Enum64.A63), Long.MIN_VALUE);
+
+ // More than 64 values Enum
+ assertArrayEquals(EnumUtils.generateBitVectors(TooMany.class, TooMany.M2), 1L, 0L);
+ assertArrayEquals(EnumUtils.generateBitVectors(TooMany.class, TooMany.L2, TooMany.M2), 1L, (1L << 63));
+
+ }
+
+ @Test
+ public void test_getEnum() {
+ assertEquals(Traffic.RED, EnumUtils.getEnum(Traffic.class, "RED"));
+ assertEquals(Traffic.AMBER, EnumUtils.getEnum(Traffic.class, "AMBER"));
+ assertEquals(Traffic.GREEN, EnumUtils.getEnum(Traffic.class, "GREEN"));
+ assertNull(EnumUtils.getEnum(Traffic.class, "PURPLE"));
+ assertNull(EnumUtils.getEnum(Traffic.class, null));
+ }
+
+ @Test
+ public void test_getEnum_defaultEnum() {
+ assertEquals(Traffic.RED, EnumUtils.getEnum(Traffic.class, "RED", Traffic.AMBER));
+ assertEquals(Traffic.AMBER, EnumUtils.getEnum(Traffic.class, "AMBER", Traffic.GREEN));
+ assertEquals(Traffic.GREEN, EnumUtils.getEnum(Traffic.class, "GREEN", Traffic.RED));
+ assertEquals(Traffic.AMBER, EnumUtils.getEnum(Traffic.class, "PURPLE", Traffic.AMBER));
+ assertEquals(Traffic.GREEN, EnumUtils.getEnum(Traffic.class, "PURPLE", Traffic.GREEN));
+ assertEquals(Traffic.RED, EnumUtils.getEnum(Traffic.class, "PURPLE", Traffic.RED));
+ assertEquals(Traffic.AMBER, EnumUtils.getEnum(Traffic.class, null, Traffic.AMBER));
+ assertEquals(Traffic.GREEN, EnumUtils.getEnum(Traffic.class, null, Traffic.GREEN));
+ assertEquals(Traffic.RED, EnumUtils.getEnum(Traffic.class, null, Traffic.RED));
+ assertNull(EnumUtils.getEnum(Traffic.class, "PURPLE", null));
+ }
+
+ /**
+ * Tests raw type.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void test_getEnum_nonEnumClass() {
+ @SuppressWarnings("rawtypes")
+ final Class rawType = Object.class;
+ assertNull(EnumUtils.getEnum(rawType, "rawType"));
+ }
+
+ @Test
+ public void test_getEnum_nullClass() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.getEnum((Class<Traffic>) null, "PURPLE"));
+ }
+
+ @Test
+ public void test_getEnumIgnoreCase() {
+ assertEquals(Traffic.RED, EnumUtils.getEnumIgnoreCase(Traffic.class, "red"));
+ assertEquals(Traffic.AMBER, EnumUtils.getEnumIgnoreCase(Traffic.class, "Amber"));
+ assertEquals(Traffic.GREEN, EnumUtils.getEnumIgnoreCase(Traffic.class, "grEEn"));
+ assertNull(EnumUtils.getEnumIgnoreCase(Traffic.class, "purple"));
+ assertNull(EnumUtils.getEnumIgnoreCase(Traffic.class, null));
+ }
+
+ @Test
+ public void test_getEnumIgnoreCase_defaultEnum() {
+ assertEquals(Traffic.RED, EnumUtils.getEnumIgnoreCase(Traffic.class, "red", Traffic.AMBER));
+ assertEquals(Traffic.AMBER, EnumUtils.getEnumIgnoreCase(Traffic.class, "Amber", Traffic.GREEN));
+ assertEquals(Traffic.GREEN, EnumUtils.getEnumIgnoreCase(Traffic.class, "grEEn", Traffic.RED));
+ assertEquals(Traffic.AMBER, EnumUtils.getEnumIgnoreCase(Traffic.class, "PURPLE", Traffic.AMBER));
+ assertEquals(Traffic.GREEN, EnumUtils.getEnumIgnoreCase(Traffic.class, "purple", Traffic.GREEN));
+ assertEquals(Traffic.RED, EnumUtils.getEnumIgnoreCase(Traffic.class, "pUrPlE", Traffic.RED));
+ assertEquals(Traffic.AMBER, EnumUtils.getEnumIgnoreCase(Traffic.class, null, Traffic.AMBER));
+ assertEquals(Traffic.GREEN, EnumUtils.getEnumIgnoreCase(Traffic.class, null, Traffic.GREEN));
+ assertEquals(Traffic.RED, EnumUtils.getEnumIgnoreCase(Traffic.class, null, Traffic.RED));
+ assertNull(EnumUtils.getEnumIgnoreCase(Traffic.class, "PURPLE", null));
+ }
+
+ /**
+ * Tests raw type.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void test_getEnumIgnoreCase_nonEnumClass() {
+ @SuppressWarnings("rawtypes")
+ final Class rawType = Object.class;
+ assertNull(EnumUtils.getEnumIgnoreCase(rawType, "rawType"));
+ }
+
+ @Test
+ public void test_getEnumIgnoreCase_nullClass() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.getEnumIgnoreCase((Class<Traffic>) null, "PURPLE"));
+ }
+
+ @Test
+ public void test_getEnumList() {
+ final List<Traffic> test = EnumUtils.getEnumList(Traffic.class);
+ assertEquals(3, test.size());
+ assertEquals(Traffic.RED, test.get(0));
+ assertEquals(Traffic.AMBER, test.get(1));
+ assertEquals(Traffic.GREEN, test.get(2));
+ }
+
+ @Test
+ public void test_getEnumMap() {
+ final Map<String, Traffic> test = EnumUtils.getEnumMap(Traffic.class);
+ assertEquals("{RED=RED, AMBER=AMBER, GREEN=GREEN}", test.toString(), "getEnumMap not created correctly");
+ assertEquals(3, test.size());
+ assertTrue(test.containsKey("RED"));
+ assertEquals(Traffic.RED, test.get("RED"));
+ assertTrue(test.containsKey("AMBER"));
+ assertEquals(Traffic.AMBER, test.get("AMBER"));
+ assertTrue(test.containsKey("GREEN"));
+ assertEquals(Traffic.GREEN, test.get("GREEN"));
+ assertFalse(test.containsKey("PURPLE"));
+ }
+
+ @Test
+ public void test_getEnumMap_keyFunction() {
+ final Map<Integer, Month> test = EnumUtils.getEnumMap(Month.class, Month::getId);
+ assertEquals("{1=JAN, 2=FEB, 3=MAR, 4=APR, 5=MAY, 6=JUN, 7=JUL, 8=AUG, 9=SEP, 10=OCT, 11=NOV, 12=DEC}", test.toString(),
+ "getEnumMap not created correctly");
+ assertEquals(12, test.size());
+ assertFalse(test.containsKey(0));
+ assertTrue(test.containsKey(1));
+ assertEquals(Month.JAN, test.get(1));
+ assertTrue(test.containsKey(2));
+ assertEquals(Month.FEB, test.get(2));
+ assertTrue(test.containsKey(3));
+ assertEquals(Month.MAR, test.get(3));
+ assertTrue(test.containsKey(4));
+ assertEquals(Month.APR, test.get(4));
+ assertTrue(test.containsKey(5));
+ assertEquals(Month.MAY, test.get(5));
+ assertTrue(test.containsKey(6));
+ assertEquals(Month.JUN, test.get(6));
+ assertTrue(test.containsKey(7));
+ assertEquals(Month.JUL, test.get(7));
+ assertTrue(test.containsKey(8));
+ assertEquals(Month.AUG, test.get(8));
+ assertTrue(test.containsKey(9));
+ assertEquals(Month.SEP, test.get(9));
+ assertTrue(test.containsKey(10));
+ assertEquals(Month.OCT, test.get(10));
+ assertTrue(test.containsKey(11));
+ assertEquals(Month.NOV, test.get(11));
+ assertTrue(test.containsKey(12));
+ assertEquals(Month.DEC, test.get(12));
+ assertFalse(test.containsKey(13));
+ }
+
+ @Test
+ public void test_getEnumSystemProperty() {
+ final String key = getClass().getName();
+ System.setProperty(key, Traffic.RED.toString());
+ try {
+ assertEquals(Traffic.RED, EnumUtils.getEnumSystemProperty(Traffic.class, key, null));
+ assertEquals(Traffic.RED, EnumUtils.getEnumSystemProperty(Traffic.class, "?", Traffic.RED));
+ assertEquals(Traffic.RED, EnumUtils.getEnumSystemProperty(null, null, Traffic.RED));
+ assertEquals(Traffic.RED, EnumUtils.getEnumSystemProperty(null, "?", Traffic.RED));
+ assertEquals(Traffic.RED, EnumUtils.getEnumSystemProperty(Traffic.class, null, Traffic.RED));
+ } finally {
+ System.getProperties().remove(key);
+ }
+ }
+
+ @Test
+ public void test_getFirstEnumIgnoreCase_defaultEnum() {
+ final Function<Traffic2, String> f = Traffic2::getLabel;
+ assertEquals(Traffic2.RED, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, "***red***", f, Traffic2.AMBER));
+ assertEquals(Traffic2.AMBER, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, "**Amber**", f, Traffic2.GREEN));
+ assertEquals(Traffic2.GREEN, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, "*grEEn*", f, Traffic2.RED));
+ assertEquals(Traffic2.AMBER, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, "PURPLE", f, Traffic2.AMBER));
+ assertEquals(Traffic2.GREEN, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, "purple", f, Traffic2.GREEN));
+ assertEquals(Traffic2.RED, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, "pUrPlE", f, Traffic2.RED));
+ assertEquals(Traffic2.AMBER, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, null, f, Traffic2.AMBER));
+ assertEquals(Traffic2.GREEN, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, null, f, Traffic2.GREEN));
+ assertEquals(Traffic2.RED, EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, null, f, Traffic2.RED));
+ assertNull(EnumUtils.getFirstEnumIgnoreCase(Traffic2.class, "PURPLE", f, null));
+ }
+
+ @Test
+ public void test_isValidEnum() {
+ assertTrue(EnumUtils.isValidEnum(Traffic.class, "RED"));
+ assertTrue(EnumUtils.isValidEnum(Traffic.class, "AMBER"));
+ assertTrue(EnumUtils.isValidEnum(Traffic.class, "GREEN"));
+ assertFalse(EnumUtils.isValidEnum(Traffic.class, "PURPLE"));
+ assertFalse(EnumUtils.isValidEnum(Traffic.class, null));
+ }
+
+ @Test
+ public void test_isValidEnum_nullClass() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.isValidEnum(null, "PURPLE"));
+ }
+
+ @Test
+ public void test_isValidEnumIgnoreCase() {
+ assertTrue(EnumUtils.isValidEnumIgnoreCase(Traffic.class, "red"));
+ assertTrue(EnumUtils.isValidEnumIgnoreCase(Traffic.class, "Amber"));
+ assertTrue(EnumUtils.isValidEnumIgnoreCase(Traffic.class, "grEEn"));
+ assertFalse(EnumUtils.isValidEnumIgnoreCase(Traffic.class, "purple"));
+ assertFalse(EnumUtils.isValidEnumIgnoreCase(Traffic.class, null));
+ }
+
+ @Test
+ public void test_isValidEnumIgnoreCase_nullClass() {
+ assertThrows(NullPointerException.class, () -> EnumUtils.isValidEnumIgnoreCase(null, "PURPLE"));
+ }
+
+ @Test
+ public void test_processBitVector() {
+ assertEquals(EnumSet.noneOf(Traffic.class), EnumUtils.processBitVector(Traffic.class, 0L));
+ assertEquals(EnumSet.of(Traffic.RED), EnumUtils.processBitVector(Traffic.class, 1L));
+ assertEquals(EnumSet.of(Traffic.AMBER), EnumUtils.processBitVector(Traffic.class, 2L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.AMBER), EnumUtils.processBitVector(Traffic.class, 3L));
+ assertEquals(EnumSet.of(Traffic.GREEN), EnumUtils.processBitVector(Traffic.class, 4L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.GREEN), EnumUtils.processBitVector(Traffic.class, 5L));
+ assertEquals(EnumSet.of(Traffic.AMBER, Traffic.GREEN), EnumUtils.processBitVector(Traffic.class, 6L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.AMBER, Traffic.GREEN),
+ EnumUtils.processBitVector(Traffic.class, 7L));
+
+ // 64 values Enum (to test whether no int<->long jdk conversion issue exists)
+ assertEquals(EnumSet.of(Enum64.A31), EnumUtils.processBitVector(Enum64.class, (1L << 31)));
+ assertEquals(EnumSet.of(Enum64.A32), EnumUtils.processBitVector(Enum64.class, (1L << 32)));
+ assertEquals(EnumSet.of(Enum64.A63), EnumUtils.processBitVector(Enum64.class, (1L << 63)));
+ assertEquals(EnumSet.of(Enum64.A63), EnumUtils.processBitVector(Enum64.class, Long.MIN_VALUE));
+ }
+
+ @Test
+ public void test_processBitVector_longClass() {
+ assertThrows(IllegalArgumentException.class, () -> EnumUtils.processBitVector(TooMany.class, 0L));
+ }
+
+ @Test
+ public void test_processBitVector_nullClass() {
+ final Class<Traffic> empty = null;
+ assertThrows(NullPointerException.class, () -> EnumUtils.processBitVector(empty, 0L));
+ }
+
+ @Test
+ public void test_processBitVectors() {
+ assertEquals(EnumSet.noneOf(Traffic.class), EnumUtils.processBitVectors(Traffic.class, 0L));
+ assertEquals(EnumSet.of(Traffic.RED), EnumUtils.processBitVectors(Traffic.class, 1L));
+ assertEquals(EnumSet.of(Traffic.AMBER), EnumUtils.processBitVectors(Traffic.class, 2L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.AMBER), EnumUtils.processBitVectors(Traffic.class, 3L));
+ assertEquals(EnumSet.of(Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 4L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 5L));
+ assertEquals(EnumSet.of(Traffic.AMBER, Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 6L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.AMBER, Traffic.GREEN),
+ EnumUtils.processBitVectors(Traffic.class, 7L));
+
+ assertEquals(EnumSet.noneOf(Traffic.class), EnumUtils.processBitVectors(Traffic.class, 0L, 0L));
+ assertEquals(EnumSet.of(Traffic.RED), EnumUtils.processBitVectors(Traffic.class, 0L, 1L));
+ assertEquals(EnumSet.of(Traffic.AMBER), EnumUtils.processBitVectors(Traffic.class, 0L, 2L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.AMBER), EnumUtils.processBitVectors(Traffic.class, 0L, 3L));
+ assertEquals(EnumSet.of(Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 0L, 4L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 0L, 5L));
+ assertEquals(EnumSet.of(Traffic.AMBER, Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 0L, 6L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.AMBER, Traffic.GREEN),
+ EnumUtils.processBitVectors(Traffic.class, 0L, 7L));
+
+ // demonstrate tolerance of irrelevant high-order digits:
+ assertEquals(EnumSet.noneOf(Traffic.class), EnumUtils.processBitVectors(Traffic.class, 666L, 0L));
+ assertEquals(EnumSet.of(Traffic.RED), EnumUtils.processBitVectors(Traffic.class, 666L, 1L));
+ assertEquals(EnumSet.of(Traffic.AMBER), EnumUtils.processBitVectors(Traffic.class, 666L, 2L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.AMBER), EnumUtils.processBitVectors(Traffic.class, 666L, 3L));
+ assertEquals(EnumSet.of(Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 666L, 4L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 666L, 5L));
+ assertEquals(EnumSet.of(Traffic.AMBER, Traffic.GREEN), EnumUtils.processBitVectors(Traffic.class, 666L, 6L));
+ assertEquals(EnumSet.of(Traffic.RED, Traffic.AMBER, Traffic.GREEN),
+ EnumUtils.processBitVectors(Traffic.class, 666L, 7L));
+
+ // 64 values Enum (to test whether no int<->long jdk conversion issue exists)
+ assertEquals(EnumSet.of(Enum64.A31), EnumUtils.processBitVectors(Enum64.class, (1L << 31)));
+ assertEquals(EnumSet.of(Enum64.A32), EnumUtils.processBitVectors(Enum64.class, (1L << 32)));
+ assertEquals(EnumSet.of(Enum64.A63), EnumUtils.processBitVectors(Enum64.class, (1L << 63)));
+ assertEquals(EnumSet.of(Enum64.A63), EnumUtils.processBitVectors(Enum64.class, Long.MIN_VALUE));
+ }
+
+ @Test
+ public void test_processBitVectors_longClass() {
+ assertEquals(EnumSet.noneOf(TooMany.class), EnumUtils.processBitVectors(TooMany.class, 0L));
+ assertEquals(EnumSet.of(TooMany.A), EnumUtils.processBitVectors(TooMany.class, 1L));
+ assertEquals(EnumSet.of(TooMany.B), EnumUtils.processBitVectors(TooMany.class, 2L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B), EnumUtils.processBitVectors(TooMany.class, 3L));
+ assertEquals(EnumSet.of(TooMany.C), EnumUtils.processBitVectors(TooMany.class, 4L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.C), EnumUtils.processBitVectors(TooMany.class, 5L));
+ assertEquals(EnumSet.of(TooMany.B, TooMany.C), EnumUtils.processBitVectors(TooMany.class, 6L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.C), EnumUtils.processBitVectors(TooMany.class, 7L));
+
+ assertEquals(EnumSet.noneOf(TooMany.class), EnumUtils.processBitVectors(TooMany.class, 0L, 0L));
+ assertEquals(EnumSet.of(TooMany.A), EnumUtils.processBitVectors(TooMany.class, 0L, 1L));
+ assertEquals(EnumSet.of(TooMany.B), EnumUtils.processBitVectors(TooMany.class, 0L, 2L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B), EnumUtils.processBitVectors(TooMany.class, 0L, 3L));
+ assertEquals(EnumSet.of(TooMany.C), EnumUtils.processBitVectors(TooMany.class, 0L, 4L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.C), EnumUtils.processBitVectors(TooMany.class, 0L, 5L));
+ assertEquals(EnumSet.of(TooMany.B, TooMany.C), EnumUtils.processBitVectors(TooMany.class, 0L, 6L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.C), EnumUtils.processBitVectors(TooMany.class, 0L, 7L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.C), EnumUtils.processBitVectors(TooMany.class, 0L, 7L));
+
+ assertEquals(EnumSet.of(TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 1L, 0L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 1L, 1L));
+ assertEquals(EnumSet.of(TooMany.B, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 1L, 2L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 1L, 3L));
+ assertEquals(EnumSet.of(TooMany.C, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 1L, 4L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.C, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 1L, 5L));
+ assertEquals(EnumSet.of(TooMany.B, TooMany.C, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 1L, 6L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.C, TooMany.M2),
+ EnumUtils.processBitVectors(TooMany.class, 1L, 7L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.C, TooMany.M2),
+ EnumUtils.processBitVectors(TooMany.class, 1L, 7L));
+
+ // demonstrate tolerance of irrelevant high-order digits:
+ assertEquals(EnumSet.of(TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 9L, 0L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 9L, 1L));
+ assertEquals(EnumSet.of(TooMany.B, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 9L, 2L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 9L, 3L));
+ assertEquals(EnumSet.of(TooMany.C, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 9L, 4L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.C, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 9L, 5L));
+ assertEquals(EnumSet.of(TooMany.B, TooMany.C, TooMany.M2), EnumUtils.processBitVectors(TooMany.class, 9L, 6L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.C, TooMany.M2),
+ EnumUtils.processBitVectors(TooMany.class, 9L, 7L));
+ assertEquals(EnumSet.of(TooMany.A, TooMany.B, TooMany.C, TooMany.M2),
+ EnumUtils.processBitVectors(TooMany.class, 9L, 7L));
+ }
+
+ @Test
+ public void test_processBitVectors_nullClass() {
+ final Class<Traffic> empty = null;
+ assertThrows(NullPointerException.class, () -> EnumUtils.processBitVectors(empty, 0L));
+ }
+
+ @Test
+ public void testConstructable() {
+ // enforce public constructor
+ new EnumUtils();
+ }
+
+}
+
+enum Month {
+ JAN(1), FEB(2), MAR(3), APR(4), MAY(5), JUN(6), JUL(7), AUG(8), SEP(9), OCT(10), NOV(11), DEC(12);
+
+ private final int id;
+
+ Month(final int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return this.id;
+ }
+}
+
+enum TooMany {
+ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A1, B1, C1, D1, E1, F1, G1, H1, I1,
+ J1, K1, L1, M1, N1, O1, P1, Q1, R1, S1, T1, U1, V1, W1, X1, Y1, Z1, A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2,
+ M2
+}
+
+enum Traffic {
+ RED, AMBER, GREEN
+}
+
+enum Traffic2 {
+
+ RED("***Red***"), AMBER("**Amber**"), GREEN("*green*");
+
+ final String label;
+
+ Traffic2(final String label) {
+ this.label = label;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/FunctionsTest.java b/src/test/java/org/apache/commons/lang3/FunctionsTest.java
new file mode 100644
index 000000000..9f767afda
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/FunctionsTest.java
@@ -0,0 +1,1097 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.reflect.UndeclaredThrowableException;
+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 org.apache.commons.lang3.Functions.FailableBiConsumer;
+import org.apache.commons.lang3.Functions.FailableBiFunction;
+import org.apache.commons.lang3.Functions.FailableCallable;
+import org.apache.commons.lang3.Functions.FailableConsumer;
+import org.apache.commons.lang3.Functions.FailableFunction;
+import org.apache.commons.lang3.Functions.FailableSupplier;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class FunctionsTest extends AbstractLangTest {
+
+ public static class CloseableObject {
+ private boolean closed;
+
+ public void close() {
+ closed = true;
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public void reset() {
+ closed = false;
+ }
+
+ public void run(final Throwable pTh) throws Throwable {
+ if (pTh != null) {
+ throw pTh;
+ }
+ }
+ }
+
+ public static class FailureOnOddInvocations {
+ private static int invocations;
+
+ static boolean failingBool() throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+
+ static boolean testDouble(final double value) throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+
+ static boolean testInt(final int value) throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+
+ static boolean testLong(final long value) throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+
+ private static void throwOnOdd() throws SomeException {
+ final int i = ++invocations;
+ if (i % 2 == 1) {
+ throw new SomeException("Odd Invocation: " + i);
+ }
+ }
+
+ FailureOnOddInvocations() throws SomeException {
+ throwOnOdd();
+ }
+
+ boolean getAsBoolean() throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+ }
+
+ public static class SomeException extends Exception {
+
+ private static final long serialVersionUID = -4965704778119283411L;
+
+ private Throwable t;
+
+ SomeException(final String message) {
+ super(message);
+ }
+
+ public void setThrowable(final Throwable throwable) {
+ t = throwable;
+ }
+
+ public void test() throws Throwable {
+ if (t != null) {
+ throw t;
+ }
+ }
+ }
+
+ public static class Testable<T, P> {
+ private T acceptedObject;
+ private P acceptedPrimitiveObject1;
+ private P acceptedPrimitiveObject2;
+ private Throwable throwable;
+
+ Testable(final Throwable throwable) {
+ this.throwable = throwable;
+ }
+
+ public T getAcceptedObject() {
+ return acceptedObject;
+ }
+
+ public P getAcceptedPrimitiveObject1() {
+ return acceptedPrimitiveObject1;
+ }
+
+ public P getAcceptedPrimitiveObject2() {
+ return acceptedPrimitiveObject2;
+ }
+
+ public void setThrowable(final Throwable throwable) {
+ this.throwable = throwable;
+ }
+
+ public void test() throws Throwable {
+ test(throwable);
+ }
+
+ public Object test(final Object input1, final Object input2) throws Throwable {
+ test(throwable);
+ return acceptedObject;
+ }
+
+ public void test(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ }
+
+ public boolean testAsBooleanPrimitive() throws Throwable {
+ return testAsBooleanPrimitive(throwable);
+ }
+
+ public boolean testAsBooleanPrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return false;
+ }
+
+ public double testAsDoublePrimitive() throws Throwable {
+ return testAsDoublePrimitive(throwable);
+ }
+
+ public double testAsDoublePrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public Integer testAsInteger() throws Throwable {
+ return testAsInteger(throwable);
+ }
+
+ public Integer testAsInteger(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public int testAsIntPrimitive() throws Throwable {
+ return testAsIntPrimitive(throwable);
+ }
+
+ public int testAsIntPrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public long testAsLongPrimitive() throws Throwable {
+ return testAsLongPrimitive(throwable);
+ }
+
+ public long testAsLongPrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public void testDouble(final double i) throws Throwable {
+ test(throwable);
+ acceptedPrimitiveObject1 = (P) ((Double) i);
+ }
+
+ public double testDoubleDouble(final double i, final double j) throws Throwable {
+ test(throwable);
+ acceptedPrimitiveObject1 = (P) ((Double) i);
+ acceptedPrimitiveObject2 = (P) ((Double) j);
+ return 3d;
+ }
+
+ public void testInt(final int i) throws Throwable {
+ test(throwable);
+ acceptedPrimitiveObject1 = (P) ((Integer) i);
+ }
+
+ public void testLong(final long i) throws Throwable {
+ test(throwable);
+ acceptedPrimitiveObject1 = (P) ((Long) i);
+ }
+
+ public void testObjDouble(final T object, final double i) throws Throwable {
+ test(throwable);
+ acceptedObject = object;
+ acceptedPrimitiveObject1 = (P) ((Double) i);
+ }
+
+ public void testObjInt(final T object, final int i) throws Throwable {
+ test(throwable);
+ acceptedObject = object;
+ acceptedPrimitiveObject1 = (P) ((Integer) i);
+ }
+
+ public void testObjLong(final T object, final long i) throws Throwable {
+ test(throwable);
+ acceptedObject = object;
+ acceptedPrimitiveObject1 = (P) ((Long) i);
+ }
+ }
+
+ @Test
+ public void testAcceptBiConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(null);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Functions.accept(Testable::test, testable, ise));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.accept(Testable::test, testable, error));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.accept(Testable::test, testable, ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ Functions.accept(Testable::test, testable, (Throwable) null);
+ }
+
+ @Test
+ public void testAcceptConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Functions.accept(Testable::test, testable));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.accept(Testable::test, testable));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.accept(Testable::test, testable));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ Functions.accept(Testable::test, testable);
+ }
+
+ @Test
+ public void testAcceptDoubleConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, Double> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Functions.accept(testable::testDouble, 1d));
+ assertSame(ise, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.accept(testable::testDouble, 1d));
+ assertSame(error, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.accept(testable::testDouble, 1d));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Functions.accept(testable::testDouble, 1d);
+ assertEquals(1, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptIntConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, Integer> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Functions.accept(testable::testInt, 1));
+ assertSame(ise, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.accept(testable::testInt, 1));
+ assertSame(error, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.accept(testable::testInt, 1));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Functions.accept(testable::testInt, 1);
+ assertEquals(1, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptLongConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, Long> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Functions.accept(testable::testLong, 1L));
+ assertSame(ise, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.accept(testable::testLong, 1L));
+ assertSame(error, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.accept(testable::testLong, 1L));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Functions.accept(testable::testLong, 1L);
+ assertEquals(1, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptObjDoubleConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<String, Double> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Functions.accept(testable::testObjDouble, "X", 1d));
+ assertSame(ise, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.accept(testable::testObjDouble, "X", 1d));
+ assertSame(error, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.accept(testable::testObjDouble, "X", 1d));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Functions.accept(testable::testObjDouble, "X", 1d);
+ assertEquals("X", testable.getAcceptedObject());
+ assertEquals(1d, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptObjIntConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<String, Integer> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Functions.accept(testable::testObjInt, "X", 1));
+ assertSame(ise, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.accept(testable::testObjInt, "X", 1));
+ assertSame(error, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.accept(testable::testObjInt, "X", 1));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Functions.accept(testable::testObjInt, "X", 1);
+ assertEquals("X", testable.getAcceptedObject());
+ assertEquals(1, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptObjLongConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<String, Long> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Functions.accept(testable::testObjLong, "X", 1L));
+ assertSame(ise, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.accept(testable::testObjLong, "X", 1L));
+ assertSame(error, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.accept(testable::testObjLong, "X", 1L));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Functions.accept(testable::testObjLong, "X", 1L);
+ assertEquals("X", testable.getAcceptedObject());
+ assertEquals(1L, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testApplyBiFunction() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(null);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Functions.apply(Testable::testAsInteger, testable, ise));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.apply(Testable::testAsInteger, testable, error));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ e = assertThrows(UncheckedIOException.class, () -> Functions.apply(Testable::testAsInteger, testable, ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ final Integer i = Functions.apply(Testable::testAsInteger, testable, (Throwable) null);
+ assertNotNull(i);
+ assertEquals(0, i.intValue());
+ }
+
+ @Test
+ public void testApplyFunction() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Functions.apply(Testable::testAsInteger, testable));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.apply(Testable::testAsInteger, testable));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.apply(Testable::testAsInteger, testable));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ final Integer i = Functions.apply(Testable::testAsInteger, testable);
+ assertNotNull(i);
+ assertEquals(0, i.intValue());
+ }
+
+ @Test
+ public void testAsCallable() {
+ FailureOnOddInvocations.invocations = 0;
+ final FailableCallable<FailureOnOddInvocations, SomeException> failableCallable = FailureOnOddInvocations::new;
+ final Callable<FailureOnOddInvocations> callable = Functions.asCallable(failableCallable);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class, callable::call);
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final FailureOnOddInvocations instance;
+ try {
+ instance = callable.call();
+ } catch (final Exception ex) {
+ throw Functions.rethrow(ex);
+ }
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testAsConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(ise);
+ final Consumer<Testable<?, ?>> consumer = Functions.asConsumer(Testable::test);
+ Throwable e = assertThrows(IllegalStateException.class, () -> consumer.accept(testable));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> consumer.accept(testable));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> consumer.accept(testable));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ Functions.accept(Testable::test, testable);
+ }
+
+ @Test
+ public void testAsRunnable() {
+ FailureOnOddInvocations.invocations = 0;
+ final Runnable runnable = Functions.asRunnable(FailureOnOddInvocations::new);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class, runnable::run);
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+
+ // Even invocations, should not throw an exception
+ runnable.run();
+ }
+
+ @Test
+ public void testAsSupplier() {
+ FailureOnOddInvocations.invocations = 0;
+ final FailableSupplier<FailureOnOddInvocations, Throwable> failableSupplier = FailureOnOddInvocations::new;
+ final Supplier<FailureOnOddInvocations> supplier = Functions.asSupplier(failableSupplier);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class, supplier::get);
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ assertNotNull(supplier.get());
+ }
+
+ @Test
+ public void testBiConsumer() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableBiConsumer<Testable<?, ?>, Throwable, Throwable> failableBiConsumer = (t, th) -> {
+ t.setThrowable(th);
+ t.test();
+ };
+ final BiConsumer<Testable<?, ?>, Throwable> consumer = Functions.asBiConsumer(failableBiConsumer);
+ Throwable e = assertThrows(IllegalStateException.class, () -> consumer.accept(testable, ise));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ e = assertThrows(OutOfMemoryError.class, () -> consumer.accept(testable, error));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> consumer.accept(testable, ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ consumer.accept(testable, null);
+ }
+
+ @Test
+ public void testBiFunction() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(ise);
+ final FailableBiFunction<Testable<?, ?>, Throwable, Integer, Throwable> failableBiFunction = (t, th) -> {
+ t.setThrowable(th);
+ return Integer.valueOf(t.testAsInteger());
+ };
+ final BiFunction<Testable<?, ?>, Throwable, Integer> biFunction = Functions.asBiFunction(failableBiFunction);
+ Throwable e = assertThrows(IllegalStateException.class, () -> biFunction.apply(testable, ise));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> biFunction.apply(testable, error));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> biFunction.apply(testable, ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ assertEquals(0, biFunction.apply(testable, null).intValue());
+ }
+
+ @Test
+ @DisplayName("Test that asPredicate(FailableBiPredicate) is converted to -> BiPredicate ")
+ public void testBiPredicate() {
+ FailureOnOddInvocations.invocations = 0;
+ final Functions.FailableBiPredicate<Object, Object, Throwable> failableBiPredicate = (t1,
+ t2) -> FailureOnOddInvocations.failingBool();
+ final BiPredicate<?, ?> predicate = Functions.asBiPredicate(failableBiPredicate);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> predicate.test(null, null));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final boolean instance = predicate.test(null, null);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testCallable() {
+ FailureOnOddInvocations.invocations = 0;
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> Functions.run(FailureOnOddInvocations::new));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final FailureOnOddInvocations instance = Functions.call(FailureOnOddInvocations::new);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testConstructor() {
+ // We allow this, which must have been an omission to make the ctor private.
+ // We could make the ctor private in 4.0.
+ new Functions();
+ }
+
+ @Test
+ public void testFunction() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(ise);
+ final FailableFunction<Throwable, Integer, Throwable> failableFunction = th -> {
+ testable.setThrowable(th);
+ return Integer.valueOf(testable.testAsInteger());
+ };
+ final Function<Throwable, Integer> function = Functions.asFunction(failableFunction);
+ Throwable e = assertThrows(IllegalStateException.class, () -> function.apply(ise));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> function.apply(error));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> function.apply(ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ assertEquals(0, function.apply(null).intValue());
+ }
+
+ @Test
+ public void testGetFromSupplier() {
+ FailureOnOddInvocations.invocations = 0;
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> Functions.run(FailureOnOddInvocations::new));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final FailureOnOddInvocations instance = Functions.call(FailureOnOddInvocations::new);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testGetSupplier() {
+ final IllegalStateException ise = new IllegalStateException();
+ final Testable<?, ?> testable = new Testable<>(ise);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Functions.get(testable::testAsInteger));
+ assertSame(ise, e);
+
+ final Error error = new OutOfMemoryError();
+ testable.setThrowable(error);
+ e = assertThrows(OutOfMemoryError.class, () -> Functions.get(testable::testAsInteger));
+ assertSame(error, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Functions.get(testable::testAsInteger));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ final Integer i = Functions.apply(Testable::testAsInteger, testable);
+ assertNotNull(i);
+ assertEquals(0, i.intValue());
+ }
+
+ @Test
+ @DisplayName("Test that asPredicate(FailablePredicate) is converted to -> Predicate ")
+ public void testPredicate() {
+ FailureOnOddInvocations.invocations = 0;
+ final Functions.FailablePredicate<Object, Throwable> failablePredicate = t -> FailureOnOddInvocations
+ .failingBool();
+ final Predicate<?> predicate = Functions.asPredicate(failablePredicate);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> predicate.test(null));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final boolean instance = predicate.test(null);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testRunnable() {
+ FailureOnOddInvocations.invocations = 0;
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> Functions.run(FailureOnOddInvocations::new));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+
+ // Even invocations, should not throw an exception
+ Functions.run(FailureOnOddInvocations::new);
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableBiConsumer_Object_Throwable() {
+ new Functions.FailableBiConsumer<Object, Object, Throwable>() {
+
+ @Override
+ public void accept(final Object object1, final Object object2) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableBiConsumer_String_IOException() {
+ new Functions.FailableBiConsumer<String, String, IOException>() {
+
+ @Override
+ public void accept(final String object1, final String object2) throws IOException {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableBiFunction_Object_Throwable() {
+ new Functions.FailableBiFunction<Object, Object, Object, Throwable>() {
+
+ @Override
+ public Object apply(final Object input1, final Object input2) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableBiFunction_String_IOException() {
+ new Functions.FailableBiFunction<String, String, String, IOException>() {
+
+ @Override
+ public String apply(final String input1, final String input2) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableBiPredicate_Object_Throwable() {
+ new Functions.FailableBiPredicate<Object, Object, Throwable>() {
+
+ @Override
+ public boolean test(final Object object1, final Object object2) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableBiPredicate_String_IOException() {
+ new Functions.FailableBiPredicate<String, String, IOException>() {
+
+ @Override
+ public boolean test(final String object1, final String object2) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableCallable_Object_Throwable() {
+ new Functions.FailableCallable<Object, Throwable>() {
+
+ @Override
+ public Object call() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableCallable_String_IOException() {
+ new Functions.FailableCallable<String, IOException>() {
+
+ @Override
+ public String call() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableConsumer_Object_Throwable() {
+ new Functions.FailableConsumer<Object, Throwable>() {
+
+ @Override
+ public void accept(final Object object) throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableConsumer_String_IOException() {
+ new Functions.FailableConsumer<String, IOException>() {
+
+ @Override
+ public void accept(final String object) throws IOException {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableFunction_Object_Throwable() {
+ new Functions.FailableFunction<Object, Object, Throwable>() {
+
+ @Override
+ public Object apply(final Object input) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableFunction_String_IOException() {
+ new Functions.FailableFunction<String, String, IOException>() {
+
+ @Override
+ public String apply(final String input) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailablePredicate_Object_Throwable() {
+ new Functions.FailablePredicate<Object, Throwable>() {
+
+ @Override
+ public boolean test(final Object object) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailablePredicate_String_IOException() {
+ new Functions.FailablePredicate<String, IOException>() {
+
+ @Override
+ public boolean test(final String object) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableRunnable_Object_Throwable() {
+ new Functions.FailableRunnable<Throwable>() {
+
+ @Override
+ public void run() throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableRunnable_String_IOException() {
+ new Functions.FailableRunnable<IOException>() {
+
+ @Override
+ public void run() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception. using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableSupplier_Object_Throwable() {
+ new Functions.FailableSupplier<Object, Throwable>() {
+
+ @Override
+ public Object get() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableSupplier_String_IOException() {
+ new Functions.FailableSupplier<String, IOException>() {
+
+ @Override
+ public String get() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ @Test
+ public void testTryWithResources() {
+ final CloseableObject co = new CloseableObject();
+ final FailableConsumer<Throwable, ? extends Throwable> consumer = co::run;
+ final IllegalStateException ise = new IllegalStateException();
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Functions.tryWithResources(() -> consumer.accept(ise), co::close));
+ assertSame(ise, e);
+
+ assertTrue(co.isClosed());
+ co.reset();
+ final Error error = new OutOfMemoryError();
+ e = assertThrows(OutOfMemoryError.class,
+ () -> Functions.tryWithResources(() -> consumer.accept(error), co::close));
+ assertSame(error, e);
+
+ assertTrue(co.isClosed());
+ co.reset();
+ final IOException ioe = new IOException("Unknown I/O error");
+ final UncheckedIOException uioe = assertThrows(UncheckedIOException.class,
+ () -> Functions.tryWithResources(() -> consumer.accept(ioe), co::close));
+ final IOException cause = uioe.getCause();
+ assertSame(ioe, cause);
+
+ assertTrue(co.isClosed());
+ co.reset();
+ Functions.tryWithResources(() -> consumer.accept(null), co::close);
+ assertTrue(co.isClosed());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/HashSetvBitSetTest.java b/src/test/java/org/apache/commons/lang3/HashSetvBitSetTest.java
new file mode 100644
index 000000000..a5ba1085c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/HashSetvBitSetTest.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;
+
+import java.util.BitSet;
+import java.util.HashSet;
+import java.util.concurrent.TimeUnit;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Test to show whether using BitSet for removeAll() methods is faster than using HashSet.
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@State(Scope.Thread)
+public class HashSetvBitSetTest extends AbstractLangTest {
+
+ private static final int numberOfElementsToCompute = 10;
+
+ @Benchmark
+ public int[] testHashSet() {
+ final HashSet<Integer> toRemove = new HashSet<>();
+ int found = 0;
+ for (int i = 0; i < numberOfElementsToCompute; i++) {
+ toRemove.add(found++);
+ }
+ return extractIndices(toRemove);
+ }
+
+ @Benchmark
+ public int[] testBitSet() {
+ final BitSet toRemove = new BitSet();
+ int found = 0;
+ for (int i = 0; i < numberOfElementsToCompute; i++) {
+ toRemove.set(found++);
+ }
+ return extractIndices(toRemove);
+ }
+
+ @Benchmark
+ public int[] timeBitSetRemoveAll() {
+ final BitSet toRemove = new BitSet();
+ final int[] array = new int[100];
+ toRemove.set(10, 20);
+ return (int[]) ArrayUtils.removeAll(array, toRemove);
+ }
+
+ @Benchmark
+ public int[] timeExtractRemoveAll() {
+ final BitSet toRemove = new BitSet();
+ final int[] array = new int[100];
+ toRemove.set(10, 20);
+ final int[] extractIndices = extractIndices(toRemove);
+ return (int[]) ArrayUtils.removeAll((Object) array, extractIndices);
+ }
+
+ // --- utility methods
+ private static int[] extractIndices(final HashSet<Integer> coll) {
+ final int[] result = new int[coll.size()];
+ int i = 0;
+ for (final Integer index : coll) {
+ result[i++] = index.intValue();
+ }
+ return result;
+ }
+
+ private static int[] extractIndices(final BitSet coll) {
+ final int[] result = new int[coll.cardinality()];
+ int i = 0;
+ int j=0;
+ while ((j=coll.nextSetBit(j)) != -1) {
+ result[i++] = j++;
+ }
+ return result;
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/IntegerRangeTest.java b/src/test/java/org/apache/commons/lang3/IntegerRangeTest.java
new file mode 100644
index 000000000..7c894f298
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/IntegerRangeTest.java
@@ -0,0 +1,411 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Comparator;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IntegerRange}.
+ */
+@SuppressWarnings("boxing")
+public class IntegerRangeTest extends AbstractLangTest {
+
+ private static IntegerRange of(final int min, final int max) {
+ return IntegerRange.of(min, max);
+ }
+
+ private static IntegerRange of(final Integer min, final Integer max) {
+ return IntegerRange.of(min, max);
+ }
+
+ private IntegerRange range1;
+
+ private IntegerRange range2;
+
+ private IntegerRange range3;
+
+ private IntegerRange rangeFull;
+
+ @BeforeEach
+ public void setUp() {
+ range1 = of(10, 20);
+ range2 = of(10, 20);
+ range3 = of(-2, -1);
+ rangeFull = of(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void testContainsInt() {
+ assertFalse(range1.contains(null));
+
+ assertTrue(rangeFull.contains(Integer.MIN_VALUE));
+ assertTrue(rangeFull.contains(Integer.MAX_VALUE));
+
+ assertFalse(range1.contains(5));
+ assertTrue(range1.contains(10));
+ assertTrue(range1.contains(15));
+ assertTrue(range1.contains(20));
+ assertFalse(range1.contains(25));
+ }
+
+ @Test
+ public void testContainsRange() {
+
+ // null handling
+ assertFalse(range1.containsRange(null));
+
+ // easy inside range
+ assertTrue(range1.containsRange(Range.of(12, 18)));
+ assertTrue(range1.containsRange(of(12, 18)));
+
+ // outside range on each side
+ assertFalse(range1.containsRange(Range.of(32, 45)));
+ assertFalse(range1.containsRange(of(32, 45)));
+ assertFalse(range1.containsRange(Range.of(2, 8)));
+ assertFalse(range1.containsRange(of(2, 8)));
+
+ // equals range
+ assertTrue(range1.containsRange(Range.of(10, 20)));
+ assertTrue(range1.containsRange(of(10, 20)));
+
+ // overlaps
+ assertFalse(range1.containsRange(Range.of(9, 14)));
+ assertFalse(range1.containsRange(of(9, 14)));
+ assertFalse(range1.containsRange(Range.of(16, 21)));
+ assertFalse(range1.containsRange(of(16, 21)));
+
+ // touches lower boundary
+ assertTrue(range1.containsRange(Range.of(10, 19)));
+ assertTrue(range1.containsRange(of(10, 19)));
+ assertFalse(range1.containsRange(Range.of(10, 21)));
+ assertFalse(range1.containsRange(of(10, 21)));
+
+ // touches upper boundary
+ assertTrue(range1.containsRange(Range.of(11, 20)));
+ assertTrue(range1.containsRange(of(11, 20)));
+ assertFalse(range1.containsRange(Range.of(9, 20)));
+ assertFalse(range1.containsRange(of(9, 20)));
+
+ // negative
+ assertFalse(range1.containsRange(Range.of(-11, -18)));
+ assertFalse(range1.containsRange(of(-11, -18)));
+ }
+
+ @Test
+ public void testElementCompareTo() {
+ assertThrows(NullPointerException.class, () -> range1.elementCompareTo(null));
+
+ assertEquals(-1, range1.elementCompareTo(5));
+ assertEquals(0, range1.elementCompareTo(10));
+ assertEquals(0, range1.elementCompareTo(15));
+ assertEquals(0, range1.elementCompareTo(20));
+ assertEquals(1, range1.elementCompareTo(25));
+ }
+
+ @Test
+ public void testEqualsObject() {
+ assertEquals(range1, range1);
+ assertEquals(range1, range2);
+ assertEquals(range2, range2);
+ assertEquals(range1, range1);
+ assertEquals(range2, range2);
+ assertEquals(range3, range3);
+ assertNotEquals(range2, range3);
+ assertNotEquals(null, range2);
+ assertNotEquals("Ni!", range2);
+ }
+
+ @Test
+ public void testFit() {
+ assertEquals(range1.getMinimum(), range1.fit(Integer.MIN_VALUE));
+ assertEquals(range1.getMinimum(), range1.fit(range1.getMinimum()));
+ assertEquals(range1.getMaximum(), range1.fit(Integer.MAX_VALUE));
+ assertEquals(range1.getMaximum(), range1.fit(range1.getMaximum()));
+ assertEquals(15, range1.fit(15));
+ }
+
+ @Test
+ public void testFitNull() {
+ assertThrows(NullPointerException.class, () -> {
+ range1.fit(null);
+ });
+ }
+
+ @Test
+ public void testGetMaximum() {
+ assertEquals(20, (int) range1.getMaximum());
+ }
+
+ @Test
+ public void testGetMinimum() {
+ assertEquals(10, (int) range1.getMinimum());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(range1.hashCode(), range2.hashCode());
+ assertNotEquals(range1.hashCode(), range3.hashCode());
+
+ assertEquals(range1.hashCode(), range1.hashCode());
+ assertTrue(range1.hashCode() != 0);
+ }
+
+ @Test
+ public void testIntersectionWith() {
+ assertSame(range1, range1.intersectionWith(range1));
+
+ assertEquals(Range.of(10, 15), range1.intersectionWith(Range.of(5, 15)));
+ }
+
+ @Test
+ public void testIntersectionWithNonOverlapping() {
+ assertThrows(IllegalArgumentException.class, () -> range1.intersectionWith(Range.of(0, 9)));
+ }
+
+ @Test
+ public void testIntersectionWithNull() {
+ assertThrows(IllegalArgumentException.class, () -> range1.intersectionWith(null));
+ }
+
+ @Test
+ public void testIsAfter() {
+ assertFalse(range1.isAfter(null));
+
+ assertTrue(range1.isAfter(5));
+ assertFalse(range1.isAfter(10));
+ assertFalse(range1.isAfter(15));
+ assertFalse(range1.isAfter(20));
+ assertFalse(range1.isAfter(25));
+ }
+
+ @Test
+ public void testIsAfterRange() {
+ assertFalse(range1.isAfterRange(null));
+
+ assertTrue(range1.isAfterRange(Range.of(5, 9)));
+
+ assertFalse(range1.isAfterRange(Range.of(5, 10)));
+ assertFalse(range1.isAfterRange(Range.of(5, 20)));
+ assertFalse(range1.isAfterRange(Range.of(5, 25)));
+ assertFalse(range1.isAfterRange(Range.of(15, 25)));
+
+ assertFalse(range1.isAfterRange(Range.of(21, 25)));
+
+ assertFalse(range1.isAfterRange(Range.of(10, 20)));
+ }
+
+ @Test
+ public void testIsBefore() {
+ assertFalse(range1.isBefore(null));
+
+ assertFalse(range1.isBefore(5));
+ assertFalse(range1.isBefore(10));
+ assertFalse(range1.isBefore(15));
+ assertFalse(range1.isBefore(20));
+ assertTrue(range1.isBefore(25));
+ }
+
+ @Test
+ public void testIsBeforeIntegerRange() {
+ assertFalse(range1.isBeforeRange(null));
+
+ assertFalse(range1.isBeforeRange(of(5, 9)));
+
+ assertFalse(range1.isBeforeRange(of(5, 10)));
+ assertFalse(range1.isBeforeRange(of(5, 20)));
+ assertFalse(range1.isBeforeRange(of(5, 25)));
+ assertFalse(range1.isBeforeRange(of(15, 25)));
+
+ assertTrue(range1.isBeforeRange(of(21, 25)));
+
+ assertFalse(range1.isBeforeRange(of(10, 20)));
+ }
+
+ @Test
+ public void testIsBeforeRange() {
+ assertFalse(range1.isBeforeRange(null));
+
+ assertFalse(range1.isBeforeRange(Range.of(5, 9)));
+
+ assertFalse(range1.isBeforeRange(Range.of(5, 10)));
+ assertFalse(range1.isBeforeRange(Range.of(5, 20)));
+ assertFalse(range1.isBeforeRange(Range.of(5, 25)));
+ assertFalse(range1.isBeforeRange(Range.of(15, 25)));
+
+ assertTrue(range1.isBeforeRange(Range.of(21, 25)));
+
+ assertFalse(range1.isBeforeRange(Range.of(10, 20)));
+ }
+
+ @Test
+ public void testIsEndedBy() {
+ assertFalse(range1.isEndedBy(null));
+
+ assertFalse(range1.isEndedBy(5));
+ assertFalse(range1.isEndedBy(10));
+ assertFalse(range1.isEndedBy(15));
+ assertTrue(range1.isEndedBy(20));
+ assertFalse(range1.isEndedBy(25));
+ }
+
+ @Test
+ public void testIsOverlappedByIntegerRange() {
+
+ // null handling
+ assertFalse(range1.isOverlappedBy(null));
+
+ // easy inside range
+ assertTrue(range1.isOverlappedBy(of(12, 18)));
+
+ // outside range on each side
+ assertFalse(range1.isOverlappedBy(of(32, 45)));
+ assertFalse(range1.isOverlappedBy(of(2, 8)));
+
+ // equals range
+ assertTrue(range1.isOverlappedBy(of(10, 20)));
+
+ // overlaps
+ assertTrue(range1.isOverlappedBy(of(9, 14)));
+ assertTrue(range1.isOverlappedBy(of(16, 21)));
+
+ // touches lower boundary
+ assertTrue(range1.isOverlappedBy(of(10, 19)));
+ assertTrue(range1.isOverlappedBy(of(10, 21)));
+
+ // touches upper boundary
+ assertTrue(range1.isOverlappedBy(of(11, 20)));
+ assertTrue(range1.isOverlappedBy(of(9, 20)));
+
+ // negative
+ assertFalse(range1.isOverlappedBy(of(-11, -18)));
+
+ // outside range whole range
+ assertTrue(range1.isOverlappedBy(of(9, 21)));
+ }
+
+ @Test
+ public void testIsOverlappedByRange() {
+
+ // null handling
+ assertFalse(range1.isOverlappedBy(null));
+
+ // easy inside range
+ assertTrue(range1.isOverlappedBy(Range.of(12, 18)));
+
+ // outside range on each side
+ assertFalse(range1.isOverlappedBy(Range.of(32, 45)));
+ assertFalse(range1.isOverlappedBy(Range.of(2, 8)));
+
+ // equals range
+ assertTrue(range1.isOverlappedBy(Range.of(10, 20)));
+
+ // overlaps
+ assertTrue(range1.isOverlappedBy(Range.of(9, 14)));
+ assertTrue(range1.isOverlappedBy(Range.of(16, 21)));
+
+ // touches lower boundary
+ assertTrue(range1.isOverlappedBy(Range.of(10, 19)));
+ assertTrue(range1.isOverlappedBy(Range.of(10, 21)));
+
+ // touches upper boundary
+ assertTrue(range1.isOverlappedBy(Range.of(11, 20)));
+ assertTrue(range1.isOverlappedBy(Range.of(9, 20)));
+
+ // negative
+ assertFalse(range1.isOverlappedBy(Range.of(-11, -18)));
+
+ // outside range whole range
+ assertTrue(range1.isOverlappedBy(Range.of(9, 21)));
+ }
+
+ @Test
+ public void testIsStartedBy() {
+ assertFalse(range1.isStartedBy(null));
+
+ assertFalse(range1.isStartedBy(5));
+ assertTrue(range1.isStartedBy(10));
+ assertFalse(range1.isStartedBy(15));
+ assertFalse(range1.isStartedBy(20));
+ assertFalse(range1.isStartedBy(25));
+ }
+
+ @Test
+ public void testIsWithCompareRange() {
+ // all integers are equal
+ final Comparator<Integer> c = (o1, o2) -> 0;
+ Range<Integer> ri = Range.is(10);
+ assertFalse(ri.contains(null), "should not contain null");
+ assertTrue(ri.contains(10), "should contain 10");
+ assertFalse(ri.contains(11), "should not contain 11");
+ ri = Range.is(10, c);
+ assertFalse(ri.contains(null), "should not contain null");
+ assertTrue(ri.contains(10), "should contain 10");
+ assertTrue(ri.contains(11), "should contain 11");
+ }
+
+ @Test
+ public void testOfWithContains() {
+ // all integers are equal
+ final IntegerRange rb = of(-10, 20);
+ assertFalse(rb.contains(null), "should not contain null");
+ assertTrue(rb.contains(10), "should contain 10");
+ assertTrue(rb.contains(-10), "should contain -10");
+ assertFalse(rb.contains(21), "should not contain 21");
+ assertFalse(rb.contains(-11), "should not contain -11");
+
+ assertThrows(NullPointerException.class, () -> of(null, null));
+ }
+
+ @Test
+ public void testRangeOfChars() {
+ final IntegerRange chars = of('a', 'z');
+ assertTrue(chars.contains((int) 'b'));
+ assertFalse(chars.contains((int) 'B'));
+ }
+
+ @Test
+ public void testSerializing() {
+ SerializationUtils.clone(range1);
+ }
+
+ @Test
+ public void testToString() {
+ assertNotNull(range1.toString());
+
+ final String str = range1.toString();
+ assertEquals("[10..20]", str);
+ assertEquals("[-20..-10]", Range.of(-20, -10).toString());
+ }
+
+ @Test
+ public void testToStringFormat() {
+ final String str = range1.toString("From %1$s to %2$s");
+ assertEquals("From 10 to 20", str);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/JavaVersionTest.java b/src/test/java/org/apache/commons/lang3/JavaVersionTest.java
new file mode 100644
index 000000000..6523e6008
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/JavaVersionTest.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;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_0_9;
+import static org.apache.commons.lang3.JavaVersion.JAVA_10;
+import static org.apache.commons.lang3.JavaVersion.JAVA_11;
+import static org.apache.commons.lang3.JavaVersion.JAVA_12;
+import static org.apache.commons.lang3.JavaVersion.JAVA_13;
+import static org.apache.commons.lang3.JavaVersion.JAVA_14;
+import static org.apache.commons.lang3.JavaVersion.JAVA_15;
+import static org.apache.commons.lang3.JavaVersion.JAVA_16;
+import static org.apache.commons.lang3.JavaVersion.JAVA_17;
+import static org.apache.commons.lang3.JavaVersion.JAVA_18;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_1;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_2;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_3;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_4;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_5;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_6;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_7;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT;
+import static org.apache.commons.lang3.JavaVersion.get;
+import static org.apache.commons.lang3.JavaVersion.getJavaVersion;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.JavaVersion}.
+ */
+public class JavaVersionTest extends AbstractLangTest {
+
+ @Test
+ public void testGetJavaVersion() {
+ assertEquals(JAVA_0_9, get("0.9"), "0.9 failed");
+ assertEquals(JAVA_1_1, get("1.1"), "1.1 failed");
+ assertEquals(JAVA_1_2, get("1.2"), "1.2 failed");
+ assertEquals(JAVA_1_3, get("1.3"), "1.3 failed");
+ assertEquals(JAVA_1_4, get("1.4"), "1.4 failed");
+ assertEquals(JAVA_1_5, get("1.5"), "1.5 failed");
+ assertEquals(JAVA_1_6, get("1.6"), "1.6 failed");
+ assertEquals(JAVA_1_7, get("1.7"), "1.7 failed");
+ assertEquals(JAVA_1_8, get("1.8"), "1.8 failed");
+ assertEquals(JAVA_9, get("9"), "9 failed");
+ assertEquals(JAVA_10, get("10"), "10 failed");
+ assertEquals(JAVA_11, get("11"), "11 failed");
+ assertEquals(JAVA_12, get("12"), "12 failed");
+ assertEquals(JAVA_13, get("13"), "13 failed");
+ assertEquals(JAVA_14, get("14"), "14 failed");
+ assertEquals(JAVA_15, get("15"), "15 failed");
+ assertEquals(JAVA_16, get("16"), "16 failed");
+ assertEquals(JAVA_17, get("17"), "17 failed");
+ assertEquals(JAVA_18, get("18"), "18 failed");
+ assertEquals(JAVA_RECENT, get("1.10"), "1.10 failed");
+ // assertNull("2.10 unexpectedly worked", get("2.10"));
+ assertEquals(get("1.5"), getJavaVersion("1.5"), "Wrapper method failed");
+ assertEquals(JAVA_RECENT, get("19"), "Unhandled"); // LANG-1384
+ }
+
+ @Test
+ public void testAtLeast() {
+ assertFalse(JAVA_1_2.atLeast(JAVA_1_5), "1.2 at least 1.5 passed");
+ assertTrue(JAVA_1_5.atLeast(JAVA_1_2), "1.5 at least 1.2 failed");
+ assertFalse(JAVA_1_6.atLeast(JAVA_1_7), "1.6 at least 1.7 passed");
+
+ assertTrue(JAVA_0_9.atLeast(JAVA_1_5), "0.9 at least 1.5 failed");
+ assertFalse(JAVA_0_9.atLeast(JAVA_1_6), "0.9 at least 1.6 passed");
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("1.2", JAVA_1_2.toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java
new file mode 100644
index 000000000..6d5e32dd2
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java
@@ -0,0 +1,555 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.apache.commons.lang3.JavaVersion.JAVA_1_4;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Unit tests for {@link LocaleUtils}.
+ */
+public class LocaleUtilsTest extends AbstractLangTest {
+
+ private static final Locale LOCALE_EN = new Locale("en", "");
+ private static final Locale LOCALE_EN_US = new Locale("en", "US");
+ private static final Locale LOCALE_EN_US_ZZZZ = new Locale("en", "US", "ZZZZ");
+ private static final Locale LOCALE_FR = new Locale("fr", "");
+ private static final Locale LOCALE_FR_CA = new Locale("fr", "CA");
+ private static final Locale LOCALE_QQ = new Locale("qq", "");
+ private static final Locale LOCALE_QQ_ZZ = new Locale("qq", "ZZ");
+
+ @BeforeEach
+ public void setUp() {
+ // Testing #LANG-304. Must be called before availableLocaleSet is called.
+ LocaleUtils.isAvailableLocale(Locale.getDefault());
+ }
+
+ /**
+ * Test that constructors are public, and work, etc.
+ */
+ @Test
+ public void testConstructor() {
+ assertNotNull(new LocaleUtils());
+ final Constructor<?>[] cons = LocaleUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(LocaleUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(LocaleUtils.class.getModifiers()));
+ }
+
+ /**
+ * Pass in a valid language, test toLocale.
+ *
+ * @param language the language string
+ */
+ private static void assertValidToLocale(final String language) {
+ final Locale locale = LocaleUtils.toLocale(language);
+ assertNotNull(locale, "valid locale");
+ assertEquals(language, locale.getLanguage());
+ //country and variant are empty
+ assertTrue(StringUtils.isEmpty(locale.getCountry()));
+ assertTrue(StringUtils.isEmpty(locale.getVariant()));
+ }
+
+ /**
+ * Pass in a valid language, test toLocale.
+ *
+ * @param localeString to pass to toLocale()
+ * @param language of the resulting Locale
+ * @param country of the resulting Locale
+ */
+ private static void assertValidToLocale(final String localeString, final String language, final String country) {
+ final Locale locale = LocaleUtils.toLocale(localeString);
+ assertNotNull(locale, "valid locale");
+ assertEquals(language, locale.getLanguage());
+ assertEquals(country, locale.getCountry());
+ //variant is empty
+ assertTrue(StringUtils.isEmpty(locale.getVariant()));
+ }
+
+ /**
+ * Pass in a valid language, test toLocale.
+ *
+ * @param localeString to pass to toLocale()
+ * @param language of the resulting Locale
+ * @param country of the resulting Locale
+ * @param variant of the resulting Locale
+ */
+ private static void assertValidToLocale(
+ final String localeString, final String language,
+ final String country, final String variant) {
+ final Locale locale = LocaleUtils.toLocale(localeString);
+ assertNotNull(locale, "valid locale");
+ assertEquals(language, locale.getLanguage());
+ assertEquals(country, locale.getCountry());
+ assertEquals(variant, locale.getVariant());
+ }
+
+ /**
+ * Test toLocale(Locale) method.
+ */
+ @Test
+ public void testToLocale_Locale_defaults() {
+ assertNull(LocaleUtils.toLocale((String) null));
+ assertEquals(Locale.getDefault(), LocaleUtils.toLocale((Locale) null));
+ assertEquals(Locale.getDefault(), LocaleUtils.toLocale(Locale.getDefault()));
+ }
+
+ /**
+ * Test toLocale(Locale) method.
+ */
+ @ParameterizedTest
+ @MethodSource("java.util.Locale#getAvailableLocales")
+ public void testToLocales(final Locale actualLocale) {
+ assertEquals(actualLocale, LocaleUtils.toLocale(actualLocale));
+ }
+
+ /**
+ * Test toLocale(String) method.
+ */
+ @Test
+ public void testToLocale_1Part() {
+ assertNull(LocaleUtils.toLocale((String) null));
+
+ assertValidToLocale("us");
+ assertValidToLocale("fr");
+ assertValidToLocale("de");
+ assertValidToLocale("zh");
+ // Valid format but lang doesn't exist, should make instance anyway
+ assertValidToLocale("qq");
+ // LANG-941: JDK 8 introduced the empty locale as one of the default locales
+ assertValidToLocale("");
+
+ assertThrows(IllegalArgumentException.class, () -> LocaleUtils.toLocale("Us"), "Should fail if not lowercase");
+ assertThrows(IllegalArgumentException.class, () -> LocaleUtils.toLocale("uS"), "Should fail if not lowercase");
+ assertThrows(IllegalArgumentException.class, () -> LocaleUtils.toLocale("u#"), "Should fail if not lowercase");
+ assertThrows(
+ IllegalArgumentException.class, () -> LocaleUtils.toLocale("u"), "Must be 2 chars if less than 5");
+ assertThrows(
+ IllegalArgumentException.class, () -> LocaleUtils.toLocale("uu_U"), "Must be 2 chars if less than 5");
+ }
+
+ /**
+ * Test toLocale() method.
+ */
+ @Test
+ public void testToLocale_2Part() {
+ assertValidToLocale("us_EN", "us", "EN");
+ assertValidToLocale("us-EN", "us", "EN");
+ //valid though doesn't exist
+ assertValidToLocale("us_ZH", "us", "ZH");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("us_En"),
+ "Should fail second part not uppercase");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("us_en"),
+ "Should fail second part not uppercase");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("us_eN"),
+ "Should fail second part not uppercase");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("uS_EN"),
+ "Should fail first part not lowercase");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("us_E3"),
+ "Should fail second part not uppercase");
+ }
+
+ /**
+ * Test toLocale() method.
+ */
+ @Test
+ public void testToLocale_3Part() {
+ assertValidToLocale("us_EN_A", "us", "EN", "A");
+ assertValidToLocale("us-EN-A", "us", "EN", "A");
+ // this isn't pretty, but was caused by a jdk bug it seems
+ // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4210525
+ if (SystemUtils.isJavaVersionAtLeast(JAVA_1_4)) {
+ assertValidToLocale("us_EN_a", "us", "EN", "a");
+ assertValidToLocale("us_EN_SFsafdFDsdfF", "us", "EN", "SFsafdFDsdfF");
+ } else {
+ assertValidToLocale("us_EN_a", "us", "EN", "A");
+ assertValidToLocale("us_EN_SFsafdFDsdfF", "us", "EN", "SFSAFDFDSDFF");
+ }
+
+ assertThrows(
+ IllegalArgumentException.class, () -> LocaleUtils.toLocale("us_EN-a"), "Should fail as no consistent delimiter");
+ assertThrows(
+ IllegalArgumentException.class, () -> LocaleUtils.toLocale("uu_UU_"), "Must be 3, 5 or 7+ in length");
+ }
+
+ /**
+ * Helper method for local lookups.
+ *
+ * @param locale the input locale
+ * @param defaultLocale the input default locale
+ * @param expected expected results
+ */
+ private static void assertLocaleLookupList(final Locale locale, final Locale defaultLocale, final Locale[] expected) {
+ final List<Locale> localeList = defaultLocale == null ?
+ LocaleUtils.localeLookupList(locale) :
+ LocaleUtils.localeLookupList(locale, defaultLocale);
+
+ assertEquals(expected.length, localeList.size());
+ assertEquals(Arrays.asList(expected), localeList);
+ assertUnmodifiableCollection(localeList);
+ }
+
+ /**
+ * Test localeLookupList() method.
+ */
+ @Test
+ public void testLocaleLookupList_Locale() {
+ assertLocaleLookupList(null, null, new Locale[0]);
+ assertLocaleLookupList(LOCALE_QQ, null, new Locale[]{LOCALE_QQ});
+ assertLocaleLookupList(LOCALE_EN, null, new Locale[]{LOCALE_EN});
+ assertLocaleLookupList(LOCALE_EN, null, new Locale[]{LOCALE_EN});
+ assertLocaleLookupList(LOCALE_EN_US, null,
+ new Locale[] {
+ LOCALE_EN_US,
+ LOCALE_EN});
+ assertLocaleLookupList(LOCALE_EN_US_ZZZZ, null,
+ new Locale[] {
+ LOCALE_EN_US_ZZZZ,
+ LOCALE_EN_US,
+ LOCALE_EN});
+ }
+
+ /**
+ * Test localeLookupList() method.
+ */
+ @Test
+ public void testLocaleLookupList_LocaleLocale() {
+ assertLocaleLookupList(LOCALE_QQ, LOCALE_QQ,
+ new Locale[]{LOCALE_QQ});
+ assertLocaleLookupList(LOCALE_EN, LOCALE_EN,
+ new Locale[]{LOCALE_EN});
+
+ assertLocaleLookupList(LOCALE_EN_US, LOCALE_EN_US,
+ new Locale[]{
+ LOCALE_EN_US,
+ LOCALE_EN});
+ assertLocaleLookupList(LOCALE_EN_US, LOCALE_QQ,
+ new Locale[] {
+ LOCALE_EN_US,
+ LOCALE_EN,
+ LOCALE_QQ});
+ assertLocaleLookupList(LOCALE_EN_US, LOCALE_QQ_ZZ,
+ new Locale[] {
+ LOCALE_EN_US,
+ LOCALE_EN,
+ LOCALE_QQ_ZZ});
+
+ assertLocaleLookupList(LOCALE_EN_US_ZZZZ, null,
+ new Locale[] {
+ LOCALE_EN_US_ZZZZ,
+ LOCALE_EN_US,
+ LOCALE_EN});
+ assertLocaleLookupList(LOCALE_EN_US_ZZZZ, LOCALE_EN_US_ZZZZ,
+ new Locale[] {
+ LOCALE_EN_US_ZZZZ,
+ LOCALE_EN_US,
+ LOCALE_EN});
+ assertLocaleLookupList(LOCALE_EN_US_ZZZZ, LOCALE_QQ,
+ new Locale[] {
+ LOCALE_EN_US_ZZZZ,
+ LOCALE_EN_US,
+ LOCALE_EN,
+ LOCALE_QQ});
+ assertLocaleLookupList(LOCALE_EN_US_ZZZZ, LOCALE_QQ_ZZ,
+ new Locale[] {
+ LOCALE_EN_US_ZZZZ,
+ LOCALE_EN_US,
+ LOCALE_EN,
+ LOCALE_QQ_ZZ});
+ assertLocaleLookupList(LOCALE_FR_CA, LOCALE_EN,
+ new Locale[] {
+ LOCALE_FR_CA,
+ LOCALE_FR,
+ LOCALE_EN});
+ }
+
+ /**
+ * Test availableLocaleList() method.
+ */
+ @Test
+ public void testAvailableLocaleList() {
+ final List<Locale> list = LocaleUtils.availableLocaleList();
+ final List<Locale> list2 = LocaleUtils.availableLocaleList();
+ assertNotNull(list);
+ assertSame(list, list2);
+ assertUnmodifiableCollection(list);
+
+ final Locale[] jdkLocaleArray = Locale.getAvailableLocales();
+ final List<Locale> jdkLocaleList = Arrays.asList(jdkLocaleArray);
+ assertEquals(jdkLocaleList, list);
+ }
+
+ /**
+ * Test availableLocaleSet() method.
+ */
+ @Test
+ public void testAvailableLocaleSet() {
+ final Set<Locale> set = LocaleUtils.availableLocaleSet();
+ final Set<Locale> set2 = LocaleUtils.availableLocaleSet();
+ assertNotNull(set);
+ assertSame(set, set2);
+ assertUnmodifiableCollection(set);
+
+ final Locale[] jdkLocaleArray = Locale.getAvailableLocales();
+ final List<Locale> jdkLocaleList = Arrays.asList(jdkLocaleArray);
+ final Set<Locale> jdkLocaleSet = new HashSet<>(jdkLocaleList);
+ assertEquals(jdkLocaleSet, set);
+ }
+
+ /**
+ * Test availableLocaleSet() method.
+ */
+ @SuppressWarnings("boxing") // JUnit4 does not support primitive equality testing apart from long
+ @Test
+ public void testIsAvailableLocale() {
+ final Set<Locale> set = LocaleUtils.availableLocaleSet();
+ assertEquals(set.contains(LOCALE_EN), LocaleUtils.isAvailableLocale(LOCALE_EN));
+ assertEquals(set.contains(LOCALE_EN_US), LocaleUtils.isAvailableLocale(LOCALE_EN_US));
+ assertEquals(set.contains(LOCALE_EN_US_ZZZZ), LocaleUtils.isAvailableLocale(LOCALE_EN_US_ZZZZ));
+ assertEquals(set.contains(LOCALE_FR), LocaleUtils.isAvailableLocale(LOCALE_FR));
+ assertEquals(set.contains(LOCALE_FR_CA), LocaleUtils.isAvailableLocale(LOCALE_FR_CA));
+ assertEquals(set.contains(LOCALE_QQ), LocaleUtils.isAvailableLocale(LOCALE_QQ));
+ assertEquals(set.contains(LOCALE_QQ_ZZ), LocaleUtils.isAvailableLocale(LOCALE_QQ_ZZ));
+ }
+
+ /**
+ * Test for 3-chars locale, further details at LANG-915
+ *
+ */
+ @Test
+ public void testThreeCharsLocale() {
+ for (final String str : Arrays.asList("udm", "tet")) {
+ final Locale locale = LocaleUtils.toLocale(str);
+ assertNotNull(locale);
+ assertEquals(str, locale.getLanguage());
+ assertTrue(StringUtils.isBlank(locale.getCountry()));
+ assertEquals(new Locale(str), locale);
+ }
+ }
+
+ /**
+ * Make sure the language by country is correct. It checks that
+ * the LocaleUtils.languagesByCountry(country) call contains the
+ * array of languages passed in. It may contain more due to JVM
+ * variations.
+ *
+ * @param country
+ * @param languages array of languages that should be returned
+ */
+ private static void assertLanguageByCountry(final String country, final String[] languages) {
+ final List<Locale> list = LocaleUtils.languagesByCountry(country);
+ final List<Locale> list2 = LocaleUtils.languagesByCountry(country);
+ assertNotNull(list);
+ assertSame(list, list2);
+ //search through languages
+ for (final String language : languages) {
+ final Iterator<Locale> iterator = list.iterator();
+ boolean found = false;
+ // see if it was returned by the set
+ while (iterator.hasNext()) {
+ final Locale locale = iterator.next();
+ // should have an en empty variant
+ assertTrue(StringUtils.isEmpty(locale.getVariant()));
+ assertEquals(country, locale.getCountry());
+ if (language.equals(locale.getLanguage())) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found, "Could not find language: " + language + " for country: " + country);
+ }
+ assertUnmodifiableCollection(list);
+ }
+
+ /**
+ * Test languagesByCountry() method.
+ */
+ @Test
+ public void testLanguagesByCountry() {
+ assertLanguageByCountry(null, new String[0]);
+ assertLanguageByCountry("GB", new String[]{"en"});
+ assertLanguageByCountry("ZZ", new String[0]);
+ assertLanguageByCountry("CH", new String[]{"fr", "de", "it"});
+ }
+
+ /**
+ * Make sure the country by language is correct. It checks that
+ * the LocaleUtils.countryByLanguage(language) call contains the
+ * array of countries passed in. It may contain more due to JVM
+ * variations.
+ *
+ *
+ * @param language
+ * @param countries array of countries that should be returned
+ */
+ private static void assertCountriesByLanguage(final String language, final String[] countries) {
+ final List<Locale> list = LocaleUtils.countriesByLanguage(language);
+ final List<Locale> list2 = LocaleUtils.countriesByLanguage(language);
+ assertNotNull(list);
+ assertSame(list, list2);
+ //search through languages
+ for (final String country : countries) {
+ final Iterator<Locale> iterator = list.iterator();
+ boolean found = false;
+ // see if it was returned by the set
+ while (iterator.hasNext()) {
+ final Locale locale = iterator.next();
+ // should have an en empty variant
+ assertTrue(StringUtils.isEmpty(locale.getVariant()));
+ assertEquals(language, locale.getLanguage());
+ if (country.equals(locale.getCountry())) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found, "Could not find language: " + country + " for country: " + language);
+ }
+ assertUnmodifiableCollection(list);
+ }
+
+ /**
+ * Test countriesByLanguage() method.
+ */
+ @Test
+ public void testCountriesByLanguage() {
+ assertCountriesByLanguage(null, new String[0]);
+ assertCountriesByLanguage("de", new String[]{"DE", "CH", "AT", "LU"});
+ assertCountriesByLanguage("zz", new String[0]);
+ assertCountriesByLanguage("it", new String[]{"IT", "CH"});
+ }
+
+ /**
+ * @param coll the collection to check
+ */
+ private static void assertUnmodifiableCollection(final Collection<?> coll) {
+ assertThrows(UnsupportedOperationException.class, () -> coll.add(null));
+ }
+
+ /**
+ * Tests #LANG-328 - only language+variant
+ */
+ @Test
+ public void testLang328() {
+ assertValidToLocale("fr__P", "fr", "", "P");
+ assertValidToLocale("fr__POSIX", "fr", "", "POSIX");
+ }
+
+ @Test
+ public void testLanguageAndUNM49Numeric3AreaCodeLang1312() {
+ assertValidToLocale("en_001", "en", "001");
+ assertValidToLocale("en_150", "en", "150");
+ assertValidToLocale("ar_001", "ar", "001");
+
+ // LANG-1312
+ assertValidToLocale("en_001_GB", "en", "001", "GB");
+ assertValidToLocale("en_150_US", "en", "150", "US");
+ }
+
+ /**
+ * Tests #LANG-865, strings starting with an underscore.
+ */
+ @Test
+ public void testLang865() {
+ assertValidToLocale("_GB", "", "GB", "");
+ assertValidToLocale("_GB_P", "", "GB", "P");
+ assertValidToLocale("_GB_POSIX", "", "GB", "POSIX");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("_G"),
+ "Must be at least 3 chars if starts with underscore");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("_Gb"),
+ "Must be uppercase if starts with underscore");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("_gB"),
+ "Must be uppercase if starts with underscore");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("_1B"),
+ "Must be letter if starts with underscore");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("_G1"),
+ "Must be letter if starts with underscore");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("_GB_"),
+ "Must be at least 5 chars if starts with underscore");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LocaleUtils.toLocale("_GBAP"),
+ "Must have underscore after the country if starts with underscore and is at least 5 chars");
+ }
+
+ @ParameterizedTest
+ @MethodSource("java.util.Locale#getAvailableLocales")
+ public void testParseAllLocales(final Locale actualLocale) {
+ // Check if it's possible to recreate the Locale using just the standard constructor
+ final Locale locale = new Locale(actualLocale.getLanguage(), actualLocale.getCountry(), actualLocale.getVariant());
+ if (actualLocale.equals(locale)) { // it is possible for LocaleUtils.toLocale to handle these Locales
+ final String str = actualLocale.toString();
+ // Look for the script/extension suffix
+ int suff = str.indexOf("_#");
+ if (suff == - 1) {
+ suff = str.indexOf("#");
+ }
+ String localeStr = str;
+ if (suff >= 0) { // we have a suffix
+ assertThrows(IllegalArgumentException.class, () -> LocaleUtils.toLocale(str));
+ // try without suffix
+ localeStr = str.substring(0, suff);
+ }
+ final Locale loc = LocaleUtils.toLocale(localeStr);
+ assertEquals(actualLocale, loc);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/LongRangeTest.java b/src/test/java/org/apache/commons/lang3/LongRangeTest.java
new file mode 100644
index 000000000..290ce8a00
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/LongRangeTest.java
@@ -0,0 +1,426 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Comparator;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link LongRange}.
+ */
+@SuppressWarnings("boxing")
+public class LongRangeTest extends AbstractLangTest {
+
+ private static LongRange of(final int min, final int max) {
+ return LongRange.of(min, max);
+ }
+
+ private static LongRange of(final Long min, final Long max) {
+ return LongRange.of(min, max);
+ }
+
+ private LongRange range1;
+
+ private LongRange range2;
+
+ private LongRange range3;
+
+ private LongRange rangeFull;
+
+ @BeforeEach
+ public void setUp() {
+ range1 = of(10, 20);
+ range2 = of(10, 20);
+ range3 = of(-2, -1);
+ rangeFull = of(Long.MIN_VALUE, Long.MAX_VALUE);
+ }
+
+ @Test
+ public void testContainsInt() {
+ assertFalse(range1.contains(null));
+
+ assertFalse(range1.contains(5L));
+ assertTrue(range1.contains(10L));
+ assertTrue(range1.contains(15L));
+ assertTrue(range1.contains(20L));
+ assertFalse(range1.contains(25L));
+ }
+
+ @Test
+ public void testContainsLong() {
+ assertFalse(range1.contains(null));
+
+ assertTrue(rangeFull.contains(Long.MAX_VALUE));
+ assertTrue(rangeFull.contains(Long.MIN_VALUE));
+ assertTrue(rangeFull.contains((long) Integer.MAX_VALUE + 1));
+ assertTrue(rangeFull.contains((long) Integer.MIN_VALUE - 1));
+ assertTrue(rangeFull.contains((long) Integer.MAX_VALUE));
+ assertTrue(rangeFull.contains((long) Integer.MIN_VALUE));
+
+ assertFalse(range1.contains(5L));
+ assertTrue(range1.contains(10L));
+ assertTrue(range1.contains(15L));
+ assertTrue(range1.contains(20L));
+ assertFalse(range1.contains(25L));
+ }
+
+ @Test
+ public void testContainsRange() {
+
+ // null handling
+ assertFalse(range1.containsRange(null));
+
+ // easy inside range
+ assertTrue(range1.containsRange(Range.of(12L, 18L)));
+ assertTrue(range1.containsRange(of(12, 18)));
+
+ // outside range on each side
+ assertFalse(range1.containsRange(Range.of(32L, 45L)));
+ assertFalse(range1.containsRange(of(32, 45)));
+ assertFalse(range1.containsRange(Range.of(2L, 8L)));
+ assertFalse(range1.containsRange(of(2, 8)));
+
+ // equals range
+ assertTrue(range1.containsRange(Range.of(10L, 20L)));
+ assertTrue(range1.containsRange(of(10, 20)));
+
+ // overlaps
+ assertFalse(range1.containsRange(Range.of(9L, 14L)));
+ assertFalse(range1.containsRange(of(9, 14)));
+ assertFalse(range1.containsRange(Range.of(16L, 21L)));
+ assertFalse(range1.containsRange(of(16, 21)));
+
+ // touches lower boundary
+ assertTrue(range1.containsRange(Range.of(10L, 19L)));
+ assertTrue(range1.containsRange(of(10, 19)));
+ assertFalse(range1.containsRange(Range.of(10L, 21L)));
+ assertFalse(range1.containsRange(of(10, 21)));
+
+ // touches upper boundary
+ assertTrue(range1.containsRange(Range.of(11L, 20L)));
+ assertTrue(range1.containsRange(of(11, 20)));
+ assertFalse(range1.containsRange(Range.of(9L, 20L)));
+ assertFalse(range1.containsRange(of(9, 20)));
+
+ // negative
+ assertFalse(range1.containsRange(Range.of(-11L, -18L)));
+ assertFalse(range1.containsRange(of(-11, -18)));
+ }
+
+ @Test
+ public void testElementCompareTo() {
+ assertThrows(NullPointerException.class, () -> range1.elementCompareTo(null));
+
+ assertEquals(-1, range1.elementCompareTo(5L));
+ assertEquals(0, range1.elementCompareTo(10L));
+ assertEquals(0, range1.elementCompareTo(15L));
+ assertEquals(0, range1.elementCompareTo(20L));
+ assertEquals(1, range1.elementCompareTo(25L));
+ }
+
+ @Test
+ public void testEqualsObject() {
+ assertEquals(range1, range1);
+ assertEquals(range1, range2);
+ assertEquals(range2, range2);
+ assertEquals(range1, range1);
+ assertEquals(range2, range2);
+ assertEquals(range3, range3);
+ assertNotEquals(range2, range3);
+ assertNotEquals(null, range2);
+ assertNotEquals("Ni!", range2);
+ }
+
+ @Test
+ public void testFit() {
+ assertEquals(range1.getMinimum(), range1.fit((long) Integer.MIN_VALUE));
+ assertEquals(range1.getMinimum(), range1.fit(range1.getMinimum()));
+ assertEquals(range1.getMaximum(), range1.fit((long) Integer.MAX_VALUE));
+ assertEquals(range1.getMaximum(), range1.fit(range1.getMaximum()));
+ assertEquals(15, range1.fit(15L));
+ }
+
+ @Test
+ public void testFitNull() {
+ assertThrows(NullPointerException.class, () -> {
+ range1.fit(null);
+ });
+ }
+
+ @Test
+ public void testGetMaximum() {
+ assertEquals(20, range1.getMaximum());
+ }
+
+ @Test
+ public void testGetMinimum() {
+ assertEquals(10, range1.getMinimum());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(range1.hashCode(), range2.hashCode());
+ assertNotEquals(range1.hashCode(), range3.hashCode());
+
+ assertEquals(range1.hashCode(), range1.hashCode());
+ assertTrue(range1.hashCode() != 0);
+ }
+
+ @Test
+ public void testIntersectionWith() {
+ assertSame(range1, range1.intersectionWith(range1));
+
+ assertEquals(Range.of(10L, 15L), range1.intersectionWith(Range.of(5L, 15L)));
+ }
+
+ @Test
+ public void testIntersectionWithNonOverlapping() {
+ assertThrows(IllegalArgumentException.class, () -> range1.intersectionWith(Range.of(0L, 9L)));
+ }
+
+ @Test
+ public void testIntersectionWithNull() {
+ assertThrows(IllegalArgumentException.class, () -> range1.intersectionWith(null));
+ }
+
+ @Test
+ public void testIsAfter() {
+ assertFalse(range1.isAfter(null));
+
+ assertTrue(range1.isAfter(5L));
+ assertFalse(range1.isAfter(10L));
+ assertFalse(range1.isAfter(15L));
+ assertFalse(range1.isAfter(20L));
+ assertFalse(range1.isAfter(25L));
+ }
+
+ @Test
+ public void testIsAfterRange() {
+ assertFalse(range1.isAfterRange(null));
+
+ assertTrue(range1.isAfterRange(Range.of(5L, 9L)));
+
+ assertFalse(range1.isAfterRange(Range.of(5L, 10L)));
+ assertFalse(range1.isAfterRange(Range.of(5L, 20L)));
+ assertFalse(range1.isAfterRange(Range.of(5L, 25L)));
+ assertFalse(range1.isAfterRange(Range.of(15L, 25L)));
+
+ assertFalse(range1.isAfterRange(Range.of(21L, 25L)));
+
+ assertFalse(range1.isAfterRange(Range.of(10L, 20L)));
+ }
+
+ @Test
+ public void testIsBefore() {
+ assertFalse(range1.isBefore(null));
+
+ assertFalse(range1.isBefore(5L));
+ assertFalse(range1.isBefore(10L));
+ assertFalse(range1.isBefore(15L));
+ assertFalse(range1.isBefore(20L));
+ assertTrue(range1.isBefore(25L));
+ }
+
+ @Test
+ public void testIsBeforeIntegerRange() {
+ assertFalse(range1.isBeforeRange(null));
+
+ assertFalse(range1.isBeforeRange(of(5, 9)));
+
+ assertFalse(range1.isBeforeRange(of(5, 10)));
+ assertFalse(range1.isBeforeRange(of(5, 20)));
+ assertFalse(range1.isBeforeRange(of(5, 25)));
+ assertFalse(range1.isBeforeRange(of(15, 25)));
+
+ assertTrue(range1.isBeforeRange(of(21, 25)));
+
+ assertFalse(range1.isBeforeRange(of(10, 20)));
+ }
+
+ @Test
+ public void testIsBeforeRange() {
+ assertFalse(range1.isBeforeRange(null));
+
+ assertFalse(range1.isBeforeRange(Range.of(5L, 9L)));
+
+ assertFalse(range1.isBeforeRange(Range.of(5L, 10L)));
+ assertFalse(range1.isBeforeRange(Range.of(5L, 20L)));
+ assertFalse(range1.isBeforeRange(Range.of(5L, 25L)));
+ assertFalse(range1.isBeforeRange(Range.of(15L, 25L)));
+
+ assertTrue(range1.isBeforeRange(Range.of(21L, 25L)));
+
+ assertFalse(range1.isBeforeRange(Range.of(10L, 20L)));
+ }
+
+ @Test
+ public void testIsEndedBy() {
+ assertFalse(range1.isEndedBy(null));
+
+ assertFalse(range1.isEndedBy(5L));
+ assertFalse(range1.isEndedBy(10L));
+ assertFalse(range1.isEndedBy(15L));
+ assertTrue(range1.isEndedBy(20L));
+ assertFalse(range1.isEndedBy(25L));
+ }
+
+ @Test
+ public void testIsOverlappedByIntegerRange() {
+
+ // null handling
+ assertFalse(range1.isOverlappedBy(null));
+
+ // easy inside range
+ assertTrue(range1.isOverlappedBy(of(12, 18)));
+
+ // outside range on each side
+ assertFalse(range1.isOverlappedBy(of(32, 45)));
+ assertFalse(range1.isOverlappedBy(of(2, 8)));
+
+ // equals range
+ assertTrue(range1.isOverlappedBy(of(10, 20)));
+
+ // overlaps
+ assertTrue(range1.isOverlappedBy(of(9, 14)));
+ assertTrue(range1.isOverlappedBy(of(16, 21)));
+
+ // touches lower boundary
+ assertTrue(range1.isOverlappedBy(of(10, 19)));
+ assertTrue(range1.isOverlappedBy(of(10, 21)));
+
+ // touches upper boundary
+ assertTrue(range1.isOverlappedBy(of(11, 20)));
+ assertTrue(range1.isOverlappedBy(of(9, 20)));
+
+ // negative
+ assertFalse(range1.isOverlappedBy(of(-11, -18)));
+
+ // outside range whole range
+ assertTrue(range1.isOverlappedBy(of(9, 21)));
+ }
+
+ @Test
+ public void testIsOverlappedByRange() {
+
+ // null handling
+ assertFalse(range1.isOverlappedBy(null));
+
+ // easy inside range
+ assertTrue(range1.isOverlappedBy(Range.of(12L, 18L)));
+
+ // outside range on each side
+ assertFalse(range1.isOverlappedBy(Range.of(32L, 45L)));
+ assertFalse(range1.isOverlappedBy(Range.of(2L, 8L)));
+
+ // equals range
+ assertTrue(range1.isOverlappedBy(Range.of(10L, 20L)));
+
+ // overlaps
+ assertTrue(range1.isOverlappedBy(Range.of(9L, 14L)));
+ assertTrue(range1.isOverlappedBy(Range.of(16L, 21L)));
+
+ // touches lower boundary
+ assertTrue(range1.isOverlappedBy(Range.of(10L, 19L)));
+ assertTrue(range1.isOverlappedBy(Range.of(10L, 21L)));
+
+ // touches upper boundary
+ assertTrue(range1.isOverlappedBy(Range.of(11L, 20L)));
+ assertTrue(range1.isOverlappedBy(Range.of(9L, 20L)));
+
+ // negative
+ assertFalse(range1.isOverlappedBy(Range.of(-11L, -18L)));
+
+ // outside range whole range
+ assertTrue(range1.isOverlappedBy(Range.of(9L, 21L)));
+ }
+
+ @Test
+ public void testIsStartedBy() {
+ assertFalse(range1.isStartedBy(null));
+
+ assertFalse(range1.isStartedBy(5L));
+ assertTrue(range1.isStartedBy(10L));
+ assertFalse(range1.isStartedBy(15L));
+ assertFalse(range1.isStartedBy(20L));
+ assertFalse(range1.isStartedBy(25L));
+ }
+
+ @Test
+ public void testIsWithCompareRange() {
+ // all integers are equal
+ final Comparator<Integer> c = (o1, o2) -> 0;
+ Range<Integer> ri = Range.is(10);
+ assertFalse(ri.contains(null), "should not contain null");
+ assertTrue(ri.contains(10), "should contain 10");
+ assertFalse(ri.contains(11), "should not contain 11");
+ ri = Range.is(10, c);
+ assertFalse(ri.contains(null), "should not contain null");
+ assertTrue(ri.contains(10), "should contain 10");
+ assertTrue(ri.contains(11), "should contain 11");
+ }
+
+ @Test
+ public void testOfWithContains() {
+ // all integers are equal
+ final LongRange rb = of(-10, 20);
+ assertFalse(rb.contains(null), "should not contain null");
+ assertTrue(rb.contains(10L), "should contain 10");
+ assertTrue(rb.contains(-10L), "should contain -10");
+ assertFalse(rb.contains(21L), "should not contain 21");
+ assertFalse(rb.contains(-11L), "should not contain -11");
+
+ assertThrows(NullPointerException.class, () -> of(null, null));
+ }
+
+ @Test
+ public void testRangeOfChars() {
+ final LongRange chars = of((long) 'a', (long) 'z');
+ assertTrue(chars.contains((long) 'b'));
+ assertFalse(chars.contains((long) 'B'));
+ }
+
+ @Test
+ public void testSerializing() {
+ SerializationUtils.clone(range1);
+ }
+
+ @Test
+ public void testToString() {
+ assertNotNull(range1.toString());
+
+ final String str = range1.toString();
+ assertEquals("[10..20]", str);
+ assertEquals("[-20..-10]", Range.of(-20, -10).toString());
+ }
+
+ @Test
+ public void testToStringFormat() {
+ final String str = range1.toString("From %1$s to %2$s");
+ assertEquals("From 10 to 20", str);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/NotImplementedExceptionTest.java b/src/test/java/org/apache/commons/lang3/NotImplementedExceptionTest.java
new file mode 100644
index 000000000..25852a3d5
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/NotImplementedExceptionTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.NotImplementedException}.
+ */
+public class NotImplementedExceptionTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructors() {
+ final Throwable nested = new RuntimeException();
+ final String message = "Not Implemented";
+ final String code = "CODE";
+
+ NotImplementedException nie = new NotImplementedException(message);
+ assertCorrect("Issue in (String)", nie, message, null, null);
+ nie = new NotImplementedException(nested);
+ assertCorrect("Issue in (Throwable)", nie, nested.toString(), nested, null);
+ nie = new NotImplementedException(message, nested);
+ assertCorrect("Issue in (String, Throwable)", nie, message, nested, null);
+ nie = new NotImplementedException(message, code);
+ assertCorrect("Issue in (String, String)", nie, message, null, code);
+ nie = new NotImplementedException(nested, code);
+ assertCorrect("Issue in (Throwable, String)", nie, nested.toString(), nested, code);
+ nie = new NotImplementedException(message, nested, code);
+ assertCorrect("Issue in (String, Throwable, String)", nie, message, nested, code);
+
+ assertNull(new NotImplementedException().getCode());
+ }
+
+ private void assertCorrect(final String assertMessage, final NotImplementedException nie, final String message, final Throwable nested, final String code) {
+ assertNotNull(nie, assertMessage + ": target is null");
+ assertEquals(message, nie.getMessage(), assertMessage + ": Message not equal");
+ assertEquals(nested, nie.getCause(), assertMessage + ": Nested throwable not equal");
+ assertEquals(code, nie.getCode(), assertMessage + ": Code not equal");
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java b/src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java
new file mode 100644
index 000000000..6574f67eb
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java
@@ -0,0 +1,860 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.exception.CloneFailedException;
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.commons.lang3.text.StrBuilder;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.ObjectUtils}.
+ */
+@SuppressWarnings("deprecation") // deliberate use of deprecated code
+public class ObjectUtilsTest extends AbstractLangTest {
+ static final class CharSequenceComparator implements Comparator<CharSequence> {
+
+ @Override
+ public int compare(final CharSequence o1, final CharSequence o2) {
+ return o1.toString().compareTo(o2.toString());
+ }
+
+ }
+
+ /**
+ * String that is cloneable.
+ */
+ static final class CloneableString extends MutableObject<String> implements Cloneable {
+ private static final long serialVersionUID = 1L;
+ CloneableString(final String s) {
+ super(s);
+ }
+
+ @Override
+ public CloneableString clone() throws CloneNotSupportedException {
+ return (CloneableString) super.clone();
+ }
+ }
+
+ static final class NonComparableCharSequence implements CharSequence {
+ final String value;
+
+ /**
+ * Create a new NonComparableCharSequence instance.
+ *
+ * @param value the CharSequence value
+ */
+ NonComparableCharSequence(final String value) {
+ Validate.notNull(value);
+ this.value = value;
+ }
+
+ @Override
+ public char charAt(final int arg0) {
+ return value.charAt(arg0);
+ }
+
+ @Override
+ public int length() {
+ return value.length();
+ }
+
+ @Override
+ public CharSequence subSequence(final int arg0, final int arg1) {
+ return value.subSequence(arg0, arg1);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
+ /**
+ * String that is not cloneable.
+ */
+ static final class UncloneableString extends MutableObject<String> implements Cloneable {
+ private static final long serialVersionUID = 1L;
+ UncloneableString(final String s) {
+ super(s);
+ }
+ }
+
+ private static final String FOO = "foo";
+ private static final String BAR = "bar";
+ private static final String[] NON_EMPTY_ARRAY = { FOO, BAR, };
+
+ private static final List<String> NON_EMPTY_LIST = Arrays.asList(NON_EMPTY_ARRAY);
+
+ private static final Set<String> NON_EMPTY_SET = new HashSet<>(NON_EMPTY_LIST);
+
+ private static final Map<String, String> NON_EMPTY_MAP = new HashMap<>();
+
+ static {
+ NON_EMPTY_MAP.put(FOO, BAR);
+ }
+
+ /**
+ * Tests {@link ObjectUtils#allNotNull(Object...)}.
+ */
+ @Test
+ public void testAllNotNull() {
+ assertFalse(ObjectUtils.allNotNull((Object) null));
+ assertFalse(ObjectUtils.allNotNull((Object[]) null));
+ assertFalse(ObjectUtils.allNotNull(null, null, null));
+ assertFalse(ObjectUtils.allNotNull(null, FOO, BAR));
+ assertFalse(ObjectUtils.allNotNull(FOO, BAR, null));
+ assertFalse(ObjectUtils.allNotNull(FOO, BAR, null, FOO, BAR));
+
+ assertTrue(ObjectUtils.allNotNull());
+ assertTrue(ObjectUtils.allNotNull(FOO));
+ assertTrue(ObjectUtils.allNotNull(FOO, BAR, 1, Boolean.TRUE, new Object(), new Object[]{}));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#allNull(Object...)}.
+ */
+ @Test
+ public void testAllNull() {
+ assertTrue(ObjectUtils.allNull());
+ assertTrue(ObjectUtils.allNull((Object) null));
+ assertTrue(ObjectUtils.allNull((Object[]) null));
+ assertTrue(ObjectUtils.allNull(null, null, null));
+
+ assertFalse(ObjectUtils.allNull(FOO));
+ assertFalse(ObjectUtils.allNull(null, FOO, null));
+ assertFalse(ObjectUtils.allNull(null, null, null, null, FOO, BAR));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#anyNotNull(Object...)}.
+ */
+ @Test
+ public void testAnyNotNull() {
+ assertFalse(ObjectUtils.anyNotNull());
+ assertFalse(ObjectUtils.anyNotNull((Object) null));
+ assertFalse(ObjectUtils.anyNotNull((Object[]) null));
+ assertFalse(ObjectUtils.anyNotNull(null, null, null));
+
+ assertTrue(ObjectUtils.anyNotNull(FOO));
+ assertTrue(ObjectUtils.anyNotNull(null, FOO, null));
+ assertTrue(ObjectUtils.anyNotNull(null, null, null, null, FOO, BAR));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#anyNull(Object...)}.
+ */
+ @Test
+ public void testAnyNull() {
+ assertTrue(ObjectUtils.anyNull((Object) null));
+ assertTrue(ObjectUtils.anyNull(null, null, null));
+ assertTrue(ObjectUtils.anyNull(null, FOO, BAR));
+ assertTrue(ObjectUtils.anyNull(FOO, BAR, null));
+ assertTrue(ObjectUtils.anyNull(FOO, BAR, null, FOO, BAR));
+
+ assertFalse(ObjectUtils.anyNull());
+ assertFalse(ObjectUtils.anyNull(FOO));
+ assertFalse(ObjectUtils.anyNull(FOO, BAR, 1, Boolean.TRUE, new Object(), new Object[]{}));
+ }
+
+ /**
+ * Test for {@link ObjectUtils#isArray(Object)}.
+ */
+ @Test
+ public void testArray() {
+ assertFalse(ObjectUtils.isArray(null));
+ assertFalse(ObjectUtils.isArray(""));
+ assertFalse(ObjectUtils.isArray("abg"));
+ assertFalse(ObjectUtils.isArray(123));
+ assertTrue(ObjectUtils.isArray(NON_EMPTY_ARRAY));
+ assertTrue(ObjectUtils.isArray(new int[]{1, 2, 3}));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_BOOLEAN_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_BOOLEAN_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_BYTE_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_BYTE_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_CHAR_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_CLASS_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_DOUBLE_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_DOUBLE_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_FIELD_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_FLOAT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_FLOAT_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_INT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_INTEGER_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_LONG_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_LONG_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_METHOD_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_SHORT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_SHORT_OBJECT_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_STRING_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_THROWABLE_ARRAY));
+ assertTrue(ObjectUtils.isArray(ArrayUtils.EMPTY_TYPE_ARRAY));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#clone(Object)} with a cloneable object.
+ */
+ @Test
+ public void testCloneOfCloneable() {
+ final CloneableString string = new CloneableString("apache");
+ final CloneableString stringClone = ObjectUtils.clone(string);
+ assertEquals("apache", stringClone.getValue());
+ }
+
+ /**
+ * Tests {@link ObjectUtils#clone(Object)} with a not cloneable object.
+ */
+ @Test
+ public void testCloneOfNotCloneable() {
+ final String string = "apache";
+ assertNull(ObjectUtils.clone(string));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#clone(Object)} with an array of primitives.
+ */
+ @Test
+ public void testCloneOfPrimitiveArray() {
+ assertArrayEquals(new int[]{1}, ObjectUtils.clone(new int[]{1}));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#clone(Object)} with an object array.
+ */
+ @Test
+ public void testCloneOfStringArray() {
+ assertTrue(Arrays.deepEquals(
+ new String[]{"string"}, ObjectUtils.clone(new String[]{"string"})));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#clone(Object)} with an uncloneable object.
+ */
+ @Test
+ public void testCloneOfUncloneable() {
+ final UncloneableString string = new UncloneableString("apache");
+ final CloneFailedException e = assertThrows(CloneFailedException.class, () -> ObjectUtils.clone(string));
+ assertEquals(NoSuchMethodException.class, e.getCause().getClass());
+ }
+
+ @Test
+ public void testComparatorMedian() {
+ final CharSequenceComparator cmp = new CharSequenceComparator();
+ final NonComparableCharSequence foo = new NonComparableCharSequence("foo");
+ final NonComparableCharSequence bar = new NonComparableCharSequence("bar");
+ final NonComparableCharSequence baz = new NonComparableCharSequence("baz");
+ final NonComparableCharSequence blah = new NonComparableCharSequence("blah");
+ final NonComparableCharSequence wah = new NonComparableCharSequence("wah");
+ assertSame(foo, ObjectUtils.median(cmp, foo));
+ assertSame(bar, ObjectUtils.median(cmp, foo, bar));
+ assertSame(baz, ObjectUtils.median(cmp, foo, bar, baz));
+ assertSame(baz, ObjectUtils.median(cmp, foo, bar, baz, blah));
+ assertSame(blah, ObjectUtils.median(cmp, foo, bar, baz, blah, wah));
+ }
+
+ @Test
+ public void testComparatorMedian_emptyItems() {
+ assertThrows(IllegalArgumentException.class, () -> ObjectUtils.median(new CharSequenceComparator()));
+ }
+
+ @Test
+ public void testComparatorMedian_nullComparator() {
+ assertThrows(NullPointerException.class,
+ () -> ObjectUtils.median((Comparator<CharSequence>) null, new NonComparableCharSequence("foo")));
+ }
+
+ @Test
+ public void testComparatorMedian_nullItems() {
+ assertThrows(NullPointerException.class,
+ () -> ObjectUtils.median(new CharSequenceComparator(), (CharSequence[]) null));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#compare(Comparable, Comparable, boolean)}.
+ */
+ @Test
+ public void testCompare() {
+ final Integer one = Integer.valueOf(1);
+ final Integer two = Integer.valueOf(2);
+ final Integer nullValue = null;
+
+ assertEquals(0, ObjectUtils.compare(nullValue, nullValue), "Null Null false");
+ assertEquals(0, ObjectUtils.compare(nullValue, nullValue, true), "Null Null true");
+
+ assertEquals(-1, ObjectUtils.compare(nullValue, one), "Null one false");
+ assertEquals(1, ObjectUtils.compare(nullValue, one, true), "Null one true");
+
+ assertEquals(1, ObjectUtils.compare(one, nullValue), "one Null false");
+ assertEquals(-1, ObjectUtils.compare(one, nullValue, true), "one Null true");
+
+ assertEquals(-1, ObjectUtils.compare(one, two), "one two false");
+ assertEquals(-1, ObjectUtils.compare(one, two, true), "one two true");
+ }
+
+ @Test
+ public void testConstMethods() {
+
+ // To truly test the CONST() method, we'd want to look in the
+ // bytecode to see if the literals were folded into the
+ // class, or if the bytecode kept the method call.
+
+ assertTrue(ObjectUtils.CONST(true), "CONST(boolean)");
+ assertEquals((byte) 3, ObjectUtils.CONST((byte) 3), "CONST(byte)");
+ assertEquals((char) 3, ObjectUtils.CONST((char) 3), "CONST(char)");
+ assertEquals((short) 3, ObjectUtils.CONST((short) 3), "CONST(short)");
+ assertEquals(3, ObjectUtils.CONST(3), "CONST(int)");
+ assertEquals(3L, ObjectUtils.CONST(3L), "CONST(long)");
+ assertEquals(3f, ObjectUtils.CONST(3f), "CONST(float)");
+ assertEquals(3.0, ObjectUtils.CONST(3.0), "CONST(double)");
+ assertEquals("abc", ObjectUtils.CONST("abc"), "CONST(Object)");
+
+ // Make sure documentation examples from Javadoc all work
+ // (this fixed a lot of my bugs when I these!)
+ //
+ // My bugs should be in a software engineering textbook
+ // for "Can you screw this up?" The answer is, yes,
+ // you can even screw this up. (When you == Julius)
+ // .
+ final boolean MAGIC_FLAG = ObjectUtils.CONST(true);
+ final byte MAGIC_BYTE1 = ObjectUtils.CONST((byte) 127);
+ final byte MAGIC_BYTE2 = ObjectUtils.CONST_BYTE(127);
+ final char MAGIC_CHAR = ObjectUtils.CONST('a');
+ final short MAGIC_SHORT1 = ObjectUtils.CONST((short) 123);
+ final short MAGIC_SHORT2 = ObjectUtils.CONST_SHORT(127);
+ final int MAGIC_INT = ObjectUtils.CONST(123);
+ final long MAGIC_LONG1 = ObjectUtils.CONST(123L);
+ final long MAGIC_LONG2 = ObjectUtils.CONST(3);
+ final float MAGIC_FLOAT = ObjectUtils.CONST(1.0f);
+ final double MAGIC_DOUBLE = ObjectUtils.CONST(1.0);
+ final String MAGIC_STRING = ObjectUtils.CONST("abc");
+
+ assertTrue(MAGIC_FLAG);
+ assertEquals(127, MAGIC_BYTE1);
+ assertEquals(127, MAGIC_BYTE2);
+ assertEquals('a', MAGIC_CHAR);
+ assertEquals(123, MAGIC_SHORT1);
+ assertEquals(127, MAGIC_SHORT2);
+ assertEquals(123, MAGIC_INT);
+ assertEquals(123, MAGIC_LONG1);
+ assertEquals(3, MAGIC_LONG2);
+ assertEquals(1.0f, MAGIC_FLOAT);
+ assertEquals(1.0, MAGIC_DOUBLE);
+ assertEquals("abc", MAGIC_STRING);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> ObjectUtils.CONST_BYTE(-129),
+ "CONST_BYTE(-129): IllegalArgumentException should have been thrown.");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> ObjectUtils.CONST_BYTE(128),
+ "CONST_BYTE(128): IllegalArgumentException should have been thrown.");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> ObjectUtils.CONST_SHORT(-32769),
+ "CONST_SHORT(-32769): IllegalArgumentException should have been thrown.");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> ObjectUtils.CONST_BYTE(32768),
+ "CONST_SHORT(32768): IllegalArgumentException should have been thrown.");
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new ObjectUtils());
+ final Constructor<?>[] cons = ObjectUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(ObjectUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(ObjectUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testDefaultIfNull() {
+ final Object o = FOO;
+ final Object dflt = BAR;
+ assertSame(dflt, ObjectUtils.defaultIfNull(null, dflt), "dflt was not returned when o was null");
+ assertSame(o, ObjectUtils.defaultIfNull(o, dflt), "dflt was returned when o was not null");
+ assertSame(dflt, ObjectUtils.getIfNull(null, () -> dflt), "dflt was not returned when o was null");
+ assertSame(o, ObjectUtils.getIfNull(o, () -> dflt), "dflt was returned when o was not null");
+ assertSame(o, ObjectUtils.getIfNull(FOO, () -> dflt), "dflt was returned when o was not null");
+ assertSame(o, ObjectUtils.getIfNull("foo", () -> dflt), "dflt was returned when o was not null");
+ final MutableInt callsCounter = new MutableInt(0);
+ final Supplier<Object> countingDefaultSupplier = () -> {
+ callsCounter.increment();
+ return dflt;
+ };
+ ObjectUtils.getIfNull(o, countingDefaultSupplier);
+ assertEquals(0, callsCounter.getValue());
+ ObjectUtils.getIfNull(null, countingDefaultSupplier);
+ assertEquals(1, callsCounter.getValue());
+ }
+
+ @Test
+ public void testEquals() {
+ assertTrue(ObjectUtils.equals(null, null), "ObjectUtils.equals(null, null) returned false");
+ assertFalse(ObjectUtils.equals(FOO, null), "ObjectUtils.equals(\"foo\", null) returned true");
+ assertFalse(ObjectUtils.equals(null, BAR), "ObjectUtils.equals(null, \"bar\") returned true");
+ assertFalse(ObjectUtils.equals(FOO, BAR), "ObjectUtils.equals(\"foo\", \"bar\") returned true");
+ assertTrue(ObjectUtils.equals(FOO, FOO), "ObjectUtils.equals(\"foo\", \"foo\") returned false");
+ }
+
+ @Test
+ public void testFirstNonNull() {
+ assertEquals("", ObjectUtils.firstNonNull(null, ""));
+ final String firstNonNullGenerics = ObjectUtils.firstNonNull(null, null, "123", "456");
+ assertEquals("123", firstNonNullGenerics);
+ assertEquals("123", ObjectUtils.firstNonNull("123", null, "456", null));
+ assertSame(Boolean.TRUE, ObjectUtils.firstNonNull(Boolean.TRUE));
+
+ // Explicitly pass in an empty array of Object type to ensure compiler doesn't complain of unchecked generic array creation
+ assertNull(ObjectUtils.firstNonNull());
+
+ // Cast to Object in line below ensures compiler doesn't complain of unchecked generic array creation
+ assertNull(ObjectUtils.firstNonNull(null, null));
+
+ assertNull(ObjectUtils.firstNonNull((Object) null));
+ assertNull(ObjectUtils.firstNonNull((Object[]) null));
+ }
+
+ @Test
+ public void testGetClass() {
+ final String[] newArray = ArrayUtils.EMPTY_STRING_ARRAY;
+ // No type-cast required.
+ final Class<String[]> cls = ObjectUtils.getClass(newArray);
+ assertEquals(String[].class, cls);
+ assertNull(ObjectUtils.getClass(null));
+ }
+
+ @Test
+ public void testGetFirstNonNull() {
+ // first non-null
+ assertEquals("", ObjectUtils.getFirstNonNull(() -> null, () -> ""));
+ // first encountered value is used
+ assertEquals("1", ObjectUtils.getFirstNonNull(() -> null, () -> "1", () -> "2", () -> null));
+ assertEquals("123", ObjectUtils.getFirstNonNull(() -> "123", () -> null, () -> "456"));
+ // don't evaluate suppliers after first value is found
+ assertEquals("123", ObjectUtils.getFirstNonNull(() -> null, () -> "123", () -> fail("Supplier after first non-null value should not be evaluated")));
+ // supplier returning null and null supplier both result in null
+ assertNull(ObjectUtils.getFirstNonNull(null, () -> null));
+ // Explicitly pass in an empty array of Object type to ensure compiler doesn't complain of unchecked generic array creation
+ assertNull(ObjectUtils.getFirstNonNull());
+ // supplier is null
+ assertNull(ObjectUtils.getFirstNonNull((Supplier<Object>) null));
+ // varargs array itself is null
+ assertNull(ObjectUtils.getFirstNonNull((Supplier<Object>[]) null));
+ // test different types
+ assertEquals(1, ObjectUtils.getFirstNonNull(() -> null, () -> 1));
+ assertEquals(Boolean.TRUE, ObjectUtils.getFirstNonNull(() -> null, () -> Boolean.TRUE));
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(0, ObjectUtils.hashCode(null));
+ assertEquals("a".hashCode(), ObjectUtils.hashCode("a"));
+ }
+
+ @Test
+ public void testHashCodeHex() {
+ final Integer i = Integer.valueOf(90);
+ assertEquals(Integer.toHexString(Objects.hashCode(i)), ObjectUtils.hashCodeHex(i));
+ final Integer zero = Integer.valueOf(0);
+ assertEquals(Integer.toHexString(Objects.hashCode(zero)), ObjectUtils.hashCodeHex(zero));
+ assertEquals(Integer.toHexString(Objects.hashCode(null)), ObjectUtils.hashCodeHex(null));
+ }
+
+ @Test
+ public void testHashCodeMulti_multiple_emptyArray() {
+ final Object[] array = {};
+ assertEquals(1, ObjectUtils.hashCodeMulti(array));
+ }
+
+ @Test
+ public void testHashCodeMulti_multiple_likeList() {
+ final List<Object> list0 = new ArrayList<>(Collections.emptyList());
+ assertEquals(list0.hashCode(), ObjectUtils.hashCodeMulti());
+
+ final List<Object> list1 = new ArrayList<>(Collections.singletonList("a"));
+ assertEquals(list1.hashCode(), ObjectUtils.hashCodeMulti("a"));
+
+ final List<Object> list2 = new ArrayList<>(Arrays.asList("a", "b"));
+ assertEquals(list2.hashCode(), ObjectUtils.hashCodeMulti("a", "b"));
+
+ final List<Object> list3 = new ArrayList<>(Arrays.asList("a", "b", "c"));
+ assertEquals(list3.hashCode(), ObjectUtils.hashCodeMulti("a", "b", "c"));
+ }
+
+ @Test
+ public void testHashCodeMulti_multiple_nullArray() {
+ final Object[] array = null;
+ assertEquals(1, ObjectUtils.hashCodeMulti(array));
+ }
+
+ @Test
+ public void testIdentityHashCodeHex() {
+ final Integer i = Integer.valueOf(90);
+ assertEquals(Integer.toHexString(System.identityHashCode(i)), ObjectUtils.identityHashCodeHex(i));
+ final Integer zero = Integer.valueOf(0);
+ assertEquals(Integer.toHexString(System.identityHashCode(zero)), ObjectUtils.identityHashCodeHex(zero));
+ assertEquals(Integer.toHexString(System.identityHashCode(null)), ObjectUtils.identityHashCodeHex(null));
+ }
+
+ @Test
+ public void testIdentityToStringAppendable() throws IOException {
+ final Integer i = Integer.valueOf(121);
+ final String expected = "java.lang.Integer@" + Integer.toHexString(System.identityHashCode(i));
+
+ final Appendable appendable = new StringBuilder();
+ ObjectUtils.identityToString(appendable, i);
+ assertEquals(expected, appendable.toString());
+
+ assertThrows(NullPointerException.class, () -> ObjectUtils.identityToString((Appendable) null, "tmp"));
+
+ assertThrows(
+ NullPointerException.class,
+ () -> ObjectUtils.identityToString((Appendable) (new StringBuilder()), null));
+ }
+
+ @Test
+ public void testIdentityToStringInteger() {
+ final Integer i = Integer.valueOf(90);
+ final String expected = "java.lang.Integer@" + Integer.toHexString(System.identityHashCode(i));
+
+ assertEquals(expected, ObjectUtils.identityToString(i));
+ }
+
+ @Test
+ public void testIdentityToStringObjectNull() {
+ assertNull(ObjectUtils.identityToString(null));
+ }
+
+ @Test
+ public void testIdentityToStringStrBuilder() {
+ final Integer i = Integer.valueOf(102);
+ final String expected = "java.lang.Integer@" + Integer.toHexString(System.identityHashCode(i));
+
+ final StrBuilder builder = new StrBuilder();
+ ObjectUtils.identityToString(builder, i);
+ assertEquals(expected, builder.toString());
+
+ assertThrows(NullPointerException.class, () -> ObjectUtils.identityToString((StrBuilder) null, "tmp"));
+
+ assertThrows(NullPointerException.class, () -> ObjectUtils.identityToString(new StrBuilder(), null));
+ }
+
+ @Test
+ public void testIdentityToStringString() {
+ assertEquals(
+ "java.lang.String@" + Integer.toHexString(System.identityHashCode(FOO)),
+ ObjectUtils.identityToString(FOO));
+ }
+
+ @Test
+ public void testIdentityToStringStringBuffer() {
+ final Integer i = Integer.valueOf(45);
+ final String expected = "java.lang.Integer@" + Integer.toHexString(System.identityHashCode(i));
+
+ final StringBuffer buffer = new StringBuffer();
+ ObjectUtils.identityToString(buffer, i);
+ assertEquals(expected, buffer.toString());
+
+ assertThrows(NullPointerException.class, () -> ObjectUtils.identityToString((StringBuffer) null, "tmp"));
+ assertThrows(NullPointerException.class, () -> ObjectUtils.identityToString(new StringBuffer(), null));
+ }
+
+ @Test
+ public void testIdentityToStringStringBuilder() {
+ final Integer i = Integer.valueOf(90);
+ final String expected = "java.lang.Integer@" + Integer.toHexString(System.identityHashCode(i));
+
+ final StringBuilder builder = new StringBuilder();
+ ObjectUtils.identityToString(builder, i);
+ assertEquals(expected, builder.toString());
+ }
+
+ @Test
+ public void testIdentityToStringStringBuilderInUse() {
+ final Integer i = Integer.valueOf(90);
+ final String expected = "ABC = java.lang.Integer@" + Integer.toHexString(System.identityHashCode(i));
+
+ final StringBuilder builder = new StringBuilder("ABC = ");
+ ObjectUtils.identityToString(builder, i);
+ assertEquals(expected, builder.toString());
+ }
+
+ @Test
+ public void testIdentityToStringStringBuilderNullStringBuilder() {
+ assertThrows(NullPointerException.class, () -> ObjectUtils.identityToString((StringBuilder) null, "tmp"));
+ }
+
+ @Test
+ public void testIdentityToStringStringBuilderNullValue() {
+ assertThrows(NullPointerException.class, () -> ObjectUtils.identityToString(new StringBuilder(), null));
+ }
+
+ @Test
+ public void testIsEmpty() {
+ assertTrue(ObjectUtils.isEmpty(null));
+ assertTrue(ObjectUtils.isEmpty(""));
+ assertTrue(ObjectUtils.isEmpty(new int[] {}));
+ assertTrue(ObjectUtils.isEmpty(Collections.emptyList()));
+ assertTrue(ObjectUtils.isEmpty(Collections.emptySet()));
+ assertTrue(ObjectUtils.isEmpty(Collections.emptyMap()));
+ assertTrue(ObjectUtils.isEmpty(Optional.empty()));
+ assertTrue(ObjectUtils.isEmpty(Optional.ofNullable(null)));
+
+ assertFalse(ObjectUtils.isEmpty(" "));
+ assertFalse(ObjectUtils.isEmpty("ab"));
+ assertFalse(ObjectUtils.isEmpty(NON_EMPTY_ARRAY));
+ assertFalse(ObjectUtils.isEmpty(NON_EMPTY_LIST));
+ assertFalse(ObjectUtils.isEmpty(NON_EMPTY_SET));
+ assertFalse(ObjectUtils.isEmpty(NON_EMPTY_MAP));
+ assertFalse(ObjectUtils.isEmpty(Optional.of(new Object())));
+ assertFalse(ObjectUtils.isEmpty(Optional.ofNullable(new Object())));
+ }
+
+ @Test
+ public void testIsNotEmpty() {
+ assertFalse(ObjectUtils.isNotEmpty(null));
+ assertFalse(ObjectUtils.isNotEmpty(""));
+ assertFalse(ObjectUtils.isNotEmpty(new int[] {}));
+ assertFalse(ObjectUtils.isNotEmpty(Collections.emptyList()));
+ assertFalse(ObjectUtils.isNotEmpty(Collections.emptySet()));
+ assertFalse(ObjectUtils.isNotEmpty(Collections.emptyMap()));
+ assertFalse(ObjectUtils.isNotEmpty(Optional.empty()));
+ assertFalse(ObjectUtils.isNotEmpty(Optional.ofNullable(null)));
+
+ assertTrue(ObjectUtils.isNotEmpty(" "));
+ assertTrue(ObjectUtils.isNotEmpty("ab"));
+ assertTrue(ObjectUtils.isNotEmpty(NON_EMPTY_ARRAY));
+ assertTrue(ObjectUtils.isNotEmpty(NON_EMPTY_LIST));
+ assertTrue(ObjectUtils.isNotEmpty(NON_EMPTY_SET));
+ assertTrue(ObjectUtils.isNotEmpty(NON_EMPTY_MAP));
+ assertTrue(ObjectUtils.isNotEmpty(Optional.of(new Object())));
+ assertTrue(ObjectUtils.isNotEmpty(Optional.ofNullable(new Object())));
+ }
+
+ @Test
+ public void testMax() {
+ final Calendar calendar = Calendar.getInstance();
+ final Date nonNullComparable1 = calendar.getTime();
+ final Date nonNullComparable2 = calendar.getTime();
+ final String[] nullArray = null;
+
+ calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) - 1);
+ final Date minComparable = calendar.getTime();
+
+ assertNotSame(nonNullComparable1, nonNullComparable2);
+
+ assertNull(ObjectUtils.max((String) null));
+ assertNull(ObjectUtils.max(nullArray));
+ assertSame(nonNullComparable1, ObjectUtils.max(null, nonNullComparable1));
+ assertSame(nonNullComparable1, ObjectUtils.max(nonNullComparable1, null));
+ assertSame(nonNullComparable1, ObjectUtils.max(null, nonNullComparable1, null));
+ assertSame(nonNullComparable1, ObjectUtils.max(nonNullComparable1, nonNullComparable2));
+ assertSame(nonNullComparable2, ObjectUtils.max(nonNullComparable2, nonNullComparable1));
+ assertSame(nonNullComparable1, ObjectUtils.max(nonNullComparable1, minComparable));
+ assertSame(nonNullComparable1, ObjectUtils.max(minComparable, nonNullComparable1));
+ assertSame(nonNullComparable1, ObjectUtils.max(null, minComparable, null, nonNullComparable1));
+
+ assertNull(ObjectUtils.max(null, null));
+ }
+
+ @Test
+ public void testMedian() {
+ assertEquals("foo", ObjectUtils.median("foo"));
+ assertEquals("bar", ObjectUtils.median("foo", "bar"));
+ assertEquals("baz", ObjectUtils.median("foo", "bar", "baz"));
+ assertEquals("baz", ObjectUtils.median("foo", "bar", "baz", "blah"));
+ assertEquals("blah", ObjectUtils.median("foo", "bar", "baz", "blah", "wah"));
+ assertEquals(Integer.valueOf(5),
+ ObjectUtils.median(Integer.valueOf(1), Integer.valueOf(5), Integer.valueOf(10)));
+ assertEquals(
+ Integer.valueOf(7),
+ ObjectUtils.median(Integer.valueOf(5), Integer.valueOf(6), Integer.valueOf(7), Integer.valueOf(8),
+ Integer.valueOf(9)));
+ assertEquals(Integer.valueOf(6),
+ ObjectUtils.median(Integer.valueOf(5), Integer.valueOf(6), Integer.valueOf(7), Integer.valueOf(8)));
+ }
+
+ @Test
+ public void testMedian_emptyItems() {
+ assertThrows(IllegalArgumentException.class, ObjectUtils::<String>median);
+ }
+
+ @Test
+ public void testMedian_nullItems() {
+ assertThrows(NullPointerException.class, () -> ObjectUtils.median((String[]) null));
+ }
+
+ @Test
+ public void testMin() {
+ final Calendar calendar = Calendar.getInstance();
+ final Date nonNullComparable1 = calendar.getTime();
+ final Date nonNullComparable2 = calendar.getTime();
+ final String[] nullArray = null;
+
+ calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) - 1);
+ final Date minComparable = calendar.getTime();
+
+ assertNotSame(nonNullComparable1, nonNullComparable2);
+
+ assertNull(ObjectUtils.min((String) null));
+ assertNull(ObjectUtils.min(nullArray));
+ assertSame(nonNullComparable1, ObjectUtils.min(null, nonNullComparable1));
+ assertSame(nonNullComparable1, ObjectUtils.min(nonNullComparable1, null));
+ assertSame(nonNullComparable1, ObjectUtils.min(null, nonNullComparable1, null));
+ assertSame(nonNullComparable1, ObjectUtils.min(nonNullComparable1, nonNullComparable2));
+ assertSame(nonNullComparable2, ObjectUtils.min(nonNullComparable2, nonNullComparable1));
+ assertSame(minComparable, ObjectUtils.min(nonNullComparable1, minComparable));
+ assertSame(minComparable, ObjectUtils.min(minComparable, nonNullComparable1));
+ assertSame(minComparable, ObjectUtils.min(null, nonNullComparable1, null, minComparable));
+
+ assertNull(ObjectUtils.min(null, null));
+ }
+
+ @Test
+ public void testMode() {
+ assertNull(ObjectUtils.mode((Object[]) null));
+ assertNull(ObjectUtils.mode());
+ assertNull(ObjectUtils.mode("foo", "bar", "baz"));
+ assertNull(ObjectUtils.mode("foo", "bar", "baz", "foo", "bar"));
+ assertEquals("foo", ObjectUtils.mode("foo", "bar", "baz", "foo"));
+ assertEquals(Integer.valueOf(9),
+ ObjectUtils.mode("foo", "bar", "baz", Integer.valueOf(9), Integer.valueOf(10), Integer.valueOf(9)));
+ }
+
+ @Test
+ public void testNotEqual() {
+ assertFalse(ObjectUtils.notEqual(null, null), "ObjectUtils.notEqual(null, null) returned false");
+ assertTrue(ObjectUtils.notEqual(FOO, null), "ObjectUtils.notEqual(\"foo\", null) returned true");
+ assertTrue(ObjectUtils.notEqual(null, BAR), "ObjectUtils.notEqual(null, \"bar\") returned true");
+ assertTrue(ObjectUtils.notEqual(FOO, BAR), "ObjectUtils.notEqual(\"foo\", \"bar\") returned true");
+ assertFalse(ObjectUtils.notEqual(FOO, FOO), "ObjectUtils.notEqual(\"foo\", \"foo\") returned false");
+ }
+
+ @SuppressWarnings("cast") // 1 OK, because we are checking for code change
+ @Test
+ public void testNull() {
+ assertNotNull(ObjectUtils.NULL);
+ // 1 Check that NULL really is a Null i.e. the definition has not been changed
+ assertTrue(ObjectUtils.NULL instanceof ObjectUtils.Null);
+ assertSame(ObjectUtils.NULL, SerializationUtils.clone(ObjectUtils.NULL));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#cloneIfPossible(Object)} with a cloneable object.
+ */
+ @Test
+ public void testPossibleCloneOfCloneable() {
+ final CloneableString string = new CloneableString("apache");
+ final CloneableString stringClone = ObjectUtils.cloneIfPossible(string);
+ assertEquals("apache", stringClone.getValue());
+ }
+
+ /**
+ * Tests {@link ObjectUtils#cloneIfPossible(Object)} with a not cloneable object.
+ */
+ @Test
+ public void testPossibleCloneOfNotCloneable() {
+ final String string = "apache";
+ assertSame(string, ObjectUtils.cloneIfPossible(string));
+ }
+
+ /**
+ * Tests {@link ObjectUtils#cloneIfPossible(Object)} with an uncloneable object.
+ */
+ @Test
+ public void testPossibleCloneOfUncloneable() {
+ final UncloneableString string = new UncloneableString("apache");
+ final CloneFailedException e = assertThrows(CloneFailedException.class,
+ () -> ObjectUtils.cloneIfPossible(string));
+ assertEquals(NoSuchMethodException.class, e.getCause().getClass());
+ }
+
+ @Test
+ public void testRequireNonEmpty() {
+ assertEquals("foo", ObjectUtils.requireNonEmpty("foo"));
+ assertEquals("foo", ObjectUtils.requireNonEmpty("foo", "foo"));
+ //
+ assertThrows(NullPointerException.class, () -> ObjectUtils.requireNonEmpty(null));
+ assertThrows(NullPointerException.class, () -> ObjectUtils.requireNonEmpty(null, "foo"));
+ //
+ assertThrows(IllegalArgumentException.class, () -> ObjectUtils.requireNonEmpty(""));
+ assertThrows(IllegalArgumentException.class, () -> ObjectUtils.requireNonEmpty("", "foo"));
+ }
+
+ @Test
+ public void testToString_Object() {
+ assertEquals("", ObjectUtils.toString(null) );
+ assertEquals(Boolean.TRUE.toString(), ObjectUtils.toString(Boolean.TRUE) );
+ }
+
+ @Test
+ public void testToString_ObjectString() {
+ assertEquals(BAR, ObjectUtils.toString(null, BAR) );
+ assertEquals(Boolean.TRUE.toString(), ObjectUtils.toString(Boolean.TRUE, BAR) );
+ }
+
+ @Test
+ public void testToString_SupplierString() {
+ assertNull(ObjectUtils.toString(null, (Supplier<String>) null));
+ assertNull(ObjectUtils.toString(null, () -> null));
+ // Pretend computing BAR is expensive.
+ assertEquals(BAR, ObjectUtils.toString(null, () -> BAR));
+ assertEquals(Boolean.TRUE.toString(), ObjectUtils.toString(Boolean.TRUE, () -> BAR));
+ }
+
+ @Test
+ public void testWaitDuration() {
+ assertThrows(IllegalMonitorStateException.class, () -> ObjectUtils.wait(new Object(), Duration.ZERO));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
new file mode 100644
index 000000000..0d051e5c5
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.RandomStringUtils}.
+ */
+public class RandomStringUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new RandomStringUtils());
+ final Constructor<?>[] cons = RandomStringUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(RandomStringUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(RandomStringUtils.class.getModifiers()));
+ }
+
+ /**
+ * Test the implementation
+ */
+ @Test
+ public void testRandomStringUtils() {
+ String r1 = RandomStringUtils.random(50);
+ assertEquals(50, r1.length(), "random(50) length");
+ String r2 = RandomStringUtils.random(50);
+ assertEquals(50, r2.length(), "random(50) length");
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ r1 = RandomStringUtils.randomAscii(50);
+ assertEquals(50, r1.length(), "randomAscii(50) length");
+ for (int i = 0; i < r1.length(); i++) {
+ assertTrue(r1.charAt(i) >= 32 && r1.charAt(i) <= 127, "char between 32 and 127");
+ }
+ r2 = RandomStringUtils.randomAscii(50);
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ r1 = RandomStringUtils.randomAlphabetic(50);
+ assertEquals(50, r1.length(), "randomAlphabetic(50)");
+ for (int i = 0; i < r1.length(); i++) {
+ assertTrue(Character.isLetter(r1.charAt(i)) && !Character.isDigit(r1.charAt(i)), "r1 contains alphabetic");
+ }
+ r2 = RandomStringUtils.randomAlphabetic(50);
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ r1 = RandomStringUtils.randomAlphanumeric(50);
+ assertEquals(50, r1.length(), "randomAlphanumeric(50)");
+ for (int i = 0; i < r1.length(); i++) {
+ assertTrue(Character.isLetterOrDigit(r1.charAt(i)), "r1 contains alphanumeric");
+ }
+ r2 = RandomStringUtils.randomAlphabetic(50);
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ r1 = RandomStringUtils.randomGraph(50);
+ assertEquals(50, r1.length(), "randomGraph(50) length");
+ for (int i = 0; i < r1.length(); i++) {
+ assertTrue(r1.charAt(i) >= 33 && r1.charAt(i) <= 126, "char between 33 and 126");
+ }
+ r2 = RandomStringUtils.randomGraph(50);
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ r1 = RandomStringUtils.randomNumeric(50);
+ assertEquals(50, r1.length(), "randomNumeric(50)");
+ for (int i = 0; i < r1.length(); i++) {
+ assertTrue(Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)), "r1 contains numeric");
+ }
+ r2 = RandomStringUtils.randomNumeric(50);
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ r1 = RandomStringUtils.randomPrint(50);
+ assertEquals(50, r1.length(), "randomPrint(50) length");
+ for (int i = 0; i < r1.length(); i++) {
+ assertTrue(r1.charAt(i) >= 32 && r1.charAt(i) <= 126, "char between 32 and 126");
+ }
+ r2 = RandomStringUtils.randomPrint(50);
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ String set = "abcdefg";
+ r1 = RandomStringUtils.random(50, set);
+ assertEquals(50, r1.length(), "random(50, \"abcdefg\")");
+ for (int i = 0; i < r1.length(); i++) {
+ assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
+ }
+ r2 = RandomStringUtils.random(50, set);
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ r1 = RandomStringUtils.random(50, (String) null);
+ assertEquals(50, r1.length(), "random(50) length");
+ r2 = RandomStringUtils.random(50, (String) null);
+ assertEquals(50, r2.length(), "random(50) length");
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ set = "stuvwxyz";
+ r1 = RandomStringUtils.random(50, set.toCharArray());
+ assertEquals(50, r1.length(), "random(50, \"stuvwxyz\")");
+ for (int i = 0; i < r1.length(); i++) {
+ assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
+ }
+ r2 = RandomStringUtils.random(50, set);
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ r1 = RandomStringUtils.random(50, (char[]) null);
+ assertEquals(50, r1.length(), "random(50) length");
+ r2 = RandomStringUtils.random(50, (char[]) null);
+ assertEquals(50, r2.length(), "random(50) length");
+ assertFalse(r1.equals(r2), "!r1.equals(r2)");
+
+ final long seedMillis = System.currentTimeMillis();
+ r1 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
+ r2 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
+ assertEquals(r1, r2, "r1.equals(r2)");
+
+ r1 = RandomStringUtils.random(0);
+ assertEquals("", r1, "random(0).equals(\"\")");
+ }
+
+ @Test
+ public void testLANG805() {
+ final long seedMillis = System.currentTimeMillis();
+ assertEquals("aaa", RandomStringUtils.random(3, 0, 0, false, false, new char[]{'a'}, new Random(seedMillis)));
+ }
+
+ @Test
+ public void testLANG807() {
+ final IllegalArgumentException ex =
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(3, 5, 5, false, false));
+ final String msg = ex.getMessage();
+ assertTrue(msg.contains("start"), "Message (" + msg + ") must contain 'start'");
+ assertTrue(msg.contains("end"), "Message (" + msg + ") must contain 'end'");
+ }
+
+ @Test
+ public void testExceptions() {
+ final char[] DUMMY = {'a'}; // valid char array
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, true, true));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, DUMMY));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(1, new char[0]));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, ""));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, (String) null));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false, DUMMY));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> RandomStringUtils.random(-1, 'a', 'z', false, false, DUMMY, new Random()));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(8, 32, 48, false, true));
+ assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(8, 32, 65, true, false));
+ }
+
+ /**
+ * Make sure boundary alphanumeric characters are generated by randomAlphaNumeric
+ * This test will fail randomly with probability = 6 * (61/62)**1000 ~ 5.2E-7
+ */
+ @Test
+ public void testRandomAlphaNumeric() {
+ final char[] testChars = {'a', 'z', 'A', 'Z', '0', '9'};
+ final boolean[] found = {false, false, false, false, false, false};
+ for (int i = 0; i < 100; i++) {
+ final String randString = RandomStringUtils.randomAlphanumeric(10);
+ for (int j = 0; j < testChars.length; j++) {
+ if (randString.indexOf(testChars[j]) > 0) {
+ found[j] = true;
+ }
+ }
+ }
+ for (int i = 0; i < testChars.length; i++) {
+ assertTrue(found[i],
+ "alphanumeric character not generated in 1000 attempts: " +
+ testChars[i] + " -- repeated failures indicate a problem ");
+ }
+ }
+
+ /**
+ * Make sure '0' and '9' are generated by randomNumeric
+ * This test will fail randomly with probability = 2 * (9/10)**1000 ~ 3.5E-46
+ */
+ @Test
+ public void testRandomNumeric() {
+ final char[] testChars = {'0', '9'};
+ final boolean[] found = {false, false};
+ for (int i = 0; i < 100; i++) {
+ final String randString = RandomStringUtils.randomNumeric(10);
+ for (int j = 0; j < testChars.length; j++) {
+ if (randString.indexOf(testChars[j]) > 0) {
+ found[j] = true;
+ }
+ }
+ }
+ for (int i = 0; i < testChars.length; i++) {
+ assertTrue(found[i],
+ "digit not generated in 1000 attempts: " + testChars[i] +
+ " -- repeated failures indicate a problem ");
+ }
+ }
+
+ /**
+ * Make sure boundary alpha characters are generated by randomAlphabetic
+ * This test will fail randomly with probability = 4 * (51/52)**1000 ~ 1.58E-8
+ */
+ @Test
+ public void testRandomAlphabetic() {
+ final char[] testChars = {'a', 'z', 'A', 'Z'};
+ final boolean[] found = {false, false, false, false};
+ for (int i = 0; i < 100; i++) {
+ final String randString = RandomStringUtils.randomAlphabetic(10);
+ for (int j = 0; j < testChars.length; j++) {
+ if (randString.indexOf(testChars[j]) > 0) {
+ found[j] = true;
+ }
+ }
+ }
+ for (int i = 0; i < testChars.length; i++) {
+ assertTrue(found[i],
+ "alphanumeric character not generated in 1000 attempts: " + testChars[i] +
+ " -- repeated failures indicate a problem ");
+ }
+ }
+
+ /**
+ * Make sure 32 and 127 are generated by randomNumeric
+ * This test will fail randomly with probability = 2*(95/96)**1000 ~ 5.7E-5
+ */
+ @Test
+ public void testRandomAscii() {
+ final char[] testChars = {(char) 32, (char) 126};
+ final boolean[] found = {false, false};
+ for (int i = 0; i < 100; i++) {
+ final String randString = RandomStringUtils.randomAscii(10);
+ for (int j = 0; j < testChars.length; j++) {
+ if (randString.indexOf(testChars[j]) > 0) {
+ found[j] = true;
+ }
+ }
+ }
+ for (int i = 0; i < testChars.length; i++) {
+ assertTrue(found[i],
+ "ascii character not generated in 1000 attempts: " + (int) testChars[i] +
+ " -- repeated failures indicate a problem");
+ }
+ }
+
+ @Test
+ public void testRandomAsciiRange() {
+ final int expectedMinLengthInclusive = 1;
+ final int expectedMaxLengthExclusive = 11;
+ final String pattern = "^\\p{ASCII}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
+
+ int maxCreatedLength = expectedMinLengthInclusive;
+ int minCreatedLength = expectedMaxLengthExclusive - 1;
+ for (int i = 0; i < 1000; i++) {
+ final String s = RandomStringUtils.randomAscii(expectedMinLengthInclusive, expectedMaxLengthExclusive);
+ assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
+ assertTrue(s.matches(pattern), s);
+
+ if (s.length() < minCreatedLength) {
+ minCreatedLength = s.length();
+ }
+
+ if (s.length() > maxCreatedLength) {
+ maxCreatedLength = s.length();
+ }
+ }
+ assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
+ assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
+ }
+
+ @Test
+ public void testRandomAlphabeticRange() {
+ final int expectedMinLengthInclusive = 1;
+ final int expectedMaxLengthExclusive = 11;
+ final String pattern = "^\\p{Alpha}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
+
+ int maxCreatedLength = expectedMinLengthInclusive;
+ int minCreatedLength = expectedMaxLengthExclusive - 1;
+ for (int i = 0; i < 1000; i++) {
+ final String s = RandomStringUtils.randomAlphabetic(expectedMinLengthInclusive, expectedMaxLengthExclusive);
+ assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
+ assertTrue(s.matches(pattern), s);
+
+ if (s.length() < minCreatedLength) {
+ minCreatedLength = s.length();
+ }
+
+ if (s.length() > maxCreatedLength) {
+ maxCreatedLength = s.length();
+ }
+ }
+ assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
+ assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
+ }
+
+ @Test
+ public void testRandomAlphanumericRange() {
+ final int expectedMinLengthInclusive = 1;
+ final int expectedMaxLengthExclusive = 11;
+ final String pattern = "^\\p{Alnum}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
+
+ int maxCreatedLength = expectedMinLengthInclusive;
+ int minCreatedLength = expectedMaxLengthExclusive - 1;
+ for (int i = 0; i < 1000; i++) {
+ final String s = RandomStringUtils.randomAlphanumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive);
+ assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
+ assertTrue(s.matches(pattern), s);
+
+ if (s.length() < minCreatedLength) {
+ minCreatedLength = s.length();
+ }
+
+ if (s.length() > maxCreatedLength) {
+ maxCreatedLength = s.length();
+ }
+ }
+ assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
+ assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
+ }
+
+ @Test
+ public void testRandomGraphRange() {
+ final int expectedMinLengthInclusive = 1;
+ final int expectedMaxLengthExclusive = 11;
+ final String pattern = "^\\p{Graph}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
+
+ int maxCreatedLength = expectedMinLengthInclusive;
+ int minCreatedLength = expectedMaxLengthExclusive - 1;
+ for (int i = 0; i < 1000; i++) {
+ final String s = RandomStringUtils.randomGraph(expectedMinLengthInclusive, expectedMaxLengthExclusive);
+ assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
+ assertTrue(s.matches(pattern), s);
+
+ if (s.length() < minCreatedLength) {
+ minCreatedLength = s.length();
+ }
+
+ if (s.length() > maxCreatedLength) {
+ maxCreatedLength = s.length();
+ }
+ }
+ assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
+ assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
+ }
+
+ @Test
+ public void testRandomNumericRange() {
+ final int expectedMinLengthInclusive = 1;
+ final int expectedMaxLengthExclusive = 11;
+ final String pattern = "^\\p{Digit}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
+
+ int maxCreatedLength = expectedMinLengthInclusive;
+ int minCreatedLength = expectedMaxLengthExclusive - 1;
+ for (int i = 0; i < 1000; i++) {
+ final String s = RandomStringUtils.randomNumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive);
+ assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
+ assertTrue(s.matches(pattern), s);
+
+ if (s.length() < minCreatedLength) {
+ minCreatedLength = s.length();
+ }
+
+ if (s.length() > maxCreatedLength) {
+ maxCreatedLength = s.length();
+ }
+ }
+ assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
+ assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
+ }
+
+ @Test
+ public void testRandomPrintRange() {
+ final int expectedMinLengthInclusive = 1;
+ final int expectedMaxLengthExclusive = 11;
+ final String pattern = "^\\p{Print}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
+
+ int maxCreatedLength = expectedMinLengthInclusive;
+ int minCreatedLength = expectedMaxLengthExclusive - 1;
+ for (int i = 0; i < 1000; i++) {
+ final String s = RandomStringUtils.randomPrint(expectedMinLengthInclusive, expectedMaxLengthExclusive);
+ assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
+ assertTrue(s.matches(pattern), s);
+
+ if (s.length() < minCreatedLength) {
+ minCreatedLength = s.length();
+ }
+
+ if (s.length() > maxCreatedLength) {
+ maxCreatedLength = s.length();
+ }
+ }
+ assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
+ assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
+ }
+
+ /**
+ * Test homogeneity of random strings generated --
+ * i.e., test that characters show up with expected frequencies
+ * in generated strings. Will fail randomly about 1 in 1000 times.
+ * Repeated failures indicate a problem.
+ */
+ @Test
+ public void testRandomStringUtilsHomog() {
+ final String set = "abc";
+ final char[] chars = set.toCharArray();
+ String gen = "";
+ final int[] counts = {0, 0, 0};
+ final int[] expected = {200, 200, 200};
+ for (int i = 0; i< 100; i++) {
+ gen = RandomStringUtils.random(6, chars);
+ for (int j = 0; j < 6; j++) {
+ switch (gen.charAt(j)) {
+ case 'a': {
+ counts[0]++;
+ break;
+ }
+ case 'b': {
+ counts[1]++;
+ break;
+ }
+ case 'c': {
+ counts[2]++;
+ break;
+ }
+ default: {
+ fail("generated character not in set");
+ }
+ }
+ }
+ }
+ // Perform chi-square test with df = 3-1 = 2, testing at .001 level
+ assertTrue(chiSquare(expected, counts) < 13.82, "test homogeneity -- will fail about 1 in 1000 times");
+ }
+
+ /**
+ * Computes Chi-Square statistic given observed and expected counts
+ * @param observed array of observed frequency counts
+ * @param expected array of expected frequency counts
+ */
+ private double chiSquare(final int[] expected, final int[] observed) {
+ double sumSq = 0.0d;
+ double dev = 0.0d;
+ for (int i = 0; i < observed.length; i++) {
+ dev = observed[i] - expected[i];
+ sumSq += dev * dev / expected[i];
+ }
+ return sumSq;
+ }
+
+ /**
+ * Checks if the string got by {@link RandomStringUtils#random(int)}
+ * can be converted to UTF-8 and back without loss.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/LANG-100">LANG-100</a>
+ */
+ @Test
+ public void testLang100() {
+ final int size = 5000;
+ final Charset charset = StandardCharsets.UTF_8;
+ final String orig = RandomStringUtils.random(size);
+ final byte[] bytes = orig.getBytes(charset);
+ final String copy = new String(bytes, charset);
+
+ // for a verbose compare:
+ for (int i=0; i < orig.length() && i < copy.length(); i++) {
+ final char o = orig.charAt(i);
+ final char c = copy.charAt(i);
+ assertEquals(o, c,
+ "differs at " + i + "(" + Integer.toHexString(Character.valueOf(o).hashCode()) + "," +
+ Integer.toHexString(Character.valueOf(c).hashCode()) + ")");
+ }
+ // compare length also
+ assertEquals(orig.length(), copy.length());
+ // just to be complete
+ assertEquals(orig, copy);
+ }
+
+
+ /**
+ * Test for LANG-1286. Creates situation where old code would
+ * overflow a char and result in a code point outside the specified
+ * range.
+ */
+ @Test
+ public void testCharOverflow() {
+ final int start = Character.MAX_VALUE;
+ final int end = Integer.MAX_VALUE;
+
+ @SuppressWarnings("serial")
+ final
+ Random fixedRandom = new Random() {
+ @Override
+ public int nextInt(final int n) {
+ // Prevents selection of 'start' as the character
+ return super.nextInt(n - 1) + 1;
+ }
+ };
+
+ final String result = RandomStringUtils.random(2, start, end, false, false, null, fixedRandom);
+ final int c = result.codePointAt(0);
+ assertTrue(c >= start && c < end, String.format("Character '%d' not in range [%d,%d).", c, start, end));
+ }
+}
+
diff --git a/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java b/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java
new file mode 100644
index 000000000..47f3a57b4
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link RandomUtils}
+ */
+public class RandomUtilsTest extends AbstractLangTest {
+
+ /**
+ * For comparing doubles and floats
+ */
+ private static final double DELTA = 1e-5;
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new RandomUtils());
+ final Constructor<?>[] cons = RandomUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(RandomUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(RandomUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testNextBytesNegative() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextBytes(-1));
+ }
+
+ @Test
+ public void testNextIntNegative() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextInt(-1, 1));
+ }
+
+ @Test
+ public void testNextLongNegative() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextLong(-1, 1));
+ }
+
+ @Test
+ public void testNextDoubleNegative() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextDouble(-1, 1));
+ }
+
+ @Test
+ public void testNextFloatNegative() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextFloat(-1, 1));
+ }
+
+ @Test
+ public void testNextIntLowerGreaterUpper() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextInt(2, 1));
+ }
+
+ @Test
+ public void testNextLongLowerGreaterUpper() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextLong(2, 1));
+ }
+
+ @Test
+ public void testNextDoubleLowerGreaterUpper() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextDouble(2, 1));
+ }
+
+ @Test
+ public void testNextFloatLowerGreaterUpper() {
+ assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextFloat(2, 1));
+ }
+
+ /**
+ * Tests next boolean
+ */
+ @Test
+ public void testBoolean() {
+ final boolean result = RandomUtils.nextBoolean();
+ assertTrue(result || !result);
+ }
+
+ /**
+ * Tests a zero byte array length.
+ */
+ @Test
+ public void testZeroLengthNextBytes() {
+ assertArrayEquals(new byte[0], RandomUtils.nextBytes(0));
+ }
+
+ /**
+ * Tests random byte array.
+ */
+ @Test
+ public void testNextBytes() {
+ final byte[] result = RandomUtils.nextBytes(20);
+ assertEquals(20, result.length);
+ }
+
+ /**
+ * Test next int range with minimal range.
+ */
+ @Test
+ public void testNextIntMinimalRange() {
+ assertEquals(42, RandomUtils.nextInt(42, 42));
+ }
+
+ /**
+ * Tests next int range.
+ */
+ @Test
+ public void testNextInt() {
+ final int result = RandomUtils.nextInt(33, 42);
+ assertTrue(result >= 33 && result < 42);
+ }
+
+ /**
+ * Tests next int range, random result.
+ */
+ @Test
+ public void testNextIntRandomResult() {
+ final int randomResult = RandomUtils.nextInt();
+ assertTrue(randomResult > 0);
+ assertTrue(randomResult < Integer.MAX_VALUE);
+ }
+
+ /**
+ * Test next double range with minimal range.
+ */
+ @Test
+ public void testNextDoubleMinimalRange() {
+ assertEquals(42.1, RandomUtils.nextDouble(42.1, 42.1), DELTA);
+ }
+
+ /**
+ * Test next float range with minimal range.
+ */
+ @Test
+ public void testNextFloatMinimalRange() {
+ assertEquals(42.1f, RandomUtils.nextFloat(42.1f, 42.1f), DELTA);
+ }
+
+ /**
+ * Tests next double range.
+ */
+ @Test
+ public void testNextDouble() {
+ final double result = RandomUtils.nextDouble(33d, 42d);
+ assertTrue(result >= 33d && result <= 42d);
+ }
+
+ /**
+ * Tests next double range, random result.
+ */
+ @Test
+ public void testNextDoubleRandomResult() {
+ final double randomResult = RandomUtils.nextDouble();
+ assertTrue(randomResult > 0);
+ assertTrue(randomResult < Double.MAX_VALUE);
+ }
+
+ /**
+ * Tests next float range.
+ */
+ @Test
+ public void testNextFloat() {
+ final double result = RandomUtils.nextFloat(33f, 42f);
+ assertTrue(result >= 33f && result <= 42f);
+ }
+
+ /**
+ * Tests next float range, random result.
+ */
+ @Test
+ public void testNextFloatRandomResult() {
+ final float randomResult = RandomUtils.nextFloat();
+ assertTrue(randomResult > 0);
+ assertTrue(randomResult < Float.MAX_VALUE);
+ }
+
+ /**
+ * Test next long range with minimal range.
+ */
+ @Test
+ public void testNextLongMinimalRange() {
+ assertEquals(42L, RandomUtils.nextLong(42L, 42L));
+ }
+
+ /**
+ * Tests next long range.
+ */
+ @Test
+ public void testNextLong() {
+ final long result = RandomUtils.nextLong(33L, 42L);
+ assertTrue(result >= 33L && result < 42L);
+ }
+
+ /**
+ * Tests next long range, random result.
+ */
+ @Test
+ public void testNextLongRandomResult() {
+ final long randomResult = RandomUtils.nextLong();
+ assertTrue(randomResult > 0);
+ assertTrue(randomResult < Long.MAX_VALUE);
+ }
+
+ /**
+ * Tests extreme range.
+ */
+ @Test
+ public void testExtremeRangeInt() {
+ final int result = RandomUtils.nextInt(0, Integer.MAX_VALUE);
+ assertTrue(result >= 0 && result < Integer.MAX_VALUE);
+ }
+
+ /**
+ * Tests extreme range.
+ */
+ @Test
+ public void testExtremeRangeLong() {
+ final long result = RandomUtils.nextLong(0, Long.MAX_VALUE);
+ assertTrue(result >= 0 && result < Long.MAX_VALUE);
+ }
+
+ /**
+ * Tests extreme range.
+ */
+ @Test
+ public void testExtremeRangeFloat() {
+ final float result = RandomUtils.nextFloat(0, Float.MAX_VALUE);
+ assertTrue(result >= 0f && result <= Float.MAX_VALUE);
+ }
+
+ /**
+ * Tests extreme range.
+ */
+ @Test
+ public void testExtremeRangeDouble() {
+ final double result = RandomUtils.nextDouble(0, Double.MAX_VALUE);
+ assertTrue(result >= 0 && result <= Double.MAX_VALUE);
+ }
+
+ /**
+ * Test a large value for long. A previous implementation using
+ * {@link RandomUtils#nextDouble(double, double)} could generate a value equal
+ * to the upper limit.
+ *
+ * <pre>
+ * return (long) nextDouble(startInclusive, endExclusive);
+ * </pre>
+ *
+ * <p>See LANG-1592.</p>
+ */
+ @Test
+ public void testLargeValueRangeLong() {
+ final long startInclusive = 12900000000001L;
+ final long endExclusive = 12900000000016L;
+ // Note: The method using 'return (long) nextDouble(startInclusive, endExclusive)'
+ // takes thousands of calls to generate an error. This size loop fails most
+ // of the time with the previous method.
+ final int n = (int) (endExclusive - startInclusive) * 1000;
+ for (int i = 0; i < n; i++) {
+ assertNotEquals(endExclusive, RandomUtils.nextLong(startInclusive, endExclusive));
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/RangeTest.java b/src/test/java/org/apache/commons/lang3/RangeTest.java
new file mode 100644
index 000000000..8b477c354
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/RangeTest.java
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Comparator;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link Range}.
+ */
+@SuppressWarnings("boxing")
+public class RangeTest extends AbstractLangTest {
+
+ abstract static class AbstractComparable implements Comparable<AbstractComparable> {
+ @Override
+ public int compareTo(final AbstractComparable o) {
+ return 0;
+ }
+ }
+ static final class DerivedComparableA extends AbstractComparable {
+ // empty
+ }
+ static final class DerivedComparableB extends AbstractComparable {
+ // empty
+ }
+
+ private Range<Byte> byteRange;
+ private Range<Byte> byteRange2;
+ private Range<Byte> byteRange3;
+ private Range<Double> doubleRange;
+ private Range<Float> floatRange;
+ private Range<Integer> intRange;
+ private Range<Long> longRange;
+
+ @BeforeEach
+ public void setUp() {
+ byteRange = Range.of((byte) 0, (byte) 5);
+ byteRange2 = Range.of((byte) 0, (byte) 5);
+ byteRange3 = Range.of((byte) 0, (byte) 10);
+
+ intRange = Range.of(10, 20);
+ longRange = Range.of(10L, 20L);
+ floatRange = Range.of((float) 10, (float) 20);
+ doubleRange = Range.of((double) 10, (double) 20);
+ }
+
+ @Test
+ public void testBetweenWithCompare() {
+ // all integers are equal
+ final Comparator<Integer> c = (o1, o2) -> 0;
+ final Comparator<String> lengthComp = Comparator.comparingInt(String::length);
+ Range<Integer> rb = Range.between(-10, 20);
+ assertFalse(rb.contains(null), "should not contain null");
+ assertTrue(rb.contains(10), "should contain 10");
+ assertTrue(rb.contains(-10), "should contain -10");
+ assertFalse(rb.contains(21), "should not contain 21");
+ assertFalse(rb.contains(-11), "should not contain -11");
+ rb = Range.between(-10, 20, c);
+ assertFalse(rb.contains(null), "should not contain null");
+ assertTrue(rb.contains(10), "should contain 10");
+ assertTrue(rb.contains(-10), "should contain -10");
+ assertTrue(rb.contains(21), "should contain 21");
+ assertTrue(rb.contains(-11), "should contain -11");
+ Range<String> rbstr = Range.between("house", "i");
+ assertFalse(rbstr.contains(null), "should not contain null");
+ assertTrue(rbstr.contains("house"), "should contain house");
+ assertTrue(rbstr.contains("i"), "should contain i");
+ assertFalse(rbstr.contains("hose"), "should not contain hose");
+ assertFalse(rbstr.contains("ice"), "should not contain ice");
+ rbstr = Range.between("house", "i", lengthComp);
+ assertFalse(rbstr.contains(null), "should not contain null");
+ assertTrue(rbstr.contains("house"), "should contain house");
+ assertTrue(rbstr.contains("i"), "should contain i");
+ assertFalse(rbstr.contains("houses"), "should not contain houses");
+ assertFalse(rbstr.contains(""), "should not contain ''");
+
+ assertThrows(NullPointerException.class, () -> Range.between(null, null, lengthComp));
+ }
+
+ @Test
+ public void testOfWithCompare() {
+ // all integers are equal
+ final Comparator<Integer> c = (o1, o2) -> 0;
+ final Comparator<String> lengthComp = Comparator.comparingInt(String::length);
+ Range<Integer> rb = Range.of(-10, 20);
+ assertFalse(rb.contains(null), "should not contain null");
+ assertTrue(rb.contains(10), "should contain 10");
+ assertTrue(rb.contains(-10), "should contain -10");
+ assertFalse(rb.contains(21), "should not contain 21");
+ assertFalse(rb.contains(-11), "should not contain -11");
+ rb = Range.of(-10, 20, c);
+ assertFalse(rb.contains(null), "should not contain null");
+ assertTrue(rb.contains(10), "should contain 10");
+ assertTrue(rb.contains(-10), "should contain -10");
+ assertTrue(rb.contains(21), "should contain 21");
+ assertTrue(rb.contains(-11), "should contain -11");
+ Range<String> rbstr = Range.of("house", "i");
+ assertFalse(rbstr.contains(null), "should not contain null");
+ assertTrue(rbstr.contains("house"), "should contain house");
+ assertTrue(rbstr.contains("i"), "should contain i");
+ assertFalse(rbstr.contains("hose"), "should not contain hose");
+ assertFalse(rbstr.contains("ice"), "should not contain ice");
+ rbstr = Range.of("house", "i", lengthComp);
+ assertFalse(rbstr.contains(null), "should not contain null");
+ assertTrue(rbstr.contains("house"), "should contain house");
+ assertTrue(rbstr.contains("i"), "should contain i");
+ assertFalse(rbstr.contains("houses"), "should not contain houses");
+ assertFalse(rbstr.contains(""), "should not contain ''");
+
+ assertThrows(NullPointerException.class, () -> Range.of(null, null, lengthComp));
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @Test
+ public void testComparableConstructors() {
+ final Comparable c = other -> 1;
+ final Range r1 = Range.is(c);
+ final Range r2 = Range.between(c, c);
+ assertTrue(r1.isNaturalOrdering());
+ assertTrue(r2.isNaturalOrdering());
+ }
+
+ @Test
+ public void testConstructorSignatureWithAbstractComparableClasses() {
+ final DerivedComparableA derivedComparableA = new DerivedComparableA();
+ final DerivedComparableB derivedComparableB = new DerivedComparableB();
+
+ Range<AbstractComparable> mixed = Range.between(derivedComparableA, derivedComparableB);
+ mixed = Range.between(derivedComparableA, derivedComparableB, null);
+ assertTrue(mixed.contains(derivedComparableA));
+
+ Range<AbstractComparable> same = Range.between(derivedComparableA, derivedComparableA);
+ same = Range.between(derivedComparableA, derivedComparableA, null);
+ assertTrue(same.contains(derivedComparableA));
+
+ Range<DerivedComparableA> rangeA = Range.between(derivedComparableA, derivedComparableA);
+ rangeA = Range.between(derivedComparableA, derivedComparableA, null);
+ assertTrue(rangeA.contains(derivedComparableA));
+
+ Range<DerivedComparableB> rangeB = Range.is(derivedComparableB);
+ rangeB = Range.is(derivedComparableB, null);
+ assertTrue(rangeB.contains(derivedComparableB));
+ }
+
+ @Test
+ public void testContains() {
+ assertFalse(intRange.contains(null));
+
+ assertFalse(intRange.contains(5));
+ assertTrue(intRange.contains(10));
+ assertTrue(intRange.contains(15));
+ assertTrue(intRange.contains(20));
+ assertFalse(intRange.contains(25));
+ }
+
+ @Test
+ public void testContainsRange() {
+
+ // null handling
+ assertFalse(intRange.containsRange(null));
+
+ // easy inside range
+ assertTrue(intRange.containsRange(Range.between(12, 18)));
+
+ // outside range on each side
+ assertFalse(intRange.containsRange(Range.between(32, 45)));
+ assertFalse(intRange.containsRange(Range.between(2, 8)));
+
+ // equals range
+ assertTrue(intRange.containsRange(Range.between(10, 20)));
+
+ // overlaps
+ assertFalse(intRange.containsRange(Range.between(9, 14)));
+ assertFalse(intRange.containsRange(Range.between(16, 21)));
+
+ // touches lower boundary
+ assertTrue(intRange.containsRange(Range.between(10, 19)));
+ assertFalse(intRange.containsRange(Range.between(10, 21)));
+
+ // touches upper boundary
+ assertTrue(intRange.containsRange(Range.between(11, 20)));
+ assertFalse(intRange.containsRange(Range.between(9, 20)));
+
+ // negative
+ assertFalse(intRange.containsRange(Range.between(-11, -18)));
+ }
+
+ @Test
+ public void testElementCompareTo() {
+ assertThrows(NullPointerException.class, () -> intRange.elementCompareTo(null));
+
+ assertEquals(-1, intRange.elementCompareTo(5));
+ assertEquals(0, intRange.elementCompareTo(10));
+ assertEquals(0, intRange.elementCompareTo(15));
+ assertEquals(0, intRange.elementCompareTo(20));
+ assertEquals(1, intRange.elementCompareTo(25));
+ }
+
+ @Test
+ public void testEqualsObject() {
+ assertEquals(byteRange, byteRange);
+ assertEquals(byteRange, byteRange2);
+ assertEquals(byteRange2, byteRange2);
+ assertEquals(byteRange, byteRange);
+ assertEquals(byteRange2, byteRange2);
+ assertEquals(byteRange3, byteRange3);
+ assertNotEquals(byteRange2, byteRange3);
+ assertNotEquals(null, byteRange2);
+ assertNotEquals("Ni!", byteRange2);
+ }
+
+ @Test
+ public void testFit() {
+ assertEquals(intRange.getMinimum(), intRange.fit(Integer.MIN_VALUE));
+ assertEquals(intRange.getMinimum(), intRange.fit(intRange.getMinimum()));
+ assertEquals(intRange.getMaximum(), intRange.fit(Integer.MAX_VALUE));
+ assertEquals(intRange.getMaximum(), intRange.fit(intRange.getMaximum()));
+ assertEquals(15, intRange.fit(15));
+ }
+
+ @Test
+ public void testFitNull() {
+ assertThrows(NullPointerException.class, () -> {
+ intRange.fit(null);
+ });
+ }
+
+ @Test
+ public void testGetMaximum() {
+ assertEquals(20, (int) intRange.getMaximum());
+ assertEquals(20L, (long) longRange.getMaximum());
+ assertEquals(20f, floatRange.getMaximum(), 0.00001f);
+ assertEquals(20d, doubleRange.getMaximum(), 0.00001d);
+ }
+
+ @Test
+ public void testGetMinimum() {
+ assertEquals(10, (int) intRange.getMinimum());
+ assertEquals(10L, (long) longRange.getMinimum());
+ assertEquals(10f, floatRange.getMinimum(), 0.00001f);
+ assertEquals(10d, doubleRange.getMinimum(), 0.00001d);
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(byteRange.hashCode(), byteRange2.hashCode());
+ assertNotEquals(byteRange.hashCode(), byteRange3.hashCode());
+
+ assertEquals(intRange.hashCode(), intRange.hashCode());
+ assertTrue(intRange.hashCode() != 0);
+ }
+
+ @Test
+ public void testIntersectionWith() {
+ assertSame(intRange, intRange.intersectionWith(intRange));
+ assertSame(byteRange, byteRange.intersectionWith(byteRange));
+ assertSame(longRange, longRange.intersectionWith(longRange));
+ assertSame(floatRange, floatRange.intersectionWith(floatRange));
+ assertSame(doubleRange, doubleRange.intersectionWith(doubleRange));
+
+ assertEquals(Range.between(10, 15), intRange.intersectionWith(Range.between(5, 15)));
+ }
+
+ @Test
+ public void testIntersectionWithNonOverlapping() {
+ assertThrows(IllegalArgumentException.class, () -> intRange.intersectionWith(Range.between(0, 9)));
+ }
+
+ @Test
+ public void testIntersectionWithNull() {
+ assertThrows(IllegalArgumentException.class, () -> intRange.intersectionWith(null));
+ }
+
+ @Test
+ public void testIsAfter() {
+ assertFalse(intRange.isAfter(null));
+
+ assertTrue(intRange.isAfter(5));
+ assertFalse(intRange.isAfter(10));
+ assertFalse(intRange.isAfter(15));
+ assertFalse(intRange.isAfter(20));
+ assertFalse(intRange.isAfter(25));
+ }
+
+ @Test
+ public void testIsAfterRange() {
+ assertFalse(intRange.isAfterRange(null));
+
+ assertTrue(intRange.isAfterRange(Range.between(5, 9)));
+
+ assertFalse(intRange.isAfterRange(Range.between(5, 10)));
+ assertFalse(intRange.isAfterRange(Range.between(5, 20)));
+ assertFalse(intRange.isAfterRange(Range.between(5, 25)));
+ assertFalse(intRange.isAfterRange(Range.between(15, 25)));
+
+ assertFalse(intRange.isAfterRange(Range.between(21, 25)));
+
+ assertFalse(intRange.isAfterRange(Range.between(10, 20)));
+ }
+
+ @Test
+ public void testIsBefore() {
+ assertFalse(intRange.isBefore(null));
+
+ assertFalse(intRange.isBefore(5));
+ assertFalse(intRange.isBefore(10));
+ assertFalse(intRange.isBefore(15));
+ assertFalse(intRange.isBefore(20));
+ assertTrue(intRange.isBefore(25));
+ }
+
+ @Test
+ public void testIsBeforeRange() {
+ assertFalse(intRange.isBeforeRange(null));
+
+ assertFalse(intRange.isBeforeRange(Range.between(5, 9)));
+
+ assertFalse(intRange.isBeforeRange(Range.between(5, 10)));
+ assertFalse(intRange.isBeforeRange(Range.between(5, 20)));
+ assertFalse(intRange.isBeforeRange(Range.between(5, 25)));
+ assertFalse(intRange.isBeforeRange(Range.between(15, 25)));
+
+ assertTrue(intRange.isBeforeRange(Range.between(21, 25)));
+
+ assertFalse(intRange.isBeforeRange(Range.between(10, 20)));
+ }
+
+ @Test
+ public void testIsEndedBy() {
+ assertFalse(intRange.isEndedBy(null));
+
+ assertFalse(intRange.isEndedBy(5));
+ assertFalse(intRange.isEndedBy(10));
+ assertFalse(intRange.isEndedBy(15));
+ assertTrue(intRange.isEndedBy(20));
+ assertFalse(intRange.isEndedBy(25));
+ }
+
+ @Test
+ public void testIsOverlappedBy() {
+
+ // null handling
+ assertFalse(intRange.isOverlappedBy(null));
+
+ // easy inside range
+ assertTrue(intRange.isOverlappedBy(Range.between(12, 18)));
+
+ // outside range on each side
+ assertFalse(intRange.isOverlappedBy(Range.between(32, 45)));
+ assertFalse(intRange.isOverlappedBy(Range.between(2, 8)));
+
+ // equals range
+ assertTrue(intRange.isOverlappedBy(Range.between(10, 20)));
+
+ // overlaps
+ assertTrue(intRange.isOverlappedBy(Range.between(9, 14)));
+ assertTrue(intRange.isOverlappedBy(Range.between(16, 21)));
+
+ // touches lower boundary
+ assertTrue(intRange.isOverlappedBy(Range.between(10, 19)));
+ assertTrue(intRange.isOverlappedBy(Range.between(10, 21)));
+
+ // touches upper boundary
+ assertTrue(intRange.isOverlappedBy(Range.between(11, 20)));
+ assertTrue(intRange.isOverlappedBy(Range.between(9, 20)));
+
+ // negative
+ assertFalse(intRange.isOverlappedBy(Range.between(-11, -18)));
+
+ // outside range whole range
+ assertTrue(intRange.isOverlappedBy(Range.between(9, 21)));
+}
+
+ @Test
+ public void testIsStartedBy() {
+ assertFalse(intRange.isStartedBy(null));
+
+ assertFalse(intRange.isStartedBy(5));
+ assertTrue(intRange.isStartedBy(10));
+ assertFalse(intRange.isStartedBy(15));
+ assertFalse(intRange.isStartedBy(20));
+ assertFalse(intRange.isStartedBy(25));
+ }
+
+ @Test
+ public void testIsWithCompare() {
+ // all integers are equal
+ final Comparator<Integer> c = (o1, o2) -> 0;
+ Range<Integer> ri = Range.is(10);
+ assertFalse(ri.contains(null), "should not contain null");
+ assertTrue(ri.contains(10), "should contain 10");
+ assertFalse(ri.contains(11), "should not contain 11");
+ ri = Range.is(10, c);
+ assertFalse(ri.contains(null), "should not contain null");
+ assertTrue(ri.contains(10), "should contain 10");
+ assertTrue(ri.contains(11), "should contain 11");
+ }
+
+ @Test
+ public void testRangeOfChars() {
+ final Range<Character> chars = Range.between('a', 'z');
+ assertTrue(chars.contains('b'));
+ assertFalse(chars.contains('B'));
+ }
+
+ @Test
+ public void testSerializing() {
+ SerializationUtils.clone(intRange);
+ }
+
+ @Test
+ public void testToString() {
+ assertNotNull(byteRange.toString());
+
+ final String str = intRange.toString();
+ assertEquals("[10..20]", str);
+ assertEquals("[-20..-10]", Range.between(-20, -10).toString());
+ }
+
+ @Test
+ public void testToStringFormat() {
+ final String str = intRange.toString("From %1$s to %2$s");
+ assertEquals("From 10 to 20", str);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/RegExUtilsTest.java b/src/test/java/org/apache/commons/lang3/RegExUtilsTest.java
new file mode 100644
index 000000000..768c788b0
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/RegExUtilsTest.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for methods of {@link org.apache.commons.lang3.RegExUtils} which been moved to their own test classes.
+ */
+public class RegExUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testRemoveAll_StringPattern() {
+ assertNull(RegExUtils.removeAll(null, Pattern.compile("")));
+ assertEquals("any", RegExUtils.removeAll("any", (Pattern) null));
+
+ assertEquals("any", RegExUtils.removeAll("any", Pattern.compile("")));
+ assertEquals("", RegExUtils.removeAll("any", Pattern.compile(".*")));
+ assertEquals("", RegExUtils.removeAll("any", Pattern.compile(".+")));
+ assertEquals("", RegExUtils.removeAll("any", Pattern.compile(".?")));
+
+ assertEquals("A\nB", RegExUtils.removeAll("A<__>\n<__>B", Pattern.compile("<.*>")));
+ assertEquals("AB", RegExUtils.removeAll("A<__>\n<__>B", Pattern.compile("(?s)<.*>")));
+ assertEquals("ABC123", RegExUtils.removeAll("ABCabc123abc", Pattern.compile("[a-z]")));
+
+ assertEquals("AB", RegExUtils.removeAll("A<__>\n<__>B", Pattern.compile("<.*>", Pattern.DOTALL)));
+ assertEquals("AB", RegExUtils.removeAll("A<__>\\n<__>B", Pattern.compile("<.*>")));
+ assertEquals("", RegExUtils.removeAll("<A>x\\ny</A>", Pattern.compile("<A>.*</A>")));
+ assertEquals("", RegExUtils.removeAll("<A>\nxy\n</A>", Pattern.compile("<A>.*</A>", Pattern.DOTALL)));
+ }
+
+ @Test
+ public void testRemoveAll_StringString() {
+ assertNull(RegExUtils.removeAll(null, ""));
+ assertEquals("any", RegExUtils.removeAll("any", (String) null));
+
+ assertEquals("any", RegExUtils.removeAll("any", ""));
+ assertEquals("", RegExUtils.removeAll("any", ".*"));
+ assertEquals("", RegExUtils.removeAll("any", ".+"));
+ assertEquals("", RegExUtils.removeAll("any", ".?"));
+
+ assertEquals("A\nB", RegExUtils.removeAll("A<__>\n<__>B", "<.*>"));
+ assertEquals("AB", RegExUtils.removeAll("A<__>\n<__>B", "(?s)<.*>"));
+ assertEquals("ABC123", RegExUtils.removeAll("ABCabc123abc", "[a-z]"));
+
+ assertThrows(
+ PatternSyntaxException.class,
+ () -> RegExUtils.removeAll("any", "{badRegexSyntax}"),
+ "RegExUtils.removeAll expecting PatternSyntaxException");
+ }
+
+ @Test
+ public void testRemoveFirst_StringPattern() {
+ assertNull(RegExUtils.removeFirst(null, Pattern.compile("")));
+ assertEquals("any", RegExUtils.removeFirst("any", (Pattern) null));
+
+ assertEquals("any", RegExUtils.removeFirst("any", Pattern.compile("")));
+ assertEquals("", RegExUtils.removeFirst("any", Pattern.compile(".*")));
+ assertEquals("", RegExUtils.removeFirst("any", Pattern.compile(".+")));
+ assertEquals("bc", RegExUtils.removeFirst("abc", Pattern.compile(".?")));
+
+ assertEquals("A\n<__>B", RegExUtils.removeFirst("A<__>\n<__>B", Pattern.compile("<.*>")));
+ assertEquals("AB", RegExUtils.removeFirst("A<__>\n<__>B", Pattern.compile("(?s)<.*>")));
+ assertEquals("ABCbc123", RegExUtils.removeFirst("ABCabc123", Pattern.compile("[a-z]")));
+ assertEquals("ABC123abc", RegExUtils.removeFirst("ABCabc123abc", Pattern.compile("[a-z]+")));
+ }
+
+ @Test
+ public void testRemoveFirst_StringString() {
+ assertNull(RegExUtils.removeFirst(null, ""));
+ assertEquals("any", RegExUtils.removeFirst("any", (String) null));
+
+ assertEquals("any", RegExUtils.removeFirst("any", ""));
+ assertEquals("", RegExUtils.removeFirst("any", ".*"));
+ assertEquals("", RegExUtils.removeFirst("any", ".+"));
+ assertEquals("bc", RegExUtils.removeFirst("abc", ".?"));
+
+ assertEquals("A\n<__>B", RegExUtils.removeFirst("A<__>\n<__>B", "<.*>"));
+ assertEquals("AB", RegExUtils.removeFirst("A<__>\n<__>B", "(?s)<.*>"));
+ assertEquals("ABCbc123", RegExUtils.removeFirst("ABCabc123", "[a-z]"));
+ assertEquals("ABC123abc", RegExUtils.removeFirst("ABCabc123abc", "[a-z]+"));
+
+ assertThrows(
+ PatternSyntaxException.class,
+ () -> RegExUtils.removeFirst("any", "{badRegexSyntax}"),
+ "RegExUtils.removeFirst expecting PatternSyntaxException");
+ }
+
+ @Test
+ public void testRemovePattern_StringString() {
+ assertNull(RegExUtils.removePattern(null, ""));
+ assertEquals("any", RegExUtils.removePattern("any", (String) null));
+
+ assertEquals("", RegExUtils.removePattern("", ""));
+ assertEquals("", RegExUtils.removePattern("", ".*"));
+ assertEquals("", RegExUtils.removePattern("", ".+"));
+
+ assertEquals("AB", RegExUtils.removePattern("A<__>\n<__>B", "<.*>"));
+ assertEquals("AB", RegExUtils.removePattern("A<__>\\n<__>B", "<.*>"));
+ assertEquals("", RegExUtils.removePattern("<A>x\\ny</A>", "<A>.*</A>"));
+ assertEquals("", RegExUtils.removePattern("<A>\nxy\n</A>", "<A>.*</A>"));
+
+ assertEquals("ABC123", RegExUtils.removePattern("ABCabc123", "[a-z]"));
+ }
+
+ @Test
+ public void testReplaceAll_StringPatternString() {
+ assertNull(RegExUtils.replaceAll(null, Pattern.compile(""), ""));
+
+ assertEquals("any", RegExUtils.replaceAll("any", (Pattern) null, ""));
+ assertEquals("any", RegExUtils.replaceAll("any", Pattern.compile(""), null));
+
+ assertEquals("zzz", RegExUtils.replaceAll("", Pattern.compile(""), "zzz"));
+ assertEquals("zzz", RegExUtils.replaceAll("", Pattern.compile(".*"), "zzz"));
+ assertEquals("", RegExUtils.replaceAll("", Pattern.compile(".+"), "zzz"));
+ assertEquals("ZZaZZbZZcZZ", RegExUtils.replaceAll("abc", Pattern.compile(""), "ZZ"));
+
+ assertEquals("z\nz", RegExUtils.replaceAll("<__>\n<__>", Pattern.compile("<.*>"), "z"));
+ assertEquals("z", RegExUtils.replaceAll("<__>\n<__>", Pattern.compile("(?s)<.*>"), "z"));
+
+ assertEquals("z", RegExUtils.replaceAll("<__>\n<__>", Pattern.compile("<.*>", Pattern.DOTALL), "z"));
+ assertEquals("z", RegExUtils.replaceAll("<__>\\n<__>", Pattern.compile("<.*>"), "z"));
+ assertEquals("X", RegExUtils.replaceAll("<A>\nxy\n</A>", Pattern.compile("<A>.*</A>", Pattern.DOTALL), "X"));
+
+ assertEquals("ABC___123", RegExUtils.replaceAll("ABCabc123", Pattern.compile("[a-z]"), "_"));
+ assertEquals("ABC_123", RegExUtils.replaceAll("ABCabc123", Pattern.compile("[^A-Z0-9]+"), "_"));
+ assertEquals("ABC123", RegExUtils.replaceAll("ABCabc123", Pattern.compile("[^A-Z0-9]+"), ""));
+ assertEquals("Lorem_ipsum_dolor_sit",
+ RegExUtils.replaceAll("Lorem ipsum dolor sit", Pattern.compile("( +)([a-z]+)"), "_$2"));
+ }
+
+ @Test
+ public void testReplaceAll_StringStringString() {
+ assertNull(RegExUtils.replaceAll(null, "", ""));
+
+ assertEquals("any", RegExUtils.replaceAll("any", (String) null, ""));
+ assertEquals("any", RegExUtils.replaceAll("any", "", null));
+
+ assertEquals("zzz", RegExUtils.replaceAll("", "", "zzz"));
+ assertEquals("zzz", RegExUtils.replaceAll("", ".*", "zzz"));
+ assertEquals("", RegExUtils.replaceAll("", ".+", "zzz"));
+ assertEquals("ZZaZZbZZcZZ", RegExUtils.replaceAll("abc", "", "ZZ"));
+
+ assertEquals("z\nz", RegExUtils.replaceAll("<__>\n<__>", "<.*>", "z"));
+ assertEquals("z", RegExUtils.replaceAll("<__>\n<__>", "(?s)<.*>", "z"));
+
+ assertEquals("ABC___123", RegExUtils.replaceAll("ABCabc123", "[a-z]", "_"));
+ assertEquals("ABC_123", RegExUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", "_"));
+ assertEquals("ABC123", RegExUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", ""));
+ assertEquals("Lorem_ipsum_dolor_sit", RegExUtils.replaceAll("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2"));
+
+ assertThrows(
+ PatternSyntaxException.class,
+ () -> RegExUtils.replaceAll("any", "{badRegexSyntax}", ""),
+ "RegExUtils.replaceAll expecting PatternSyntaxException");
+ }
+
+ @Test
+ public void testReplaceFirst_StringPatternString() {
+ assertNull(RegExUtils.replaceFirst(null, Pattern.compile(""), ""));
+
+ assertEquals("any", RegExUtils.replaceFirst("any", (Pattern) null, ""));
+ assertEquals("any", RegExUtils.replaceFirst("any", Pattern.compile(""), null));
+
+ assertEquals("zzz", RegExUtils.replaceFirst("", Pattern.compile(""), "zzz"));
+ assertEquals("zzz", RegExUtils.replaceFirst("", Pattern.compile(".*"), "zzz"));
+ assertEquals("", RegExUtils.replaceFirst("", Pattern.compile(".+"), "zzz"));
+ assertEquals("ZZabc", RegExUtils.replaceFirst("abc", Pattern.compile(""), "ZZ"));
+
+ assertEquals("z\n<__>", RegExUtils.replaceFirst("<__>\n<__>", Pattern.compile("<.*>"), "z"));
+ assertEquals("z", RegExUtils.replaceFirst("<__>\n<__>", Pattern.compile("(?s)<.*>"), "z"));
+
+ assertEquals("ABC_bc123", RegExUtils.replaceFirst("ABCabc123", Pattern.compile("[a-z]"), "_"));
+ assertEquals("ABC_123abc", RegExUtils.replaceFirst("ABCabc123abc", Pattern.compile("[^A-Z0-9]+"), "_"));
+ assertEquals("ABC123abc", RegExUtils.replaceFirst("ABCabc123abc", Pattern.compile("[^A-Z0-9]+"), ""));
+ assertEquals("Lorem_ipsum dolor sit",
+ RegExUtils.replaceFirst("Lorem ipsum dolor sit", Pattern.compile("( +)([a-z]+)"), "_$2"));
+ }
+
+ @Test
+ public void testReplaceFirst_StringStringString() {
+ assertNull(RegExUtils.replaceFirst(null, "", ""));
+
+ assertEquals("any", RegExUtils.replaceFirst("any", (String) null, ""));
+ assertEquals("any", RegExUtils.replaceFirst("any", "", null));
+
+ assertEquals("zzz", RegExUtils.replaceFirst("", "", "zzz"));
+ assertEquals("zzz", RegExUtils.replaceFirst("", ".*", "zzz"));
+ assertEquals("", RegExUtils.replaceFirst("", ".+", "zzz"));
+ assertEquals("ZZabc", RegExUtils.replaceFirst("abc", "", "ZZ"));
+
+ assertEquals("z\n<__>", RegExUtils.replaceFirst("<__>\n<__>", "<.*>", "z"));
+ assertEquals("z", RegExUtils.replaceFirst("<__>\n<__>", "(?s)<.*>", "z"));
+
+ assertEquals("ABC_bc123", RegExUtils.replaceFirst("ABCabc123", "[a-z]", "_"));
+ assertEquals("ABC_123abc", RegExUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", "_"));
+ assertEquals("ABC123abc", RegExUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", ""));
+ assertEquals("Lorem_ipsum dolor sit",
+ RegExUtils.replaceFirst("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2"));
+
+ assertThrows(
+ PatternSyntaxException.class,
+ () -> RegExUtils.replaceFirst("any", "{badRegexSyntax}", ""),
+ "RegExUtils.replaceFirst expecting PatternSyntaxException");
+ }
+
+ @Test
+ public void testReplacePattern_StringStringString() {
+ assertNull(RegExUtils.replacePattern(null, "", ""));
+ assertEquals("any", RegExUtils.replacePattern("any", (String) null, ""));
+ assertEquals("any", RegExUtils.replacePattern("any", "", null));
+
+ assertEquals("zzz", RegExUtils.replacePattern("", "", "zzz"));
+ assertEquals("zzz", RegExUtils.replacePattern("", ".*", "zzz"));
+ assertEquals("", RegExUtils.replacePattern("", ".+", "zzz"));
+
+ assertEquals("z", RegExUtils.replacePattern("<__>\n<__>", "<.*>", "z"));
+ assertEquals("z", RegExUtils.replacePattern("<__>\\n<__>", "<.*>", "z"));
+ assertEquals("X", RegExUtils.replacePattern("<A>\nxy\n</A>", "<A>.*</A>", "X"));
+
+ assertEquals("ABC___123", RegExUtils.replacePattern("ABCabc123", "[a-z]", "_"));
+ assertEquals("ABC_123", RegExUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "_"));
+ assertEquals("ABC123", RegExUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", ""));
+ assertEquals("Lorem_ipsum_dolor_sit",
+ RegExUtils.replacePattern("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/SerializationUtilsTest.java b/src/test/java/org/apache/commons/lang3/SerializationUtilsTest.java
new file mode 100644
index 000000000..f31610343
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/SerializationUtilsTest.java
@@ -0,0 +1,368 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+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.OutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.SerializationUtils}.
+ */
+public class SerializationUtilsTest extends AbstractLangTest {
+
+ static final String CLASS_NOT_FOUND_MESSAGE = "ClassNotFoundSerialization.readObject fake exception";
+ protected static final String SERIALIZE_IO_EXCEPTION_MESSAGE = "Anonymous OutputStream I/O exception";
+
+ private String iString;
+ private Integer iInteger;
+ private HashMap<Object, Object> iMap;
+
+ @BeforeEach
+ public void setUp() {
+ iString = "foo";
+ iInteger = Integer.valueOf(7);
+ iMap = new HashMap<>();
+ iMap.put("FOO", iString);
+ iMap.put("BAR", iInteger);
+ }
+
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new SerializationUtils());
+ final Constructor<?>[] cons = SerializationUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(SerializationUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(SerializationUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testException() {
+ SerializationException serEx;
+ final Exception ex = new Exception();
+
+ serEx = new SerializationException();
+ assertSame(null, serEx.getMessage());
+ assertSame(null, serEx.getCause());
+
+ serEx = new SerializationException("Message");
+ assertSame("Message", serEx.getMessage());
+ assertSame(null, serEx.getCause());
+
+ serEx = new SerializationException(ex);
+ assertEquals("java.lang.Exception", serEx.getMessage());
+ assertSame(ex, serEx.getCause());
+
+ serEx = new SerializationException("Message", ex);
+ assertSame("Message", serEx.getMessage());
+ assertSame(ex, serEx.getCause());
+ }
+
+
+ @Test
+ public void testSerializeStream() throws Exception {
+ final ByteArrayOutputStream streamTest = new ByteArrayOutputStream();
+ SerializationUtils.serialize(iMap, streamTest);
+
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(iMap);
+ oos.flush();
+ }
+
+ final byte[] testBytes = streamTest.toByteArray();
+ final byte[] realBytes = streamReal.toByteArray();
+ assertEquals(testBytes.length, realBytes.length);
+ assertArrayEquals(realBytes, testBytes);
+ }
+
+ @Test
+ public void testSerializeStreamUnserializable() {
+ final ByteArrayOutputStream streamTest = new ByteArrayOutputStream();
+ iMap.put(new Object(), new Object());
+ assertThrows(SerializationException.class, () -> SerializationUtils.serialize(iMap, streamTest));
+ }
+
+ @Test
+ public void testSerializeStreamNullObj() throws Exception {
+ final ByteArrayOutputStream streamTest = new ByteArrayOutputStream();
+ SerializationUtils.serialize(null, streamTest);
+
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(null);
+ oos.flush();
+ }
+
+ final byte[] testBytes = streamTest.toByteArray();
+ final byte[] realBytes = streamReal.toByteArray();
+ assertEquals(testBytes.length, realBytes.length);
+ assertArrayEquals(realBytes, testBytes);
+ }
+
+ @Test
+ public void testSerializeStreamObjNull() {
+ assertThrows(NullPointerException.class, () -> SerializationUtils.serialize(iMap, null));
+ }
+
+ @Test
+ public void testSerializeStreamNullNull() {
+ assertThrows(NullPointerException.class, () -> SerializationUtils.serialize(null, null));
+ }
+
+ @Test
+ public void testSerializeIOException() {
+ // forces an IOException when the ObjectOutputStream is created, to test not closing the stream
+ // in the finally block
+ final OutputStream streamTest = new OutputStream() {
+ @Override
+ public void write(final int arg0) throws IOException {
+ throw new IOException(SERIALIZE_IO_EXCEPTION_MESSAGE);
+ }
+ };
+ final SerializationException e =
+ assertThrows(SerializationException.class, () -> SerializationUtils.serialize(iMap, streamTest));
+ assertEquals("java.io.IOException: " + SERIALIZE_IO_EXCEPTION_MESSAGE, e.getMessage());
+ }
+
+
+ @Test
+ public void testDeserializeStream() throws Exception {
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(iMap);
+ oos.flush();
+ }
+
+ final ByteArrayInputStream inTest = new ByteArrayInputStream(streamReal.toByteArray());
+ final Object test = SerializationUtils.deserialize(inTest);
+ assertNotNull(test);
+ assertTrue(test instanceof HashMap<?, ?>);
+ assertNotSame(test, iMap);
+ final HashMap<?, ?> testMap = (HashMap<?, ?>) test;
+ assertEquals(iString, testMap.get("FOO"));
+ assertNotSame(iString, testMap.get("FOO"));
+ assertEquals(iInteger, testMap.get("BAR"));
+ assertNotSame(iInteger, testMap.get("BAR"));
+ assertEquals(iMap, testMap);
+ }
+
+ @Test
+ public void testDeserializeClassCastException() {
+ final String value = "Hello";
+ final byte[] serialized = SerializationUtils.serialize(value);
+ assertEquals(value, SerializationUtils.deserialize(serialized));
+ assertThrows(ClassCastException.class, () -> {
+ // Causes ClassCastException in call site, not in SerializationUtils.deserialize
+ @SuppressWarnings("unused") // needed to cause Exception
+ final Integer i = SerializationUtils.deserialize(serialized);
+ });
+ }
+
+ @Test
+ public void testDeserializeStreamOfNull() throws Exception {
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(null);
+ oos.flush();
+ }
+
+ final ByteArrayInputStream inTest = new ByteArrayInputStream(streamReal.toByteArray());
+ final Object test = SerializationUtils.deserialize(inTest);
+ assertNull(test);
+ }
+
+ @Test
+ public void testDeserializeStreamNull() {
+ assertThrows(NullPointerException.class, () -> SerializationUtils.deserialize((InputStream) null));
+ }
+
+ @Test
+ public void testDeserializeStreamBadStream() {
+ assertThrows(SerializationException.class,
+ () -> SerializationUtils.deserialize(new ByteArrayInputStream(new byte[0])));
+ }
+
+ @Test
+ public void testDeserializeStreamClassNotFound() throws Exception {
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(new ClassNotFoundSerialization());
+ oos.flush();
+ }
+
+ final ByteArrayInputStream inTest = new ByteArrayInputStream(streamReal.toByteArray());
+ final SerializationException se = assertThrows(SerializationException.class, () -> SerializationUtils.deserialize(inTest));
+ assertEquals("java.lang.ClassNotFoundException: " + CLASS_NOT_FOUND_MESSAGE, se.getMessage());
+ }
+
+ @Test
+ public void testRoundtrip() {
+ final HashMap<Object, Object> newMap = SerializationUtils.roundtrip(iMap);
+ assertEquals(iMap, newMap);
+ }
+
+ @Test
+ public void testSerializeBytes() throws Exception {
+ final byte[] testBytes = SerializationUtils.serialize(iMap);
+
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(iMap);
+ oos.flush();
+ }
+
+ final byte[] realBytes = streamReal.toByteArray();
+ assertEquals(testBytes.length, realBytes.length);
+ assertArrayEquals(realBytes, testBytes);
+ }
+
+ @Test
+ public void testSerializeBytesUnserializable() {
+ iMap.put(new Object(), new Object());
+ assertThrows(SerializationException.class, () -> SerializationUtils.serialize(iMap));
+ }
+
+ @Test
+ public void testSerializeBytesNull() throws Exception {
+ final byte[] testBytes = SerializationUtils.serialize(null);
+
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(null);
+ oos.flush();
+ }
+
+ final byte[] realBytes = streamReal.toByteArray();
+ assertEquals(testBytes.length, realBytes.length);
+ assertArrayEquals(realBytes, testBytes);
+ }
+
+
+ @Test
+ public void testDeserializeBytes() throws Exception {
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(iMap);
+ oos.flush();
+ }
+
+ final Object test = SerializationUtils.deserialize(streamReal.toByteArray());
+ assertNotNull(test);
+ assertTrue(test instanceof HashMap<?, ?>);
+ assertNotSame(test, iMap);
+ final HashMap<?, ?> testMap = (HashMap<?, ?>) test;
+ assertEquals(iString, testMap.get("FOO"));
+ assertNotSame(iString, testMap.get("FOO"));
+ assertEquals(iInteger, testMap.get("BAR"));
+ assertNotSame(iInteger, testMap.get("BAR"));
+ assertEquals(iMap, testMap);
+ }
+
+ @Test
+ public void testDeserializeBytesOfNull() throws Exception {
+ final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
+ oos.writeObject(null);
+ oos.flush();
+ }
+
+ final Object test = SerializationUtils.deserialize(streamReal.toByteArray());
+ assertNull(test);
+ }
+
+ @Test
+ public void testDeserializeBytesNull() {
+ assertThrows(NullPointerException.class, () -> SerializationUtils.deserialize((byte[]) null));
+ }
+
+ @Test
+ public void testDeserializeBytesBadStream() {
+ assertThrows(SerializationException.class, () -> SerializationUtils.deserialize(new byte[0]));
+ }
+
+
+ @Test
+ public void testClone() {
+ final Object test = SerializationUtils.clone(iMap);
+ assertNotNull(test);
+ assertTrue(test instanceof HashMap<?, ?>);
+ assertNotSame(test, iMap);
+ final HashMap<?, ?> testMap = (HashMap<?, ?>) test;
+ assertEquals(iString, testMap.get("FOO"));
+ assertNotSame(iString, testMap.get("FOO"));
+ assertEquals(iInteger, testMap.get("BAR"));
+ assertNotSame(iInteger, testMap.get("BAR"));
+ assertEquals(iMap, testMap);
+ }
+
+ @Test
+ public void testCloneNull() {
+ final Object test = SerializationUtils.clone(null);
+ assertNull(test);
+ }
+
+ @Test
+ public void testCloneUnserializable() {
+ iMap.put(new Object(), new Object());
+ assertThrows(SerializationException.class, () -> SerializationUtils.clone(iMap));
+ }
+
+ @Test
+ public void testPrimitiveTypeClassSerialization() {
+ final Class<?>[] primitiveTypes = { byte.class, short.class, int.class, long.class, float.class, double.class,
+ boolean.class, char.class, void.class };
+
+ for (final Class<?> primitiveType : primitiveTypes) {
+ final Class<?> clone = SerializationUtils.clone(primitiveType);
+ assertEquals(primitiveType, clone);
+ }
+ }
+
+}
+
+class ClassNotFoundSerialization implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private void readObject(final ObjectInputStream in) throws ClassNotFoundException {
+ throw new ClassNotFoundException(SerializationUtilsTest.CLASS_NOT_FOUND_MESSAGE);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/StreamsTest.java b/src/test/java/org/apache/commons/lang3/StreamsTest.java
new file mode 100644
index 000000000..fc6d30681
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StreamsTest.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.Functions.FailableConsumer;
+import org.apache.commons.lang3.Functions.FailablePredicate;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.function.Executable;
+import org.xml.sax.SAXException;
+
+public class StreamsTest extends AbstractLangTest {
+
+ @Test
+ public void testSimpleStreamMap() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ final List<Integer> output = Functions.stream(input).map(Integer::valueOf).collect(Collectors.toList());
+ assertEquals(6, output.size());
+ for (int i = 0; i < 6; i++) {
+ assertEquals(i+1, output.get(i).intValue());
+ }
+ }
+
+ @Test
+ public void testSimpleStreamMapFailing() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4 ", "5", "6");
+ final Executable testMethod = () -> Functions.stream(input).map(Integer::valueOf).collect(Collectors.toList());
+ final NumberFormatException thrown = assertThrows(NumberFormatException.class, testMethod);
+ assertEquals("For input string: \"4 \"", thrown.getMessage());
+ }
+
+ @Test
+ public void testSimpleStreamForEach() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ final List<Integer> output = new ArrayList<>();
+ Functions.stream(input).forEach(s -> output.add(Integer.valueOf(s)));
+ assertEquals(6, output.size());
+ for (int i = 0; i < 6; i++) {
+ assertEquals(i+1, output.get(i).intValue());
+ }
+ }
+
+ @Test
+ public void testToArray() {
+ final String[] array = Arrays.asList("2", "3", "1").stream().collect(Streams.toArray(String.class));
+ assertNotNull(array);
+ assertEquals(3, array.length);
+ assertEquals("2", array[0]);
+ assertEquals("3", array[1]);
+ assertEquals("1", array[2]);
+ }
+
+ protected <T extends Throwable> FailableConsumer<String, T> asIntConsumer(final T pThrowable) {
+ return s -> {
+ final int i = Integer.parseInt(s);
+ if (i == 4) {
+ throw pThrowable;
+ }
+ };
+ }
+
+ @TestFactory
+ public Stream<DynamicTest> simpleStreamForEachFailing() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+
+ return Stream.of(
+
+ dynamicTest("IllegalArgumentException", () -> {
+ final IllegalArgumentException ise = new IllegalArgumentException();
+ final Executable testMethod = () -> Functions.stream(input)
+ .forEach(asIntConsumer(ise));
+ final IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, testMethod);
+ assertThat(thrown.getMessage(), is(nullValue()));
+ }),
+
+ dynamicTest("OutOfMemoryError", () -> {
+ final OutOfMemoryError oome = new OutOfMemoryError();
+ final Executable oomeTestMethod = () -> Functions.stream(input)
+ .forEach(asIntConsumer(oome));
+ final OutOfMemoryError oomeThrown = assertThrows(OutOfMemoryError.class, oomeTestMethod);
+ assertThat(oomeThrown.getMessage(), is(nullValue()));
+ }),
+
+ dynamicTest("SAXException", () -> {
+ final SAXException se = new SAXException();
+ final Executable seTestMethod = () -> Functions.stream(input)
+ .forEach(asIntConsumer(se));
+ final UndeclaredThrowableException seThrown = assertThrows(UndeclaredThrowableException.class, seTestMethod);
+ assertAll(
+ () -> assertThat(seThrown.getMessage(), is(nullValue())),
+ () -> assertThat(seThrown.getCause(), is(equalTo(se)))
+ );
+ })
+ );
+ }
+
+ @Test
+ public void testSimpleStreamFilter() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ final List<Integer> output = Functions.stream(input)
+ .map(Integer::valueOf)
+ .filter(i -> (i.intValue() %2 == 0))
+ .collect(Collectors.toList());
+ assertEvenNumbers(output);
+ }
+
+ private void assertEvenNumbers(final List<Integer> output) {
+ assertEquals(3, output.size());
+ for (int i = 0; i < 3; i++) {
+ assertEquals((i+1)*2, output.get(i).intValue());
+ }
+ }
+
+ protected <T extends Throwable> FailablePredicate<Integer, T> asIntPredicate(final T pThrowable) {
+ return i -> {
+ if (i.intValue() == 5 && pThrowable != null) {
+ throw pThrowable;
+ }
+ return i%2==0;
+ };
+ }
+
+ @TestFactory
+ public Stream<DynamicTest> simpleStreamFilterFailing() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ final List<Integer> output = Functions.stream(input)
+ .map(Integer::valueOf)
+ .filter(asIntPredicate(null))
+ .collect(Collectors.toList());
+ assertEvenNumbers(output);
+
+ return Stream.of(
+
+ dynamicTest("IllegalArgumentException", () -> {
+ final IllegalArgumentException iae = new IllegalArgumentException("Invalid argument: " + 5);
+ final Executable testMethod = () -> Functions.stream(input)
+ .map(Integer::valueOf)
+ .filter(asIntPredicate(iae))
+ .collect(Collectors.toList());
+ final IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, testMethod);
+ assertThat(thrown.getMessage(), is(equalTo("Invalid argument: " + 5)));
+ }),
+
+ dynamicTest("OutOfMemoryError", () -> {
+ final OutOfMemoryError oome = new OutOfMemoryError();
+ final Executable testMethod = () -> Functions.stream(input)
+ .map(Integer::valueOf)
+ .filter(asIntPredicate(oome))
+ .collect(Collectors.toList());
+ final OutOfMemoryError thrown = assertThrows(OutOfMemoryError.class, testMethod);
+ assertThat(thrown.getMessage(), is(nullValue()));
+ }),
+
+ dynamicTest("SAXException", () -> {
+ final SAXException se = new SAXException();
+ final Executable testMethod = () -> Functions.stream(input)
+ .map(Integer::valueOf)
+ .filter(asIntPredicate(se))
+ .collect(Collectors.toList());
+ final UndeclaredThrowableException thrown = assertThrows(UndeclaredThrowableException.class, testMethod);
+ assertAll(
+ () -> assertThat(thrown.getMessage(), is(nullValue())),
+ () -> assertThat(thrown.getCause(), is(equalTo(se)))
+ );
+ })
+ );
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java b/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
new file mode 100644
index 000000000..87e12e108
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
@@ -0,0 +1,573 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
+import org.apache.commons.lang3.text.translate.NumericEntityEscaper;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link StringEscapeUtils}.
+ */
+@Deprecated
+public class StringEscapeUtilsTest extends AbstractLangTest {
+ private static final String FOO = "foo";
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new StringEscapeUtils());
+ final Constructor<?>[] cons = StringEscapeUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(StringEscapeUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(StringEscapeUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testEscapeJava() throws IOException {
+ assertNull(StringEscapeUtils.escapeJava(null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.ESCAPE_JAVA.translate(null, null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.ESCAPE_JAVA.translate("", null));
+
+ assertEscapeJava("empty string", "", "");
+ assertEscapeJava(FOO, FOO);
+ assertEscapeJava("tab", "\\t", "\t");
+ assertEscapeJava("backslash", "\\\\", "\\");
+ assertEscapeJava("single quote should not be escaped", "'", "'");
+ assertEscapeJava("\\\\\\b\\t\\r", "\\\b\t\r");
+ assertEscapeJava("\\u1234", "\u1234");
+ assertEscapeJava("\\u0234", "\u0234");
+ assertEscapeJava("\\u00EF", "\u00ef");
+ assertEscapeJava("\\u0001", "\u0001");
+ assertEscapeJava("Should use capitalized Unicode hex", "\\uABCD", "\uabcd");
+
+ assertEscapeJava("He didn't say, \\\"stop!\\\"",
+ "He didn't say, \"stop!\"");
+ assertEscapeJava("non-breaking space", "This space is non-breaking:" + "\\u00A0",
+ "This space is non-breaking:\u00a0");
+ assertEscapeJava("\\uABCD\\u1234\\u012C",
+ "\uABCD\u1234\u012C");
+ }
+
+ /**
+ * Tests https://issues.apache.org/jira/browse/LANG-421
+ */
+ @Test
+ public void testEscapeJavaWithSlash() {
+ final String input = "String with a slash (/) in it";
+
+ final String expected = input;
+ final String actual = StringEscapeUtils.escapeJava(input);
+
+ /*
+ * In 2.4 StringEscapeUtils.escapeJava(String) escapes '/' characters, which are not a valid character to escape
+ * in a Java string.
+ */
+ assertEquals(expected, actual);
+ }
+
+ private void assertEscapeJava(final String escaped, final String original) throws IOException {
+ assertEscapeJava(null, escaped, original);
+ }
+
+ private void assertEscapeJava(String message, final String expected, final String original) throws IOException {
+ final String converted = StringEscapeUtils.escapeJava(original);
+ message = "escapeJava(String) failed" + (message == null ? "" : (": " + message));
+ assertEquals(expected, converted, message);
+
+ final StringWriter writer = new StringWriter();
+ StringEscapeUtils.ESCAPE_JAVA.translate(original, writer);
+ assertEquals(expected, writer.toString());
+ }
+
+ @Test
+ public void testUnescapeJava() throws IOException {
+ assertNull(StringEscapeUtils.unescapeJava(null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.UNESCAPE_JAVA.translate(null, null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.UNESCAPE_JAVA.translate("", null));
+ assertThrows(RuntimeException.class, () -> StringEscapeUtils.unescapeJava("\\u02-3"));
+
+ assertUnescapeJava("", "");
+ assertUnescapeJava("test", "test");
+ assertUnescapeJava("\ntest\b", "\\ntest\\b");
+ assertUnescapeJava("\u123425foo\ntest\b", "\\u123425foo\\ntest\\b");
+ assertUnescapeJava("'\foo\teste\r", "\\'\\foo\\teste\\r");
+ assertUnescapeJava("", "\\");
+ //foo
+ assertUnescapeJava("lowercase Unicode", "\uABCDx", "\\uabcdx");
+ assertUnescapeJava("uppercase Unicode", "\uABCDx", "\\uABCDx");
+ assertUnescapeJava("Unicode as final character", "\uABCD", "\\uabcd");
+ }
+
+ private void assertUnescapeJava(final String unescaped, final String original) throws IOException {
+ assertUnescapeJava(null, unescaped, original);
+ }
+
+ private void assertUnescapeJava(final String message, final String unescaped, final String original) throws IOException {
+ final String expected = unescaped;
+ final String actual = StringEscapeUtils.unescapeJava(original);
+
+ assertEquals(expected, actual,
+ "unescape(String) failed" +
+ (message == null ? "" : (": " + message)) +
+ ": expected '" + StringEscapeUtils.escapeJava(expected) +
+ // we escape this so we can see it in the error message
+ "' actual '" + StringEscapeUtils.escapeJava(actual) + "'");
+
+ final StringWriter writer = new StringWriter();
+ StringEscapeUtils.UNESCAPE_JAVA.translate(original, writer);
+ assertEquals(unescaped, writer.toString());
+
+ }
+
+ @Test
+ public void testEscapeEcmaScript() {
+ assertNull(StringEscapeUtils.escapeEcmaScript(null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.ESCAPE_ECMASCRIPT.translate(null, null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.ESCAPE_ECMASCRIPT.translate("", null));
+
+ assertEquals("He didn\\'t say, \\\"stop!\\\"", StringEscapeUtils.escapeEcmaScript("He didn't say, \"stop!\""));
+ assertEquals("document.getElementById(\\\"test\\\").value = \\'<script>alert(\\'aaa\\');<\\/script>\\';",
+ StringEscapeUtils.escapeEcmaScript("document.getElementById(\"test\").value = '<script>alert('aaa');</script>';"));
+ }
+
+ @Test
+ public void testUnescapeEcmaScript() {
+ assertNull(StringEscapeUtils.escapeEcmaScript(null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.UNESCAPE_ECMASCRIPT.translate(null, null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.UNESCAPE_ECMASCRIPT.translate("", null));
+
+ assertEquals("He didn't say, \"stop!\"", StringEscapeUtils.unescapeEcmaScript("He didn\\'t say, \\\"stop!\\\""));
+ assertEquals("document.getElementById(\"test\").value = '<script>alert('aaa');</script>';",
+ StringEscapeUtils.unescapeEcmaScript("document.getElementById(\\\"test\\\").value = \\'<script>alert(\\'aaa\\');<\\/script>\\';"));
+ }
+
+
+ /** HTML and XML */
+ private static final String[][] HTML_ESCAPES = {
+ {"no escaping", "plain text", "plain text"},
+ {"no escaping", "plain text", "plain text"},
+ {"empty string", "", ""},
+ {"null", null, null},
+ {"ampersand", "bread &amp; butter", "bread & butter"},
+ {"quotes", "&quot;bread&quot; &amp; butter", "\"bread\" & butter"},
+ {"final character only", "greater than &gt;", "greater than >"},
+ {"first character only", "&lt; less than", "< less than"},
+ {"apostrophe", "Huntington's chorea", "Huntington's chorea"},
+ {"languages", "English,Fran&ccedil;ais,\u65E5\u672C\u8A9E (nihongo)", "English,Fran\u00E7ais,\u65E5\u672C\u8A9E (nihongo)"},
+ {"8-bit ascii shouldn't number-escape", "\u0080\u009F", "\u0080\u009F"},
+ };
+
+ @Test
+ public void testEscapeHtml() throws IOException {
+ for (final String[] element : HTML_ESCAPES) {
+ final String message = element[0];
+ final String expected = element[1];
+ final String original = element[2];
+ assertEquals(expected, StringEscapeUtils.escapeHtml4(original), message);
+ final StringWriter sw = new StringWriter();
+ StringEscapeUtils.ESCAPE_HTML4.translate(original, sw);
+ final String actual = original == null ? null : sw.toString();
+ assertEquals(expected, actual, message);
+ }
+ }
+
+ @Test
+ public void testUnescapeHtml4() throws IOException {
+ for (final String[] element : HTML_ESCAPES) {
+ final String message = element[0];
+ final String expected = element[2];
+ final String original = element[1];
+ assertEquals(expected, StringEscapeUtils.unescapeHtml4(original), message);
+
+ final StringWriter sw = new StringWriter();
+ StringEscapeUtils.UNESCAPE_HTML4.translate(original, sw);
+ final String actual = original == null ? null : sw.toString();
+ assertEquals(expected, actual, message);
+ }
+ // \u00E7 is a cedilla (c with wiggle under)
+ // note that the test string must be 7-bit-clean (Unicode escaped) or else it will compile incorrectly
+ // on some locales
+ assertEquals("Fran\u00E7ais", StringEscapeUtils.unescapeHtml4("Fran\u00E7ais"), "funny chars pass through OK");
+
+ assertEquals("Hello&;World", StringEscapeUtils.unescapeHtml4("Hello&;World"));
+ assertEquals("Hello&#;World", StringEscapeUtils.unescapeHtml4("Hello&#;World"));
+ assertEquals("Hello&# ;World", StringEscapeUtils.unescapeHtml4("Hello&# ;World"));
+ assertEquals("Hello&##;World", StringEscapeUtils.unescapeHtml4("Hello&##;World"));
+ }
+
+ @Test
+ public void testUnescapeHexCharsHtml() {
+ // Simple easy to grok test
+ assertEquals("\u0080\u009F", StringEscapeUtils.unescapeHtml4("&#x80;&#x9F;"), "hex number unescape");
+ assertEquals("\u0080\u009F", StringEscapeUtils.unescapeHtml4("&#X80;&#X9F;"), "hex number unescape");
+ // Test all Character values:
+ for (char i = Character.MIN_VALUE; i < Character.MAX_VALUE; i++) {
+ final Character c1 = Character.valueOf(i);
+ final Character c2 = Character.valueOf((char) (i+1));
+ final String expected = c1.toString() + c2.toString();
+ final String escapedC1 = "&#x" + Integer.toHexString((c1.charValue())) + ";";
+ final String escapedC2 = "&#x" + Integer.toHexString((c2.charValue())) + ";";
+ assertEquals(expected, StringEscapeUtils.unescapeHtml4(escapedC1 + escapedC2), "hex number unescape index " + (int) i);
+ }
+ }
+
+ @Test
+ public void testUnescapeUnknownEntity() {
+ assertEquals("&zzzz;", StringEscapeUtils.unescapeHtml4("&zzzz;"));
+ }
+
+ @Test
+ public void testEscapeHtmlVersions() {
+ assertEquals("&Beta;", StringEscapeUtils.escapeHtml4("\u0392"));
+ assertEquals("\u0392", StringEscapeUtils.unescapeHtml4("&Beta;"));
+
+ // TODO: refine API for escaping/unescaping specific HTML versions
+ }
+
+ @Test
+ public void testEscapeXml() throws Exception {
+ assertEquals("&lt;abc&gt;", StringEscapeUtils.escapeXml("<abc>"));
+ assertEquals("<abc>", StringEscapeUtils.unescapeXml("&lt;abc&gt;"));
+
+ assertEquals("\u00A1", StringEscapeUtils.escapeXml("\u00A1"), "XML should not escape >0x7f values");
+ assertEquals("\u00A0", StringEscapeUtils.unescapeXml("&#160;"), "XML should be able to unescape >0x7f values");
+ assertEquals("\u00A0", StringEscapeUtils.unescapeXml("&#0160;"),
+ "XML should be able to unescape >0x7f values with one leading 0");
+ assertEquals("\u00A0", StringEscapeUtils.unescapeXml("&#00160;"),
+ "XML should be able to unescape >0x7f values with two leading 0s");
+ assertEquals("\u00A0", StringEscapeUtils.unescapeXml("&#000160;"),
+ "XML should be able to unescape >0x7f values with three leading 0s");
+
+ assertEquals("ain't", StringEscapeUtils.unescapeXml("ain&apos;t"));
+ assertEquals("ain&apos;t", StringEscapeUtils.escapeXml("ain't"));
+ assertEquals("", StringEscapeUtils.escapeXml(""));
+ assertNull(StringEscapeUtils.escapeXml(null));
+ assertNull(StringEscapeUtils.unescapeXml(null));
+
+ StringWriter sw = new StringWriter();
+ StringEscapeUtils.ESCAPE_XML.translate("<abc>", sw);
+ assertEquals("&lt;abc&gt;", sw.toString(), "XML was escaped incorrectly");
+
+ sw = new StringWriter();
+ StringEscapeUtils.UNESCAPE_XML.translate("&lt;abc&gt;", sw);
+ assertEquals("<abc>", sw.toString(), "XML was unescaped incorrectly");
+ }
+
+ @Test
+ public void testEscapeXml10() {
+ assertEquals("a&lt;b&gt;c&quot;d&apos;e&amp;f", StringEscapeUtils.escapeXml10("a<b>c\"d'e&f"));
+ assertEquals("a\tb\rc\nd", StringEscapeUtils.escapeXml10("a\tb\rc\nd"), "XML 1.0 should not escape \t \n \r");
+ assertEquals("ab", StringEscapeUtils.escapeXml10("a\u0000\u0001\u0008\u000b\u000c\u000e\u001fb"),
+ "XML 1.0 should omit most #x0-x8 | #xb | #xc | #xe-#x19");
+ assertEquals("a\ud7ff \ue000b", StringEscapeUtils.escapeXml10("a\ud7ff\ud800 \udfff \ue000b"),
+ "XML 1.0 should omit #xd800-#xdfff");
+ assertEquals("a\ufffdb", StringEscapeUtils.escapeXml10("a\ufffd\ufffe\uffffb"),
+ "XML 1.0 should omit #xfffe | #xffff");
+ assertEquals("a\u007e&#127;&#132;\u0085&#134;&#159;\u00a0b",
+ StringEscapeUtils.escapeXml10("a\u007e\u007f\u0084\u0085\u0086\u009f\u00a0b"),
+ "XML 1.0 should escape #x7f-#x84 | #x86 - #x9f, for XML 1.1 compatibility");
+ }
+
+ @Test
+ public void testEscapeXml11() {
+ assertEquals("a&lt;b&gt;c&quot;d&apos;e&amp;f", StringEscapeUtils.escapeXml11("a<b>c\"d'e&f"));
+ assertEquals("a\tb\rc\nd", StringEscapeUtils.escapeXml11("a\tb\rc\nd"), "XML 1.1 should not escape \t \n \r");
+ assertEquals("ab", StringEscapeUtils.escapeXml11("a\u0000b"), "XML 1.1 should omit #x0");
+ assertEquals("a&#1;&#8;&#11;&#12;&#14;&#31;b",
+ StringEscapeUtils.escapeXml11("a\u0001\u0008\u000b\u000c\u000e\u001fb"),
+ "XML 1.1 should escape #x1-x8 | #xb | #xc | #xe-#x19");
+ assertEquals("a\u007e&#127;&#132;\u0085&#134;&#159;\u00a0b",
+ StringEscapeUtils.escapeXml11("a\u007e\u007f\u0084\u0085\u0086\u009f\u00a0b"),
+ "XML 1.1 should escape #x7F-#x84 | #x86-#x9F");
+ assertEquals("a\ud7ff \ue000b", StringEscapeUtils.escapeXml11("a\ud7ff\ud800 \udfff \ue000b"),
+ "XML 1.1 should omit #xd800-#xdfff");
+ assertEquals("a\ufffdb", StringEscapeUtils.escapeXml11("a\ufffd\ufffe\uffffb"),
+ "XML 1.1 should omit #xfffe | #xffff");
+ }
+
+ /**
+ * Tests Supplementary characters.
+ * <p>
+ * From https://www.w3.org/International/questions/qa-escapes
+ * </p>
+ * <blockquote>
+ * Supplementary characters are those Unicode characters that have code points higher than the characters in
+ * the Basic Multilingual Plane (BMP). In UTF-16 a supplementary character is encoded using two 16-bit surrogate code points from the
+ * BMP. Because of this, some people think that supplementary characters need to be represented using two escapes, but this is incorrect
+ * - you must use the single, code point value for that character. For example, use &amp;&#35;x233B4&#59; rather than
+ * &amp;&#35;xD84C&#59;&amp;&#35;xDFB4&#59;.
+ * </blockquote>
+ * @see <a href="https://www.w3.org/International/questions/qa-escapes">Using character escapes in markup and CSS</a>
+ * @see <a href="https://issues.apache.org/jira/browse/LANG-728">LANG-728</a>
+ */
+ @Test
+ public void testEscapeXmlSupplementaryCharacters() {
+ final CharSequenceTranslator escapeXml =
+ StringEscapeUtils.ESCAPE_XML.with( NumericEntityEscaper.between(0x7f, Integer.MAX_VALUE) );
+
+ assertEquals("&#144308;", escapeXml.translate("\uD84C\uDFB4"),
+ "Supplementary character must be represented using a single escape");
+
+ assertEquals("a b c &#144308;", escapeXml.translate("a b c \uD84C\uDFB4"),
+ "Supplementary characters mixed with basic characters should be encoded correctly");
+ }
+
+ @Test
+ public void testEscapeXmlAllCharacters() {
+ // https://www.w3.org/TR/xml/#charsets says:
+ // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character,
+ // excluding the surrogate blocks, FFFE, and FFFF. */
+ final CharSequenceTranslator escapeXml = StringEscapeUtils.ESCAPE_XML
+ .with(NumericEntityEscaper.below(9), NumericEntityEscaper.between(0xB, 0xC), NumericEntityEscaper.between(0xE, 0x19),
+ NumericEntityEscaper.between(0xD800, 0xDFFF), NumericEntityEscaper.between(0xFFFE, 0xFFFF), NumericEntityEscaper.above(0x110000));
+
+ assertEquals("&#0;&#1;&#2;&#3;&#4;&#5;&#6;&#7;&#8;", escapeXml.translate("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008"));
+ assertEquals("\t", escapeXml.translate("\t")); // 0x9
+ assertEquals("\n", escapeXml.translate("\n")); // 0xA
+ assertEquals("&#11;&#12;", escapeXml.translate("\u000B\u000C"));
+ assertEquals("\r", escapeXml.translate("\r")); // 0xD
+ assertEquals("Hello World! Ain&apos;t this great?", escapeXml.translate("Hello World! Ain't this great?"));
+ assertEquals("&#14;&#15;&#24;&#25;", escapeXml.translate("\u000E\u000F\u0018\u0019"));
+ }
+
+ /**
+ * Reverse of the above.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/LANG-729">LANG-729</a>
+ */
+ @Test
+ public void testUnescapeXmlSupplementaryCharacters() {
+ assertEquals("\uD84C\uDFB4", StringEscapeUtils.unescapeXml("&#144308;"),
+ "Supplementary character must be represented using a single escape");
+
+ assertEquals("a b c \uD84C\uDFB4", StringEscapeUtils.unescapeXml("a b c &#144308;"),
+ "Supplementary characters mixed with basic characters should be decoded correctly");
+ }
+
+ // Tests issue LANG-150
+ // https://issues.apache.org/jira/browse/LANG-150
+ @Test
+ public void testStandaloneAmphersand() {
+ assertEquals("<P&O>", StringEscapeUtils.unescapeHtml4("&lt;P&O&gt;"));
+ assertEquals("test & <", StringEscapeUtils.unescapeHtml4("test & &lt;"));
+ assertEquals("<P&O>", StringEscapeUtils.unescapeXml("&lt;P&O&gt;"));
+ assertEquals("test & <", StringEscapeUtils.unescapeXml("test & &lt;"));
+ }
+
+ @Test
+ public void testLang313() {
+ assertEquals("& &", StringEscapeUtils.unescapeHtml4("& &amp;"));
+ }
+
+ @Test
+ public void testEscapeCsvString() {
+ assertEquals("foo.bar", StringEscapeUtils.escapeCsv("foo.bar"));
+ assertEquals("\"foo,bar\"", StringEscapeUtils.escapeCsv("foo,bar"));
+ assertEquals("\"foo\nbar\"", StringEscapeUtils.escapeCsv("foo\nbar"));
+ assertEquals("\"foo\rbar\"", StringEscapeUtils.escapeCsv("foo\rbar"));
+ assertEquals("\"foo\"\"bar\"", StringEscapeUtils.escapeCsv("foo\"bar"));
+ assertEquals("foo\uD84C\uDFB4bar", StringEscapeUtils.escapeCsv("foo\uD84C\uDFB4bar"));
+ assertEquals("", StringEscapeUtils.escapeCsv(""));
+ assertNull(StringEscapeUtils.escapeCsv(null));
+ }
+
+ @Test
+ public void testEscapeCsvWriter() throws Exception {
+ checkCsvEscapeWriter("foo.bar", "foo.bar");
+ checkCsvEscapeWriter("\"foo,bar\"", "foo,bar");
+ checkCsvEscapeWriter("\"foo\nbar\"", "foo\nbar");
+ checkCsvEscapeWriter("\"foo\rbar\"", "foo\rbar");
+ checkCsvEscapeWriter("\"foo\"\"bar\"", "foo\"bar");
+ checkCsvEscapeWriter("foo\uD84C\uDFB4bar", "foo\uD84C\uDFB4bar");
+ checkCsvEscapeWriter("", null);
+ checkCsvEscapeWriter("", "");
+ }
+
+ private void checkCsvEscapeWriter(final String expected, final String value) throws IOException {
+ final StringWriter writer = new StringWriter();
+ StringEscapeUtils.ESCAPE_CSV.translate(value, writer);
+ assertEquals(expected, writer.toString());
+ }
+
+ @Test
+ public void testEscapeCsvIllegalStateException() {
+ final StringWriter writer = new StringWriter();
+ assertThrows(IllegalStateException.class, () -> StringEscapeUtils.ESCAPE_CSV.translate("foo", -1, writer));
+ }
+
+ @Test
+ public void testUnescapeCsvString() {
+ assertEquals("foo.bar", StringEscapeUtils.unescapeCsv("foo.bar"));
+ assertEquals("foo,bar", StringEscapeUtils.unescapeCsv("\"foo,bar\""));
+ assertEquals("foo\nbar", StringEscapeUtils.unescapeCsv("\"foo\nbar\""));
+ assertEquals("foo\rbar", StringEscapeUtils.unescapeCsv("\"foo\rbar\""));
+ assertEquals("foo\"bar", StringEscapeUtils.unescapeCsv("\"foo\"\"bar\""));
+ assertEquals("foo\uD84C\uDFB4bar", StringEscapeUtils.unescapeCsv("foo\uD84C\uDFB4bar"));
+ assertEquals("", StringEscapeUtils.unescapeCsv(""));
+ assertNull(StringEscapeUtils.unescapeCsv(null));
+
+ assertEquals("\"foo.bar\"", StringEscapeUtils.unescapeCsv("\"foo.bar\""));
+ }
+
+ @Test
+ public void testUnescapeCsvWriter() throws Exception {
+ checkCsvUnescapeWriter("foo.bar", "foo.bar");
+ checkCsvUnescapeWriter("foo,bar", "\"foo,bar\"");
+ checkCsvUnescapeWriter("foo\nbar", "\"foo\nbar\"");
+ checkCsvUnescapeWriter("foo\rbar", "\"foo\rbar\"");
+ checkCsvUnescapeWriter("foo\"bar", "\"foo\"\"bar\"");
+ checkCsvUnescapeWriter("foo\uD84C\uDFB4bar", "foo\uD84C\uDFB4bar");
+ checkCsvUnescapeWriter("", null);
+ checkCsvUnescapeWriter("", "");
+
+ checkCsvUnescapeWriter("\"foo.bar\"", "\"foo.bar\"");
+ }
+
+ private void checkCsvUnescapeWriter(final String expected, final String value) throws IOException {
+ final StringWriter writer = new StringWriter();
+ StringEscapeUtils.UNESCAPE_CSV.translate(value, writer);
+ assertEquals(expected, writer.toString());
+ }
+
+ @Test
+ public void testUnescapeCsvIllegalStateException() {
+ final StringWriter writer = new StringWriter();
+ assertThrows(IllegalStateException.class, () -> StringEscapeUtils.UNESCAPE_CSV.translate("foo", -1, writer));
+ }
+
+ /**
+ * Tests // https://issues.apache.org/jira/browse/LANG-480
+ */
+ @Test
+ public void testEscapeHtmlHighUnicode() {
+ // this is the utf8 representation of the character:
+ // COUNTING ROD UNIT DIGIT THREE
+ // in Unicode
+ // code point: U+1D362
+ final byte[] data = { (byte) 0xF0, (byte) 0x9D, (byte) 0x8D, (byte) 0xA2 };
+
+ final String original = new String(data, StandardCharsets.UTF_8);
+
+ final String escaped = StringEscapeUtils.escapeHtml4( original );
+ assertEquals(original, escaped, "High Unicode should not have been escaped");
+
+ final String unescaped = StringEscapeUtils.unescapeHtml4( escaped );
+ assertEquals(original, unescaped, "High Unicode should have been unchanged");
+
+// TODO: I think this should hold, needs further investigation
+// String unescapedFromEntity = StringEscapeUtils.unescapeHtml4( "&#119650;" );
+// assertEquals( "High Unicode should have been unescaped", original, unescapedFromEntity);
+ }
+
+ /**
+ * Tests https://issues.apache.org/jira/browse/LANG-339
+ */
+ @Test
+ public void testEscapeHiragana() {
+ // Some random Japanese Unicode characters
+ final String original = "\u304B\u304C\u3068";
+ final String escaped = StringEscapeUtils.escapeHtml4(original);
+ assertEquals(original, escaped,
+ "Hiragana character Unicode behavior should not be being escaped by escapeHtml4");
+
+ final String unescaped = StringEscapeUtils.unescapeHtml4( escaped );
+
+ assertEquals(escaped, unescaped, "Hiragana character Unicode behavior has changed - expected no unescaping");
+ }
+
+ /**
+ * Tests https://issues.apache.org/jira/browse/LANG-708
+ *
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ @Test
+ public void testLang708() throws IOException {
+ final byte[] inputBytes = Files.readAllBytes(Paths.get("src/test/resources/lang-708-input.txt"));
+ final String input = new String(inputBytes, StandardCharsets.UTF_8);
+ final String escaped = StringEscapeUtils.escapeEcmaScript(input);
+ // just the end:
+ assertTrue(escaped.endsWith("}]"), escaped);
+ // a little more:
+ assertTrue(escaped.endsWith("\"valueCode\\\":\\\"\\\"}]"), escaped);
+ }
+
+ /**
+ * Tests https://issues.apache.org/jira/browse/LANG-720
+ */
+ @Test
+ public void testLang720() {
+ final String input = "\ud842\udfb7" + "A";
+ final String escaped = StringEscapeUtils.escapeXml(input);
+ assertEquals(input, escaped);
+ }
+
+ /**
+ * Tests https://issues.apache.org/jira/browse/LANG-911
+ */
+ @Test
+ public void testLang911() {
+ final String bellsTest = "\ud83d\udc80\ud83d\udd14";
+ final String value = StringEscapeUtils.escapeJava(bellsTest);
+ final String valueTest = StringEscapeUtils.unescapeJava(value);
+ assertEquals(bellsTest, valueTest);
+ }
+
+ @Test
+ public void testEscapeJson() {
+ assertNull(StringEscapeUtils.escapeJson(null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.ESCAPE_JSON.translate(null, null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.ESCAPE_JSON.translate("", null));
+
+ assertEquals("He didn't say, \\\"stop!\\\"", StringEscapeUtils.escapeJson("He didn't say, \"stop!\""));
+
+ final String expected = "\\\"foo\\\" isn't \\\"bar\\\". specials: \\b\\r\\n\\f\\t\\\\\\/";
+ final String input ="\"foo\" isn't \"bar\". specials: \b\r\n\f\t\\/";
+
+ assertEquals(expected, StringEscapeUtils.escapeJson(input));
+ }
+
+ @Test
+ public void testUnescapeJson() {
+ assertNull(StringEscapeUtils.unescapeJson(null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.UNESCAPE_JSON.translate(null, null));
+ assertThrows(NullPointerException.class, () -> StringEscapeUtils.UNESCAPE_JSON.translate("", null));
+
+ assertEquals("He didn't say, \"stop!\"", StringEscapeUtils.unescapeJson("He didn't say, \\\"stop!\\\""));
+
+ final String expected ="\"foo\" isn't \"bar\". specials: \b\r\n\f\t\\/";
+ final String input = "\\\"foo\\\" isn't \\\"bar\\\". specials: \\b\\r\\n\\f\\t\\\\\\/";
+
+ assertEquals(expected, StringEscapeUtils.unescapeJson(input));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsContainsTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsContainsTest.java
new file mode 100644
index 000000000..b152a8df2
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsContainsTest.java
@@ -0,0 +1,467 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.apache.commons.lang3.Supplementary.CharU20000;
+import static org.apache.commons.lang3.Supplementary.CharU20001;
+import static org.apache.commons.lang3.Supplementary.CharUSuppCharHigh;
+import static org.apache.commons.lang3.Supplementary.CharUSuppCharLow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Locale;
+
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.DefaultLocale;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.StringUtils} - Contains methods
+ */
+public class StringUtilsContainsTest extends AbstractLangTest {
+ @Test
+ public void testContains_Char() {
+ assertFalse(StringUtils.contains(null, ' '));
+ assertFalse(StringUtils.contains("", ' '));
+ assertFalse(StringUtils.contains("", null));
+ assertFalse(StringUtils.contains(null, null));
+ assertTrue(StringUtils.contains("abc", 'a'));
+ assertTrue(StringUtils.contains("abc", 'b'));
+ assertTrue(StringUtils.contains("abc", 'c'));
+ assertFalse(StringUtils.contains("abc", 'z'));
+ }
+
+ @Test
+ public void testContains_String() {
+ assertFalse(StringUtils.contains(null, null));
+ assertFalse(StringUtils.contains(null, ""));
+ assertFalse(StringUtils.contains(null, "a"));
+ assertFalse(StringUtils.contains("", null));
+ assertTrue(StringUtils.contains("", ""));
+ assertFalse(StringUtils.contains("", "a"));
+ assertTrue(StringUtils.contains("abc", "a"));
+ assertTrue(StringUtils.contains("abc", "b"));
+ assertTrue(StringUtils.contains("abc", "c"));
+ assertTrue(StringUtils.contains("abc", "abc"));
+ assertFalse(StringUtils.contains("abc", "z"));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContains_StringWithBadSupplementaryChars() {
+ // Test edge case: 1/2 of a (broken) supplementary char
+ assertFalse(StringUtils.contains(CharUSuppCharHigh, CharU20001));
+ assertFalse(StringUtils.contains(CharUSuppCharLow, CharU20001));
+ assertFalse(StringUtils.contains(CharU20001, CharUSuppCharHigh));
+ assertEquals(0, CharU20001.indexOf(CharUSuppCharLow));
+ assertTrue(StringUtils.contains(CharU20001, CharUSuppCharLow));
+ assertTrue(StringUtils.contains(CharU20001 + CharUSuppCharLow + "a", "a"));
+ assertTrue(StringUtils.contains(CharU20001 + CharUSuppCharHigh + "a", "a"));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContains_StringWithSupplementaryChars() {
+ assertTrue(StringUtils.contains(CharU20000 + CharU20001, CharU20000));
+ assertTrue(StringUtils.contains(CharU20000 + CharU20001, CharU20001));
+ assertTrue(StringUtils.contains(CharU20000, CharU20000));
+ assertFalse(StringUtils.contains(CharU20000, CharU20001));
+ }
+
+ @Test
+ public void testContainsAny_StringCharArray() {
+ assertFalse(StringUtils.containsAny(null, (char[]) null));
+ assertFalse(StringUtils.containsAny(null, new char[0]));
+ assertFalse(StringUtils.containsAny(null, 'a', 'b'));
+
+ assertFalse(StringUtils.containsAny("", (char[]) null));
+ assertFalse(StringUtils.containsAny("", new char[0]));
+ assertFalse(StringUtils.containsAny("", 'a', 'b'));
+
+ assertFalse(StringUtils.containsAny("zzabyycdxx", (char[]) null));
+ assertFalse(StringUtils.containsAny("zzabyycdxx", new char[0]));
+ assertTrue(StringUtils.containsAny("zzabyycdxx", 'z', 'a'));
+ assertTrue(StringUtils.containsAny("zzabyycdxx", 'b', 'y'));
+ assertTrue(StringUtils.containsAny("zzabyycdxx", 'z', 'y'));
+ assertFalse(StringUtils.containsAny("ab", 'z'));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContainsAny_StringCharArrayWithBadSupplementaryChars() {
+ // Test edge case: 1/2 of a (broken) supplementary char
+ assertFalse(StringUtils.containsAny(CharUSuppCharHigh, CharU20001.toCharArray()));
+ assertFalse(StringUtils.containsAny("abc" + CharUSuppCharHigh + "xyz", CharU20001.toCharArray()));
+ assertEquals(-1, CharUSuppCharLow.indexOf(CharU20001));
+ assertFalse(StringUtils.containsAny(CharUSuppCharLow, CharU20001.toCharArray()));
+ assertFalse(StringUtils.containsAny(CharU20001, CharUSuppCharHigh.toCharArray()));
+ assertEquals(0, CharU20001.indexOf(CharUSuppCharLow));
+ assertTrue(StringUtils.containsAny(CharU20001, CharUSuppCharLow.toCharArray()));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContainsAny_StringCharArrayWithSupplementaryChars() {
+ assertTrue(StringUtils.containsAny(CharU20000 + CharU20001, CharU20000.toCharArray()));
+ assertTrue(StringUtils.containsAny("a" + CharU20000 + CharU20001, "a".toCharArray()));
+ assertTrue(StringUtils.containsAny(CharU20000 + "a" + CharU20001, "a".toCharArray()));
+ assertTrue(StringUtils.containsAny(CharU20000 + CharU20001 + "a", "a".toCharArray()));
+ assertTrue(StringUtils.containsAny(CharU20000 + CharU20001, CharU20001.toCharArray()));
+ assertTrue(StringUtils.containsAny(CharU20000, CharU20000.toCharArray()));
+ // Sanity check:
+ assertEquals(-1, CharU20000.indexOf(CharU20001));
+ assertEquals(0, CharU20000.indexOf(CharU20001.charAt(0)));
+ assertEquals(-1, CharU20000.indexOf(CharU20001.charAt(1)));
+ // Test:
+ assertFalse(StringUtils.containsAny(CharU20000, CharU20001.toCharArray()));
+ assertFalse(StringUtils.containsAny(CharU20001, CharU20000.toCharArray()));
+ }
+
+ @Test
+ public void testContainsAny_StringString() {
+ assertFalse(StringUtils.containsAny(null, (String) null));
+ assertFalse(StringUtils.containsAny(null, ""));
+ assertFalse(StringUtils.containsAny(null, "ab"));
+
+ assertFalse(StringUtils.containsAny("", (String) null));
+ assertFalse(StringUtils.containsAny("", ""));
+ assertFalse(StringUtils.containsAny("", "ab"));
+
+ assertFalse(StringUtils.containsAny("zzabyycdxx", (String) null));
+ assertFalse(StringUtils.containsAny("zzabyycdxx", ""));
+ assertTrue(StringUtils.containsAny("zzabyycdxx", "za"));
+ assertTrue(StringUtils.containsAny("zzabyycdxx", "by"));
+ assertTrue(StringUtils.containsAny("zzabyycdxx", "zy"));
+ assertFalse(StringUtils.containsAny("ab", "z"));
+ }
+
+ @Test
+ public void testContainsAny_StringStringArray() {
+ assertFalse(StringUtils.containsAny(null, (String[]) null));
+ assertFalse(StringUtils.containsAny(null, new String[0]));
+ assertFalse(StringUtils.containsAny(null, new String[] { "hello" }));
+ assertFalse(StringUtils.containsAny("", (String[]) null));
+ assertFalse(StringUtils.containsAny("", new String[0]));
+ assertFalse(StringUtils.containsAny("", new String[] { "hello" }));
+ assertFalse(StringUtils.containsAny("hello, goodbye", (String[]) null));
+ assertFalse(StringUtils.containsAny("hello, goodbye", new String[0]));
+ assertTrue(StringUtils.containsAny("hello, goodbye", new String[]{"hello", "goodbye"}));
+ assertTrue(StringUtils.containsAny("hello, goodbye", new String[]{"hello", "Goodbye"}));
+ assertFalse(StringUtils.containsAny("hello, goodbye", new String[]{"Hello", "Goodbye"}));
+ assertFalse(StringUtils.containsAny("hello, goodbye", new String[]{"Hello", null}));
+ assertFalse(StringUtils.containsAny("hello, null", new String[] { "Hello", null }));
+ // Javadoc examples:
+ assertTrue(StringUtils.containsAny("abcd", "ab", null));
+ assertTrue(StringUtils.containsAny("abcd", "ab", "cd"));
+ assertTrue(StringUtils.containsAny("abc", "d", "abc"));
+ }
+
+ @Test
+ public void testContainsAnyIgnoreCase_StringStringArray() {
+ assertFalse(StringUtils.containsAnyIgnoreCase(null, (String[]) null));
+ assertFalse(StringUtils.containsAnyIgnoreCase(null, new String[0]));
+ assertFalse(StringUtils.containsAnyIgnoreCase(null, new String[] { "hello" }));
+ assertFalse(StringUtils.containsAnyIgnoreCase("", (String[]) null));
+ assertFalse(StringUtils.containsAnyIgnoreCase("", new String[0]));
+ assertFalse(StringUtils.containsAnyIgnoreCase("", new String[] { "hello" }));
+ assertFalse(StringUtils.containsAnyIgnoreCase("hello, goodbye", (String[]) null));
+ assertFalse(StringUtils.containsAnyIgnoreCase("hello, goodbye", new String[0]));
+ assertTrue(StringUtils.containsAnyIgnoreCase("hello, goodbye", new String[]{"hello", "goodbye"}));
+ assertTrue(StringUtils.containsAnyIgnoreCase("hello, goodbye", new String[]{"hello", "Goodbye"}));
+ assertTrue(StringUtils.containsAnyIgnoreCase("hello, goodbye", new String[]{"Hello", "Goodbye"}));
+ assertTrue(StringUtils.containsAnyIgnoreCase("hello, goodbye", new String[]{"Hello", null}));
+ assertTrue(StringUtils.containsAnyIgnoreCase("hello, null", new String[] { "Hello", null }));
+ // Javadoc examples:
+ assertTrue(StringUtils.containsAnyIgnoreCase("abcd", "ab", null));
+ assertTrue(StringUtils.containsAnyIgnoreCase("abcd", "ab", "cd"));
+ assertTrue(StringUtils.containsAnyIgnoreCase("abc", "d", "abc"));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContainsAny_StringWithBadSupplementaryChars() {
+ // Test edge case: 1/2 of a (broken) supplementary char
+ assertFalse(StringUtils.containsAny(CharUSuppCharHigh, CharU20001));
+ assertEquals(-1, CharUSuppCharLow.indexOf(CharU20001));
+ assertFalse(StringUtils.containsAny(CharUSuppCharLow, CharU20001));
+ assertFalse(StringUtils.containsAny(CharU20001, CharUSuppCharHigh));
+ assertEquals(0, CharU20001.indexOf(CharUSuppCharLow));
+ assertTrue(StringUtils.containsAny(CharU20001, CharUSuppCharLow));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContainsAny_StringWithSupplementaryChars() {
+ assertTrue(StringUtils.containsAny(CharU20000 + CharU20001, CharU20000));
+ assertTrue(StringUtils.containsAny(CharU20000 + CharU20001, CharU20001));
+ assertTrue(StringUtils.containsAny(CharU20000, CharU20000));
+ // Sanity check:
+ assertEquals(-1, CharU20000.indexOf(CharU20001));
+ assertEquals(0, CharU20000.indexOf(CharU20001.charAt(0)));
+ assertEquals(-1, CharU20000.indexOf(CharU20001.charAt(1)));
+ // Test:
+ assertFalse(StringUtils.containsAny(CharU20000, CharU20001));
+ assertFalse(StringUtils.containsAny(CharU20001, CharU20000));
+ }
+
+ @DefaultLocale(language = "de", country = "DE")
+ @Test
+ public void testContainsIgnoreCase_LocaleIndependence() {
+ final Locale[] locales = { Locale.ENGLISH, new Locale("tr"), Locale.getDefault() };
+
+ final String[][] tdata = { { "i", "I" }, { "I", "i" }, { "\u03C2", "\u03C3" }, { "\u03A3", "\u03C2" },
+ { "\u03A3", "\u03C3" }, };
+
+ final String[][] fdata = { { "\u00DF", "SS" }, };
+
+ for (final Locale testLocale : locales) {
+ Locale.setDefault(testLocale);
+ for (int j = 0; j < tdata.length; j++) {
+ assertTrue(StringUtils.containsIgnoreCase(tdata[j][0], tdata[j][1]),
+ Locale.getDefault() + ": " + j + " " + tdata[j][0] + " " + tdata[j][1]);
+ }
+ for (int j = 0; j < fdata.length; j++) {
+ assertFalse(StringUtils.containsIgnoreCase(fdata[j][0], fdata[j][1]),
+ Locale.getDefault() + ": " + j + " " + fdata[j][0] + " " + fdata[j][1]);
+ }
+ }
+ }
+
+ @Test
+ public void testContainsIgnoreCase_StringString() {
+ assertFalse(StringUtils.containsIgnoreCase(null, null));
+
+ // Null tests
+ assertFalse(StringUtils.containsIgnoreCase(null, ""));
+ assertFalse(StringUtils.containsIgnoreCase(null, "a"));
+ assertFalse(StringUtils.containsIgnoreCase(null, "abc"));
+
+ assertFalse(StringUtils.containsIgnoreCase("", null));
+ assertFalse(StringUtils.containsIgnoreCase("a", null));
+ assertFalse(StringUtils.containsIgnoreCase("abc", null));
+
+ // Match len = 0
+ assertTrue(StringUtils.containsIgnoreCase("", ""));
+ assertTrue(StringUtils.containsIgnoreCase("a", ""));
+ assertTrue(StringUtils.containsIgnoreCase("abc", ""));
+
+ // Match len = 1
+ assertFalse(StringUtils.containsIgnoreCase("", "a"));
+ assertTrue(StringUtils.containsIgnoreCase("a", "a"));
+ assertTrue(StringUtils.containsIgnoreCase("abc", "a"));
+ assertFalse(StringUtils.containsIgnoreCase("", "A"));
+ assertTrue(StringUtils.containsIgnoreCase("a", "A"));
+ assertTrue(StringUtils.containsIgnoreCase("abc", "A"));
+
+ // Match len > 1
+ assertFalse(StringUtils.containsIgnoreCase("", "abc"));
+ assertFalse(StringUtils.containsIgnoreCase("a", "abc"));
+ assertTrue(StringUtils.containsIgnoreCase("xabcz", "abc"));
+ assertFalse(StringUtils.containsIgnoreCase("", "ABC"));
+ assertFalse(StringUtils.containsIgnoreCase("a", "ABC"));
+ assertTrue(StringUtils.containsIgnoreCase("xabcz", "ABC"));
+ }
+
+ @Test
+ public void testContainsNone_CharArray() {
+ final String str1 = "a";
+ final String str2 = "b";
+ final String str3 = "ab.";
+ final char[] chars1= {'b'};
+ final char[] chars2= {'.'};
+ final char[] chars3= {'c', 'd'};
+ final char[] emptyChars = {};
+ assertTrue(StringUtils.containsNone(null, (char[]) null));
+ assertTrue(StringUtils.containsNone("", (char[]) null));
+ assertTrue(StringUtils.containsNone(null, emptyChars));
+ assertTrue(StringUtils.containsNone(str1, emptyChars));
+ assertTrue(StringUtils.containsNone("", emptyChars));
+ assertTrue(StringUtils.containsNone("", chars1));
+ assertTrue(StringUtils.containsNone(str1, chars1));
+ assertTrue(StringUtils.containsNone(str1, chars2));
+ assertTrue(StringUtils.containsNone(str1, chars3));
+ assertFalse(StringUtils.containsNone(str2, chars1));
+ assertTrue(StringUtils.containsNone(str2, chars2));
+ assertTrue(StringUtils.containsNone(str2, chars3));
+ assertFalse(StringUtils.containsNone(str3, chars1));
+ assertFalse(StringUtils.containsNone(str3, chars2));
+ assertTrue(StringUtils.containsNone(str3, chars3));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContainsNone_CharArrayWithBadSupplementaryChars() {
+ // Test edge case: 1/2 of a (broken) supplementary char
+ assertTrue(StringUtils.containsNone(CharUSuppCharHigh, CharU20001.toCharArray()));
+ assertEquals(-1, CharUSuppCharLow.indexOf(CharU20001));
+ assertTrue(StringUtils.containsNone(CharUSuppCharLow, CharU20001.toCharArray()));
+ assertEquals(-1, CharU20001.indexOf(CharUSuppCharHigh));
+ assertTrue(StringUtils.containsNone(CharU20001, CharUSuppCharHigh.toCharArray()));
+ assertEquals(0, CharU20001.indexOf(CharUSuppCharLow));
+ assertFalse(StringUtils.containsNone(CharU20001, CharUSuppCharLow.toCharArray()));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContainsNone_CharArrayWithSupplementaryChars() {
+ assertFalse(StringUtils.containsNone(CharU20000 + CharU20001, CharU20000.toCharArray()));
+ assertFalse(StringUtils.containsNone(CharU20000 + CharU20001, CharU20001.toCharArray()));
+ assertFalse(StringUtils.containsNone(CharU20000, CharU20000.toCharArray()));
+ // Sanity check:
+ assertEquals(-1, CharU20000.indexOf(CharU20001));
+ assertEquals(0, CharU20000.indexOf(CharU20001.charAt(0)));
+ assertEquals(-1, CharU20000.indexOf(CharU20001.charAt(1)));
+ // Test:
+ assertTrue(StringUtils.containsNone(CharU20000, CharU20001.toCharArray()));
+ assertTrue(StringUtils.containsNone(CharU20001, CharU20000.toCharArray()));
+ }
+
+ @Test
+ public void testContainsNone_String() {
+ final String str1 = "a";
+ final String str2 = "b";
+ final String str3 = "ab.";
+ final String chars1= "b";
+ final String chars2= ".";
+ final String chars3= "cd";
+ assertTrue(StringUtils.containsNone(null, (String) null));
+ assertTrue(StringUtils.containsNone("", (String) null));
+ assertTrue(StringUtils.containsNone(null, ""));
+ assertTrue(StringUtils.containsNone(str1, ""));
+ assertTrue(StringUtils.containsNone("", ""));
+ assertTrue(StringUtils.containsNone("", chars1));
+ assertTrue(StringUtils.containsNone(str1, chars1));
+ assertTrue(StringUtils.containsNone(str1, chars2));
+ assertTrue(StringUtils.containsNone(str1, chars3));
+ assertFalse(StringUtils.containsNone(str2, chars1));
+ assertTrue(StringUtils.containsNone(str2, chars2));
+ assertTrue(StringUtils.containsNone(str2, chars3));
+ assertFalse(StringUtils.containsNone(str3, chars1));
+ assertFalse(StringUtils.containsNone(str3, chars2));
+ assertTrue(StringUtils.containsNone(str3, chars3));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContainsNone_StringWithBadSupplementaryChars() {
+ // Test edge case: 1/2 of a (broken) supplementary char
+ assertTrue(StringUtils.containsNone(CharUSuppCharHigh, CharU20001));
+ assertEquals(-1, CharUSuppCharLow.indexOf(CharU20001));
+ assertTrue(StringUtils.containsNone(CharUSuppCharLow, CharU20001));
+ assertEquals(-1, CharU20001.indexOf(CharUSuppCharHigh));
+ assertTrue(StringUtils.containsNone(CharU20001, CharUSuppCharHigh));
+ assertEquals(0, CharU20001.indexOf(CharUSuppCharLow));
+ assertFalse(StringUtils.containsNone(CharU20001, CharUSuppCharLow));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testContainsNone_StringWithSupplementaryChars() {
+ assertFalse(StringUtils.containsNone(CharU20000 + CharU20001, CharU20000));
+ assertFalse(StringUtils.containsNone(CharU20000 + CharU20001, CharU20001));
+ assertFalse(StringUtils.containsNone(CharU20000, CharU20000));
+ // Sanity check:
+ assertEquals(-1, CharU20000.indexOf(CharU20001));
+ assertEquals(0, CharU20000.indexOf(CharU20001.charAt(0)));
+ assertEquals(-1, CharU20000.indexOf(CharU20001.charAt(1)));
+ // Test:
+ assertTrue(StringUtils.containsNone(CharU20000, CharU20001));
+ assertTrue(StringUtils.containsNone(CharU20001, CharU20000));
+ }
+
+ @Test
+ public void testContainsOnly_CharArray() {
+ final String str1 = "a";
+ final String str2 = "b";
+ final String str3 = "ab";
+ final char[] chars1= {'b'};
+ final char[] chars2= {'a'};
+ final char[] chars3= {'a', 'b'};
+ final char[] emptyChars = {};
+ assertFalse(StringUtils.containsOnly(null, (char[]) null));
+ assertFalse(StringUtils.containsOnly("", (char[]) null));
+ assertFalse(StringUtils.containsOnly(null, emptyChars));
+ assertFalse(StringUtils.containsOnly(str1, emptyChars));
+ assertTrue(StringUtils.containsOnly("", emptyChars));
+ assertTrue(StringUtils.containsOnly("", chars1));
+ assertFalse(StringUtils.containsOnly(str1, chars1));
+ assertTrue(StringUtils.containsOnly(str1, chars2));
+ assertTrue(StringUtils.containsOnly(str1, chars3));
+ assertTrue(StringUtils.containsOnly(str2, chars1));
+ assertFalse(StringUtils.containsOnly(str2, chars2));
+ assertTrue(StringUtils.containsOnly(str2, chars3));
+ assertFalse(StringUtils.containsOnly(str3, chars1));
+ assertFalse(StringUtils.containsOnly(str3, chars2));
+ assertTrue(StringUtils.containsOnly(str3, chars3));
+ }
+
+ @Test
+ public void testContainsOnly_String() {
+ final String str1 = "a";
+ final String str2 = "b";
+ final String str3 = "ab";
+ final String chars1= "b";
+ final String chars2= "a";
+ final String chars3= "ab";
+ assertFalse(StringUtils.containsOnly(null, (String) null));
+ assertFalse(StringUtils.containsOnly("", (String) null));
+ assertFalse(StringUtils.containsOnly(null, ""));
+ assertFalse(StringUtils.containsOnly(str1, ""));
+ assertTrue(StringUtils.containsOnly("", ""));
+ assertTrue(StringUtils.containsOnly("", chars1));
+ assertFalse(StringUtils.containsOnly(str1, chars1));
+ assertTrue(StringUtils.containsOnly(str1, chars2));
+ assertTrue(StringUtils.containsOnly(str1, chars3));
+ assertTrue(StringUtils.containsOnly(str2, chars1));
+ assertFalse(StringUtils.containsOnly(str2, chars2));
+ assertTrue(StringUtils.containsOnly(str2, chars3));
+ assertFalse(StringUtils.containsOnly(str3, chars1));
+ assertFalse(StringUtils.containsOnly(str3, chars2));
+ assertTrue(StringUtils.containsOnly(str3, chars3));
+ }
+
+ @Test
+ public void testContainsWhitespace() {
+ assertFalse( StringUtils.containsWhitespace("") );
+ assertTrue( StringUtils.containsWhitespace(" ") );
+ assertFalse( StringUtils.containsWhitespace("a") );
+ assertTrue( StringUtils.containsWhitespace("a ") );
+ assertTrue( StringUtils.containsWhitespace(" a") );
+ assertTrue( StringUtils.containsWhitespace("a\t") );
+ assertTrue( StringUtils.containsWhitespace("\n") );
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java
new file mode 100644
index 000000000..68493989f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.StringUtils} - Empty/Blank methods
+ */
+public class StringUtilsEmptyBlankTest extends AbstractLangTest {
+
+ @Test
+ public void testIsEmpty() {
+ assertTrue(StringUtils.isEmpty(null));
+ assertTrue(StringUtils.isEmpty(""));
+ assertFalse(StringUtils.isEmpty(" "));
+ assertFalse(StringUtils.isEmpty("foo"));
+ assertFalse(StringUtils.isEmpty(" foo "));
+ }
+
+ @Test
+ public void testIsNotEmpty() {
+ assertFalse(StringUtils.isNotEmpty(null));
+ assertFalse(StringUtils.isNotEmpty(""));
+ assertTrue(StringUtils.isNotEmpty(" "));
+ assertTrue(StringUtils.isNotEmpty("foo"));
+ assertTrue(StringUtils.isNotEmpty(" foo "));
+ }
+
+ @Test
+ public void testIsAnyEmpty() {
+ assertTrue(StringUtils.isAnyEmpty((String) null));
+ assertFalse(StringUtils.isAnyEmpty((String[]) null));
+ assertTrue(StringUtils.isAnyEmpty(null, "foo"));
+ assertTrue(StringUtils.isAnyEmpty("", "bar"));
+ assertTrue(StringUtils.isAnyEmpty("bob", ""));
+ assertTrue(StringUtils.isAnyEmpty(" bob ", null));
+ assertFalse(StringUtils.isAnyEmpty(" ", "bar"));
+ assertFalse(StringUtils.isAnyEmpty("foo", "bar"));
+ }
+
+ @Test
+ public void testIsNoneEmpty() {
+ assertFalse(StringUtils.isNoneEmpty((String) null));
+ assertTrue(StringUtils.isNoneEmpty((String[]) null));
+ assertFalse(StringUtils.isNoneEmpty(null, "foo"));
+ assertFalse(StringUtils.isNoneEmpty("", "bar"));
+ assertFalse(StringUtils.isNoneEmpty("bob", ""));
+ assertFalse(StringUtils.isNoneEmpty(" bob ", null));
+ assertTrue(StringUtils.isNoneEmpty(" ", "bar"));
+ assertTrue(StringUtils.isNoneEmpty("foo", "bar"));
+ }
+
+ @Test
+ public void testIsAllEmpty() {
+ assertTrue(StringUtils.isAllEmpty());
+ assertTrue(StringUtils.isAllEmpty());
+ assertTrue(StringUtils.isAllEmpty((String) null));
+ assertTrue(StringUtils.isAllEmpty((String[]) null));
+ assertFalse(StringUtils.isAllEmpty(null, "foo"));
+ assertFalse(StringUtils.isAllEmpty("", "bar"));
+ assertFalse(StringUtils.isAllEmpty("bob", ""));
+ assertFalse(StringUtils.isAllEmpty(" bob ", null));
+ assertFalse(StringUtils.isAllEmpty(" ", "bar"));
+ assertFalse(StringUtils.isAllEmpty("foo", "bar"));
+ assertTrue(StringUtils.isAllEmpty("", null));
+ }
+
+ @Test
+ public void testIsBlank() {
+ assertTrue(StringUtils.isBlank(null));
+ assertTrue(StringUtils.isBlank(""));
+ assertTrue(StringUtils.isBlank(StringUtilsTest.WHITESPACE));
+ assertFalse(StringUtils.isBlank("foo"));
+ assertFalse(StringUtils.isBlank(" foo "));
+ }
+
+ @Test
+ public void testIsNotBlank() {
+ assertFalse(StringUtils.isNotBlank(null));
+ assertFalse(StringUtils.isNotBlank(""));
+ assertFalse(StringUtils.isNotBlank(StringUtilsTest.WHITESPACE));
+ assertTrue(StringUtils.isNotBlank("foo"));
+ assertTrue(StringUtils.isNotBlank(" foo "));
+ }
+
+ @Test
+ public void testIsAnyBlank() {
+ assertTrue(StringUtils.isAnyBlank((String) null));
+ assertFalse(StringUtils.isAnyBlank((String[]) null));
+ assertTrue(StringUtils.isAnyBlank(null, "foo"));
+ assertTrue(StringUtils.isAnyBlank(null, null));
+ assertTrue(StringUtils.isAnyBlank("", "bar"));
+ assertTrue(StringUtils.isAnyBlank("bob", ""));
+ assertTrue(StringUtils.isAnyBlank(" bob ", null));
+ assertTrue(StringUtils.isAnyBlank(" ", "bar"));
+ assertFalse(StringUtils.isAnyBlank("foo", "bar"));
+ }
+
+ @Test
+ public void testIsNoneBlank() {
+ assertFalse(StringUtils.isNoneBlank((String) null));
+ assertTrue(StringUtils.isNoneBlank((String[]) null));
+ assertFalse(StringUtils.isNoneBlank(null, "foo"));
+ assertFalse(StringUtils.isNoneBlank(null, null));
+ assertFalse(StringUtils.isNoneBlank("", "bar"));
+ assertFalse(StringUtils.isNoneBlank("bob", ""));
+ assertFalse(StringUtils.isNoneBlank(" bob ", null));
+ assertFalse(StringUtils.isNoneBlank(" ", "bar"));
+ assertTrue(StringUtils.isNoneBlank("foo", "bar"));
+ }
+
+ @Test
+ public void testIsAllBlank() {
+ assertTrue(StringUtils.isAllBlank((String) null));
+ assertTrue(StringUtils.isAllBlank((String[]) null));
+ assertTrue(StringUtils.isAllBlank(null, null));
+ assertTrue(StringUtils.isAllBlank(null, " "));
+ assertFalse(StringUtils.isAllBlank(null, "foo"));
+ assertFalse(StringUtils.isAllBlank("", "bar"));
+ assertFalse(StringUtils.isAllBlank("bob", ""));
+ assertFalse(StringUtils.isAllBlank(" bob ", null));
+ assertFalse(StringUtils.isAllBlank(" ", "bar"));
+ assertFalse(StringUtils.isAllBlank("foo", "bar"));
+ }
+
+ @Test
+ public void testFirstNonBlank() {
+ assertNull(StringUtils.firstNonBlank());
+ assertNull(StringUtils.firstNonBlank((String[]) null));
+ assertNull(StringUtils.firstNonBlank(null, null, null));
+ assertNull(StringUtils.firstNonBlank(null, "", " "));
+ assertNull(StringUtils.firstNonBlank(null, null, " "));
+ assertEquals("zz", StringUtils.firstNonBlank(null, "zz"));
+ assertEquals("abc", StringUtils.firstNonBlank("abc"));
+ assertEquals("xyz", StringUtils.firstNonBlank(null, "xyz"));
+ assertEquals("xyz", StringUtils.firstNonBlank(null, "xyz", "abc"));
+ }
+
+ @Test
+ public void testFirstNonEmpty() {
+ assertNull(StringUtils.firstNonEmpty());
+ assertNull(StringUtils.firstNonEmpty((String[]) null));
+ assertNull(StringUtils.firstNonEmpty(null, null, null));
+ assertEquals(" ", StringUtils.firstNonEmpty(null, "", " "));
+ assertNull(StringUtils.firstNonEmpty(null, null, ""));
+ assertEquals("zz", StringUtils.firstNonEmpty(null, "zz"));
+ assertEquals("abc", StringUtils.firstNonEmpty("abc"));
+ assertEquals("xyz", StringUtils.firstNonEmpty(null, "xyz"));
+ assertEquals("xyz", StringUtils.firstNonEmpty(null, "xyz", "abc"));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java
new file mode 100644
index 000000000..456018f0e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java
@@ -0,0 +1,800 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.apache.commons.lang3.Supplementary.CharU20000;
+import static org.apache.commons.lang3.Supplementary.CharU20001;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.CharBuffer;
+import java.util.Locale;
+
+import org.hamcrest.core.IsNot;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.StringUtils} - Equals/IndexOf methods
+ */
+public class StringUtilsEqualsIndexOfTest extends AbstractLangTest {
+
+ private static final String BAR = "bar";
+
+ private static final String FOO = "foo";
+
+ private static final String FOOBAR = "foobar";
+
+ private static final String[] FOOBAR_SUB_ARRAY = {"ob", "ba"};
+
+ // The purpose of this class is to test StringUtils#equals(CharSequence, CharSequence)
+ // with a CharSequence implementation whose equals(Object) override requires that the
+ // other object be an instance of CustomCharSequence, even though, as char sequences,
+ // `seq` may equal the other object.
+ private static class CustomCharSequence implements CharSequence {
+ private final CharSequence seq;
+
+ CustomCharSequence(final CharSequence seq) {
+ this.seq = seq;
+ }
+
+ @Override
+ public char charAt(final int index) {
+ return seq.charAt(index);
+ }
+
+ @Override
+ public int length() {
+ return seq.length();
+ }
+
+ @Override
+ public CharSequence subSequence(final int start, final int end) {
+ return new CustomCharSequence(seq.subSequence(start, end));
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof CustomCharSequence)) {
+ return false;
+ }
+ final CustomCharSequence other = (CustomCharSequence) obj;
+ return seq.equals(other.seq);
+ }
+
+ @Override
+ public int hashCode() {
+ return seq.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return seq.toString();
+ }
+ }
+
+ @Test
+ public void testCustomCharSequence() {
+ assertThat(new CustomCharSequence(FOO), IsNot.<CharSequence>not(FOO));
+ assertThat(FOO, IsNot.<CharSequence>not(new CustomCharSequence(FOO)));
+ assertEquals(new CustomCharSequence(FOO), new CustomCharSequence(FOO));
+ }
+
+ @Test
+ public void testEquals() {
+ final CharSequence fooCs = new StringBuilder(FOO), barCs = new StringBuilder(BAR), foobarCs = new StringBuilder(FOOBAR);
+ assertTrue(StringUtils.equals(null, null));
+ assertTrue(StringUtils.equals(fooCs, fooCs));
+ assertTrue(StringUtils.equals(fooCs, new StringBuilder(FOO)));
+ assertTrue(StringUtils.equals(fooCs, new String(new char[] { 'f', 'o', 'o' })));
+ assertTrue(StringUtils.equals(fooCs, new CustomCharSequence(FOO)));
+ assertTrue(StringUtils.equals(new CustomCharSequence(FOO), fooCs));
+ assertFalse(StringUtils.equals(fooCs, new String(new char[] { 'f', 'O', 'O' })));
+ assertFalse(StringUtils.equals(fooCs, barCs));
+ assertFalse(StringUtils.equals(fooCs, null));
+ assertFalse(StringUtils.equals(null, fooCs));
+ assertFalse(StringUtils.equals(fooCs, foobarCs));
+ assertFalse(StringUtils.equals(foobarCs, fooCs));
+ }
+
+ @Test
+ public void testEqualsOnStrings() {
+ assertTrue(StringUtils.equals(null, null));
+ assertTrue(StringUtils.equals(FOO, FOO));
+ assertTrue(StringUtils.equals(FOO, new String(new char[] { 'f', 'o', 'o' })));
+ assertFalse(StringUtils.equals(FOO, new String(new char[] { 'f', 'O', 'O' })));
+ assertFalse(StringUtils.equals(FOO, BAR));
+ assertFalse(StringUtils.equals(FOO, null));
+ assertFalse(StringUtils.equals(null, FOO));
+ assertFalse(StringUtils.equals(FOO, FOOBAR));
+ assertFalse(StringUtils.equals(FOOBAR, FOO));
+ }
+
+ @Test
+ public void testEqualsIgnoreCase() {
+ assertTrue(StringUtils.equalsIgnoreCase(null, null));
+ assertTrue(StringUtils.equalsIgnoreCase(FOO, FOO));
+ assertTrue(StringUtils.equalsIgnoreCase(FOO, new String(new char[] { 'f', 'o', 'o' })));
+ assertTrue(StringUtils.equalsIgnoreCase(FOO, new String(new char[] { 'f', 'O', 'O' })));
+ assertFalse(StringUtils.equalsIgnoreCase(FOO, BAR));
+ assertFalse(StringUtils.equalsIgnoreCase(FOO, null));
+ assertFalse(StringUtils.equalsIgnoreCase(null, FOO));
+ assertTrue(StringUtils.equalsIgnoreCase("", ""));
+ assertFalse(StringUtils.equalsIgnoreCase("abcd", "abcd "));
+ }
+
+ @Test
+ public void testEqualsAny() {
+ assertFalse(StringUtils.equalsAny(FOO));
+ assertFalse(StringUtils.equalsAny(FOO, new String[]{}));
+
+ assertTrue(StringUtils.equalsAny(FOO, FOO));
+ assertTrue(StringUtils.equalsAny(FOO, BAR, new String(new char[] { 'f', 'o', 'o' })));
+ assertFalse(StringUtils.equalsAny(FOO, BAR, new String(new char[] { 'f', 'O', 'O' })));
+ assertFalse(StringUtils.equalsAny(FOO, BAR));
+ assertFalse(StringUtils.equalsAny(FOO, BAR, null));
+ assertFalse(StringUtils.equalsAny(null, FOO));
+ assertFalse(StringUtils.equalsAny(FOO, FOOBAR));
+ assertFalse(StringUtils.equalsAny(FOOBAR, FOO));
+
+ assertTrue(StringUtils.equalsAny(null, null, null));
+ assertFalse(StringUtils.equalsAny(null, FOO, BAR, FOOBAR));
+ assertFalse(StringUtils.equalsAny(FOO, null, BAR));
+ assertTrue(StringUtils.equalsAny(FOO, BAR, null, "", FOO, BAR));
+ assertFalse(StringUtils.equalsAny(FOO, FOO.toUpperCase(Locale.ROOT)));
+
+ assertFalse(StringUtils.equalsAny(null, (CharSequence[]) null));
+ assertTrue(StringUtils.equalsAny(FOO, new CustomCharSequence("foo")));
+ assertTrue(StringUtils.equalsAny(FOO, new StringBuilder("foo")));
+ assertFalse(StringUtils.equalsAny(FOO, new CustomCharSequence("fOo")));
+ assertFalse(StringUtils.equalsAny(FOO, new StringBuilder("fOo")));
+ }
+
+ @Test
+ public void testEqualsAnyIgnoreCase() {
+ assertFalse(StringUtils.equalsAnyIgnoreCase(FOO));
+ assertFalse(StringUtils.equalsAnyIgnoreCase(FOO, new String[]{}));
+
+ assertTrue(StringUtils.equalsAnyIgnoreCase(FOO, FOO));
+ assertTrue(StringUtils.equalsAnyIgnoreCase(FOO, FOO.toUpperCase(Locale.ROOT)));
+ assertTrue(StringUtils.equalsAnyIgnoreCase(FOO, FOO, new String(new char[]{'f', 'o', 'o'})));
+ assertTrue(StringUtils.equalsAnyIgnoreCase(FOO, BAR, new String(new char[]{'f', 'O', 'O'})));
+ assertFalse(StringUtils.equalsAnyIgnoreCase(FOO, BAR));
+ assertFalse(StringUtils.equalsAnyIgnoreCase(FOO, BAR, null));
+ assertFalse(StringUtils.equalsAnyIgnoreCase(null, FOO));
+ assertFalse(StringUtils.equalsAnyIgnoreCase(FOO, FOOBAR));
+ assertFalse(StringUtils.equalsAnyIgnoreCase(FOOBAR, FOO));
+
+ assertTrue(StringUtils.equalsAnyIgnoreCase(null, null, null));
+ assertFalse(StringUtils.equalsAnyIgnoreCase(null, FOO, BAR, FOOBAR));
+ assertFalse(StringUtils.equalsAnyIgnoreCase(FOO, null, BAR));
+ assertTrue(StringUtils.equalsAnyIgnoreCase(FOO, BAR, null, "", FOO.toUpperCase(Locale.ROOT), BAR));
+ assertTrue(StringUtils.equalsAnyIgnoreCase(FOO, FOO.toUpperCase(Locale.ROOT)));
+
+ assertFalse(StringUtils.equalsAnyIgnoreCase(null, (CharSequence[]) null));
+ assertTrue(StringUtils.equalsAnyIgnoreCase(FOO, new CustomCharSequence("fOo")));
+ assertTrue(StringUtils.equalsAnyIgnoreCase(FOO, new StringBuilder("fOo")));
+ }
+
+ @Test
+ public void testCompare_StringString() {
+ assertEquals(0, StringUtils.compare(null, null));
+ assertTrue(StringUtils.compare(null, "a") < 0);
+ assertTrue(StringUtils.compare("a", null) > 0);
+ assertEquals(0, StringUtils.compare("abc", "abc"));
+ assertTrue(StringUtils.compare("a", "b") < 0);
+ assertTrue(StringUtils.compare("b", "a") > 0);
+ assertTrue(StringUtils.compare("a", "B") > 0);
+ assertTrue(StringUtils.compare("abc", "abd") < 0);
+ assertTrue(StringUtils.compare("ab", "abc") < 0);
+ assertTrue(StringUtils.compare("ab", "ab ") < 0);
+ assertTrue(StringUtils.compare("abc", "ab ") > 0);
+ }
+
+ @Test
+ public void testCompare_StringStringBoolean() {
+ assertEquals(0, StringUtils.compare(null, null, false));
+ assertTrue(StringUtils.compare(null, "a", true) < 0);
+ assertTrue(StringUtils.compare(null, "a", false) > 0);
+ assertTrue(StringUtils.compare("a", null, true) > 0);
+ assertTrue(StringUtils.compare("a", null, false) < 0);
+ assertEquals(0, StringUtils.compare("abc", "abc", false));
+ assertTrue(StringUtils.compare("a", "b", false) < 0);
+ assertTrue(StringUtils.compare("b", "a", false) > 0);
+ assertTrue(StringUtils.compare("a", "B", false) > 0);
+ assertTrue(StringUtils.compare("abc", "abd", false) < 0);
+ assertTrue(StringUtils.compare("ab", "abc", false) < 0);
+ assertTrue(StringUtils.compare("ab", "ab ", false) < 0);
+ assertTrue(StringUtils.compare("abc", "ab ", false) > 0);
+ }
+
+ @Test
+ public void testCompareIgnoreCase_StringString() {
+ assertEquals(0, StringUtils.compareIgnoreCase(null, null));
+ assertTrue(StringUtils.compareIgnoreCase(null, "a") < 0);
+ assertTrue(StringUtils.compareIgnoreCase("a", null) > 0);
+ assertEquals(0, StringUtils.compareIgnoreCase("abc", "abc"));
+ assertEquals(0, StringUtils.compareIgnoreCase("abc", "ABC"));
+ assertTrue(StringUtils.compareIgnoreCase("a", "b") < 0);
+ assertTrue(StringUtils.compareIgnoreCase("b", "a") > 0);
+ assertTrue(StringUtils.compareIgnoreCase("a", "B") < 0);
+ assertTrue(StringUtils.compareIgnoreCase("A", "b") < 0);
+ assertTrue(StringUtils.compareIgnoreCase("abc", "ABD") < 0);
+ assertTrue(StringUtils.compareIgnoreCase("ab", "ABC") < 0);
+ assertTrue(StringUtils.compareIgnoreCase("ab", "AB ") < 0);
+ assertTrue(StringUtils.compareIgnoreCase("abc", "AB ") > 0);
+ }
+
+ @Test
+ public void testCompareIgnoreCase_StringStringBoolean() {
+ assertEquals(0, StringUtils.compareIgnoreCase(null, null, false));
+ assertTrue(StringUtils.compareIgnoreCase(null, "a", true) < 0);
+ assertTrue(StringUtils.compareIgnoreCase(null, "a", false) > 0);
+ assertTrue(StringUtils.compareIgnoreCase("a", null, true) > 0);
+ assertTrue(StringUtils.compareIgnoreCase("a", null, false) < 0);
+ assertEquals(0, StringUtils.compareIgnoreCase("abc", "abc", false));
+ assertEquals(0, StringUtils.compareIgnoreCase("abc", "ABC", false));
+ assertTrue(StringUtils.compareIgnoreCase("a", "b", false) < 0);
+ assertTrue(StringUtils.compareIgnoreCase("b", "a", false) > 0);
+ assertTrue(StringUtils.compareIgnoreCase("a", "B", false) < 0);
+ assertTrue(StringUtils.compareIgnoreCase("A", "b", false) < 0);
+ assertTrue(StringUtils.compareIgnoreCase("abc", "ABD", false) < 0);
+ assertTrue(StringUtils.compareIgnoreCase("ab", "ABC", false) < 0);
+ assertTrue(StringUtils.compareIgnoreCase("ab", "AB ", false) < 0);
+ assertTrue(StringUtils.compareIgnoreCase("abc", "AB ", false) > 0);
+ }
+
+ @Test
+ public void testIndexOf_char() {
+ assertEquals(-1, StringUtils.indexOf(null, ' '));
+ assertEquals(-1, StringUtils.indexOf("", ' '));
+ assertEquals(0, StringUtils.indexOf("aabaabaa", 'a'));
+ assertEquals(2, StringUtils.indexOf("aabaabaa", 'b'));
+
+ assertEquals(2, StringUtils.indexOf(new StringBuilder("aabaabaa"), 'b'));
+ assertEquals(StringUtils.INDEX_NOT_FOUND, StringUtils.indexOf(new StringBuilder("aabaabaa"), -1738));
+ }
+
+ @Test
+ public void testIndexOf_charInt() {
+ assertEquals(-1, StringUtils.indexOf(null, ' ', 0));
+ assertEquals(-1, StringUtils.indexOf(null, ' ', -1));
+ assertEquals(-1, StringUtils.indexOf("", ' ', 0));
+ assertEquals(-1, StringUtils.indexOf("", ' ', -1));
+ assertEquals(0, StringUtils.indexOf("aabaabaa", 'a', 0));
+ assertEquals(2, StringUtils.indexOf("aabaabaa", 'b', 0));
+ assertEquals(5, StringUtils.indexOf("aabaabaa", 'b', 3));
+ assertEquals(-1, StringUtils.indexOf("aabaabaa", 'b', 9));
+ assertEquals(2, StringUtils.indexOf("aabaabaa", 'b', -1));
+
+ assertEquals(5, StringUtils.indexOf(new StringBuilder("aabaabaa"), 'b', 3));
+
+ //LANG-1300 tests go here
+ final int CODE_POINT = 0x2070E;
+ StringBuilder builder = new StringBuilder();
+ builder.appendCodePoint(CODE_POINT);
+ assertEquals(0, StringUtils.indexOf(builder, CODE_POINT, 0));
+ assertEquals(0, StringUtils.indexOf(builder.toString(), CODE_POINT, 0));
+ builder.appendCodePoint(CODE_POINT);
+ assertEquals(2, StringUtils.indexOf(builder, CODE_POINT, 1));
+ assertEquals(2, StringUtils.indexOf(builder.toString(), CODE_POINT, 1));
+ // inner branch on the supplementary character block
+ final char[] tmp = { (char) 55361 };
+ builder = new StringBuilder();
+ builder.append(tmp);
+ assertEquals(-1, StringUtils.indexOf(builder, CODE_POINT, 0));
+ assertEquals(-1, StringUtils.indexOf(builder.toString(), CODE_POINT, 0));
+ builder.appendCodePoint(CODE_POINT);
+ assertEquals(1, StringUtils.indexOf(builder, CODE_POINT, 0));
+ assertEquals(1, StringUtils.indexOf(builder.toString(), CODE_POINT, 0));
+ assertEquals(-1, StringUtils.indexOf(builder, CODE_POINT, 2));
+ assertEquals(-1, StringUtils.indexOf(builder.toString(), CODE_POINT, 2));
+ }
+
+ @Test
+ public void testIndexOf_String() {
+ assertEquals(-1, StringUtils.indexOf(null, null));
+ assertEquals(-1, StringUtils.indexOf("", null));
+ assertEquals(0, StringUtils.indexOf("", ""));
+ assertEquals(0, StringUtils.indexOf("aabaabaa", "a"));
+ assertEquals(2, StringUtils.indexOf("aabaabaa", "b"));
+ assertEquals(1, StringUtils.indexOf("aabaabaa", "ab"));
+ assertEquals(0, StringUtils.indexOf("aabaabaa", ""));
+
+ assertEquals(2, StringUtils.indexOf(new StringBuilder("aabaabaa"), "b"));
+ }
+
+ @Test
+ public void testIndexOf_StringInt() {
+ assertEquals(-1, StringUtils.indexOf(null, null, 0));
+ assertEquals(-1, StringUtils.indexOf(null, null, -1));
+ assertEquals(-1, StringUtils.indexOf(null, "", 0));
+ assertEquals(-1, StringUtils.indexOf(null, "", -1));
+ assertEquals(-1, StringUtils.indexOf("", null, 0));
+ assertEquals(-1, StringUtils.indexOf("", null, -1));
+ assertEquals(0, StringUtils.indexOf("", "", 0));
+ assertEquals(0, StringUtils.indexOf("", "", -1));
+ assertEquals(0, StringUtils.indexOf("", "", 9));
+ assertEquals(0, StringUtils.indexOf("abc", "", 0));
+ assertEquals(0, StringUtils.indexOf("abc", "", -1));
+ assertEquals(3, StringUtils.indexOf("abc", "", 9));
+ assertEquals(3, StringUtils.indexOf("abc", "", 3));
+ assertEquals(0, StringUtils.indexOf("aabaabaa", "a", 0));
+ assertEquals(2, StringUtils.indexOf("aabaabaa", "b", 0));
+ assertEquals(1, StringUtils.indexOf("aabaabaa", "ab", 0));
+ assertEquals(5, StringUtils.indexOf("aabaabaa", "b", 3));
+ assertEquals(-1, StringUtils.indexOf("aabaabaa", "b", 9));
+ assertEquals(2, StringUtils.indexOf("aabaabaa", "b", -1));
+ assertEquals(2, StringUtils.indexOf("aabaabaa", "", 2));
+
+ // Test that startIndex works correctly, i.e. cannot match before startIndex
+ assertEquals(7, StringUtils.indexOf("12345678", "8", 5));
+ assertEquals(7, StringUtils.indexOf("12345678", "8", 6));
+ assertEquals(7, StringUtils.indexOf("12345678", "8", 7)); // 7 is last index
+ assertEquals(-1, StringUtils.indexOf("12345678", "8", 8));
+
+ assertEquals(5, StringUtils.indexOf(new StringBuilder("aabaabaa"), "b", 3));
+ }
+
+ @Test
+ public void testIndexOfAny_StringCharArray() {
+ assertEquals(-1, StringUtils.indexOfAny(null, (char[]) null));
+ assertEquals(-1, StringUtils.indexOfAny(null, new char[0]));
+ assertEquals(-1, StringUtils.indexOfAny(null, 'a', 'b'));
+
+ assertEquals(-1, StringUtils.indexOfAny("", (char[]) null));
+ assertEquals(-1, StringUtils.indexOfAny("", new char[0]));
+ assertEquals(-1, StringUtils.indexOfAny("", 'a', 'b'));
+
+ assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", (char[]) null));
+ assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", new char[0]));
+ assertEquals(0, StringUtils.indexOfAny("zzabyycdxx", 'z', 'a'));
+ assertEquals(3, StringUtils.indexOfAny("zzabyycdxx", 'b', 'y'));
+ assertEquals(-1, StringUtils.indexOfAny("ab", 'z'));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testIndexOfAny_StringCharArrayWithSupplementaryChars() {
+ assertEquals(0, StringUtils.indexOfAny(CharU20000 + CharU20001, CharU20000.toCharArray()));
+ assertEquals(2, StringUtils.indexOfAny(CharU20000 + CharU20001, CharU20001.toCharArray()));
+ assertEquals(0, StringUtils.indexOfAny(CharU20000, CharU20000.toCharArray()));
+ assertEquals(-1, StringUtils.indexOfAny(CharU20000, CharU20001.toCharArray()));
+ }
+
+ @Test
+ public void testIndexOfAny_StringString() {
+ assertEquals(-1, StringUtils.indexOfAny(null, (String) null));
+ assertEquals(-1, StringUtils.indexOfAny(null, ""));
+ assertEquals(-1, StringUtils.indexOfAny(null, "ab"));
+
+ assertEquals(-1, StringUtils.indexOfAny("", (String) null));
+ assertEquals(-1, StringUtils.indexOfAny("", ""));
+ assertEquals(-1, StringUtils.indexOfAny("", "ab"));
+
+ assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", (String) null));
+ assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", ""));
+ assertEquals(0, StringUtils.indexOfAny("zzabyycdxx", "za"));
+ assertEquals(3, StringUtils.indexOfAny("zzabyycdxx", "by"));
+ assertEquals(-1, StringUtils.indexOfAny("ab", "z"));
+ }
+
+ @Test
+ public void testIndexOfAny_StringStringArray() {
+ assertEquals(-1, StringUtils.indexOfAny(null, (String[]) null));
+ assertEquals(-1, StringUtils.indexOfAny(null, FOOBAR_SUB_ARRAY));
+ assertEquals(-1, StringUtils.indexOfAny(FOOBAR, (String[]) null));
+ assertEquals(2, StringUtils.indexOfAny(FOOBAR, FOOBAR_SUB_ARRAY));
+ assertEquals(-1, StringUtils.indexOfAny(FOOBAR, new String[0]));
+ assertEquals(-1, StringUtils.indexOfAny(null, new String[0]));
+ assertEquals(-1, StringUtils.indexOfAny("", new String[0]));
+ assertEquals(-1, StringUtils.indexOfAny(FOOBAR, new String[] {"llll"}));
+ assertEquals(0, StringUtils.indexOfAny(FOOBAR, new String[] {""}));
+ assertEquals(0, StringUtils.indexOfAny("", new String[] {""}));
+ assertEquals(-1, StringUtils.indexOfAny("", new String[] {"a"}));
+ assertEquals(-1, StringUtils.indexOfAny("", new String[] {null}));
+ assertEquals(-1, StringUtils.indexOfAny(FOOBAR, new String[] {null}));
+ assertEquals(-1, StringUtils.indexOfAny(null, new String[] {null}));
+ }
+
+ /**
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ @Test
+ public void testIndexOfAny_StringStringWithSupplementaryChars() {
+ assertEquals(0, StringUtils.indexOfAny(CharU20000 + CharU20001, CharU20000));
+ assertEquals(2, StringUtils.indexOfAny(CharU20000 + CharU20001, CharU20001));
+ assertEquals(0, StringUtils.indexOfAny(CharU20000, CharU20000));
+ assertEquals(-1, StringUtils.indexOfAny(CharU20000, CharU20001));
+ }
+
+ @Test
+ public void testIndexOfAnyBut_StringCharArray() {
+ assertEquals(-1, StringUtils.indexOfAnyBut(null, (char[]) null));
+ assertEquals(-1, StringUtils.indexOfAnyBut(null));
+ assertEquals(-1, StringUtils.indexOfAnyBut(null, 'a', 'b'));
+
+ assertEquals(-1, StringUtils.indexOfAnyBut("", (char[]) null));
+ assertEquals(-1, StringUtils.indexOfAnyBut(""));
+ assertEquals(-1, StringUtils.indexOfAnyBut("", 'a', 'b'));
+
+ assertEquals(-1, StringUtils.indexOfAnyBut("zzabyycdxx", (char[]) null));
+ assertEquals(-1, StringUtils.indexOfAnyBut("zzabyycdxx"));
+ assertEquals(3, StringUtils.indexOfAnyBut("zzabyycdxx", 'z', 'a'));
+ assertEquals(0, StringUtils.indexOfAnyBut("zzabyycdxx", 'b', 'y'));
+ assertEquals(-1, StringUtils.indexOfAnyBut("aba", 'a', 'b'));
+ assertEquals(0, StringUtils.indexOfAnyBut("aba", 'z'));
+ }
+
+ @Test
+ public void testIndexOfAnyBut_StringCharArrayWithSupplementaryChars() {
+ assertEquals(2, StringUtils.indexOfAnyBut(CharU20000 + CharU20001, CharU20000.toCharArray()));
+ assertEquals(0, StringUtils.indexOfAnyBut(CharU20000 + CharU20001, CharU20001.toCharArray()));
+ assertEquals(-1, StringUtils.indexOfAnyBut(CharU20000, CharU20000.toCharArray()));
+ assertEquals(0, StringUtils.indexOfAnyBut(CharU20000, CharU20001.toCharArray()));
+ }
+
+ @Test
+ public void testIndexOfAnyBut_StringString() {
+ assertEquals(-1, StringUtils.indexOfAnyBut(null, (String) null));
+ assertEquals(-1, StringUtils.indexOfAnyBut(null, ""));
+ assertEquals(-1, StringUtils.indexOfAnyBut(null, "ab"));
+
+ assertEquals(-1, StringUtils.indexOfAnyBut("", (String) null));
+ assertEquals(-1, StringUtils.indexOfAnyBut("", ""));
+ assertEquals(-1, StringUtils.indexOfAnyBut("", "ab"));
+
+ assertEquals(-1, StringUtils.indexOfAnyBut("zzabyycdxx", (String) null));
+ assertEquals(-1, StringUtils.indexOfAnyBut("zzabyycdxx", ""));
+ assertEquals(3, StringUtils.indexOfAnyBut("zzabyycdxx", "za"));
+ assertEquals(0, StringUtils.indexOfAnyBut("zzabyycdxx", "by"));
+ assertEquals(0, StringUtils.indexOfAnyBut("ab", "z"));
+ }
+
+ @Test
+ public void testIndexOfAnyBut_StringStringWithSupplementaryChars() {
+ assertEquals(2, StringUtils.indexOfAnyBut(CharU20000 + CharU20001, CharU20000));
+ assertEquals(0, StringUtils.indexOfAnyBut(CharU20000 + CharU20001, CharU20001));
+ assertEquals(-1, StringUtils.indexOfAnyBut(CharU20000, CharU20000));
+ assertEquals(0, StringUtils.indexOfAnyBut(CharU20000, CharU20001));
+ }
+
+ @Test
+ public void testIndexOfIgnoreCase_String() {
+ assertEquals(-1, StringUtils.indexOfIgnoreCase(null, null));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase(null, ""));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase("", null));
+ assertEquals(0, StringUtils.indexOfIgnoreCase("", ""));
+ assertEquals(0, StringUtils.indexOfIgnoreCase("aabaabaa", "a"));
+ assertEquals(0, StringUtils.indexOfIgnoreCase("aabaabaa", "A"));
+ assertEquals(2, StringUtils.indexOfIgnoreCase("aabaabaa", "b"));
+ assertEquals(2, StringUtils.indexOfIgnoreCase("aabaabaa", "B"));
+ assertEquals(1, StringUtils.indexOfIgnoreCase("aabaabaa", "ab"));
+ assertEquals(1, StringUtils.indexOfIgnoreCase("aabaabaa", "AB"));
+ assertEquals(0, StringUtils.indexOfIgnoreCase("aabaabaa", ""));
+ }
+
+ @Test
+ public void testIndexOfIgnoreCase_StringInt() {
+ assertEquals(1, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", -1));
+ assertEquals(1, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 0));
+ assertEquals(1, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 1));
+ assertEquals(4, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 2));
+ assertEquals(4, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 3));
+ assertEquals(4, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 4));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 5));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 6));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 7));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 8));
+ assertEquals(1, StringUtils.indexOfIgnoreCase("aab", "AB", 1));
+ assertEquals(5, StringUtils.indexOfIgnoreCase("aabaabaa", "", 5));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase("ab", "AAB", 0));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase("aab", "AAB", 1));
+ assertEquals(-1, StringUtils.indexOfIgnoreCase("abc", "", 9));
+ }
+
+ @Test
+ public void testLastIndexOf_char() {
+ assertEquals(-1, StringUtils.lastIndexOf(null, ' '));
+ assertEquals(-1, StringUtils.lastIndexOf("", ' '));
+ assertEquals(7, StringUtils.lastIndexOf("aabaabaa", 'a'));
+ assertEquals(5, StringUtils.lastIndexOf("aabaabaa", 'b'));
+
+ assertEquals(5, StringUtils.lastIndexOf(new StringBuilder("aabaabaa"), 'b'));
+ }
+
+ @Test
+ public void testLastIndexOf_charInt() {
+ assertEquals(-1, StringUtils.lastIndexOf(null, ' ', 0));
+ assertEquals(-1, StringUtils.lastIndexOf(null, ' ', -1));
+ assertEquals(-1, StringUtils.lastIndexOf("", ' ', 0));
+ assertEquals(-1, StringUtils.lastIndexOf("", ' ', -1));
+ assertEquals(7, StringUtils.lastIndexOf("aabaabaa", 'a', 8));
+ assertEquals(5, StringUtils.lastIndexOf("aabaabaa", 'b', 8));
+ assertEquals(2, StringUtils.lastIndexOf("aabaabaa", 'b', 3));
+ assertEquals(5, StringUtils.lastIndexOf("aabaabaa", 'b', 9));
+ assertEquals(-1, StringUtils.lastIndexOf("aabaabaa", 'b', -1));
+ assertEquals(0, StringUtils.lastIndexOf("aabaabaa", 'a', 0));
+
+ assertEquals(2, StringUtils.lastIndexOf(new StringBuilder("aabaabaa"), 'b', 2));
+
+ //LANG-1300 addition test
+ final int CODE_POINT = 0x2070E;
+ StringBuilder builder = new StringBuilder();
+ builder.appendCodePoint(CODE_POINT);
+ assertEquals(0, StringUtils.lastIndexOf(builder, CODE_POINT, 0));
+ builder.appendCodePoint(CODE_POINT);
+ assertEquals(0, StringUtils.lastIndexOf(builder, CODE_POINT, 0));
+ assertEquals(0, StringUtils.lastIndexOf(builder, CODE_POINT, 1));
+ assertEquals(2, StringUtils.lastIndexOf(builder, CODE_POINT, 2));
+
+ builder.append("aaaaa");
+ assertEquals(2, StringUtils.lastIndexOf(builder, CODE_POINT, 4));
+ // inner branch on the supplementary character block
+ final char[] tmp = { (char) 55361 };
+ builder = new StringBuilder();
+ builder.append(tmp);
+ assertEquals(-1, StringUtils.lastIndexOf(builder, CODE_POINT, 0));
+ builder.appendCodePoint(CODE_POINT);
+ assertEquals(-1, StringUtils.lastIndexOf(builder, CODE_POINT, 0));
+ assertEquals(1, StringUtils.lastIndexOf(builder, CODE_POINT, 1 ));
+ assertEquals(-1, StringUtils.lastIndexOf(builder.toString(), CODE_POINT, 0));
+ assertEquals(1, StringUtils.lastIndexOf(builder.toString(), CODE_POINT, 1));
+ assertEquals(StringUtils.INDEX_NOT_FOUND, StringUtils.lastIndexOf(CharBuffer.wrap("[%{.c.0rro"), -1738, 982));
+ }
+
+ @Test
+ public void testLastIndexOf_String() {
+ assertEquals(-1, StringUtils.lastIndexOf(null, null));
+ assertEquals(-1, StringUtils.lastIndexOf("", null));
+ assertEquals(-1, StringUtils.lastIndexOf("", "a"));
+ assertEquals(0, StringUtils.lastIndexOf("", ""));
+ assertEquals(8, StringUtils.lastIndexOf("aabaabaa", ""));
+ assertEquals(7, StringUtils.lastIndexOf("aabaabaa", "a"));
+ assertEquals(5, StringUtils.lastIndexOf("aabaabaa", "b"));
+ assertEquals(4, StringUtils.lastIndexOf("aabaabaa", "ab"));
+
+ assertEquals(4, StringUtils.lastIndexOf(new StringBuilder("aabaabaa"), "ab"));
+ }
+
+ @Test
+ public void testLastIndexOf_StringInt() {
+ assertEquals(-1, StringUtils.lastIndexOf(null, null, 0));
+ assertEquals(-1, StringUtils.lastIndexOf(null, null, -1));
+ assertEquals(-1, StringUtils.lastIndexOf(null, "", 0));
+ assertEquals(-1, StringUtils.lastIndexOf(null, "", -1));
+ assertEquals(-1, StringUtils.lastIndexOf("", null, 0));
+ assertEquals(-1, StringUtils.lastIndexOf("", null, -1));
+ assertEquals(0, StringUtils.lastIndexOf("", "", 0));
+ assertEquals(-1, StringUtils.lastIndexOf("", "", -1));
+ assertEquals(0, StringUtils.lastIndexOf("", "", 9));
+ assertEquals(0, StringUtils.lastIndexOf("abc", "", 0));
+ assertEquals(-1, StringUtils.lastIndexOf("abc", "", -1));
+ assertEquals(3, StringUtils.lastIndexOf("abc", "", 9));
+ assertEquals(7, StringUtils.lastIndexOf("aabaabaa", "a", 8));
+ assertEquals(5, StringUtils.lastIndexOf("aabaabaa", "b", 8));
+ assertEquals(4, StringUtils.lastIndexOf("aabaabaa", "ab", 8));
+ assertEquals(2, StringUtils.lastIndexOf("aabaabaa", "b", 3));
+ assertEquals(5, StringUtils.lastIndexOf("aabaabaa", "b", 9));
+ assertEquals(-1, StringUtils.lastIndexOf("aabaabaa", "b", -1));
+ assertEquals(-1, StringUtils.lastIndexOf("aabaabaa", "b", 0));
+ assertEquals(0, StringUtils.lastIndexOf("aabaabaa", "a", 0));
+ assertEquals(-1, StringUtils.lastIndexOf("aabaabaa", "a", -1));
+
+ // Test that fromIndex works correctly, i.e. cannot match after fromIndex
+ assertEquals(7, StringUtils.lastIndexOf("12345678", "8", 9));
+ assertEquals(7, StringUtils.lastIndexOf("12345678", "8", 8));
+ assertEquals(7, StringUtils.lastIndexOf("12345678", "8", 7)); // 7 is last index
+ assertEquals(-1, StringUtils.lastIndexOf("12345678", "8", 6));
+
+ assertEquals(-1, StringUtils.lastIndexOf("aabaabaa", "b", 1));
+ assertEquals(2, StringUtils.lastIndexOf("aabaabaa", "b", 2));
+ assertEquals(2, StringUtils.lastIndexOf("aabaabaa", "ba", 2));
+ assertEquals(2, StringUtils.lastIndexOf("aabaabaa", "ba", 3));
+
+ assertEquals(2, StringUtils.lastIndexOf(new StringBuilder("aabaabaa"), "b", 3));
+ }
+
+ @Test
+ public void testLastIndexOfAny_StringStringArray() {
+ assertEquals(-1, StringUtils.lastIndexOfAny(null, (CharSequence) null)); // test both types of ...
+ assertEquals(-1, StringUtils.lastIndexOfAny(null, (CharSequence[]) null)); // ... varargs invocation
+ assertEquals(-1, StringUtils.lastIndexOfAny(null)); // Missing varag
+ assertEquals(-1, StringUtils.lastIndexOfAny(null, FOOBAR_SUB_ARRAY));
+ assertEquals(-1, StringUtils.lastIndexOfAny(FOOBAR, (CharSequence) null)); // test both types of ...
+ assertEquals(-1, StringUtils.lastIndexOfAny(FOOBAR, (CharSequence[]) null)); // ... varargs invocation
+ assertEquals(-1, StringUtils.lastIndexOfAny(FOOBAR)); // Missing vararg
+ assertEquals(3, StringUtils.lastIndexOfAny(FOOBAR, FOOBAR_SUB_ARRAY));
+ assertEquals(-1, StringUtils.lastIndexOfAny(FOOBAR, new String[0]));
+ assertEquals(-1, StringUtils.lastIndexOfAny(null, new String[0]));
+ assertEquals(-1, StringUtils.lastIndexOfAny("", new String[0]));
+ assertEquals(-1, StringUtils.lastIndexOfAny(FOOBAR, new String[] {"llll"}));
+ assertEquals(6, StringUtils.lastIndexOfAny(FOOBAR, new String[] {""}));
+ assertEquals(0, StringUtils.lastIndexOfAny("", new String[] {""}));
+ assertEquals(-1, StringUtils.lastIndexOfAny("", new String[] {"a"}));
+ assertEquals(-1, StringUtils.lastIndexOfAny("", new String[] {null}));
+ assertEquals(-1, StringUtils.lastIndexOfAny(FOOBAR, new String[] {null}));
+ assertEquals(-1, StringUtils.lastIndexOfAny(null, new String[] {null}));
+ }
+
+ @Test
+ public void testLastIndexOfIgnoreCase_String() {
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase(null, null));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("", null));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase(null, ""));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("", "a"));
+ assertEquals(0, StringUtils.lastIndexOfIgnoreCase("", ""));
+ assertEquals(8, StringUtils.lastIndexOfIgnoreCase("aabaabaa", ""));
+ assertEquals(7, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "a"));
+ assertEquals(7, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A"));
+ assertEquals(5, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "b"));
+ assertEquals(5, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B"));
+ assertEquals(4, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "ab"));
+ assertEquals(4, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB"));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("ab", "AAB"));
+ assertEquals(0, StringUtils.lastIndexOfIgnoreCase("aab", "AAB"));
+ }
+
+ @Test
+ public void testLastIndexOfIgnoreCase_StringInt() {
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase(null, null, 0));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase(null, null, -1));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase(null, "", 0));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase(null, "", -1));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("", null, 0));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("", null, -1));
+ assertEquals(0, StringUtils.lastIndexOfIgnoreCase("", "", 0));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("", "", -1));
+ assertEquals(0, StringUtils.lastIndexOfIgnoreCase("", "", 9));
+ assertEquals(0, StringUtils.lastIndexOfIgnoreCase("abc", "", 0));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("abc", "", -1));
+ assertEquals(3, StringUtils.lastIndexOfIgnoreCase("abc", "", 9));
+ assertEquals(7, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 8));
+ assertEquals(5, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 8));
+ assertEquals(4, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB", 8));
+ assertEquals(2, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 3));
+ assertEquals(5, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 9));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", -1));
+ assertEquals(-1, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 0));
+ assertEquals(0, StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 0));
+ assertEquals(1, StringUtils.lastIndexOfIgnoreCase("aab", "AB", 1));
+ }
+
+ @Test
+ public void testLastOrdinalIndexOf() {
+ assertEquals(-1, StringUtils.lastOrdinalIndexOf(null, "*", 42) );
+ assertEquals(-1, StringUtils.lastOrdinalIndexOf("*", null, 42) );
+ assertEquals(0, StringUtils.lastOrdinalIndexOf("", "", 42) );
+ assertEquals(7, StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 1) );
+ assertEquals(6, StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 2) );
+ assertEquals(5, StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 1) );
+ assertEquals(2, StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 2) );
+ assertEquals(4, StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 1) );
+ assertEquals(1, StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 2) );
+ assertEquals(8, StringUtils.lastOrdinalIndexOf("aabaabaa", "", 1) );
+ assertEquals(8, StringUtils.lastOrdinalIndexOf("aabaabaa", "", 2) );
+ }
+
+ @Test
+ public void testOrdinalIndexOf() {
+ assertEquals(-1, StringUtils.ordinalIndexOf(null, null, Integer.MIN_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", null, Integer.MIN_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", "", Integer.MIN_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "a", Integer.MIN_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "b", Integer.MIN_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "ab", Integer.MIN_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "", Integer.MIN_VALUE));
+
+ assertEquals(-1, StringUtils.ordinalIndexOf(null, null, -1));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", null, -1));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", "", -1));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "a", -1));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "b", -1));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "ab", -1));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "", -1));
+
+ assertEquals(-1, StringUtils.ordinalIndexOf(null, null, 0));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", null, 0));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", "", 0));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "a", 0));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "b", 0));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "ab", 0));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "", 0));
+
+ assertEquals(-1, StringUtils.ordinalIndexOf(null, null, 1));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", null, 1));
+ assertEquals(0, StringUtils.ordinalIndexOf("", "", 1));
+ assertEquals(0, StringUtils.ordinalIndexOf("aabaabaa", "a", 1));
+ assertEquals(2, StringUtils.ordinalIndexOf("aabaabaa", "b", 1));
+ assertEquals(1, StringUtils.ordinalIndexOf("aabaabaa", "ab", 1));
+ assertEquals(0, StringUtils.ordinalIndexOf("aabaabaa", "", 1));
+
+ assertEquals(-1, StringUtils.ordinalIndexOf(null, null, 2));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", null, 2));
+ assertEquals(0, StringUtils.ordinalIndexOf("", "", 2));
+ assertEquals(1, StringUtils.ordinalIndexOf("aabaabaa", "a", 2));
+ assertEquals(5, StringUtils.ordinalIndexOf("aabaabaa", "b", 2));
+ assertEquals(4, StringUtils.ordinalIndexOf("aabaabaa", "ab", 2));
+ assertEquals(0, StringUtils.ordinalIndexOf("aabaabaa", "", 2));
+
+ assertEquals(-1, StringUtils.ordinalIndexOf(null, null, Integer.MAX_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("", null, Integer.MAX_VALUE));
+ assertEquals(0, StringUtils.ordinalIndexOf("", "", Integer.MAX_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "a", Integer.MAX_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "b", Integer.MAX_VALUE));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aabaabaa", "ab", Integer.MAX_VALUE));
+ assertEquals(0, StringUtils.ordinalIndexOf("aabaabaa", "", Integer.MAX_VALUE));
+
+ assertEquals(-1, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 0));
+ assertEquals(0, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 1));
+ assertEquals(1, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 2));
+ assertEquals(2, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 3));
+ assertEquals(3, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 4));
+ assertEquals(4, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 5));
+ assertEquals(5, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 6));
+ assertEquals(6, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 7));
+ assertEquals(7, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 8));
+ assertEquals(8, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 9));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aaaaaaaaa", "a", 10));
+
+ // match at each possible position
+ assertEquals(0, StringUtils.ordinalIndexOf("aaaaaa", "aa", 1));
+ assertEquals(1, StringUtils.ordinalIndexOf("aaaaaa", "aa", 2));
+ assertEquals(2, StringUtils.ordinalIndexOf("aaaaaa", "aa", 3));
+ assertEquals(3, StringUtils.ordinalIndexOf("aaaaaa", "aa", 4));
+ assertEquals(4, StringUtils.ordinalIndexOf("aaaaaa", "aa", 5));
+ assertEquals(-1, StringUtils.ordinalIndexOf("aaaaaa", "aa", 6));
+
+ assertEquals(0, StringUtils.ordinalIndexOf("ababab", "aba", 1));
+ assertEquals(2, StringUtils.ordinalIndexOf("ababab", "aba", 2));
+ assertEquals(-1, StringUtils.ordinalIndexOf("ababab", "aba", 3));
+
+ assertEquals(0, StringUtils.ordinalIndexOf("abababab", "abab", 1));
+ assertEquals(2, StringUtils.ordinalIndexOf("abababab", "abab", 2));
+ assertEquals(4, StringUtils.ordinalIndexOf("abababab", "abab", 3));
+ assertEquals(-1, StringUtils.ordinalIndexOf("abababab", "abab", 4));
+ }
+
+ @Test
+ public void testLANG1193() {
+ assertEquals(0, StringUtils.ordinalIndexOf("abc", "ab", 1));
+ }
+
+ @Test
+ // Non-overlapping test
+ public void testLANG1241_1() {
+ // 0 3 6
+ assertEquals(0, StringUtils.ordinalIndexOf("abaabaab", "ab", 1));
+ assertEquals(3, StringUtils.ordinalIndexOf("abaabaab", "ab", 2));
+ assertEquals(6, StringUtils.ordinalIndexOf("abaabaab", "ab", 3));
+ }
+
+ @Test
+ // Overlapping matching test
+ public void testLANG1241_2() {
+ // 0 2 4
+ assertEquals(0, StringUtils.ordinalIndexOf("abababa", "aba", 1));
+ assertEquals(2, StringUtils.ordinalIndexOf("abababa", "aba", 2));
+ assertEquals(4, StringUtils.ordinalIndexOf("abababa", "aba", 3));
+ assertEquals(0, StringUtils.ordinalIndexOf("abababab", "abab", 1));
+ assertEquals(2, StringUtils.ordinalIndexOf("abababab", "abab", 2));
+ assertEquals(4, StringUtils.ordinalIndexOf("abababab", "abab", 3));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsIsTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsIsTest.java
new file mode 100644
index 000000000..eb6c19aa8
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsIsTest.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.StringUtils} - IsX methods
+ */
+public class StringUtilsIsTest extends AbstractLangTest {
+
+ @Test
+ public void testIsAlpha() {
+ assertFalse(StringUtils.isAlpha(null));
+ assertFalse(StringUtils.isAlpha(""));
+ assertFalse(StringUtils.isAlpha(" "));
+ assertTrue(StringUtils.isAlpha("a"));
+ assertTrue(StringUtils.isAlpha("A"));
+ assertTrue(StringUtils.isAlpha("kgKgKgKgkgkGkjkjlJlOKLgHdGdHgl"));
+ assertFalse(StringUtils.isAlpha("ham kso"));
+ assertFalse(StringUtils.isAlpha("1"));
+ assertFalse(StringUtils.isAlpha("hkHKHik6iUGHKJgU7tUJgKJGI87GIkug"));
+ assertFalse(StringUtils.isAlpha("_"));
+ assertFalse(StringUtils.isAlpha("hkHKHik*khbkuh"));
+ }
+
+ @Test
+ public void testIsAlphanumeric() {
+ assertFalse(StringUtils.isAlphanumeric(null));
+ assertFalse(StringUtils.isAlphanumeric(""));
+ assertFalse(StringUtils.isAlphanumeric(" "));
+ assertTrue(StringUtils.isAlphanumeric("a"));
+ assertTrue(StringUtils.isAlphanumeric("A"));
+ assertTrue(StringUtils.isAlphanumeric("kgKgKgKgkgkGkjkjlJlOKLgHdGdHgl"));
+ assertFalse(StringUtils.isAlphanumeric("ham kso"));
+ assertTrue(StringUtils.isAlphanumeric("1"));
+ assertTrue(StringUtils.isAlphanumeric("hkHKHik6iUGHKJgU7tUJgKJGI87GIkug"));
+ assertFalse(StringUtils.isAlphanumeric("_"));
+ assertFalse(StringUtils.isAlphanumeric("hkHKHik*khbkuh"));
+ }
+
+ @Test
+ public void testIsAlphanumericSpace() {
+ assertFalse(StringUtils.isAlphanumericSpace(null));
+ assertTrue(StringUtils.isAlphanumericSpace(""));
+ assertTrue(StringUtils.isAlphanumericSpace(" "));
+ assertTrue(StringUtils.isAlphanumericSpace("a"));
+ assertTrue(StringUtils.isAlphanumericSpace("A"));
+ assertTrue(StringUtils.isAlphanumericSpace("kgKgKgKgkgkGkjkjlJlOKLgHdGdHgl"));
+ assertTrue(StringUtils.isAlphanumericSpace("ham kso"));
+ assertTrue(StringUtils.isAlphanumericSpace("1"));
+ assertTrue(StringUtils.isAlphanumericSpace("hkHKHik6iUGHKJgU7tUJgKJGI87GIkug"));
+ assertFalse(StringUtils.isAlphanumericSpace("_"));
+ assertFalse(StringUtils.isAlphanumericSpace("hkHKHik*khbkuh"));
+ }
+
+ @Test
+ public void testIsAlphaspace() {
+ assertFalse(StringUtils.isAlphaSpace(null));
+ assertTrue(StringUtils.isAlphaSpace(""));
+ assertTrue(StringUtils.isAlphaSpace(" "));
+ assertTrue(StringUtils.isAlphaSpace("a"));
+ assertTrue(StringUtils.isAlphaSpace("A"));
+ assertTrue(StringUtils.isAlphaSpace("kgKgKgKgkgkGkjkjlJlOKLgHdGdHgl"));
+ assertTrue(StringUtils.isAlphaSpace("ham kso"));
+ assertFalse(StringUtils.isAlphaSpace("1"));
+ assertFalse(StringUtils.isAlphaSpace("hkHKHik6iUGHKJgU7tUJgKJGI87GIkug"));
+ assertFalse(StringUtils.isAlphaSpace("_"));
+ assertFalse(StringUtils.isAlphaSpace("hkHKHik*khbkuh"));
+ }
+
+ @Test
+ public void testIsAsciiPrintable_String() {
+ assertFalse(StringUtils.isAsciiPrintable(null));
+ assertTrue(StringUtils.isAsciiPrintable(""));
+ assertTrue(StringUtils.isAsciiPrintable(" "));
+ assertTrue(StringUtils.isAsciiPrintable("a"));
+ assertTrue(StringUtils.isAsciiPrintable("A"));
+ assertTrue(StringUtils.isAsciiPrintable("1"));
+ assertTrue(StringUtils.isAsciiPrintable("Ceki"));
+ assertTrue(StringUtils.isAsciiPrintable("!ab2c~"));
+ assertTrue(StringUtils.isAsciiPrintable("1000"));
+ assertTrue(StringUtils.isAsciiPrintable("10 00"));
+ assertFalse(StringUtils.isAsciiPrintable("10\t00"));
+ assertTrue(StringUtils.isAsciiPrintable("10.00"));
+ assertTrue(StringUtils.isAsciiPrintable("10,00"));
+ assertTrue(StringUtils.isAsciiPrintable("!ab-c~"));
+ assertTrue(StringUtils.isAsciiPrintable("hkHK=Hik6i?UGH_KJgU7.tUJgKJ*GI87GI,kug"));
+ assertTrue(StringUtils.isAsciiPrintable("\u0020"));
+ assertTrue(StringUtils.isAsciiPrintable("\u0021"));
+ assertTrue(StringUtils.isAsciiPrintable("\u007e"));
+ assertFalse(StringUtils.isAsciiPrintable("\u007f"));
+ assertTrue(StringUtils.isAsciiPrintable("G?lc?"));
+ assertTrue(StringUtils.isAsciiPrintable("=?iso-8859-1?Q?G=FClc=FC?="));
+ assertFalse(StringUtils.isAsciiPrintable("G\u00fclc\u00fc"));
+ }
+
+ @Test
+ public void testIsNumeric() {
+ assertFalse(StringUtils.isNumeric(null));
+ assertFalse(StringUtils.isNumeric(""));
+ assertFalse(StringUtils.isNumeric(" "));
+ assertFalse(StringUtils.isNumeric("a"));
+ assertFalse(StringUtils.isNumeric("A"));
+ assertFalse(StringUtils.isNumeric("kgKgKgKgkgkGkjkjlJlOKLgHdGdHgl"));
+ assertFalse(StringUtils.isNumeric("ham kso"));
+ assertTrue(StringUtils.isNumeric("1"));
+ assertTrue(StringUtils.isNumeric("1000"));
+ assertTrue(StringUtils.isNumeric("\u0967\u0968\u0969"));
+ assertFalse(StringUtils.isNumeric("\u0967\u0968 \u0969"));
+ assertFalse(StringUtils.isNumeric("2.3"));
+ assertFalse(StringUtils.isNumeric("10 00"));
+ assertFalse(StringUtils.isNumeric("hkHKHik6iUGHKJgU7tUJgKJGI87GIkug"));
+ assertFalse(StringUtils.isNumeric("_"));
+ assertFalse(StringUtils.isNumeric("hkHKHik*khbkuh"));
+ assertFalse(StringUtils.isNumeric("+123"));
+ assertFalse(StringUtils.isNumeric("-123"));
+ }
+
+ @Test
+ public void testIsNumericSpace() {
+ assertFalse(StringUtils.isNumericSpace(null));
+ assertTrue(StringUtils.isNumericSpace(""));
+ assertTrue(StringUtils.isNumericSpace(" "));
+ assertFalse(StringUtils.isNumericSpace("a"));
+ assertFalse(StringUtils.isNumericSpace("A"));
+ assertFalse(StringUtils.isNumericSpace("kgKgKgKgkgkGkjkjlJlOKLgHdGdHgl"));
+ assertFalse(StringUtils.isNumericSpace("ham kso"));
+ assertTrue(StringUtils.isNumericSpace("1"));
+ assertTrue(StringUtils.isNumericSpace("1000"));
+ assertFalse(StringUtils.isNumericSpace("2.3"));
+ assertTrue(StringUtils.isNumericSpace("10 00"));
+ assertTrue(StringUtils.isNumericSpace("\u0967\u0968\u0969"));
+ assertTrue(StringUtils.isNumericSpace("\u0967\u0968 \u0969"));
+ assertFalse(StringUtils.isNumericSpace("hkHKHik6iUGHKJgU7tUJgKJGI87GIkug"));
+ assertFalse(StringUtils.isNumericSpace("_"));
+ assertFalse(StringUtils.isNumericSpace("hkHKHik*khbkuh"));
+ }
+
+ @Test
+ public void testIsWhitespace() {
+ assertFalse(StringUtils.isWhitespace(null));
+ assertTrue(StringUtils.isWhitespace(""));
+ assertTrue(StringUtils.isWhitespace(" "));
+ assertTrue(StringUtils.isWhitespace("\t \n \t"));
+ assertFalse(StringUtils.isWhitespace("\t aa\n \t"));
+ assertTrue(StringUtils.isWhitespace(" "));
+ assertFalse(StringUtils.isWhitespace(" a "));
+ assertFalse(StringUtils.isWhitespace("a "));
+ assertFalse(StringUtils.isWhitespace(" a"));
+ assertFalse(StringUtils.isWhitespace("aba"));
+ assertTrue(StringUtils.isWhitespace(StringUtilsTest.WHITESPACE));
+ assertFalse(StringUtils.isWhitespace(StringUtilsTest.NON_WHITESPACE));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsStartsEndsWithTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsStartsEndsWithTest.java
new file mode 100644
index 000000000..d1f68d57b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsStartsEndsWithTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.StringUtils} - StartsWith/EndsWith methods
+ */
+public class StringUtilsStartsEndsWithTest extends AbstractLangTest {
+ private static final String foo = "foo";
+ private static final String bar = "bar";
+ private static final String foobar = "foobar";
+ private static final String FOO = "FOO";
+ private static final String BAR = "BAR";
+ private static final String FOOBAR = "FOOBAR";
+
+
+ /**
+ * Test StringUtils.startsWith()
+ */
+ @Test
+ public void testStartsWith() {
+ assertTrue(StringUtils.startsWith(null, null), "startsWith(null, null)");
+ assertFalse(StringUtils.startsWith(FOOBAR, null), "startsWith(FOOBAR, null)");
+ assertFalse(StringUtils.startsWith(null, FOO), "startsWith(null, FOO)");
+ assertTrue(StringUtils.startsWith(FOOBAR, ""), "startsWith(FOOBAR, \"\")");
+
+ assertTrue(StringUtils.startsWith(foobar, foo), "startsWith(foobar, foo)");
+ assertTrue(StringUtils.startsWith(FOOBAR, FOO), "startsWith(FOOBAR, FOO)");
+ assertFalse(StringUtils.startsWith(foobar, FOO), "startsWith(foobar, FOO)");
+ assertFalse(StringUtils.startsWith(FOOBAR, foo), "startsWith(FOOBAR, foo)");
+
+ assertFalse(StringUtils.startsWith(foo, foobar), "startsWith(foo, foobar)");
+ assertFalse(StringUtils.startsWith(bar, foobar), "startsWith(foo, foobar)");
+
+ assertFalse(StringUtils.startsWith(foobar, bar), "startsWith(foobar, bar)");
+ assertFalse(StringUtils.startsWith(FOOBAR, BAR), "startsWith(FOOBAR, BAR)");
+ assertFalse(StringUtils.startsWith(foobar, BAR), "startsWith(foobar, BAR)");
+ assertFalse(StringUtils.startsWith(FOOBAR, bar), "startsWith(FOOBAR, bar)");
+ }
+
+ /**
+ * Test StringUtils.testStartsWithIgnoreCase()
+ */
+ @Test
+ public void testStartsWithIgnoreCase() {
+ assertTrue(StringUtils.startsWithIgnoreCase(null, null), "startsWithIgnoreCase(null, null)");
+ assertFalse(StringUtils.startsWithIgnoreCase(FOOBAR, null), "startsWithIgnoreCase(FOOBAR, null)");
+ assertFalse(StringUtils.startsWithIgnoreCase(null, FOO), "startsWithIgnoreCase(null, FOO)");
+ assertTrue(StringUtils.startsWithIgnoreCase(FOOBAR, ""), "startsWithIgnoreCase(FOOBAR, \"\")");
+
+ assertTrue(StringUtils.startsWithIgnoreCase(foobar, foo), "startsWithIgnoreCase(foobar, foo)");
+ assertTrue(StringUtils.startsWithIgnoreCase(FOOBAR, FOO), "startsWithIgnoreCase(FOOBAR, FOO)");
+ assertTrue(StringUtils.startsWithIgnoreCase(foobar, FOO), "startsWithIgnoreCase(foobar, FOO)");
+ assertTrue(StringUtils.startsWithIgnoreCase(FOOBAR, foo), "startsWithIgnoreCase(FOOBAR, foo)");
+
+ assertFalse(StringUtils.startsWithIgnoreCase(foo, foobar), "startsWithIgnoreCase(foo, foobar)");
+ assertFalse(StringUtils.startsWithIgnoreCase(bar, foobar), "startsWithIgnoreCase(foo, foobar)");
+
+ assertFalse(StringUtils.startsWithIgnoreCase(foobar, bar), "startsWithIgnoreCase(foobar, bar)");
+ assertFalse(StringUtils.startsWithIgnoreCase(FOOBAR, BAR), "startsWithIgnoreCase(FOOBAR, BAR)");
+ assertFalse(StringUtils.startsWithIgnoreCase(foobar, BAR), "startsWithIgnoreCase(foobar, BAR)");
+ assertFalse(StringUtils.startsWithIgnoreCase(FOOBAR, bar), "startsWithIgnoreCase(FOOBAR, bar)");
+ }
+
+ @Test
+ public void testStartsWithAny() {
+ assertFalse(StringUtils.startsWithAny(null, (String[]) null));
+ assertFalse(StringUtils.startsWithAny(null, "abc"));
+ assertFalse(StringUtils.startsWithAny("abcxyz", (String[]) null));
+ assertFalse(StringUtils.startsWithAny("abcxyz"));
+ assertTrue(StringUtils.startsWithAny("abcxyz", "abc"));
+ assertTrue(StringUtils.startsWithAny("abcxyz", null, "xyz", "abc"));
+ assertFalse(StringUtils.startsWithAny("abcxyz", null, "xyz", "abcd"));
+ assertTrue(StringUtils.startsWithAny("abcxyz", ""));
+ assertFalse(StringUtils.startsWithAny("abcxyz", null, "xyz", "ABCX"));
+ assertFalse(StringUtils.startsWithAny("ABCXYZ", null, "xyz", "abc"));
+
+ assertTrue(StringUtils.startsWithAny("abcxyz", new StringBuilder("xyz"), new StringBuffer("abc")), "StringUtils.startsWithAny(abcxyz, StringBuilder(xyz), StringBuffer(abc))");
+ assertTrue(StringUtils.startsWithAny(new StringBuffer("abcxyz"), new StringBuilder("xyz"), new StringBuffer("abc")), "StringUtils.startsWithAny(StringBuffer(abcxyz), StringBuilder(xyz), StringBuffer(abc))");
+ }
+
+
+ /**
+ * Test StringUtils.endsWith()
+ */
+ @Test
+ public void testEndsWith() {
+ assertTrue(StringUtils.endsWith(null, null), "endsWith(null, null)");
+ assertFalse(StringUtils.endsWith(FOOBAR, null), "endsWith(FOOBAR, null)");
+ assertFalse(StringUtils.endsWith(null, FOO), "endsWith(null, FOO)");
+ assertTrue(StringUtils.endsWith(FOOBAR, ""), "endsWith(FOOBAR, \"\")");
+
+ assertFalse(StringUtils.endsWith(foobar, foo), "endsWith(foobar, foo)");
+ assertFalse(StringUtils.endsWith(FOOBAR, FOO), "endsWith(FOOBAR, FOO)");
+ assertFalse(StringUtils.endsWith(foobar, FOO), "endsWith(foobar, FOO)");
+ assertFalse(StringUtils.endsWith(FOOBAR, foo), "endsWith(FOOBAR, foo)");
+
+ assertFalse(StringUtils.endsWith(foo, foobar), "endsWith(foo, foobar)");
+ assertFalse(StringUtils.endsWith(bar, foobar), "endsWith(foo, foobar)");
+
+ assertTrue(StringUtils.endsWith(foobar, bar), "endsWith(foobar, bar)");
+ assertTrue(StringUtils.endsWith(FOOBAR, BAR), "endsWith(FOOBAR, BAR)");
+ assertFalse(StringUtils.endsWith(foobar, BAR), "endsWith(foobar, BAR)");
+ assertFalse(StringUtils.endsWith(FOOBAR, bar), "endsWith(FOOBAR, bar)");
+
+ // "alpha, beta, gamma, delta".endsWith("delta")
+ assertTrue(StringUtils.endsWith("\u03B1\u03B2\u03B3\u03B4", "\u03B4"),
+ "endsWith(\u03B1\u03B2\u03B3\u03B4, \u03B4)");
+ // "alpha, beta, gamma, delta".endsWith("gamma, DELTA")
+ assertFalse(StringUtils.endsWith("\u03B1\u03B2\u03B3\u03B4", "\u03B3\u0394"),
+ "endsWith(\u03B1\u03B2\u03B3\u03B4, \u03B3\u0394)");
+ }
+
+ /**
+ * Test StringUtils.endsWithIgnoreCase()
+ */
+ @Test
+ public void testEndsWithIgnoreCase() {
+ assertTrue(StringUtils.endsWithIgnoreCase(null, null), "endsWithIgnoreCase(null, null)");
+ assertFalse(StringUtils.endsWithIgnoreCase(FOOBAR, null), "endsWithIgnoreCase(FOOBAR, null)");
+ assertFalse(StringUtils.endsWithIgnoreCase(null, FOO), "endsWithIgnoreCase(null, FOO)");
+ assertTrue(StringUtils.endsWithIgnoreCase(FOOBAR, ""), "endsWithIgnoreCase(FOOBAR, \"\")");
+
+ assertFalse(StringUtils.endsWithIgnoreCase(foobar, foo), "endsWithIgnoreCase(foobar, foo)");
+ assertFalse(StringUtils.endsWithIgnoreCase(FOOBAR, FOO), "endsWithIgnoreCase(FOOBAR, FOO)");
+ assertFalse(StringUtils.endsWithIgnoreCase(foobar, FOO), "endsWithIgnoreCase(foobar, FOO)");
+ assertFalse(StringUtils.endsWithIgnoreCase(FOOBAR, foo), "endsWithIgnoreCase(FOOBAR, foo)");
+
+ assertFalse(StringUtils.endsWithIgnoreCase(foo, foobar), "endsWithIgnoreCase(foo, foobar)");
+ assertFalse(StringUtils.endsWithIgnoreCase(bar, foobar), "endsWithIgnoreCase(foo, foobar)");
+
+ assertTrue(StringUtils.endsWithIgnoreCase(foobar, bar), "endsWithIgnoreCase(foobar, bar)");
+ assertTrue(StringUtils.endsWithIgnoreCase(FOOBAR, BAR), "endsWithIgnoreCase(FOOBAR, BAR)");
+ assertTrue(StringUtils.endsWithIgnoreCase(foobar, BAR), "endsWithIgnoreCase(foobar, BAR)");
+ assertTrue(StringUtils.endsWithIgnoreCase(FOOBAR, bar), "endsWithIgnoreCase(FOOBAR, bar)");
+
+ // javadoc
+ assertTrue(StringUtils.endsWithIgnoreCase("abcdef", "def"));
+ assertTrue(StringUtils.endsWithIgnoreCase("ABCDEF", "def"));
+ assertFalse(StringUtils.endsWithIgnoreCase("ABCDEF", "cde"));
+
+ // "alpha, beta, gamma, delta".endsWith("DELTA")
+ assertTrue(StringUtils.endsWithIgnoreCase("\u03B1\u03B2\u03B3\u03B4", "\u0394"),
+ "endsWith(\u03B1\u03B2\u03B3\u03B4, \u0394)");
+ // "alpha, beta, gamma, delta".endsWith("GAMMA")
+ assertFalse(StringUtils.endsWithIgnoreCase("\u03B1\u03B2\u03B3\u03B4", "\u0393"),
+ "endsWith(\u03B1\u03B2\u03B3\u03B4, \u0393)");
+ }
+
+ @Test
+ public void testEndsWithAny() {
+ assertFalse(StringUtils.endsWithAny(null, (String) null), "StringUtils.endsWithAny(null, null)");
+ assertFalse(StringUtils.endsWithAny(null, "abc"), "StringUtils.endsWithAny(null, new String[] {abc})");
+ assertFalse(StringUtils.endsWithAny("abcxyz", (String) null), "StringUtils.endsWithAny(abcxyz, null)");
+ assertTrue(StringUtils.endsWithAny("abcxyz", ""), "StringUtils.endsWithAny(abcxyz, new String[] {\"\"})");
+ assertTrue(StringUtils.endsWithAny("abcxyz", "xyz"), "StringUtils.endsWithAny(abcxyz, new String[] {xyz})");
+ assertTrue(StringUtils.endsWithAny("abcxyz", null, "xyz", "abc"), "StringUtils.endsWithAny(abcxyz, new String[] {null, xyz, abc})");
+ assertFalse(StringUtils.endsWithAny("defg", null, "xyz", "abc"), "StringUtils.endsWithAny(defg, new String[] {null, xyz, abc})");
+ assertTrue(StringUtils.endsWithAny("abcXYZ", "def", "XYZ"));
+ assertFalse(StringUtils.endsWithAny("abcXYZ", "def", "xyz"));
+ assertTrue(StringUtils.endsWithAny("abcXYZ", "def", "YZ"));
+
+ /*
+ * Type null of the last argument to method endsWithAny(CharSequence, CharSequence...)
+ * doesn't exactly match the vararg parameter type.
+ * Cast to CharSequence[] to confirm the non-varargs invocation,
+ * or pass individual arguments of type CharSequence for a varargs invocation.
+ *
+ * assertFalse(StringUtils.endsWithAny("abcXYZ", null)); // replace with specific types to avoid warning
+ */
+ assertFalse(StringUtils.endsWithAny("abcXYZ", (CharSequence) null));
+ assertFalse(StringUtils.endsWithAny("abcXYZ", (CharSequence[]) null));
+ assertTrue(StringUtils.endsWithAny("abcXYZ", ""));
+
+ assertTrue(StringUtils.endsWithAny("abcxyz", new StringBuilder("abc"), new StringBuffer("xyz")), "StringUtils.endsWithAny(abcxyz, StringBuilder(abc), StringBuffer(xyz))");
+ assertTrue(StringUtils.endsWithAny(new StringBuffer("abcxyz"), new StringBuilder("abc"), new StringBuffer("xyz")), "StringUtils.endsWithAny(StringBuffer(abcxyz), StringBuilder(abc), StringBuffer(xyz))");
+ }
+
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsSubstringTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsSubstringTest.java
new file mode 100644
index 000000000..a6f6bf5d0
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsSubstringTest.java
@@ -0,0 +1,386 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.StringUtils} - Substring methods
+ */
+public class StringUtilsSubstringTest extends AbstractLangTest {
+ private static final String FOO = "foo";
+ private static final String BAR = "bar";
+ private static final String BAZ = "baz";
+ private static final String FOOBAR = "foobar";
+ private static final String SENTENCE = "foo bar baz";
+
+
+ @Test
+ public void testSubstring_StringInt() {
+ assertNull(StringUtils.substring(null, 0));
+ assertEquals("", StringUtils.substring("", 0));
+ assertEquals("", StringUtils.substring("", 2));
+
+ assertEquals("", StringUtils.substring(SENTENCE, 80));
+ assertEquals(BAZ, StringUtils.substring(SENTENCE, 8));
+ assertEquals(BAZ, StringUtils.substring(SENTENCE, -3));
+ assertEquals(SENTENCE, StringUtils.substring(SENTENCE, 0));
+ assertEquals("abc", StringUtils.substring("abc", -4));
+ assertEquals("abc", StringUtils.substring("abc", -3));
+ assertEquals("bc", StringUtils.substring("abc", -2));
+ assertEquals("c", StringUtils.substring("abc", -1));
+ assertEquals("abc", StringUtils.substring("abc", 0));
+ assertEquals("bc", StringUtils.substring("abc", 1));
+ assertEquals("c", StringUtils.substring("abc", 2));
+ assertEquals("", StringUtils.substring("abc", 3));
+ assertEquals("", StringUtils.substring("abc", 4));
+ }
+
+ @Test
+ public void testSubstring_StringIntInt() {
+ assertNull(StringUtils.substring(null, 0, 0));
+ assertNull(StringUtils.substring(null, 1, 2));
+ assertEquals("", StringUtils.substring("", 0, 0));
+ assertEquals("", StringUtils.substring("", 1, 2));
+ assertEquals("", StringUtils.substring("", -2, -1));
+
+ assertEquals("", StringUtils.substring(SENTENCE, 8, 6));
+ assertEquals(FOO, StringUtils.substring(SENTENCE, 0, 3));
+ assertEquals("o", StringUtils.substring(SENTENCE, -9, 3));
+ assertEquals(FOO, StringUtils.substring(SENTENCE, 0, -8));
+ assertEquals("o", StringUtils.substring(SENTENCE, -9, -8));
+ assertEquals(SENTENCE, StringUtils.substring(SENTENCE, 0, 80));
+ assertEquals("", StringUtils.substring(SENTENCE, 2, 2));
+ assertEquals("b", StringUtils.substring("abc", -2, -1));
+ }
+
+ @Test
+ public void testLeft_String() {
+ assertSame(null, StringUtils.left(null, -1));
+ assertSame(null, StringUtils.left(null, 0));
+ assertSame(null, StringUtils.left(null, 2));
+
+ assertEquals("", StringUtils.left("", -1));
+ assertEquals("", StringUtils.left("", 0));
+ assertEquals("", StringUtils.left("", 2));
+
+ assertEquals("", StringUtils.left(FOOBAR, -1));
+ assertEquals("", StringUtils.left(FOOBAR, 0));
+ assertEquals(FOO, StringUtils.left(FOOBAR, 3));
+ assertSame(FOOBAR, StringUtils.left(FOOBAR, 80));
+ }
+
+ @Test
+ public void testRight_String() {
+ assertSame(null, StringUtils.right(null, -1));
+ assertSame(null, StringUtils.right(null, 0));
+ assertSame(null, StringUtils.right(null, 2));
+
+ assertEquals("", StringUtils.right("", -1));
+ assertEquals("", StringUtils.right("", 0));
+ assertEquals("", StringUtils.right("", 2));
+
+ assertEquals("", StringUtils.right(FOOBAR, -1));
+ assertEquals("", StringUtils.right(FOOBAR, 0));
+ assertEquals(BAR, StringUtils.right(FOOBAR, 3));
+ assertSame(FOOBAR, StringUtils.right(FOOBAR, 80));
+ }
+
+ @Test
+ public void testMid_String() {
+ assertSame(null, StringUtils.mid(null, -1, 0));
+ assertSame(null, StringUtils.mid(null, 0, -1));
+ assertSame(null, StringUtils.mid(null, 3, 0));
+ assertSame(null, StringUtils.mid(null, 3, 2));
+
+ assertEquals("", StringUtils.mid("", 0, -1));
+ assertEquals("", StringUtils.mid("", 0, 0));
+ assertEquals("", StringUtils.mid("", 0, 2));
+
+ assertEquals("", StringUtils.mid(FOOBAR, 3, -1));
+ assertEquals("", StringUtils.mid(FOOBAR, 3, 0));
+ assertEquals("b", StringUtils.mid(FOOBAR, 3, 1));
+ assertEquals(FOO, StringUtils.mid(FOOBAR, 0, 3));
+ assertEquals(BAR, StringUtils.mid(FOOBAR, 3, 3));
+ assertEquals(FOOBAR, StringUtils.mid(FOOBAR, 0, 80));
+ assertEquals(BAR, StringUtils.mid(FOOBAR, 3, 80));
+ assertEquals("", StringUtils.mid(FOOBAR, 9, 3));
+ assertEquals(FOO, StringUtils.mid(FOOBAR, -1, 3));
+ }
+
+ @Test
+ public void testSubstringBefore_StringInt() {
+ assertEquals("foo", StringUtils.substringBefore("fooXXbarXXbaz", 'X'));
+
+ assertNull(StringUtils.substringBefore(null, 0));
+ assertNull(StringUtils.substringBefore(null, 'X'));
+ assertEquals("", StringUtils.substringBefore("", 0));
+ assertEquals("", StringUtils.substringBefore("", 'X'));
+
+ assertEquals("foo", StringUtils.substringBefore("foo", 0));
+ assertEquals("foo", StringUtils.substringBefore("foo", 'b'));
+ assertEquals("f", StringUtils.substringBefore("foot", 'o'));
+ assertEquals("", StringUtils.substringBefore("abc", 'a'));
+ assertEquals("a", StringUtils.substringBefore("abcba", 'b'));
+ assertEquals("ab", StringUtils.substringBefore("abc", 'c'));
+ assertEquals("abc", StringUtils.substringBefore("abc", 0));
+ }
+
+ @Test
+ public void testSubstringBefore_StringString() {
+ assertEquals("foo", StringUtils.substringBefore("fooXXbarXXbaz", "XX"));
+
+ assertNull(StringUtils.substringBefore(null, null));
+ assertNull(StringUtils.substringBefore(null, ""));
+ assertNull(StringUtils.substringBefore(null, "XX"));
+ assertEquals("", StringUtils.substringBefore("", null));
+ assertEquals("", StringUtils.substringBefore("", ""));
+ assertEquals("", StringUtils.substringBefore("", "XX"));
+
+ assertEquals("foo", StringUtils.substringBefore("foo", null));
+ assertEquals("foo", StringUtils.substringBefore("foo", "b"));
+ assertEquals("f", StringUtils.substringBefore("foot", "o"));
+ assertEquals("", StringUtils.substringBefore("abc", "a"));
+ assertEquals("a", StringUtils.substringBefore("abcba", "b"));
+ assertEquals("ab", StringUtils.substringBefore("abc", "c"));
+ assertEquals("", StringUtils.substringBefore("abc", ""));
+ assertEquals("abc", StringUtils.substringBefore("abc", "X"));
+ }
+
+ @Test
+ public void testSubstringAfter_StringString() {
+ assertEquals("barXXbaz", StringUtils.substringAfter("fooXXbarXXbaz", "XX"));
+
+ assertNull(StringUtils.substringAfter(null, null));
+ assertNull(StringUtils.substringAfter(null, ""));
+ assertNull(StringUtils.substringAfter(null, "XX"));
+ assertEquals("", StringUtils.substringAfter("", null));
+ assertEquals("", StringUtils.substringAfter("", ""));
+ assertEquals("", StringUtils.substringAfter("", "XX"));
+
+ assertEquals("", StringUtils.substringAfter("foo", null));
+ assertEquals("ot", StringUtils.substringAfter("foot", "o"));
+ assertEquals("bc", StringUtils.substringAfter("abc", "a"));
+ assertEquals("cba", StringUtils.substringAfter("abcba", "b"));
+ assertEquals("", StringUtils.substringAfter("abc", "c"));
+ assertEquals("abc", StringUtils.substringAfter("abc", ""));
+ assertEquals("", StringUtils.substringAfter("abc", "d"));
+ }
+
+ @Test
+ public void testSubstringAfter_StringInt() {
+ assertNull(StringUtils.substringAfter(null, 0));
+ assertNull(StringUtils.substringAfter(null, 'X'));
+ assertEquals("", StringUtils.substringAfter("", 0));
+ assertEquals("", StringUtils.substringAfter("", 'X'));
+
+ assertEquals("", StringUtils.substringAfter("foo", 0));
+ assertEquals("ot", StringUtils.substringAfter("foot", 'o'));
+ assertEquals("bc", StringUtils.substringAfter("abc", 'a'));
+ assertEquals("cba", StringUtils.substringAfter("abcba", 'b'));
+ assertEquals("", StringUtils.substringAfter("abc", 'c'));
+ assertEquals("", StringUtils.substringAfter("abc", 'd'));
+ }
+
+ @Test
+ public void testSubstringBeforeLast_StringString() {
+ assertEquals("fooXXbar", StringUtils.substringBeforeLast("fooXXbarXXbaz", "XX"));
+
+ assertNull(StringUtils.substringBeforeLast(null, null));
+ assertNull(StringUtils.substringBeforeLast(null, ""));
+ assertNull(StringUtils.substringBeforeLast(null, "XX"));
+ assertEquals("", StringUtils.substringBeforeLast("", null));
+ assertEquals("", StringUtils.substringBeforeLast("", ""));
+ assertEquals("", StringUtils.substringBeforeLast("", "XX"));
+
+ assertEquals("foo", StringUtils.substringBeforeLast("foo", null));
+ assertEquals("foo", StringUtils.substringBeforeLast("foo", "b"));
+ assertEquals("fo", StringUtils.substringBeforeLast("foo", "o"));
+ assertEquals("abc\r\n", StringUtils.substringBeforeLast("abc\r\n", "d"));
+ assertEquals("abc", StringUtils.substringBeforeLast("abcdabc", "d"));
+ assertEquals("abcdabc", StringUtils.substringBeforeLast("abcdabcd", "d"));
+ assertEquals("a", StringUtils.substringBeforeLast("abc", "b"));
+ assertEquals("abc ", StringUtils.substringBeforeLast("abc \n", "\n"));
+ assertEquals("a", StringUtils.substringBeforeLast("a", null));
+ assertEquals("a", StringUtils.substringBeforeLast("a", ""));
+ assertEquals("", StringUtils.substringBeforeLast("a", "a"));
+ }
+
+ @Test
+ public void testSubstringAfterLast_StringString() {
+ assertEquals("baz", StringUtils.substringAfterLast("fooXXbarXXbaz", "XX"));
+
+ assertNull(StringUtils.substringAfterLast(null, null));
+ assertNull(StringUtils.substringAfterLast(null, ""));
+ assertNull(StringUtils.substringAfterLast(null, "XX"));
+ assertEquals("", StringUtils.substringAfterLast("", null));
+ assertEquals("", StringUtils.substringAfterLast("", ""));
+ assertEquals("", StringUtils.substringAfterLast("", "a"));
+
+ assertEquals("", StringUtils.substringAfterLast("foo", null));
+ assertEquals("", StringUtils.substringAfterLast("foo", "b"));
+ assertEquals("t", StringUtils.substringAfterLast("foot", "o"));
+ assertEquals("bc", StringUtils.substringAfterLast("abc", "a"));
+ assertEquals("a", StringUtils.substringAfterLast("abcba", "b"));
+ assertEquals("", StringUtils.substringAfterLast("abc", "c"));
+ assertEquals("", StringUtils.substringAfterLast("", "d"));
+ assertEquals("", StringUtils.substringAfterLast("abc", ""));
+ }
+
+ @Test
+ public void testSubstringAfterLast_StringInt() {
+ assertNull(StringUtils.substringAfterLast(null, 0));
+ assertNull(StringUtils.substringAfterLast(null, 'X'));
+ assertEquals("", StringUtils.substringAfterLast("", 0));
+ assertEquals("", StringUtils.substringAfterLast("", 'a'));
+
+ assertEquals("", StringUtils.substringAfterLast("foo", 0));
+ assertEquals("", StringUtils.substringAfterLast("foo", 'b'));
+ assertEquals("t", StringUtils.substringAfterLast("foot", 'o'));
+ assertEquals("bc", StringUtils.substringAfterLast("abc", 'a'));
+ assertEquals("a", StringUtils.substringAfterLast("abcba", 'b'));
+ assertEquals("", StringUtils.substringAfterLast("abc", 'c'));
+ assertEquals("", StringUtils.substringAfterLast("", 'd'));
+ }
+
+ @Test
+ public void testSubstringBetween_StringString() {
+ assertNull(StringUtils.substringBetween(null, "tag"));
+ assertEquals("", StringUtils.substringBetween("", ""));
+ assertNull(StringUtils.substringBetween("", "abc"));
+ assertEquals("", StringUtils.substringBetween(" ", " "));
+ assertNull(StringUtils.substringBetween("abc", null));
+ assertEquals("", StringUtils.substringBetween("abc", ""));
+ assertNull(StringUtils.substringBetween("abc", "a"));
+ assertEquals("bc", StringUtils.substringBetween("abca", "a"));
+ assertEquals("bc", StringUtils.substringBetween("abcabca", "a"));
+ assertEquals("bar", StringUtils.substringBetween("\nbar\n", "\n"));
+ }
+
+ @Test
+ public void testSubstringBetween_StringStringString() {
+ assertNull(StringUtils.substringBetween(null, "", ""));
+ assertNull(StringUtils.substringBetween("", null, ""));
+ assertNull(StringUtils.substringBetween("", "", null));
+ assertEquals("", StringUtils.substringBetween("", "", ""));
+ assertEquals("", StringUtils.substringBetween("foo", "", ""));
+ assertNull(StringUtils.substringBetween("foo", "", "]"));
+ assertNull(StringUtils.substringBetween("foo", "[", "]"));
+ assertEquals("", StringUtils.substringBetween(" ", " ", " "));
+ assertEquals("bar", StringUtils.substringBetween("<foo>bar</foo>", "<foo>", "</foo>") );
+ assertEquals("abc", StringUtils.substringBetween("yabczyabcz", "y", "z"));
+ }
+
+ /**
+ * Tests the substringsBetween method that returns a String Array of substrings.
+ */
+ @Test
+ public void testSubstringsBetween_StringStringString() {
+
+ String[] results = StringUtils.substringsBetween("[one], [two], [three]", "[", "]");
+ assertEquals(3, results.length);
+ assertEquals("one", results[0]);
+ assertEquals("two", results[1]);
+ assertEquals("three", results[2]);
+
+ results = StringUtils.substringsBetween("[one], [two], three", "[", "]");
+ assertEquals(2, results.length);
+ assertEquals("one", results[0]);
+ assertEquals("two", results[1]);
+
+ results = StringUtils.substringsBetween("[one], [two], three]", "[", "]");
+ assertEquals(2, results.length);
+ assertEquals("one", results[0]);
+ assertEquals("two", results[1]);
+
+ results = StringUtils.substringsBetween("[one], two], three]", "[", "]");
+ assertEquals(1, results.length);
+ assertEquals("one", results[0]);
+
+ results = StringUtils.substringsBetween("one], two], [three]", "[", "]");
+ assertEquals(1, results.length);
+ assertEquals("three", results[0]);
+
+ // 'ab hello ba' will match, but 'ab non ba' won't
+ // this is because the 'a' is shared between the two and can't be matched twice
+ results = StringUtils.substringsBetween("aabhellobabnonba", "ab", "ba");
+ assertEquals(1, results.length);
+ assertEquals("hello", results[0]);
+
+ results = StringUtils.substringsBetween("one, two, three", "[", "]");
+ assertNull(results);
+
+ results = StringUtils.substringsBetween("[one, two, three", "[", "]");
+ assertNull(results);
+
+ results = StringUtils.substringsBetween("one, two, three]", "[", "]");
+ assertNull(results);
+
+ results = StringUtils.substringsBetween("[one], [two], [three]", "[", null);
+ assertNull(results);
+
+ results = StringUtils.substringsBetween("[one], [two], [three]", null, "]");
+ assertNull(results);
+
+ results = StringUtils.substringsBetween("[one], [two], [three]", "", "");
+ assertNull(results);
+
+ results = StringUtils.substringsBetween(null, "[", "]");
+ assertNull(results);
+
+ results = StringUtils.substringsBetween("", "[", "]");
+ assertEquals(0, results.length);
+ }
+
+ @Test
+ public void testCountMatches_String() {
+ assertEquals(0, StringUtils.countMatches(null, null));
+ assertEquals(0, StringUtils.countMatches("blah", null));
+ assertEquals(0, StringUtils.countMatches(null, "DD"));
+
+ assertEquals(0, StringUtils.countMatches("x", ""));
+ assertEquals(0, StringUtils.countMatches("", ""));
+
+ assertEquals(3,
+ StringUtils.countMatches("one long someone sentence of one", "one"));
+ assertEquals(0,
+ StringUtils.countMatches("one long someone sentence of one", "two"));
+ assertEquals(4,
+ StringUtils.countMatches("oooooooooooo", "ooo"));
+ assertEquals(0, StringUtils.countMatches(null, "?"));
+ assertEquals(0, StringUtils.countMatches("", "?"));
+ assertEquals(0, StringUtils.countMatches("abba", null));
+ assertEquals(0, StringUtils.countMatches("abba", ""));
+ assertEquals(2, StringUtils.countMatches("abba", "a"));
+ assertEquals(1, StringUtils.countMatches("abba", "ab"));
+ assertEquals(0, StringUtils.countMatches("abba", "xxx"));
+ assertEquals(1, StringUtils.countMatches("ababa", "aba"));
+ }
+
+ @Test
+ public void testCountMatches_char() {
+ assertEquals(0, StringUtils.countMatches(null, 'D'));
+ assertEquals(5, StringUtils.countMatches("one long someone sentence of one", ' '));
+ assertEquals(6, StringUtils.countMatches("one long someone sentence of one", 'o'));
+ assertEquals(4, StringUtils.countMatches("oooooooooooo", "ooo"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsTest.java
new file mode 100644
index 000000000..bc5aeef80
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsTest.java
@@ -0,0 +1,3391 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Supplier;
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.text.WordUtils;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Unit tests for methods of {@link org.apache.commons.lang3.StringUtils}
+ * which been moved to their own test classes.
+ */
+@SuppressWarnings("deprecation") // deliberate use of deprecated code
+public class StringUtilsTest extends AbstractLangTest {
+
+ static final String WHITESPACE;
+ static final String NON_WHITESPACE;
+ static final String HARD_SPACE;
+ static final String TRIMMABLE;
+ static final String NON_TRIMMABLE;
+
+ static {
+ final StringBuilder ws = new StringBuilder();
+ final StringBuilder nws = new StringBuilder();
+ final String hs = String.valueOf((char) 160);
+ final StringBuilder tr = new StringBuilder();
+ final StringBuilder ntr = new StringBuilder();
+ for (int i = 0; i < Character.MAX_VALUE; i++) {
+ if (Character.isWhitespace((char) i)) {
+ ws.append(String.valueOf((char) i));
+ if (i > 32) {
+ ntr.append(String.valueOf((char) i));
+ }
+ } else if (i < 40) {
+ nws.append(String.valueOf((char) i));
+ }
+ }
+ for (int i = 0; i <= 32; i++) {
+ tr.append(String.valueOf((char) i));
+ }
+ WHITESPACE = ws.toString();
+ NON_WHITESPACE = nws.toString();
+ HARD_SPACE = hs;
+ TRIMMABLE = tr.toString();
+ NON_TRIMMABLE = ntr.toString();
+ }
+
+ private static final String[] ARRAY_LIST = {"foo", "bar", "baz"};
+ private static final String[] EMPTY_ARRAY_LIST = {};
+ private static final String[] NULL_ARRAY_LIST = {null};
+ private static final Object[] NULL_TO_STRING_LIST = {
+ new Object() {
+ @Override
+ public String toString() {
+ return null;
+ }
+ }
+ };
+ private static final String[] MIXED_ARRAY_LIST = {null, "", "foo"};
+ private static final Object[] MIXED_TYPE_LIST = {"foo", Long.valueOf(2L)};
+ private static final long[] LONG_PRIM_LIST = {1, 2};
+ private static final int[] INT_PRIM_LIST = {1, 2};
+ private static final byte[] BYTE_PRIM_LIST = {1, 2};
+ private static final short[] SHORT_PRIM_LIST = {1, 2};
+ private static final char[] CHAR_PRIM_LIST = {'1', '2'};
+ private static final float[] FLOAT_PRIM_LIST = {1, 2};
+ private static final double[] DOUBLE_PRIM_LIST = {1, 2};
+ private static final List<String> MIXED_STRING_LIST = Arrays.asList(null, "", "foo");
+ private static final List<Object> MIXED_TYPE_OBJECT_LIST = Arrays.<Object>asList("foo", Long.valueOf(2L));
+ private static final List<String> STRING_LIST = Arrays.asList("foo", "bar", "baz");
+ private static final List<String> EMPTY_STRING_LIST = Collections.emptyList();
+ private static final List<String> NULL_STRING_LIST = Collections.singletonList(null);
+
+ private static final String SEPARATOR = ",";
+ private static final char SEPARATOR_CHAR = ';';
+ private static final char COMMA_SEPARATOR_CHAR = ',';
+
+ private static final String TEXT_LIST = "foo,bar,baz";
+ private static final String TEXT_LIST_CHAR = "foo;bar;baz";
+ private static final String TEXT_LIST_NOSEP = "foobarbaz";
+
+ private static final String FOO_UNCAP = "foo";
+ private static final String FOO_CAP = "Foo";
+
+ private static final String SENTENCE_UNCAP = "foo bar baz";
+ private static final String SENTENCE_CAP = "Foo Bar Baz";
+
+ private static final boolean[] EMPTY = {};
+ private static final boolean[] ARRAY_FALSE_FALSE = {false, false};
+ private static final boolean[] ARRAY_FALSE_TRUE = {false, true};
+ private static final boolean[] ARRAY_FALSE_TRUE_FALSE = {false, true, false};
+
+ private void assertAbbreviateWithAbbrevMarkerAndOffset(final String expected, final String abbrevMarker, final int offset, final int maxWidth) {
+ final String abcdefghijklmno = "abcdefghijklmno";
+ final String message = "abbreviate(String,String,int,int) failed";
+ final String actual = StringUtils.abbreviate(abcdefghijklmno, abbrevMarker, offset, maxWidth);
+ if (offset >= 0 && offset < abcdefghijklmno.length()) {
+ assertTrue(actual.indexOf((char) ('a' + offset)) != -1,
+ message + " -- should contain offset character");
+ }
+ assertTrue(actual.length() <= maxWidth,
+ message + " -- should not be greater than maxWidth");
+ assertEquals(expected, actual, message);
+ }
+
+ private void assertAbbreviateWithOffset(final String expected, final int offset, final int maxWidth) {
+ final String abcdefghijklmno = "abcdefghijklmno";
+ final String message = "abbreviate(String,int,int) failed";
+ final String actual = StringUtils.abbreviate(abcdefghijklmno, offset, maxWidth);
+ if (offset >= 0 && offset < abcdefghijklmno.length()) {
+ assertTrue(actual.indexOf((char) ('a' + offset)) != -1,
+ message + " -- should contain offset character");
+ }
+ assertTrue(actual.length() <= maxWidth,
+ message + " -- should not be greater than maxWidth");
+ assertEquals(expected, actual, message);
+ }
+
+ private void innerTestSplit(final char separator, final String sepStr, final char noMatch) {
+ final String msg = "Failed on separator hex(" + Integer.toHexString(separator) +
+ "), noMatch hex(" + Integer.toHexString(noMatch) + "), sepStr(" + sepStr + ")";
+
+ final String str = "a" + separator + "b" + separator + separator + noMatch + "c";
+ String[] res;
+ // (str, sepStr)
+ res = StringUtils.split(str, sepStr);
+ assertEquals(3, res.length, msg);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals(noMatch + "c", res[2]);
+
+ final String str2 = separator + "a" + separator;
+ res = StringUtils.split(str2, sepStr);
+ assertEquals(1, res.length, msg);
+ assertEquals("a", res[0], msg);
+
+ res = StringUtils.split(str, sepStr, -1);
+ assertEquals(3, res.length, msg);
+ assertEquals("a", res[0], msg);
+ assertEquals("b", res[1], msg);
+ assertEquals(noMatch + "c", res[2], msg);
+
+ res = StringUtils.split(str, sepStr, 0);
+ assertEquals(3, res.length, msg);
+ assertEquals("a", res[0], msg);
+ assertEquals("b", res[1], msg);
+ assertEquals(noMatch + "c", res[2], msg);
+
+ res = StringUtils.split(str, sepStr, 1);
+ assertEquals(1, res.length, msg);
+ assertEquals(str, res[0], msg);
+
+ res = StringUtils.split(str, sepStr, 2);
+ assertEquals(2, res.length, msg);
+ assertEquals("a", res[0], msg);
+ assertEquals(str.substring(2), res[1], msg);
+ }
+
+ private void innerTestSplitPreserveAllTokens(final char separator, final String sepStr, final char noMatch) {
+ final String msg = "Failed on separator hex(" + Integer.toHexString(separator) +
+ "), noMatch hex(" + Integer.toHexString(noMatch) + "), sepStr(" + sepStr + ")";
+
+ final String str = "a" + separator + "b" + separator + separator + noMatch + "c";
+ String[] res;
+ // (str, sepStr)
+ res = StringUtils.splitPreserveAllTokens(str, sepStr);
+ assertEquals(4, res.length, msg);
+ assertEquals("a", res[0], msg);
+ assertEquals("b", res[1], msg);
+ assertEquals("", res[2], msg);
+ assertEquals(noMatch + "c", res[3], msg);
+
+ final String str2 = separator + "a" + separator;
+ res = StringUtils.splitPreserveAllTokens(str2, sepStr);
+ assertEquals(3, res.length, msg);
+ assertEquals("", res[0], msg);
+ assertEquals("a", res[1], msg);
+ assertEquals("", res[2], msg);
+
+ res = StringUtils.splitPreserveAllTokens(str, sepStr, -1);
+ assertEquals(4, res.length, msg);
+ assertEquals("a", res[0], msg);
+ assertEquals("b", res[1], msg);
+ assertEquals("", res[2], msg);
+ assertEquals(noMatch + "c", res[3], msg);
+
+ res = StringUtils.splitPreserveAllTokens(str, sepStr, 0);
+ assertEquals(4, res.length, msg);
+ assertEquals("a", res[0], msg);
+ assertEquals("b", res[1], msg);
+ assertEquals("", res[2], msg);
+ assertEquals(noMatch + "c", res[3], msg);
+
+ res = StringUtils.splitPreserveAllTokens(str, sepStr, 1);
+ assertEquals(1, res.length, msg);
+ assertEquals(str, res[0], msg);
+
+ res = StringUtils.splitPreserveAllTokens(str, sepStr, 2);
+ assertEquals(2, res.length, msg);
+ assertEquals("a", res[0], msg);
+ assertEquals(str.substring(2), res[1], msg);
+ }
+
+ //Fixed LANG-1463
+ @Test
+ public void testAbbreviateMarkerWithEmptyString() {
+ final String greaterThanMaxTest = "much too long text";
+ assertEquals("much too long", StringUtils.abbreviate(greaterThanMaxTest, "", 13));
+ }
+
+ @Test
+ public void testAbbreviate_StringInt() {
+ assertNull(StringUtils.abbreviate(null, 10));
+ assertEquals("", StringUtils.abbreviate("", 10));
+ assertEquals("short", StringUtils.abbreviate("short", 10));
+ assertEquals("Now is ...", StringUtils.abbreviate("Now is the time for all good men to come to the aid of their party.", 10));
+
+ final String raspberry = "raspberry peach";
+ assertEquals("raspberry p...", StringUtils.abbreviate(raspberry, 14));
+ assertEquals("raspberry peach", StringUtils.abbreviate("raspberry peach", 15));
+ assertEquals("raspberry peach", StringUtils.abbreviate("raspberry peach", 16));
+ assertEquals("abc...", StringUtils.abbreviate("abcdefg", 6));
+ assertEquals("abcdefg", StringUtils.abbreviate("abcdefg", 7));
+ assertEquals("abcdefg", StringUtils.abbreviate("abcdefg", 8));
+ assertEquals("a...", StringUtils.abbreviate("abcdefg", 4));
+ assertEquals("", StringUtils.abbreviate("", 4));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.abbreviate("abc", 3),
+ "StringUtils.abbreviate expecting IllegalArgumentException");
+ }
+
+ @Test
+ public void testAbbreviate_StringIntInt() {
+ assertNull(StringUtils.abbreviate(null, 10, 12));
+ assertEquals("", StringUtils.abbreviate("", 0, 10));
+ assertEquals("", StringUtils.abbreviate("", 2, 10));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.abbreviate("abcdefghij", 0, 3),
+ "StringUtils.abbreviate expecting IllegalArgumentException");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.abbreviate("abcdefghij", 5, 6),
+ "StringUtils.abbreviate expecting IllegalArgumentException");
+
+ final String raspberry = "raspberry peach";
+ assertEquals("raspberry peach", StringUtils.abbreviate(raspberry, 11, 15));
+
+ assertNull(StringUtils.abbreviate(null, 7, 14));
+ assertAbbreviateWithOffset("abcdefg...", -1, 10);
+ assertAbbreviateWithOffset("abcdefg...", 0, 10);
+ assertAbbreviateWithOffset("abcdefg...", 1, 10);
+ assertAbbreviateWithOffset("abcdefg...", 2, 10);
+ assertAbbreviateWithOffset("abcdefg...", 3, 10);
+ assertAbbreviateWithOffset("abcdefg...", 4, 10);
+ assertAbbreviateWithOffset("...fghi...", 5, 10);
+ assertAbbreviateWithOffset("...ghij...", 6, 10);
+ assertAbbreviateWithOffset("...hijk...", 7, 10);
+ assertAbbreviateWithOffset("...ijklmno", 8, 10);
+ assertAbbreviateWithOffset("...ijklmno", 9, 10);
+ assertAbbreviateWithOffset("...ijklmno", 10, 10);
+ assertAbbreviateWithOffset("...ijklmno", 11, 10);
+ assertAbbreviateWithOffset("...ijklmno", 12, 10);
+ assertAbbreviateWithOffset("...ijklmno", 13, 10);
+ assertAbbreviateWithOffset("...ijklmno", 14, 10);
+ assertAbbreviateWithOffset("...ijklmno", 15, 10);
+ assertAbbreviateWithOffset("...ijklmno", 16, 10);
+ assertAbbreviateWithOffset("...ijklmno", Integer.MAX_VALUE, 10);
+ }
+
+ @Test
+ public void testAbbreviate_StringStringInt() {
+ assertNull(StringUtils.abbreviate(null, null, 10));
+ assertNull(StringUtils.abbreviate(null, "...", 10));
+ assertEquals("paranaguacu", StringUtils.abbreviate("paranaguacu", null, 10));
+ assertEquals("", StringUtils.abbreviate("", "...", 2));
+ assertEquals("wai**", StringUtils.abbreviate("waiheke", "**", 5));
+ assertEquals("And af,,,,", StringUtils.abbreviate("And after a long time, he finally met his son.", ",,,,", 10));
+
+ final String raspberry = "raspberry peach";
+ assertEquals("raspberry pe..", StringUtils.abbreviate(raspberry, "..", 14));
+ assertEquals("raspberry peach", StringUtils.abbreviate("raspberry peach", "---*---", 15));
+ assertEquals("raspberry peach", StringUtils.abbreviate("raspberry peach", ".", 16));
+ assertEquals("abc()(", StringUtils.abbreviate("abcdefg", "()(", 6));
+ assertEquals("abcdefg", StringUtils.abbreviate("abcdefg", ";", 7));
+ assertEquals("abcdefg", StringUtils.abbreviate("abcdefg", "_-", 8));
+ assertEquals("abc.", StringUtils.abbreviate("abcdefg", ".", 4));
+ assertEquals("", StringUtils.abbreviate("", 4));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.abbreviate("abcdefghij", "...", 3),
+ "StringUtils.abbreviate expecting IllegalArgumentException");
+ }
+
+ @Test
+ public void testAbbreviate_StringStringIntInt() {
+ assertNull(StringUtils.abbreviate(null, null, 10, 12));
+ assertNull(StringUtils.abbreviate(null, "...", 10, 12));
+ assertEquals("", StringUtils.abbreviate("", null, 0, 10));
+ assertEquals("", StringUtils.abbreviate("", "...", 2, 10));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.abbreviate("abcdefghij", "::", 0, 2),
+ "StringUtils.abbreviate expecting IllegalArgumentException");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.abbreviate("abcdefghij", "!!!", 5, 6),
+ "StringUtils.abbreviate expecting IllegalArgumentException");
+
+ final String raspberry = "raspberry peach";
+ assertEquals("raspberry peach", StringUtils.abbreviate(raspberry, "--", 12, 15));
+
+ assertNull(StringUtils.abbreviate(null, ";", 7, 14));
+ assertAbbreviateWithAbbrevMarkerAndOffset("abcdefgh;;", ";;", -1, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("abcdefghi.", ".", 0, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("abcdefgh++", "++", 1, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("abcdefghi*", "*", 2, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("abcdef{{{{", "{{{{", 4, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("abcdef____", "____", 5, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("==fghijk==", "==", 5, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("___ghij___", "___", 6, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 7, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 8, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 9, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("///ijklmno", "///", 10, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("//hijklmno", "//", 10, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("//hijklmno", "//", 11, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("...ijklmno", "...", 12, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 13, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 14, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("999ijklmno", "999", 15, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("_ghijklmno", "_", 16, 10);
+ assertAbbreviateWithAbbrevMarkerAndOffset("+ghijklmno", "+", Integer.MAX_VALUE, 10);
+ }
+
+ @Test
+ public void testAbbreviateMiddle() {
+ // javadoc examples
+ assertNull(StringUtils.abbreviateMiddle(null, null, 0));
+ assertEquals("abc", StringUtils.abbreviateMiddle("abc", null, 0));
+ assertEquals("abc", StringUtils.abbreviateMiddle("abc", ".", 0));
+ assertEquals("abc", StringUtils.abbreviateMiddle("abc", ".", 3));
+ assertEquals("ab.f", StringUtils.abbreviateMiddle("abcdef", ".", 4));
+
+ // JIRA issue (LANG-405) example (slightly different than actual expected result)
+ assertEquals(
+ "A very long text with un...f the text is complete.",
+ StringUtils.abbreviateMiddle(
+ "A very long text with unimportant stuff in the middle but interesting start and " +
+ "end to see if the text is complete.", "...", 50));
+
+ // Test a much longer text :)
+ final String longText = "Start text" + StringUtils.repeat("x", 10000) + "Close text";
+ assertEquals(
+ "Start text->Close text",
+ StringUtils.abbreviateMiddle(longText, "->", 22));
+
+ // Test negative length
+ assertEquals("abc", StringUtils.abbreviateMiddle("abc", ".", -1));
+
+ // Test boundaries
+ // Fails to change anything as method ensures first and last char are kept
+ assertEquals("abc", StringUtils.abbreviateMiddle("abc", ".", 1));
+ assertEquals("abc", StringUtils.abbreviateMiddle("abc", ".", 2));
+
+ // Test length of n=1
+ assertEquals("a", StringUtils.abbreviateMiddle("a", ".", 1));
+
+ // Test smallest length that can lead to success
+ assertEquals("a.d", StringUtils.abbreviateMiddle("abcd", ".", 3));
+
+ // More from LANG-405
+ assertEquals("a..f", StringUtils.abbreviateMiddle("abcdef", "..", 4));
+ assertEquals("ab.ef", StringUtils.abbreviateMiddle("abcdef", ".", 5));
+ }
+
+ /**
+ * Tests {@code appendIfMissing}.
+ */
+ @Test
+ public void testAppendIfMissing() {
+ assertNull(StringUtils.appendIfMissing(null, null), "appendIfMissing(null,null)");
+ assertEquals("abc", StringUtils.appendIfMissing("abc", null), "appendIfMissing(abc,null)");
+ assertEquals("xyz", StringUtils.appendIfMissing("", "xyz"), "appendIfMissing(\"\",xyz)");
+ assertEquals("abcxyz", StringUtils.appendIfMissing("abc", "xyz"), "appendIfMissing(abc,xyz)");
+ assertEquals("abcxyz", StringUtils.appendIfMissing("abcxyz", "xyz"), "appendIfMissing(abcxyz,xyz)");
+ assertEquals("aXYZxyz", StringUtils.appendIfMissing("aXYZ", "xyz"), "appendIfMissing(aXYZ,xyz)");
+
+ assertNull(StringUtils.appendIfMissing(null, null, (CharSequence[]) null), "appendIfMissing(null,null,null)");
+ assertEquals("abc", StringUtils.appendIfMissing("abc", null, (CharSequence[]) null), "appendIfMissing(abc,null,null)");
+ assertEquals("xyz", StringUtils.appendIfMissing("", "xyz", (CharSequence[]) null), "appendIfMissing(\"\",xyz,null))");
+ assertEquals("abcxyz", StringUtils.appendIfMissing("abc", "xyz", null), "appendIfMissing(abc,xyz,{null})");
+ assertEquals("abc", StringUtils.appendIfMissing("abc", "xyz", ""), "appendIfMissing(abc,xyz,\"\")");
+ assertEquals("abcxyz", StringUtils.appendIfMissing("abc", "xyz", "mno"), "appendIfMissing(abc,xyz,mno)");
+ assertEquals("abcxyz", StringUtils.appendIfMissing("abcxyz", "xyz", "mno"), "appendIfMissing(abcxyz,xyz,mno)");
+ assertEquals("abcmno", StringUtils.appendIfMissing("abcmno", "xyz", "mno"), "appendIfMissing(abcmno,xyz,mno)");
+ assertEquals("abcXYZxyz", StringUtils.appendIfMissing("abcXYZ", "xyz", "mno"), "appendIfMissing(abcXYZ,xyz,mno)");
+ assertEquals("abcMNOxyz", StringUtils.appendIfMissing("abcMNO", "xyz", "mno"), "appendIfMissing(abcMNO,xyz,mno)");
+ }
+
+ /**
+ * Tests {@code appendIfMissingIgnoreCase}.
+ */
+ @Test
+ public void testAppendIfMissingIgnoreCase() {
+ assertNull(StringUtils.appendIfMissingIgnoreCase(null, null), "appendIfMissingIgnoreCase(null,null)");
+ assertEquals("abc", StringUtils.appendIfMissingIgnoreCase("abc", null), "appendIfMissingIgnoreCase(abc,null)");
+ assertEquals("xyz", StringUtils.appendIfMissingIgnoreCase("", "xyz"), "appendIfMissingIgnoreCase(\"\",xyz)");
+ assertEquals("abcxyz", StringUtils.appendIfMissingIgnoreCase("abc", "xyz"), "appendIfMissingIgnoreCase(abc,xyz)");
+ assertEquals("abcxyz", StringUtils.appendIfMissingIgnoreCase("abcxyz", "xyz"), "appendIfMissingIgnoreCase(abcxyz,xyz)");
+ assertEquals("abcXYZ", StringUtils.appendIfMissingIgnoreCase("abcXYZ", "xyz"), "appendIfMissingIgnoreCase(abcXYZ,xyz)");
+
+ assertNull(StringUtils.appendIfMissingIgnoreCase(null, null, (CharSequence[]) null), "appendIfMissingIgnoreCase(null,null,null)");
+ assertEquals("abc", StringUtils.appendIfMissingIgnoreCase("abc", null, (CharSequence[]) null), "appendIfMissingIgnoreCase(abc,null,null)");
+ assertEquals("xyz", StringUtils.appendIfMissingIgnoreCase("", "xyz", (CharSequence[]) null), "appendIfMissingIgnoreCase(\"\",xyz,null)");
+ assertEquals("abcxyz", StringUtils.appendIfMissingIgnoreCase("abc", "xyz", null), "appendIfMissingIgnoreCase(abc,xyz,{null})");
+ assertEquals("abc", StringUtils.appendIfMissingIgnoreCase("abc", "xyz", ""), "appendIfMissingIgnoreCase(abc,xyz,\"\")");
+ assertEquals("abcxyz", StringUtils.appendIfMissingIgnoreCase("abc", "xyz", "mno"), "appendIfMissingIgnoreCase(abc,xyz,mno)");
+ assertEquals("abcxyz", StringUtils.appendIfMissingIgnoreCase("abcxyz", "xyz", "mno"), "appendIfMissingIgnoreCase(abcxyz,xyz,mno)");
+ assertEquals("abcmno", StringUtils.appendIfMissingIgnoreCase("abcmno", "xyz", "mno"), "appendIfMissingIgnoreCase(abcmno,xyz,mno)");
+ assertEquals("abcXYZ", StringUtils.appendIfMissingIgnoreCase("abcXYZ", "xyz", "mno"), "appendIfMissingIgnoreCase(abcXYZ,xyz,mno)");
+ assertEquals("abcMNO", StringUtils.appendIfMissingIgnoreCase("abcMNO", "xyz", "mno"), "appendIfMissingIgnoreCase(abcMNO,xyz,mno)");
+ }
+
+ @Test
+ public void testCapitalize() {
+ assertNull(StringUtils.capitalize(null));
+
+ assertEquals("", StringUtils.capitalize(""), "capitalize(empty-string) failed");
+ assertEquals("X", StringUtils.capitalize("x"), "capitalize(single-char-string) failed");
+ assertEquals(FOO_CAP, StringUtils.capitalize(FOO_CAP), "capitalize(String) failed");
+ assertEquals(FOO_CAP, StringUtils.capitalize(FOO_UNCAP), "capitalize(string) failed");
+
+ assertEquals("\u01C8", StringUtils.capitalize("\u01C9"), "capitalize(String) is not using TitleCase");
+
+ // Javadoc examples
+ assertNull(StringUtils.capitalize(null));
+ assertEquals("", StringUtils.capitalize(""));
+ assertEquals("Cat", StringUtils.capitalize("cat"));
+ assertEquals("CAt", StringUtils.capitalize("cAt"));
+ assertEquals("'cat'", StringUtils.capitalize("'cat'"));
+ }
+
+ @Test
+ public void testCenter_StringInt() {
+ assertNull(StringUtils.center(null, -1));
+ assertNull(StringUtils.center(null, 4));
+ assertEquals(" ", StringUtils.center("", 4));
+ assertEquals("ab", StringUtils.center("ab", 0));
+ assertEquals("ab", StringUtils.center("ab", -1));
+ assertEquals("ab", StringUtils.center("ab", 1));
+ assertEquals(" ", StringUtils.center("", 4));
+ assertEquals(" ab ", StringUtils.center("ab", 4));
+ assertEquals("abcd", StringUtils.center("abcd", 2));
+ assertEquals(" a ", StringUtils.center("a", 4));
+ assertEquals(" a ", StringUtils.center("a", 5));
+ }
+
+ @Test
+ public void testCenter_StringIntChar() {
+ assertNull(StringUtils.center(null, -1, ' '));
+ assertNull(StringUtils.center(null, 4, ' '));
+ assertEquals(" ", StringUtils.center("", 4, ' '));
+ assertEquals("ab", StringUtils.center("ab", 0, ' '));
+ assertEquals("ab", StringUtils.center("ab", -1, ' '));
+ assertEquals("ab", StringUtils.center("ab", 1, ' '));
+ assertEquals(" ", StringUtils.center("", 4, ' '));
+ assertEquals(" ab ", StringUtils.center("ab", 4, ' '));
+ assertEquals("abcd", StringUtils.center("abcd", 2, ' '));
+ assertEquals(" a ", StringUtils.center("a", 4, ' '));
+ assertEquals(" a ", StringUtils.center("a", 5, ' '));
+ assertEquals("xxaxx", StringUtils.center("a", 5, 'x'));
+ }
+
+ @Test
+ public void testCenter_StringIntString() {
+ assertNull(StringUtils.center(null, 4, null));
+ assertNull(StringUtils.center(null, -1, " "));
+ assertNull(StringUtils.center(null, 4, " "));
+ assertEquals(" ", StringUtils.center("", 4, " "));
+ assertEquals("ab", StringUtils.center("ab", 0, " "));
+ assertEquals("ab", StringUtils.center("ab", -1, " "));
+ assertEquals("ab", StringUtils.center("ab", 1, " "));
+ assertEquals(" ", StringUtils.center("", 4, " "));
+ assertEquals(" ab ", StringUtils.center("ab", 4, " "));
+ assertEquals("abcd", StringUtils.center("abcd", 2, " "));
+ assertEquals(" a ", StringUtils.center("a", 4, " "));
+ assertEquals("yayz", StringUtils.center("a", 4, "yz"));
+ assertEquals("yzyayzy", StringUtils.center("a", 7, "yz"));
+ assertEquals(" abc ", StringUtils.center("abc", 7, null));
+ assertEquals(" abc ", StringUtils.center("abc", 7, ""));
+ }
+
+ @Test
+ public void testChomp() {
+
+ final String[][] chompCases = {
+ {FOO_UNCAP + "\r\n", FOO_UNCAP},
+ {FOO_UNCAP + "\n", FOO_UNCAP},
+ {FOO_UNCAP + "\r", FOO_UNCAP},
+ {FOO_UNCAP + " \r", FOO_UNCAP + " "},
+ {FOO_UNCAP, FOO_UNCAP},
+ {FOO_UNCAP + "\n\n", FOO_UNCAP + "\n"},
+ {FOO_UNCAP + "\r\n\r\n", FOO_UNCAP + "\r\n"},
+ {"foo\nfoo", "foo\nfoo"},
+ {"foo\n\rfoo", "foo\n\rfoo"},
+ {"\n", ""},
+ {"\r", ""},
+ {"a", "a"},
+ {"\r\n", ""},
+ {"", ""},
+ {null, null},
+ {FOO_UNCAP + "\n\r", FOO_UNCAP + "\n"}
+ };
+ for (final String[] chompCase : chompCases) {
+ final String original = chompCase[0];
+ final String expectedResult = chompCase[1];
+ assertEquals(expectedResult, StringUtils.chomp(original), "chomp(String) failed");
+ }
+
+ assertEquals("foo", StringUtils.chomp("foobar", "bar"), "chomp(String, String) failed");
+ assertEquals("foobar", StringUtils.chomp("foobar", "baz"), "chomp(String, String) failed");
+ assertEquals("foo", StringUtils.chomp("foo", "foooo"), "chomp(String, String) failed");
+ assertEquals("foobar", StringUtils.chomp("foobar", ""), "chomp(String, String) failed");
+ assertEquals("foobar", StringUtils.chomp("foobar", null), "chomp(String, String) failed");
+ assertEquals("", StringUtils.chomp("", "foo"), "chomp(String, String) failed");
+ assertEquals("", StringUtils.chomp("", null), "chomp(String, String) failed");
+ assertEquals("", StringUtils.chomp("", ""), "chomp(String, String) failed");
+ assertNull(StringUtils.chomp(null, "foo"), "chomp(String, String) failed");
+ assertNull(StringUtils.chomp(null, null), "chomp(String, String) failed");
+ assertNull(StringUtils.chomp(null, ""), "chomp(String, String) failed");
+ assertEquals("", StringUtils.chomp("foo", "foo"), "chomp(String, String) failed");
+ assertEquals(" ", StringUtils.chomp(" foo", "foo"), "chomp(String, String) failed");
+ assertEquals("foo ", StringUtils.chomp("foo ", "foo"), "chomp(String, String) failed");
+ }
+
+ @Test
+ public void testChop() {
+
+ final String[][] chopCases = {
+ {FOO_UNCAP + "\r\n", FOO_UNCAP},
+ {FOO_UNCAP + "\n", FOO_UNCAP},
+ {FOO_UNCAP + "\r", FOO_UNCAP},
+ {FOO_UNCAP + " \r", FOO_UNCAP + " "},
+ {"foo", "fo"},
+ {"foo\nfoo", "foo\nfo"},
+ {"\n", ""},
+ {"\r", ""},
+ {"\r\n", ""},
+ {null, null},
+ {"", ""},
+ {"a", ""},
+ };
+ for (final String[] chopCase : chopCases) {
+ final String original = chopCase[0];
+ final String expectedResult = chopCase[1];
+ assertEquals(expectedResult, StringUtils.chop(original), "chop(String) failed");
+ }
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new StringUtils());
+ final Constructor<?>[] cons = StringUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(StringUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(StringUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testDefault_String() {
+ assertEquals("", StringUtils.defaultString(null));
+ assertEquals("", StringUtils.defaultString(""));
+ assertEquals("abc", StringUtils.defaultString("abc"));
+ }
+
+ @Test
+ public void testDefault_StringString() {
+ assertEquals("NULL", StringUtils.defaultString(null, "NULL"));
+ assertEquals("", StringUtils.defaultString("", "NULL"));
+ assertEquals("abc", StringUtils.defaultString("abc", "NULL"));
+ }
+
+ @Test
+ public void testDefaultIfBlank_CharBuffers() {
+ assertEquals("NULL", StringUtils.defaultIfBlank(CharBuffer.wrap(""), CharBuffer.wrap("NULL")).toString());
+ assertEquals("NULL", StringUtils.defaultIfBlank(CharBuffer.wrap(" "), CharBuffer.wrap("NULL")).toString());
+ assertEquals("abc", StringUtils.defaultIfBlank(CharBuffer.wrap("abc"), CharBuffer.wrap("NULL")).toString());
+ assertNull(StringUtils.defaultIfBlank(CharBuffer.wrap(""), (CharBuffer) null));
+ // Tests compatibility for the API return type
+ final CharBuffer s = StringUtils.defaultIfBlank(CharBuffer.wrap("abc"), CharBuffer.wrap("NULL"));
+ assertEquals("abc", s.toString());
+ }
+
+ @Test
+ public void testDefaultIfBlank_StringBuffers() {
+ assertEquals("NULL", StringUtils.defaultIfBlank(new StringBuffer(""), new StringBuffer("NULL")).toString());
+ assertEquals("NULL", StringUtils.defaultIfBlank(new StringBuffer(" "), new StringBuffer("NULL")).toString());
+ assertEquals("abc", StringUtils.defaultIfBlank(new StringBuffer("abc"), new StringBuffer("NULL")).toString());
+ assertNull(StringUtils.defaultIfBlank(new StringBuffer(""), (StringBuffer) null));
+ // Tests compatibility for the API return type
+ final StringBuffer s = StringUtils.defaultIfBlank(new StringBuffer("abc"), new StringBuffer("NULL"));
+ assertEquals("abc", s.toString());
+ }
+
+ @Test
+ public void testDefaultIfBlank_StringBuilders() {
+ assertEquals("NULL", StringUtils.defaultIfBlank(new StringBuilder(""), new StringBuilder("NULL")).toString());
+ assertEquals("NULL", StringUtils.defaultIfBlank(new StringBuilder(" "), new StringBuilder("NULL")).toString());
+ assertEquals("abc", StringUtils.defaultIfBlank(new StringBuilder("abc"), new StringBuilder("NULL")).toString());
+ assertNull(StringUtils.defaultIfBlank(new StringBuilder(""), (StringBuilder) null));
+ // Tests compatibility for the API return type
+ final StringBuilder s = StringUtils.defaultIfBlank(new StringBuilder("abc"), new StringBuilder("NULL"));
+ assertEquals("abc", s.toString());
+ }
+
+ @Test
+ public void testDefaultIfBlank_StringString() {
+ assertEquals("NULL", StringUtils.defaultIfBlank(null, "NULL"));
+ assertEquals("NULL", StringUtils.defaultIfBlank("", "NULL"));
+ assertEquals("NULL", StringUtils.defaultIfBlank(" ", "NULL"));
+ assertEquals("abc", StringUtils.defaultIfBlank("abc", "NULL"));
+ assertNull(StringUtils.defaultIfBlank("", (String) null));
+ // Tests compatibility for the API return type
+ final String s = StringUtils.defaultIfBlank("abc", "NULL");
+ assertEquals("abc", s);
+ }
+
+
+ @Test
+ public void testGetIfBlank_StringStringSupplier() {
+ assertEquals("NULL", StringUtils.getIfBlank(null, () -> "NULL"));
+ assertEquals("NULL", StringUtils.getIfBlank("", () -> "NULL"));
+ assertEquals("NULL", StringUtils.getIfBlank(" ", () -> "NULL"));
+ assertEquals("abc", StringUtils.getIfBlank("abc", () -> "NULL"));
+ assertNull(StringUtils.getIfBlank("", () -> null));
+ assertNull(StringUtils.defaultIfBlank("", (String) null));
+ // Tests compatibility for the API return type
+ final String s = StringUtils.getIfBlank("abc", () -> "NULL");
+ assertEquals("abc", s);
+ //Checking that default value supplied only on demand
+ final MutableInt numberOfCalls = new MutableInt(0);
+ final Supplier<String> countingDefaultSupplier = () -> {
+ numberOfCalls.increment();
+ return "NULL";
+ };
+ StringUtils.getIfBlank("abc", countingDefaultSupplier);
+ assertEquals(0, numberOfCalls.getValue());
+ StringUtils.getIfBlank("", countingDefaultSupplier);
+ assertEquals(1, numberOfCalls.getValue());
+ StringUtils.getIfBlank(" ", countingDefaultSupplier);
+ assertEquals(2, numberOfCalls.getValue());
+ StringUtils.getIfBlank(null, countingDefaultSupplier);
+ assertEquals(3, numberOfCalls.getValue());
+ }
+
+ @Test
+ public void testDefaultIfEmpty_CharBuffers() {
+ assertEquals("NULL", StringUtils.defaultIfEmpty(CharBuffer.wrap(""), CharBuffer.wrap("NULL")).toString());
+ assertEquals("abc", StringUtils.defaultIfEmpty(CharBuffer.wrap("abc"), CharBuffer.wrap("NULL")).toString());
+ assertNull(StringUtils.defaultIfEmpty(CharBuffer.wrap(""), (CharBuffer) null));
+ // Tests compatibility for the API return type
+ final CharBuffer s = StringUtils.defaultIfEmpty(CharBuffer.wrap("abc"), CharBuffer.wrap("NULL"));
+ assertEquals("abc", s.toString());
+ }
+
+
+ @Test
+ public void testDefaultIfEmpty_StringBuffers() {
+ assertEquals("NULL", StringUtils.defaultIfEmpty(new StringBuffer(""), new StringBuffer("NULL")).toString());
+ assertEquals("abc", StringUtils.defaultIfEmpty(new StringBuffer("abc"), new StringBuffer("NULL")).toString());
+ assertNull(StringUtils.defaultIfEmpty(new StringBuffer(""), (StringBuffer) null));
+ // Tests compatibility for the API return type
+ final StringBuffer s = StringUtils.defaultIfEmpty(new StringBuffer("abc"), new StringBuffer("NULL"));
+ assertEquals("abc", s.toString());
+ }
+
+ @Test
+ public void testDefaultIfEmpty_StringBuilders() {
+ assertEquals("NULL", StringUtils.defaultIfEmpty(new StringBuilder(""), new StringBuilder("NULL")).toString());
+ assertEquals("abc", StringUtils.defaultIfEmpty(new StringBuilder("abc"), new StringBuilder("NULL")).toString());
+ assertNull(StringUtils.defaultIfEmpty(new StringBuilder(""), (StringBuilder) null));
+ // Tests compatibility for the API return type
+ final StringBuilder s = StringUtils.defaultIfEmpty(new StringBuilder("abc"), new StringBuilder("NULL"));
+ assertEquals("abc", s.toString());
+ }
+
+ @Test
+ public void testDefaultIfEmpty_StringString() {
+ assertEquals("NULL", StringUtils.defaultIfEmpty(null, "NULL"));
+ assertEquals("NULL", StringUtils.defaultIfEmpty("", "NULL"));
+ assertEquals("abc", StringUtils.defaultIfEmpty("abc", "NULL"));
+ assertNull(StringUtils.getIfEmpty("", null));
+ // Tests compatibility for the API return type
+ final String s = StringUtils.defaultIfEmpty("abc", "NULL");
+ assertEquals("abc", s);
+ }
+
+ @Test
+ public void testGetIfEmpty_StringStringSupplier() {
+ assertEquals("NULL", StringUtils.getIfEmpty((String) null, () -> "NULL"));
+ assertEquals("NULL", StringUtils.getIfEmpty("", () -> "NULL"));
+ assertEquals("abc", StringUtils.getIfEmpty("abc", () -> "NULL"));
+ assertNull(StringUtils.getIfEmpty("", () -> null));
+ assertNull(StringUtils.defaultIfEmpty("", (String) null));
+ // Tests compatibility for the API return type
+ final String s = StringUtils.getIfEmpty("abc", () -> "NULL");
+ assertEquals("abc", s);
+ //Checking that default value supplied only on demand
+ final MutableInt numberOfCalls = new MutableInt(0);
+ final Supplier<String> countingDefaultSupplier = () -> {
+ numberOfCalls.increment();
+ return "NULL";
+ };
+ StringUtils.getIfEmpty("abc", countingDefaultSupplier);
+ assertEquals(0, numberOfCalls.getValue());
+ StringUtils.getIfEmpty("", countingDefaultSupplier);
+ assertEquals(1, numberOfCalls.getValue());
+ StringUtils.getIfEmpty(null, countingDefaultSupplier);
+ assertEquals(2, numberOfCalls.getValue());
+ }
+
+
+ @Test
+ public void testDeleteWhitespace_String() {
+ assertNull(StringUtils.deleteWhitespace(null));
+ assertEquals("", StringUtils.deleteWhitespace(""));
+ assertEquals("", StringUtils.deleteWhitespace(" \u000C \t\t\u001F\n\n \u000B "));
+ assertEquals("", StringUtils.deleteWhitespace(StringUtilsTest.WHITESPACE));
+ assertEquals(StringUtilsTest.NON_WHITESPACE, StringUtils.deleteWhitespace(StringUtilsTest.NON_WHITESPACE));
+ // Note: u-2007 and u-000A both cause problems in the source code
+ // it should ignore 2007 but delete 000A
+ assertEquals("\u00A0\u202F", StringUtils.deleteWhitespace(" \u00A0 \t\t\n\n \u202F "));
+ assertEquals("\u00A0\u202F", StringUtils.deleteWhitespace("\u00A0\u202F"));
+ assertEquals("test", StringUtils.deleteWhitespace("\u000Bt \t\n\u0009e\rs\n\n \tt"));
+ }
+
+ @Test
+ public void testDifference_StringString() {
+ assertNull(StringUtils.difference(null, null));
+ assertEquals("", StringUtils.difference("", ""));
+ assertEquals("abc", StringUtils.difference("", "abc"));
+ assertEquals("", StringUtils.difference("abc", ""));
+ assertEquals("i am a robot", StringUtils.difference(null, "i am a robot"));
+ assertEquals("i am a machine", StringUtils.difference("i am a machine", null));
+ assertEquals("robot", StringUtils.difference("i am a machine", "i am a robot"));
+ assertEquals("", StringUtils.difference("abc", "abc"));
+ assertEquals("you are a robot", StringUtils.difference("i am a robot", "you are a robot"));
+ }
+
+ @Test
+ public void testDifferenceAt_StringArray() {
+ assertEquals(-1, StringUtils.indexOfDifference((String[]) null));
+ assertEquals(-1, StringUtils.indexOfDifference());
+ assertEquals(-1, StringUtils.indexOfDifference("abc"));
+ assertEquals(-1, StringUtils.indexOfDifference(null, null));
+ assertEquals(-1, StringUtils.indexOfDifference("", ""));
+ assertEquals(0, StringUtils.indexOfDifference("", null));
+ assertEquals(0, StringUtils.indexOfDifference("abc", null, null));
+ assertEquals(0, StringUtils.indexOfDifference(null, null, "abc"));
+ assertEquals(0, StringUtils.indexOfDifference("", "abc"));
+ assertEquals(0, StringUtils.indexOfDifference("abc", ""));
+ assertEquals(-1, StringUtils.indexOfDifference("abc", "abc"));
+ assertEquals(1, StringUtils.indexOfDifference("abc", "a"));
+ assertEquals(2, StringUtils.indexOfDifference("ab", "abxyz"));
+ assertEquals(2, StringUtils.indexOfDifference("abcde", "abxyz"));
+ assertEquals(0, StringUtils.indexOfDifference("abcde", "xyz"));
+ assertEquals(0, StringUtils.indexOfDifference("xyz", "abcde"));
+ assertEquals(7, StringUtils.indexOfDifference("i am a machine", "i am a robot"));
+ }
+
+ @Test
+ public void testDifferenceAt_StringString() {
+ assertEquals(-1, StringUtils.indexOfDifference(null, null));
+ assertEquals(0, StringUtils.indexOfDifference(null, "i am a robot"));
+ assertEquals(-1, StringUtils.indexOfDifference("", ""));
+ assertEquals(0, StringUtils.indexOfDifference("", "abc"));
+ assertEquals(0, StringUtils.indexOfDifference("abc", ""));
+ assertEquals(0, StringUtils.indexOfDifference("i am a machine", null));
+ assertEquals(7, StringUtils.indexOfDifference("i am a machine", "i am a robot"));
+ assertEquals(-1, StringUtils.indexOfDifference("foo", "foo"));
+ assertEquals(0, StringUtils.indexOfDifference("i am a robot", "you are a robot"));
+ }
+
+ /**
+ * A sanity check for {@link StringUtils#EMPTY}.
+ */
+ @Test
+ public void testEMPTY() {
+ assertNotNull(StringUtils.EMPTY);
+ assertEquals("", StringUtils.EMPTY);
+ assertEquals(0, StringUtils.EMPTY.length());
+ }
+
+ @Test
+ public void testEscapeSurrogatePairs() {
+ assertEquals("\uD83D\uDE30", StringEscapeUtils.escapeCsv("\uD83D\uDE30"));
+ // Examples from https://en.wikipedia.org/wiki/UTF-16
+ assertEquals("\uD800\uDC00", StringEscapeUtils.escapeCsv("\uD800\uDC00"));
+ assertEquals("\uD834\uDD1E", StringEscapeUtils.escapeCsv("\uD834\uDD1E"));
+ assertEquals("\uDBFF\uDFFD", StringEscapeUtils.escapeCsv("\uDBFF\uDFFD"));
+ assertEquals("\uDBFF\uDFFD", StringEscapeUtils.escapeHtml3("\uDBFF\uDFFD"));
+ assertEquals("\uDBFF\uDFFD", StringEscapeUtils.escapeHtml4("\uDBFF\uDFFD"));
+ assertEquals("\uDBFF\uDFFD", StringEscapeUtils.escapeXml("\uDBFF\uDFFD"));
+ }
+
+ /**
+ * Tests LANG-858.
+ */
+ @Test
+ public void testEscapeSurrogatePairsLang858() {
+ assertEquals("\\uDBFF\\uDFFD", StringEscapeUtils.escapeJava("\uDBFF\uDFFD")); //fail LANG-858
+ assertEquals("\\uDBFF\\uDFFD", StringEscapeUtils.escapeEcmaScript("\uDBFF\uDFFD")); //fail LANG-858
+ }
+
+ @Test
+ public void testGetBytes_Charset() {
+ assertEquals(ArrayUtils.EMPTY_BYTE_ARRAY, StringUtils.getBytes(null, (Charset) null));
+ assertArrayEquals(StringUtils.EMPTY.getBytes(), StringUtils.getBytes(StringUtils.EMPTY, (Charset) null));
+ assertArrayEquals(StringUtils.EMPTY.getBytes(StandardCharsets.US_ASCII),
+ StringUtils.getBytes(StringUtils.EMPTY, StandardCharsets.US_ASCII));
+ }
+
+ @Test
+ public void testGetBytes_String() throws UnsupportedEncodingException {
+ assertEquals(ArrayUtils.EMPTY_BYTE_ARRAY, StringUtils.getBytes(null, (String) null));
+ assertArrayEquals(StringUtils.EMPTY.getBytes(), StringUtils.getBytes(StringUtils.EMPTY, (String) null));
+ assertArrayEquals(StringUtils.EMPTY.getBytes(StandardCharsets.US_ASCII.name()),
+ StringUtils.getBytes(StringUtils.EMPTY, StandardCharsets.US_ASCII.name()));
+ }
+
+ @Test
+ public void testGetCommonPrefix_StringArray() {
+ assertEquals("", StringUtils.getCommonPrefix((String[]) null));
+ assertEquals("", StringUtils.getCommonPrefix());
+ assertEquals("abc", StringUtils.getCommonPrefix("abc"));
+ assertEquals("", StringUtils.getCommonPrefix(null, null));
+ assertEquals("", StringUtils.getCommonPrefix("", ""));
+ assertEquals("", StringUtils.getCommonPrefix("", null));
+ assertEquals("", StringUtils.getCommonPrefix("abc", null, null));
+ assertEquals("", StringUtils.getCommonPrefix(null, null, "abc"));
+ assertEquals("", StringUtils.getCommonPrefix("", "abc"));
+ assertEquals("", StringUtils.getCommonPrefix("abc", ""));
+ assertEquals("abc", StringUtils.getCommonPrefix("abc", "abc"));
+ assertEquals("a", StringUtils.getCommonPrefix("abc", "a"));
+ assertEquals("ab", StringUtils.getCommonPrefix("ab", "abxyz"));
+ assertEquals("ab", StringUtils.getCommonPrefix("abcde", "abxyz"));
+ assertEquals("", StringUtils.getCommonPrefix("abcde", "xyz"));
+ assertEquals("", StringUtils.getCommonPrefix("xyz", "abcde"));
+ assertEquals("i am a ", StringUtils.getCommonPrefix("i am a machine", "i am a robot"));
+ }
+
+ @Test
+ public void testGetDigits() {
+ assertNull(StringUtils.getDigits(null));
+ assertEquals("", StringUtils.getDigits(""));
+ assertEquals("", StringUtils.getDigits("abc"));
+ assertEquals("1000", StringUtils.getDigits("1000$"));
+ assertEquals("12345", StringUtils.getDigits("123password45"));
+ assertEquals("5417543010", StringUtils.getDigits("(541) 754-3010"));
+ assertEquals("\u0967\u0968\u0969", StringUtils.getDigits("\u0967\u0968\u0969"));
+ }
+
+ @Test
+ public void testGetFuzzyDistance() {
+ assertEquals(0, StringUtils.getFuzzyDistance("", "", Locale.ENGLISH));
+ assertEquals(0, StringUtils.getFuzzyDistance("Workshop", "b", Locale.ENGLISH));
+ assertEquals(1, StringUtils.getFuzzyDistance("Room", "o", Locale.ENGLISH));
+ assertEquals(1, StringUtils.getFuzzyDistance("Workshop", "w", Locale.ENGLISH));
+ assertEquals(2, StringUtils.getFuzzyDistance("Workshop", "ws", Locale.ENGLISH));
+ assertEquals(4, StringUtils.getFuzzyDistance("Workshop", "wo", Locale.ENGLISH));
+ assertEquals(3, StringUtils.getFuzzyDistance("Apache Software Foundation", "asf", Locale.ENGLISH));
+ }
+
+ @Test
+ public void testGetFuzzyDistance_NullNullNull() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getFuzzyDistance(null, null, null));
+ }
+
+ @Test
+ public void testGetFuzzyDistance_NullStringLocale() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getFuzzyDistance(null, "clear", Locale.ENGLISH));
+ }
+
+ @Test
+ public void testGetFuzzyDistance_StringNullLoclae() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getFuzzyDistance(" ", null, Locale.ENGLISH));
+ }
+
+ @Test
+ public void testGetFuzzyDistance_StringStringNull() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getFuzzyDistance(" ", "clear", null));
+ }
+
+ @Test
+ public void testGetJaroWinklerDistance_NullNull() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getJaroWinklerDistance(null, null));
+ }
+
+ @Test
+ public void testGetJaroWinklerDistance_NullString() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getJaroWinklerDistance(null, "clear"));
+ }
+
+ @Test
+ public void testGetJaroWinklerDistance_StringNull() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getJaroWinklerDistance(" ", null));
+ }
+
+ @Test
+ public void testGetJaroWinklerDistance_StringString() {
+ assertEquals(0.93d, StringUtils.getJaroWinklerDistance("frog", "fog"));
+ assertEquals(0.0d, StringUtils.getJaroWinklerDistance("fly", "ant"));
+ assertEquals(0.44d, StringUtils.getJaroWinklerDistance("elephant", "hippo"));
+ assertEquals(0.84d, StringUtils.getJaroWinklerDistance("dwayne", "duane"));
+ assertEquals(0.93d, StringUtils.getJaroWinklerDistance("ABC Corporation", "ABC Corp"));
+ assertEquals(0.95d, StringUtils.getJaroWinklerDistance("D N H Enterprises Inc", "D & H Enterprises, Inc."));
+ assertEquals(0.92d, StringUtils.getJaroWinklerDistance("My Gym Children's Fitness Center", "My Gym. Childrens Fitness"));
+ assertEquals(0.88d, StringUtils.getJaroWinklerDistance("PENNSYLVANIA", "PENNCISYLVNIA"));
+ assertEquals(0.63d, StringUtils.getJaroWinklerDistance("Haus Ingeborg", "Ingeborg Esser"));
+ }
+
+ @Test
+ public void testGetLevenshteinDistance_NullString() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getLevenshteinDistance("a", null));
+ }
+
+ @Test
+ public void testGetLevenshteinDistance_NullStringInt() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getLevenshteinDistance(null, "a", 0));
+ }
+
+ @Test
+ public void testGetLevenshteinDistance_StringNull() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getLevenshteinDistance(null, "a"));
+ }
+
+ @Test
+ public void testGetLevenshteinDistance_StringNullInt() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getLevenshteinDistance("a", null, 0));
+ }
+
+ @Test
+ public void testGetLevenshteinDistance_StringString() {
+ assertEquals(0, StringUtils.getLevenshteinDistance("", ""));
+ assertEquals(1, StringUtils.getLevenshteinDistance("", "a"));
+ assertEquals(7, StringUtils.getLevenshteinDistance("aaapppp", ""));
+ assertEquals(1, StringUtils.getLevenshteinDistance("frog", "fog"));
+ assertEquals(3, StringUtils.getLevenshteinDistance("fly", "ant"));
+ assertEquals(7, StringUtils.getLevenshteinDistance("elephant", "hippo"));
+ assertEquals(7, StringUtils.getLevenshteinDistance("hippo", "elephant"));
+ assertEquals(8, StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz"));
+ assertEquals(8, StringUtils.getLevenshteinDistance("zzzzzzzz", "hippo"));
+ assertEquals(1, StringUtils.getLevenshteinDistance("hello", "hallo"));
+ }
+
+ @Test
+ public void testGetLevenshteinDistance_StringStringInt() {
+ // empty strings
+ assertEquals(0, StringUtils.getLevenshteinDistance("", "", 0));
+ assertEquals(7, StringUtils.getLevenshteinDistance("aaapppp", "", 8));
+ assertEquals(7, StringUtils.getLevenshteinDistance("aaapppp", "", 7));
+ assertEquals(-1, StringUtils.getLevenshteinDistance("aaapppp", "", 6));
+
+ // unequal strings, zero threshold
+ assertEquals(-1, StringUtils.getLevenshteinDistance("b", "a", 0));
+ assertEquals(-1, StringUtils.getLevenshteinDistance("a", "b", 0));
+
+ // equal strings
+ assertEquals(0, StringUtils.getLevenshteinDistance("aa", "aa", 0));
+ assertEquals(0, StringUtils.getLevenshteinDistance("aa", "aa", 2));
+
+ // same length
+ assertEquals(-1, StringUtils.getLevenshteinDistance("aaa", "bbb", 2));
+ assertEquals(3, StringUtils.getLevenshteinDistance("aaa", "bbb", 3));
+
+ // big stripe
+ assertEquals(6, StringUtils.getLevenshteinDistance("aaaaaa", "b", 10));
+
+ // distance less than threshold
+ assertEquals(7, StringUtils.getLevenshteinDistance("aaapppp", "b", 8));
+ assertEquals(3, StringUtils.getLevenshteinDistance("a", "bbb", 4));
+
+ // distance equal to threshold
+ assertEquals(7, StringUtils.getLevenshteinDistance("aaapppp", "b", 7));
+ assertEquals(3, StringUtils.getLevenshteinDistance("a", "bbb", 3));
+
+ // distance greater than threshold
+ assertEquals(-1, StringUtils.getLevenshteinDistance("a", "bbb", 2));
+ assertEquals(-1, StringUtils.getLevenshteinDistance("bbb", "a", 2));
+ assertEquals(-1, StringUtils.getLevenshteinDistance("aaapppp", "b", 6));
+
+ // stripe runs off array, strings not similar
+ assertEquals(-1, StringUtils.getLevenshteinDistance("a", "bbb", 1));
+ assertEquals(-1, StringUtils.getLevenshteinDistance("bbb", "a", 1));
+
+ // stripe runs off array, strings are similar
+ assertEquals(-1, StringUtils.getLevenshteinDistance("12345", "1234567", 1));
+ assertEquals(-1, StringUtils.getLevenshteinDistance("1234567", "12345", 1));
+
+ // old getLevenshteinDistance test cases
+ assertEquals(1, StringUtils.getLevenshteinDistance("frog", "fog", 1));
+ assertEquals(3, StringUtils.getLevenshteinDistance("fly", "ant", 3));
+ assertEquals(7, StringUtils.getLevenshteinDistance("elephant", "hippo", 7));
+ assertEquals(-1, StringUtils.getLevenshteinDistance("elephant", "hippo", 6));
+ assertEquals(7, StringUtils.getLevenshteinDistance("hippo", "elephant", 7));
+ assertEquals(-1, StringUtils.getLevenshteinDistance("hippo", "elephant", 6));
+ assertEquals(8, StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz", 8));
+ assertEquals(8, StringUtils.getLevenshteinDistance("zzzzzzzz", "hippo", 8));
+ assertEquals(1, StringUtils.getLevenshteinDistance("hello", "hallo", 1));
+
+ assertEquals(1, StringUtils.getLevenshteinDistance("frog", "fog", Integer.MAX_VALUE));
+ assertEquals(3, StringUtils.getLevenshteinDistance("fly", "ant", Integer.MAX_VALUE));
+ assertEquals(7, StringUtils.getLevenshteinDistance("elephant", "hippo", Integer.MAX_VALUE));
+ assertEquals(7, StringUtils.getLevenshteinDistance("hippo", "elephant", Integer.MAX_VALUE));
+ assertEquals(8, StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz", Integer.MAX_VALUE));
+ assertEquals(8, StringUtils.getLevenshteinDistance("zzzzzzzz", "hippo", Integer.MAX_VALUE));
+ assertEquals(1, StringUtils.getLevenshteinDistance("hello", "hallo", Integer.MAX_VALUE));
+ }
+
+ @Test
+ public void testGetLevenshteinDistance_StringStringNegativeInt() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.getLevenshteinDistance("a", "a", -1));
+ }
+
+ /**
+ * Test for {@link StringUtils#isAllLowerCase(CharSequence)}.
+ */
+ @Test
+ public void testIsAllLowerCase() {
+ assertFalse(StringUtils.isAllLowerCase(null));
+ assertFalse(StringUtils.isAllLowerCase(StringUtils.EMPTY));
+ assertFalse(StringUtils.isAllLowerCase(" "));
+ assertTrue(StringUtils.isAllLowerCase("abc"));
+ assertFalse(StringUtils.isAllLowerCase("abc "));
+ assertFalse(StringUtils.isAllLowerCase("abc\n"));
+ assertFalse(StringUtils.isAllLowerCase("abC"));
+ assertFalse(StringUtils.isAllLowerCase("ab c"));
+ assertFalse(StringUtils.isAllLowerCase("ab1c"));
+ assertFalse(StringUtils.isAllLowerCase("ab/c"));
+ }
+
+ /**
+ * Test for {@link StringUtils#isAllUpperCase(CharSequence)}.
+ */
+ @Test
+ public void testIsAllUpperCase() {
+ assertFalse(StringUtils.isAllUpperCase(null));
+ assertFalse(StringUtils.isAllUpperCase(StringUtils.EMPTY));
+ assertFalse(StringUtils.isAllUpperCase(" "));
+ assertTrue(StringUtils.isAllUpperCase("ABC"));
+ assertFalse(StringUtils.isAllUpperCase("ABC "));
+ assertFalse(StringUtils.isAllUpperCase("ABC\n"));
+ assertFalse(StringUtils.isAllUpperCase("aBC"));
+ assertFalse(StringUtils.isAllUpperCase("A C"));
+ assertFalse(StringUtils.isAllUpperCase("A1C"));
+ assertFalse(StringUtils.isAllUpperCase("A/C"));
+ }
+
+ /**
+ * Test for {@link StringUtils#isMixedCase(CharSequence)}.
+ */
+ @Test
+ public void testIsMixedCase() {
+ assertFalse(StringUtils.isMixedCase(null));
+ assertFalse(StringUtils.isMixedCase(StringUtils.EMPTY));
+ assertFalse(StringUtils.isMixedCase(" "));
+ assertFalse(StringUtils.isMixedCase("A"));
+ assertFalse(StringUtils.isMixedCase("a"));
+ assertFalse(StringUtils.isMixedCase("/"));
+ assertFalse(StringUtils.isMixedCase("A/"));
+ assertFalse(StringUtils.isMixedCase("/b"));
+ assertFalse(StringUtils.isMixedCase("abc"));
+ assertFalse(StringUtils.isMixedCase("ABC"));
+ assertTrue(StringUtils.isMixedCase("aBc"));
+ assertTrue(StringUtils.isMixedCase("aBc "));
+ assertTrue(StringUtils.isMixedCase("A c"));
+ assertTrue(StringUtils.isMixedCase("aBc\n"));
+ assertTrue(StringUtils.isMixedCase("A1c"));
+ assertTrue(StringUtils.isMixedCase("a/C"));
+ }
+
+ @Test
+ public void testJoin_ArrayCharSeparator() {
+ assertNull(StringUtils.join((Object[]) null, ','));
+ assertEquals(TEXT_LIST_CHAR, StringUtils.join(ARRAY_LIST, SEPARATOR_CHAR));
+ assertEquals("", StringUtils.join(EMPTY_ARRAY_LIST, SEPARATOR_CHAR));
+ assertEquals(";;foo", StringUtils.join(MIXED_ARRAY_LIST, SEPARATOR_CHAR));
+ assertEquals("foo;2", StringUtils.join(MIXED_TYPE_LIST, SEPARATOR_CHAR));
+
+ assertNull(StringUtils.join((Object[]) null, ',', 0, 1));
+ assertEquals("/", StringUtils.join(MIXED_ARRAY_LIST, '/', 0, MIXED_ARRAY_LIST.length - 1));
+ assertEquals("foo", StringUtils.join(MIXED_TYPE_LIST, '/', 0, 1));
+ assertEquals("null", StringUtils.join(NULL_TO_STRING_LIST, '/', 0, 1));
+ assertEquals("foo/2", StringUtils.join(MIXED_TYPE_LIST, '/', 0, 2));
+ assertEquals("2", StringUtils.join(MIXED_TYPE_LIST, '/', 1, 2));
+ assertEquals("", StringUtils.join(MIXED_TYPE_LIST, '/', 2, 1));
+ }
+
+ @Test
+ public void testJoin_ArrayOfBytes() {
+ assertNull(StringUtils.join((byte[]) null, ','));
+ assertEquals("1;2", StringUtils.join(BYTE_PRIM_LIST, SEPARATOR_CHAR));
+ assertEquals("2", StringUtils.join(BYTE_PRIM_LIST, SEPARATOR_CHAR, 1, 2));
+ assertNull(StringUtils.join((byte[]) null, SEPARATOR_CHAR, 0, 1));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(BYTE_PRIM_LIST, SEPARATOR_CHAR, 0, 0));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(BYTE_PRIM_LIST, SEPARATOR_CHAR, 1, 0));
+ }
+
+
+ @Test
+ public void testJoin_ArrayOfBooleans() {
+ assertNull(StringUtils.join((boolean[]) null, COMMA_SEPARATOR_CHAR));
+ assertEquals("false;false", StringUtils.join(ARRAY_FALSE_FALSE, SEPARATOR_CHAR));
+ assertEquals("", StringUtils.join(EMPTY, SEPARATOR_CHAR));
+ assertEquals("false,true,false", StringUtils.join(ARRAY_FALSE_TRUE_FALSE, COMMA_SEPARATOR_CHAR));
+ assertEquals("true", StringUtils.join(ARRAY_FALSE_TRUE, SEPARATOR_CHAR, 1, 2));
+ assertNull(StringUtils.join((boolean[]) null, SEPARATOR_CHAR, 0, 1));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(ARRAY_FALSE_FALSE, SEPARATOR_CHAR, 0, 0));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(ARRAY_FALSE_TRUE_FALSE, SEPARATOR_CHAR, 1, 0));
+ }
+
+ @Test
+ public void testJoin_ArrayOfChars() {
+ assertNull(StringUtils.join((char[]) null, ','));
+ assertEquals("1;2", StringUtils.join(CHAR_PRIM_LIST, SEPARATOR_CHAR));
+ assertEquals("2", StringUtils.join(CHAR_PRIM_LIST, SEPARATOR_CHAR, 1, 2));
+ assertNull(StringUtils.join((char[]) null, SEPARATOR_CHAR, 0, 1));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(CHAR_PRIM_LIST, SEPARATOR_CHAR, 0, 0));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(CHAR_PRIM_LIST, SEPARATOR_CHAR, 1, 0));
+ }
+
+ @Test
+ public void testJoin_ArrayOfDoubles() {
+ assertNull(StringUtils.join((double[]) null, ','));
+ assertEquals("1.0;2.0", StringUtils.join(DOUBLE_PRIM_LIST, SEPARATOR_CHAR));
+ assertEquals("2.0", StringUtils.join(DOUBLE_PRIM_LIST, SEPARATOR_CHAR, 1, 2));
+ assertNull(StringUtils.join((double[]) null, SEPARATOR_CHAR, 0, 1));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(DOUBLE_PRIM_LIST, SEPARATOR_CHAR, 0, 0));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(DOUBLE_PRIM_LIST, SEPARATOR_CHAR, 1, 0));
+ }
+
+ @Test
+ public void testJoin_ArrayOfFloats() {
+ assertNull(StringUtils.join((float[]) null, ','));
+ assertEquals("1.0;2.0", StringUtils.join(FLOAT_PRIM_LIST, SEPARATOR_CHAR));
+ assertEquals("2.0", StringUtils.join(FLOAT_PRIM_LIST, SEPARATOR_CHAR, 1, 2));
+ assertNull(StringUtils.join((float[]) null, SEPARATOR_CHAR, 0, 1));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(FLOAT_PRIM_LIST, SEPARATOR_CHAR, 0, 0));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(FLOAT_PRIM_LIST, SEPARATOR_CHAR, 1, 0));
+ }
+
+ @Test
+ public void testJoin_ArrayOfInts() {
+ assertNull(StringUtils.join((int[]) null, ','));
+ assertEquals("1;2", StringUtils.join(INT_PRIM_LIST, SEPARATOR_CHAR));
+ assertEquals("2", StringUtils.join(INT_PRIM_LIST, SEPARATOR_CHAR, 1, 2));
+ assertNull(StringUtils.join((int[]) null, SEPARATOR_CHAR, 0, 1));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(INT_PRIM_LIST, SEPARATOR_CHAR, 0, 0));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(INT_PRIM_LIST, SEPARATOR_CHAR, 1, 0));
+ }
+
+ @Test
+ public void testJoin_ArrayOfLongs() {
+ assertNull(StringUtils.join((long[]) null, ','));
+ assertEquals("1;2", StringUtils.join(LONG_PRIM_LIST, SEPARATOR_CHAR));
+ assertEquals("2", StringUtils.join(LONG_PRIM_LIST, SEPARATOR_CHAR, 1, 2));
+ assertNull(StringUtils.join((long[]) null, SEPARATOR_CHAR, 0, 1));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(LONG_PRIM_LIST, SEPARATOR_CHAR, 0, 0));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(LONG_PRIM_LIST, SEPARATOR_CHAR, 1, 0));
+ }
+
+ @Test
+ public void testJoin_ArrayOfShorts() {
+ assertNull(StringUtils.join((short[]) null, ','));
+ assertEquals("1;2", StringUtils.join(SHORT_PRIM_LIST, SEPARATOR_CHAR));
+ assertEquals("2", StringUtils.join(SHORT_PRIM_LIST, SEPARATOR_CHAR, 1, 2));
+ assertNull(StringUtils.join((short[]) null, SEPARATOR_CHAR, 0, 1));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(SHORT_PRIM_LIST, SEPARATOR_CHAR, 0, 0));
+ assertEquals(StringUtils.EMPTY, StringUtils.join(SHORT_PRIM_LIST, SEPARATOR_CHAR, 1, 0));
+ }
+
+ @Test
+ public void testJoin_ArrayString_EmptyDelimiter() {
+ assertNull(StringUtils.join((Object[]) null, null));
+ assertEquals(TEXT_LIST_NOSEP, StringUtils.join(ARRAY_LIST, null));
+ assertEquals(TEXT_LIST_NOSEP, StringUtils.join(ARRAY_LIST, ""));
+
+ assertEquals("", StringUtils.join(NULL_ARRAY_LIST, null));
+
+ assertEquals("", StringUtils.join(EMPTY_ARRAY_LIST, null));
+ assertEquals("", StringUtils.join(EMPTY_ARRAY_LIST, ""));
+
+ assertEquals("", StringUtils.join(MIXED_ARRAY_LIST, "", 0, MIXED_ARRAY_LIST.length - 1));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {",", ";", Supplementary.CharU20000, Supplementary.CharU20001})
+ public void testJoin_ArrayString_NonEmptyDelimiter(final String delimiter) {
+ assertEquals("", StringUtils.join(EMPTY_ARRAY_LIST, delimiter));
+
+ assertEquals(String.join(delimiter, ARRAY_LIST), StringUtils.join(ARRAY_LIST, delimiter));
+ assertEquals(delimiter + delimiter + "foo", StringUtils.join(MIXED_ARRAY_LIST, delimiter));
+ assertEquals(String.join(delimiter, "foo", "2"), StringUtils.join(MIXED_TYPE_LIST, delimiter));
+
+ assertEquals(delimiter, StringUtils.join(MIXED_ARRAY_LIST, delimiter, 0, MIXED_ARRAY_LIST.length - 1));
+ assertEquals("foo", StringUtils.join(MIXED_TYPE_LIST, delimiter, 0, 1));
+ assertEquals(String.join(delimiter, "foo", "2"), StringUtils.join(MIXED_TYPE_LIST, delimiter, 0, 2));
+ assertEquals("2", StringUtils.join(MIXED_TYPE_LIST, delimiter, 1, 2));
+ assertEquals("", StringUtils.join(MIXED_TYPE_LIST, delimiter, 2, 1));
+ }
+
+ @Test
+ public void testJoin_IterableChar() {
+ assertNull(StringUtils.join((Iterable<?>) null, ','));
+ assertEquals(TEXT_LIST_CHAR, StringUtils.join(Arrays.asList(ARRAY_LIST), SEPARATOR_CHAR));
+ assertEquals("", StringUtils.join(Arrays.asList(NULL_ARRAY_LIST), SEPARATOR_CHAR));
+ assertEquals("", StringUtils.join(Arrays.asList(EMPTY_ARRAY_LIST), SEPARATOR_CHAR));
+ assertEquals("foo", StringUtils.join(Collections.singleton("foo"), 'x'));
+ }
+
+ @Test
+ public void testJoin_IterableString() {
+ assertNull(StringUtils.join((Iterable<?>) null, null));
+ assertEquals(TEXT_LIST_NOSEP, StringUtils.join(Arrays.asList(ARRAY_LIST), null));
+ assertEquals(TEXT_LIST_NOSEP, StringUtils.join(Arrays.asList(ARRAY_LIST), ""));
+ assertEquals("foo", StringUtils.join(Collections.singleton("foo"), "x"));
+ assertEquals("foo", StringUtils.join(Collections.singleton("foo"), null));
+
+ assertEquals("", StringUtils.join(Arrays.asList(NULL_ARRAY_LIST), null));
+
+ assertEquals("", StringUtils.join(Arrays.asList(EMPTY_ARRAY_LIST), null));
+ assertEquals("", StringUtils.join(Arrays.asList(EMPTY_ARRAY_LIST), ""));
+ assertEquals("", StringUtils.join(Arrays.asList(EMPTY_ARRAY_LIST), SEPARATOR));
+
+ assertEquals(TEXT_LIST, StringUtils.join(Arrays.asList(ARRAY_LIST), SEPARATOR));
+ }
+
+ @Test
+ public void testJoin_IteratorChar() {
+ assertNull(StringUtils.join((Iterator<?>) null, ','));
+ assertEquals(TEXT_LIST_CHAR, StringUtils.join(Arrays.asList(ARRAY_LIST).iterator(), SEPARATOR_CHAR));
+ assertEquals("", StringUtils.join(Arrays.asList(NULL_ARRAY_LIST).iterator(), SEPARATOR_CHAR));
+ assertEquals("", StringUtils.join(Arrays.asList(EMPTY_ARRAY_LIST).iterator(), SEPARATOR_CHAR));
+ assertEquals("foo", StringUtils.join(Collections.singleton("foo").iterator(), 'x'));
+ assertEquals("null", StringUtils.join(Arrays.asList(NULL_TO_STRING_LIST).iterator(), SEPARATOR_CHAR));
+ }
+
+ @Test
+ public void testJoin_IteratorString() {
+ assertNull(StringUtils.join((Iterator<?>) null, null));
+ assertEquals(TEXT_LIST_NOSEP, StringUtils.join(Arrays.asList(ARRAY_LIST).iterator(), null));
+ assertEquals(TEXT_LIST_NOSEP, StringUtils.join(Arrays.asList(ARRAY_LIST).iterator(), ""));
+ assertEquals("foo", StringUtils.join(Collections.singleton("foo").iterator(), "x"));
+ assertEquals("foo", StringUtils.join(Collections.singleton("foo").iterator(), null));
+
+ assertEquals("", StringUtils.join(Arrays.asList(NULL_ARRAY_LIST).iterator(), null));
+
+ assertEquals("", StringUtils.join(Arrays.asList(EMPTY_ARRAY_LIST).iterator(), null));
+ assertEquals("", StringUtils.join(Arrays.asList(EMPTY_ARRAY_LIST).iterator(), ""));
+ assertEquals("", StringUtils.join(Arrays.asList(EMPTY_ARRAY_LIST).iterator(), SEPARATOR));
+
+ assertEquals(TEXT_LIST, StringUtils.join(Arrays.asList(ARRAY_LIST).iterator(), SEPARATOR));
+
+ assertEquals("null", StringUtils.join(Arrays.asList(NULL_TO_STRING_LIST).iterator(), SEPARATOR));
+ }
+
+ @Test
+ public void testJoin_List_EmptyDelimiter() {
+ assertNull(StringUtils.join((List<String>) null, null));
+ assertEquals(TEXT_LIST_NOSEP, StringUtils.join(STRING_LIST, null));
+ assertEquals(TEXT_LIST_NOSEP, StringUtils.join(STRING_LIST, ""));
+
+ assertEquals("", StringUtils.join(NULL_STRING_LIST, null));
+
+ assertEquals("", StringUtils.join(EMPTY_STRING_LIST, null));
+ assertEquals("", StringUtils.join(EMPTY_STRING_LIST, ""));
+
+ assertEquals("", StringUtils.join(MIXED_STRING_LIST, "", 0, MIXED_STRING_LIST.size()- 1));
+ }
+
+ @Test
+ public void testJoin_List_CharDelimiter() {
+ assertEquals("/", StringUtils.join(MIXED_STRING_LIST, '/', 0, MIXED_STRING_LIST.size() - 1));
+ assertEquals("foo", StringUtils.join(MIXED_TYPE_OBJECT_LIST, '/', 0, 1));
+ assertEquals("foo/2", StringUtils.join(MIXED_TYPE_OBJECT_LIST, '/', 0, 2));
+ assertEquals("2", StringUtils.join(MIXED_TYPE_OBJECT_LIST, '/', 1, 2));
+ assertEquals("", StringUtils.join(MIXED_TYPE_OBJECT_LIST, '/', 2, 1));
+ assertNull(null, StringUtils.join((List<?>) null, '/', 0, 1));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {",", ";", Supplementary.CharU20000, Supplementary.CharU20001})
+ public void testJoin_List_NonEmptyDelimiter(final String delimiter) {
+ assertEquals("", StringUtils.join(EMPTY_STRING_LIST, delimiter));
+
+ assertEquals(String.join(delimiter, STRING_LIST), StringUtils.join(STRING_LIST, delimiter));
+ assertEquals(delimiter + delimiter + "foo", StringUtils.join(MIXED_STRING_LIST, delimiter));
+ assertEquals(String.join(delimiter, "foo", "2"), StringUtils.join(MIXED_TYPE_OBJECT_LIST, delimiter));
+
+ assertEquals(delimiter, StringUtils.join(MIXED_STRING_LIST, delimiter, 0, MIXED_STRING_LIST.size() - 1));
+ assertEquals("foo", StringUtils.join(MIXED_TYPE_OBJECT_LIST, delimiter, 0, 1));
+ assertEquals(String.join(delimiter, "foo", "2"), StringUtils.join(MIXED_TYPE_OBJECT_LIST, delimiter, 0, 2));
+ assertEquals("2", StringUtils.join(MIXED_TYPE_OBJECT_LIST, delimiter, 1, 2));
+ assertEquals("", StringUtils.join(MIXED_TYPE_OBJECT_LIST, delimiter, 2, 1));
+ assertNull(null, StringUtils.join((List<?>) null, delimiter, 0, 1));
+ }
+
+ @Test
+ public void testJoin_Objectarray() {
+// assertNull(StringUtils.join(null)); // generates warning
+ assertNull(StringUtils.join((Object[]) null)); // equivalent explicit cast
+ // test additional varargs calls
+ assertEquals("", StringUtils.join()); // empty array
+ assertEquals("", StringUtils.join((Object) null)); // => new Object[]{null}
+
+ assertEquals("", StringUtils.join(EMPTY_ARRAY_LIST));
+ assertEquals("", StringUtils.join(NULL_ARRAY_LIST));
+ assertEquals("null", StringUtils.join(NULL_TO_STRING_LIST));
+ assertEquals("abc", StringUtils.join("a", "b", "c"));
+ assertEquals("a", StringUtils.join(null, "a", ""));
+ assertEquals("foo", StringUtils.join(MIXED_ARRAY_LIST));
+ assertEquals("foo2", StringUtils.join(MIXED_TYPE_LIST));
+ }
+
+ @Disabled
+ @Test
+ public void testLang1593() {
+ final int[] arr = {1, 2, 3, 4, 5, 6, 7};
+ final String expected = StringUtils.join(arr, '-');
+ final String actual = StringUtils.join(arr, "-");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testJoin_Objects() {
+ assertEquals("abc", StringUtils.join("a", "b", "c"));
+ assertEquals("a", StringUtils.join(null, "", "a"));
+ assertNull(StringUtils.join((Object[]) null));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {",", ";", Supplementary.CharU20000, Supplementary.CharU20001})
+ public void testJoinWith(final String delimiter) {
+ assertEquals("", StringUtils.joinWith(delimiter)); // empty array
+ assertEquals("", StringUtils.joinWith(delimiter, (Object[]) NULL_ARRAY_LIST));
+ assertEquals("null", StringUtils.joinWith(delimiter, NULL_TO_STRING_LIST)); // toString method prints 'null'
+
+ assertEquals(String.join(delimiter, "a", "b", "c"), StringUtils.joinWith(delimiter, "a", "b", "c"));
+ assertEquals(String.join(delimiter, "", "a", ""), StringUtils.joinWith(delimiter, null, "a", ""));
+ assertEquals(String.join(delimiter, "", "a", ""), StringUtils.joinWith(delimiter, "", "a", ""));
+
+ assertEquals("ab", StringUtils.joinWith(null, "a", "b"));
+ }
+
+ @Test
+ public void testJoinWithThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> StringUtils.joinWith(",", (Object[]) null));
+ }
+
+ @Test
+ public void testLang623() {
+ assertEquals("t", StringUtils.replaceChars("\u00DE", '\u00DE', 't'));
+ assertEquals("t", StringUtils.replaceChars("\u00FE", '\u00FE', 't'));
+ }
+
+ @Test
+ public void testLANG666() {
+ assertEquals("12", StringUtils.stripEnd("120.00", ".0"));
+ assertEquals("121", StringUtils.stripEnd("121.00", ".0"));
+ }
+
+ @Test
+ public void testLeftPad_StringInt() {
+ assertNull(StringUtils.leftPad(null, 5));
+ assertEquals(" ", StringUtils.leftPad("", 5));
+ assertEquals(" abc", StringUtils.leftPad("abc", 5));
+ assertEquals("abc", StringUtils.leftPad("abc", 2));
+ }
+
+ @Test
+ public void testLeftPad_StringIntChar() {
+ assertNull(StringUtils.leftPad(null, 5, ' '));
+ assertEquals(" ", StringUtils.leftPad("", 5, ' '));
+ assertEquals(" abc", StringUtils.leftPad("abc", 5, ' '));
+ assertEquals("xxabc", StringUtils.leftPad("abc", 5, 'x'));
+ assertEquals("\uffff\uffffabc", StringUtils.leftPad("abc", 5, '\uffff'));
+ assertEquals("abc", StringUtils.leftPad("abc", 2, ' '));
+ final String str = StringUtils.leftPad("aaa", 10000, 'a'); // bigger than pad length
+ assertEquals(10000, str.length());
+ assertTrue(StringUtils.containsOnly(str, 'a'));
+ }
+
+ @Test
+ public void testLeftPad_StringIntString() {
+ assertNull(StringUtils.leftPad(null, 5, "-+"));
+ assertNull(StringUtils.leftPad(null, 5, null));
+ assertEquals(" ", StringUtils.leftPad("", 5, " "));
+ assertEquals("-+-+abc", StringUtils.leftPad("abc", 7, "-+"));
+ assertEquals("-+~abc", StringUtils.leftPad("abc", 6, "-+~"));
+ assertEquals("-+abc", StringUtils.leftPad("abc", 5, "-+~"));
+ assertEquals("abc", StringUtils.leftPad("abc", 2, " "));
+ assertEquals("abc", StringUtils.leftPad("abc", -1, " "));
+ assertEquals(" abc", StringUtils.leftPad("abc", 5, null));
+ assertEquals(" abc", StringUtils.leftPad("abc", 5, ""));
+ }
+
+ @Test
+ public void testLength_CharBuffer() {
+ assertEquals(0, StringUtils.length(CharBuffer.wrap("")));
+ assertEquals(1, StringUtils.length(CharBuffer.wrap("A")));
+ assertEquals(1, StringUtils.length(CharBuffer.wrap(" ")));
+ assertEquals(8, StringUtils.length(CharBuffer.wrap("ABCDEFGH")));
+ }
+
+ @Test
+ public void testLengthString() {
+ assertEquals(0, StringUtils.length(null));
+ assertEquals(0, StringUtils.length(""));
+ assertEquals(0, StringUtils.length(StringUtils.EMPTY));
+ assertEquals(1, StringUtils.length("A"));
+ assertEquals(1, StringUtils.length(" "));
+ assertEquals(8, StringUtils.length("ABCDEFGH"));
+ }
+
+ @Test
+ public void testLengthStringBuffer() {
+ assertEquals(0, StringUtils.length(new StringBuffer("")));
+ assertEquals(0, StringUtils.length(new StringBuffer(StringUtils.EMPTY)));
+ assertEquals(1, StringUtils.length(new StringBuffer("A")));
+ assertEquals(1, StringUtils.length(new StringBuffer(" ")));
+ assertEquals(8, StringUtils.length(new StringBuffer("ABCDEFGH")));
+ }
+
+ @Test
+ public void testLengthStringBuilder() {
+ assertEquals(0, StringUtils.length(new StringBuilder("")));
+ assertEquals(0, StringUtils.length(new StringBuilder(StringUtils.EMPTY)));
+ assertEquals(1, StringUtils.length(new StringBuilder("A")));
+ assertEquals(1, StringUtils.length(new StringBuilder(" ")));
+ assertEquals(8, StringUtils.length(new StringBuilder("ABCDEFGH")));
+ }
+
+ @Test
+ public void testLowerCase() {
+ assertNull(StringUtils.lowerCase(null));
+ assertNull(StringUtils.lowerCase(null, Locale.ENGLISH));
+ assertEquals("foo test thing", StringUtils.lowerCase("fOo test THING"), "lowerCase(String) failed");
+ assertEquals("", StringUtils.lowerCase(""), "lowerCase(empty-string) failed");
+ assertEquals("foo test thing", StringUtils.lowerCase("fOo test THING", Locale.ENGLISH),
+ "lowerCase(String, Locale) failed");
+ assertEquals("", StringUtils.lowerCase("", Locale.ENGLISH), "lowerCase(empty-string, Locale) failed");
+ }
+
+ @Test
+ public void testNormalizeSpace() {
+ // Java says a non-breaking whitespace is not a whitespace.
+ assertFalse(Character.isWhitespace('\u00A0'));
+ //
+ assertNull(StringUtils.normalizeSpace(null));
+ assertEquals("", StringUtils.normalizeSpace(""));
+ assertEquals("", StringUtils.normalizeSpace(" "));
+ assertEquals("", StringUtils.normalizeSpace("\t"));
+ assertEquals("", StringUtils.normalizeSpace("\n"));
+ assertEquals("", StringUtils.normalizeSpace("\u0009"));
+ assertEquals("", StringUtils.normalizeSpace("\u000B"));
+ assertEquals("", StringUtils.normalizeSpace("\u000C"));
+ assertEquals("", StringUtils.normalizeSpace("\u001C"));
+ assertEquals("", StringUtils.normalizeSpace("\u001D"));
+ assertEquals("", StringUtils.normalizeSpace("\u001E"));
+ assertEquals("", StringUtils.normalizeSpace("\u001F"));
+ assertEquals("", StringUtils.normalizeSpace("\f"));
+ assertEquals("", StringUtils.normalizeSpace("\r"));
+ assertEquals("a", StringUtils.normalizeSpace(" a "));
+ assertEquals("a b c", StringUtils.normalizeSpace(" a b c "));
+ assertEquals("a b c", StringUtils.normalizeSpace("a\t\f\r b\u000B c\n"));
+ assertEquals("a b c", StringUtils.normalizeSpace("a\t\f\r " + HARD_SPACE + HARD_SPACE + "b\u000B c\n"));
+ assertEquals("b", StringUtils.normalizeSpace("\u0000b"));
+ assertEquals("b", StringUtils.normalizeSpace("b\u0000"));
+ }
+
+ @Test
+ public void testOverlay_StringStringIntInt() {
+ assertNull(StringUtils.overlay(null, null, 2, 4));
+ assertNull(StringUtils.overlay(null, null, -2, -4));
+
+ assertEquals("", StringUtils.overlay("", null, 0, 0));
+ assertEquals("", StringUtils.overlay("", "", 0, 0));
+ assertEquals("zzzz", StringUtils.overlay("", "zzzz", 0, 0));
+ assertEquals("zzzz", StringUtils.overlay("", "zzzz", 2, 4));
+ assertEquals("zzzz", StringUtils.overlay("", "zzzz", -2, -4));
+
+ assertEquals("abef", StringUtils.overlay("abcdef", null, 2, 4));
+ assertEquals("abef", StringUtils.overlay("abcdef", null, 4, 2));
+ assertEquals("abef", StringUtils.overlay("abcdef", "", 2, 4));
+ assertEquals("abef", StringUtils.overlay("abcdef", "", 4, 2));
+ assertEquals("abzzzzef", StringUtils.overlay("abcdef", "zzzz", 2, 4));
+ assertEquals("abzzzzef", StringUtils.overlay("abcdef", "zzzz", 4, 2));
+
+ assertEquals("zzzzef", StringUtils.overlay("abcdef", "zzzz", -1, 4));
+ assertEquals("zzzzef", StringUtils.overlay("abcdef", "zzzz", 4, -1));
+ assertEquals("zzzzabcdef", StringUtils.overlay("abcdef", "zzzz", -2, -1));
+ assertEquals("zzzzabcdef", StringUtils.overlay("abcdef", "zzzz", -1, -2));
+ assertEquals("abcdzzzz", StringUtils.overlay("abcdef", "zzzz", 4, 10));
+ assertEquals("abcdzzzz", StringUtils.overlay("abcdef", "zzzz", 10, 4));
+ assertEquals("abcdefzzzz", StringUtils.overlay("abcdef", "zzzz", 8, 10));
+ assertEquals("abcdefzzzz", StringUtils.overlay("abcdef", "zzzz", 10, 8));
+ }
+
+ /**
+ * Tests {@code prependIfMissing}.
+ */
+ @Test
+ public void testPrependIfMissing() {
+ assertNull(StringUtils.prependIfMissing(null, null), "prependIfMissing(null,null)");
+ assertEquals("abc", StringUtils.prependIfMissing("abc", null), "prependIfMissing(abc,null)");
+ assertEquals("xyz", StringUtils.prependIfMissing("", "xyz"), "prependIfMissing(\"\",xyz)");
+ assertEquals("xyzabc", StringUtils.prependIfMissing("abc", "xyz"), "prependIfMissing(abc,xyz)");
+ assertEquals("xyzabc", StringUtils.prependIfMissing("xyzabc", "xyz"), "prependIfMissing(xyzabc,xyz)");
+ assertEquals("xyzXYZabc", StringUtils.prependIfMissing("XYZabc", "xyz"), "prependIfMissing(XYZabc,xyz)");
+
+ assertNull(StringUtils.prependIfMissing(null, null, (CharSequence[]) null), "prependIfMissing(null,null null)");
+ assertEquals("abc", StringUtils.prependIfMissing("abc", null, (CharSequence[]) null), "prependIfMissing(abc,null,null)");
+ assertEquals("xyz", StringUtils.prependIfMissing("", "xyz", (CharSequence[]) null), "prependIfMissing(\"\",xyz,null)");
+ assertEquals("xyzabc", StringUtils.prependIfMissing("abc", "xyz", null), "prependIfMissing(abc,xyz,{null})");
+ assertEquals("abc", StringUtils.prependIfMissing("abc", "xyz", ""), "prependIfMissing(abc,xyz,\"\")");
+ assertEquals("xyzabc", StringUtils.prependIfMissing("abc", "xyz", "mno"), "prependIfMissing(abc,xyz,mno)");
+ assertEquals("xyzabc", StringUtils.prependIfMissing("xyzabc", "xyz", "mno"), "prependIfMissing(xyzabc,xyz,mno)");
+ assertEquals("mnoabc", StringUtils.prependIfMissing("mnoabc", "xyz", "mno"), "prependIfMissing(mnoabc,xyz,mno)");
+ assertEquals("xyzXYZabc", StringUtils.prependIfMissing("XYZabc", "xyz", "mno"), "prependIfMissing(XYZabc,xyz,mno)");
+ assertEquals("xyzMNOabc", StringUtils.prependIfMissing("MNOabc", "xyz", "mno"), "prependIfMissing(MNOabc,xyz,mno)");
+ }
+
+ /**
+ * Tests {@code prependIfMissingIgnoreCase}.
+ */
+ @Test
+ public void testPrependIfMissingIgnoreCase() {
+ assertNull(StringUtils.prependIfMissingIgnoreCase(null, null), "prependIfMissingIgnoreCase(null,null)");
+ assertEquals("abc", StringUtils.prependIfMissingIgnoreCase("abc", null), "prependIfMissingIgnoreCase(abc,null)");
+ assertEquals("xyz", StringUtils.prependIfMissingIgnoreCase("", "xyz"), "prependIfMissingIgnoreCase(\"\",xyz)");
+ assertEquals("xyzabc", StringUtils.prependIfMissingIgnoreCase("abc", "xyz"), "prependIfMissingIgnoreCase(abc,xyz)");
+ assertEquals("xyzabc", StringUtils.prependIfMissingIgnoreCase("xyzabc", "xyz"), "prependIfMissingIgnoreCase(xyzabc,xyz)");
+ assertEquals("XYZabc", StringUtils.prependIfMissingIgnoreCase("XYZabc", "xyz"), "prependIfMissingIgnoreCase(XYZabc,xyz)");
+
+ assertNull(StringUtils.prependIfMissingIgnoreCase(null, null, (CharSequence[]) null), "prependIfMissingIgnoreCase(null,null null)");
+ assertEquals("abc", StringUtils.prependIfMissingIgnoreCase("abc", null, (CharSequence[]) null), "prependIfMissingIgnoreCase(abc,null,null)");
+ assertEquals("xyz", StringUtils.prependIfMissingIgnoreCase("", "xyz", (CharSequence[]) null), "prependIfMissingIgnoreCase(\"\",xyz,null)");
+ assertEquals("xyzabc", StringUtils.prependIfMissingIgnoreCase("abc", "xyz", null), "prependIfMissingIgnoreCase(abc,xyz,{null})");
+ assertEquals("abc", StringUtils.prependIfMissingIgnoreCase("abc", "xyz", ""), "prependIfMissingIgnoreCase(abc,xyz,\"\")");
+ assertEquals("xyzabc", StringUtils.prependIfMissingIgnoreCase("abc", "xyz", "mno"), "prependIfMissingIgnoreCase(abc,xyz,mno)");
+ assertEquals("xyzabc", StringUtils.prependIfMissingIgnoreCase("xyzabc", "xyz", "mno"), "prependIfMissingIgnoreCase(xyzabc,xyz,mno)");
+ assertEquals("mnoabc", StringUtils.prependIfMissingIgnoreCase("mnoabc", "xyz", "mno"), "prependIfMissingIgnoreCase(mnoabc,xyz,mno)");
+ assertEquals("XYZabc", StringUtils.prependIfMissingIgnoreCase("XYZabc", "xyz", "mno"), "prependIfMissingIgnoreCase(XYZabc,xyz,mno)");
+ assertEquals("MNOabc", StringUtils.prependIfMissingIgnoreCase("MNOabc", "xyz", "mno"), "prependIfMissingIgnoreCase(MNOabc,xyz,mno)");
+ }
+
+ @Test
+ public void testReCapitalize() {
+ // reflection type of tests: Sentences.
+ assertEquals(SENTENCE_UNCAP, StringUtils.uncapitalize(StringUtils.capitalize(SENTENCE_UNCAP)),
+ "uncapitalize(capitalize(String)) failed");
+ assertEquals(SENTENCE_CAP, StringUtils.capitalize(StringUtils.uncapitalize(SENTENCE_CAP)),
+ "capitalize(uncapitalize(String)) failed");
+
+ // reflection type of tests: One word.
+ assertEquals(FOO_UNCAP, StringUtils.uncapitalize(StringUtils.capitalize(FOO_UNCAP)),
+ "uncapitalize(capitalize(String)) failed");
+ assertEquals(FOO_CAP, StringUtils.capitalize(StringUtils.uncapitalize(FOO_CAP)),
+ "capitalize(uncapitalize(String)) failed");
+ }
+
+ @Test
+ public void testRemove_char() {
+ // StringUtils.remove(null, *) = null
+ assertNull(StringUtils.remove(null, null));
+ assertNull(StringUtils.remove(null, 'a'));
+
+ // StringUtils.remove("", *) = ""
+ assertEquals("", StringUtils.remove("", null));
+ assertEquals("", StringUtils.remove("", 'a'));
+
+ // StringUtils.remove("queued", 'u') = "qeed"
+ assertEquals("qeed", StringUtils.remove("queued", 'u'));
+
+ // StringUtils.remove("queued", 'z') = "queued"
+ assertEquals("queued", StringUtils.remove("queued", 'z'));
+ }
+
+ @Test
+ public void testRemove_String() {
+ // StringUtils.remove(null, *) = null
+ assertNull(StringUtils.remove(null, null));
+ assertNull(StringUtils.remove(null, ""));
+ assertNull(StringUtils.remove(null, "a"));
+
+ // StringUtils.remove("", *) = ""
+ assertEquals("", StringUtils.remove("", null));
+ assertEquals("", StringUtils.remove("", ""));
+ assertEquals("", StringUtils.remove("", "a"));
+
+ // StringUtils.remove(*, null) = *
+ assertNull(StringUtils.remove(null, null));
+ assertEquals("", StringUtils.remove("", null));
+ assertEquals("a", StringUtils.remove("a", null));
+
+ // StringUtils.remove(*, "") = *
+ assertNull(StringUtils.remove(null, ""));
+ assertEquals("", StringUtils.remove("", ""));
+ assertEquals("a", StringUtils.remove("a", ""));
+
+ // StringUtils.remove("queued", "ue") = "qd"
+ assertEquals("qd", StringUtils.remove("queued", "ue"));
+
+ // StringUtils.remove("queued", "zz") = "queued"
+ assertEquals("queued", StringUtils.remove("queued", "zz"));
+ }
+
+ @Test
+ public void testRemoveAll_StringString() {
+ assertNull(StringUtils.removeAll(null, ""));
+ assertEquals("any", StringUtils.removeAll("any", null));
+
+ assertEquals("any", StringUtils.removeAll("any", ""));
+ assertEquals("", StringUtils.removeAll("any", ".*"));
+ assertEquals("", StringUtils.removeAll("any", ".+"));
+ assertEquals("", StringUtils.removeAll("any", ".?"));
+
+ assertEquals("A\nB", StringUtils.removeAll("A<__>\n<__>B", "<.*>"));
+ assertEquals("AB", StringUtils.removeAll("A<__>\n<__>B", "(?s)<.*>"));
+ assertEquals("ABC123", StringUtils.removeAll("ABCabc123abc", "[a-z]"));
+
+ assertThrows(
+ PatternSyntaxException.class,
+ () -> StringUtils.removeAll("any", "{badRegexSyntax}"),
+ "StringUtils.removeAll expecting PatternSyntaxException");
+ }
+
+ @Test
+ public void testRemoveEnd() {
+ // StringUtils.removeEnd("", *) = ""
+ assertNull(StringUtils.removeEnd(null, null));
+ assertNull(StringUtils.removeEnd(null, ""));
+ assertNull(StringUtils.removeEnd(null, "a"));
+
+ // StringUtils.removeEnd(*, null) = *
+ assertEquals(StringUtils.removeEnd("", null), "");
+ assertEquals(StringUtils.removeEnd("", ""), "");
+ assertEquals(StringUtils.removeEnd("", "a"), "");
+
+ // All others:
+ assertEquals(StringUtils.removeEnd("www.domain.com.", ".com"), "www.domain.com.");
+ assertEquals(StringUtils.removeEnd("www.domain.com", ".com"), "www.domain");
+ assertEquals(StringUtils.removeEnd("www.domain", ".com"), "www.domain");
+ assertEquals(StringUtils.removeEnd("domain.com", ""), "domain.com");
+ assertEquals(StringUtils.removeEnd("domain.com", null), "domain.com");
+ }
+
+ @Test
+ public void testRemoveEndIgnoreCase() {
+ // StringUtils.removeEndIgnoreCase("", *) = ""
+ assertNull(StringUtils.removeEndIgnoreCase(null, null), "removeEndIgnoreCase(null, null)");
+ assertNull(StringUtils.removeEndIgnoreCase(null, ""), "removeEndIgnoreCase(null, \"\")");
+ assertNull(StringUtils.removeEndIgnoreCase(null, "a"), "removeEndIgnoreCase(null, \"a\")");
+
+ // StringUtils.removeEnd(*, null) = *
+ assertEquals(StringUtils.removeEndIgnoreCase("", null), "", "removeEndIgnoreCase(\"\", null)");
+ assertEquals(StringUtils.removeEndIgnoreCase("", ""), "", "removeEndIgnoreCase(\"\", \"\")");
+ assertEquals(StringUtils.removeEndIgnoreCase("", "a"), "", "removeEndIgnoreCase(\"\", \"a\")");
+
+ // All others:
+ assertEquals(StringUtils.removeEndIgnoreCase("www.domain.com.", ".com"), "www.domain.com.", "removeEndIgnoreCase(\"www.domain.com.\", \".com\")");
+ assertEquals(StringUtils.removeEndIgnoreCase("www.domain.com", ".com"), "www.domain", "removeEndIgnoreCase(\"www.domain.com\", \".com\")");
+ assertEquals(StringUtils.removeEndIgnoreCase("www.domain", ".com"), "www.domain", "removeEndIgnoreCase(\"www.domain\", \".com\")");
+ assertEquals(StringUtils.removeEndIgnoreCase("domain.com", ""), "domain.com", "removeEndIgnoreCase(\"domain.com\", \"\")");
+ assertEquals(StringUtils.removeEndIgnoreCase("domain.com", null), "domain.com", "removeEndIgnoreCase(\"domain.com\", null)");
+
+ // Case-insensitive:
+ assertEquals(StringUtils.removeEndIgnoreCase("www.domain.com", ".COM"), "www.domain", "removeEndIgnoreCase(\"www.domain.com\", \".COM\")");
+ assertEquals(StringUtils.removeEndIgnoreCase("www.domain.COM", ".com"), "www.domain", "removeEndIgnoreCase(\"www.domain.COM\", \".com\")");
+ }
+
+ @Test
+ public void testRemoveFirst_StringString() {
+ assertNull(StringUtils.removeFirst(null, ""));
+ assertEquals("any", StringUtils.removeFirst("any", null));
+
+ assertEquals("any", StringUtils.removeFirst("any", ""));
+ assertEquals("", StringUtils.removeFirst("any", ".*"));
+ assertEquals("", StringUtils.removeFirst("any", ".+"));
+ assertEquals("bc", StringUtils.removeFirst("abc", ".?"));
+
+ assertEquals("A\n<__>B", StringUtils.removeFirst("A<__>\n<__>B", "<.*>"));
+ assertEquals("AB", StringUtils.removeFirst("A<__>\n<__>B", "(?s)<.*>"));
+ assertEquals("ABCbc123", StringUtils.removeFirst("ABCabc123", "[a-z]"));
+ assertEquals("ABC123abc", StringUtils.removeFirst("ABCabc123abc", "[a-z]+"));
+
+ assertThrows(
+ PatternSyntaxException.class,
+ () -> StringUtils.removeFirst("any", "{badRegexSyntax}"),
+ "StringUtils.removeFirst expecting PatternSyntaxException");
+ }
+
+ @Test
+ public void testRemoveIgnoreCase_String() {
+ // StringUtils.removeIgnoreCase(null, *) = null
+ assertNull(StringUtils.removeIgnoreCase(null, null));
+ assertNull(StringUtils.removeIgnoreCase(null, ""));
+ assertNull(StringUtils.removeIgnoreCase(null, "a"));
+
+ // StringUtils.removeIgnoreCase("", *) = ""
+ assertEquals("", StringUtils.removeIgnoreCase("", null));
+ assertEquals("", StringUtils.removeIgnoreCase("", ""));
+ assertEquals("", StringUtils.removeIgnoreCase("", "a"));
+
+ // StringUtils.removeIgnoreCase(*, null) = *
+ assertNull(StringUtils.removeIgnoreCase(null, null));
+ assertEquals("", StringUtils.removeIgnoreCase("", null));
+ assertEquals("a", StringUtils.removeIgnoreCase("a", null));
+
+ // StringUtils.removeIgnoreCase(*, "") = *
+ assertNull(StringUtils.removeIgnoreCase(null, ""));
+ assertEquals("", StringUtils.removeIgnoreCase("", ""));
+ assertEquals("a", StringUtils.removeIgnoreCase("a", ""));
+
+ // StringUtils.removeIgnoreCase("queued", "ue") = "qd"
+ assertEquals("qd", StringUtils.removeIgnoreCase("queued", "ue"));
+
+ // StringUtils.removeIgnoreCase("queued", "zz") = "queued"
+ assertEquals("queued", StringUtils.removeIgnoreCase("queued", "zz"));
+
+ // IgnoreCase
+ // StringUtils.removeIgnoreCase("quEUed", "UE") = "qd"
+ assertEquals("qd", StringUtils.removeIgnoreCase("quEUed", "UE"));
+
+ // StringUtils.removeIgnoreCase("queued", "zZ") = "queued"
+ assertEquals("queued", StringUtils.removeIgnoreCase("queued", "zZ"));
+
+ // StringUtils.removeIgnoreCase("\u0130x", "x") = "\u0130"
+ assertEquals("\u0130", StringUtils.removeIgnoreCase("\u0130x", "x"));
+
+ // LANG-1453
+ StringUtils.removeIgnoreCase("İa", "a");
+ }
+
+ @Test
+ public void testRemovePattern_StringString() {
+ assertNull(StringUtils.removePattern(null, ""));
+ assertEquals("any", StringUtils.removePattern("any", null));
+
+ assertEquals("", StringUtils.removePattern("", ""));
+ assertEquals("", StringUtils.removePattern("", ".*"));
+ assertEquals("", StringUtils.removePattern("", ".+"));
+
+ assertEquals("AB", StringUtils.removePattern("A<__>\n<__>B", "<.*>"));
+ assertEquals("AB", StringUtils.removePattern("A<__>\\n<__>B", "<.*>"));
+ assertEquals("", StringUtils.removePattern("<A>x\\ny</A>", "<A>.*</A>"));
+ assertEquals("", StringUtils.removePattern("<A>\nxy\n</A>", "<A>.*</A>"));
+
+ assertEquals("ABC123", StringUtils.removePattern("ABCabc123", "[a-z]"));
+ }
+
+ @Test
+ public void testRemoveStartChar() {
+ // StringUtils.removeStart("", *) = ""
+ assertNull(StringUtils.removeStart(null, '\0'));
+ assertNull(StringUtils.removeStart(null, 'a'));
+
+ // StringUtils.removeStart(*, null) = *
+ assertEquals(StringUtils.removeStart("", '\0'), "");
+ assertEquals(StringUtils.removeStart("", 'a'), "");
+
+ // All others:
+ assertEquals(StringUtils.removeStart("/path", '/'), "path");
+ assertEquals(StringUtils.removeStart("path", '/'), "path");
+ assertEquals(StringUtils.removeStart("path", '\0'), "path");
+ }
+
+ @Test
+ public void testRemoveStartString() {
+ // StringUtils.removeStart("", *) = ""
+ assertNull(StringUtils.removeStart(null, null));
+ assertNull(StringUtils.removeStart(null, ""));
+ assertNull(StringUtils.removeStart(null, "a"));
+
+ // StringUtils.removeStart(*, null) = *
+ assertEquals(StringUtils.removeStart("", null), "");
+ assertEquals(StringUtils.removeStart("", ""), "");
+ assertEquals(StringUtils.removeStart("", "a"), "");
+
+ // All others:
+ assertEquals(StringUtils.removeStart("www.domain.com", "www."), "domain.com");
+ assertEquals(StringUtils.removeStart("domain.com", "www."), "domain.com");
+ assertEquals(StringUtils.removeStart("domain.com", ""), "domain.com");
+ assertEquals(StringUtils.removeStart("domain.com", null), "domain.com");
+ }
+
+ @Test
+ public void testRemoveStartIgnoreCase() {
+ // StringUtils.removeStart("", *) = ""
+ assertNull(StringUtils.removeStartIgnoreCase(null, null), "removeStartIgnoreCase(null, null)");
+ assertNull(StringUtils.removeStartIgnoreCase(null, ""), "removeStartIgnoreCase(null, \"\")");
+ assertNull(StringUtils.removeStartIgnoreCase(null, "a"), "removeStartIgnoreCase(null, \"a\")");
+
+ // StringUtils.removeStart(*, null) = *
+ assertEquals(StringUtils.removeStartIgnoreCase("", null), "", "removeStartIgnoreCase(\"\", null)");
+ assertEquals(StringUtils.removeStartIgnoreCase("", ""), "", "removeStartIgnoreCase(\"\", \"\")");
+ assertEquals(StringUtils.removeStartIgnoreCase("", "a"), "", "removeStartIgnoreCase(\"\", \"a\")");
+
+ // All others:
+ assertEquals(StringUtils.removeStartIgnoreCase("www.domain.com", "www."), "domain.com", "removeStartIgnoreCase(\"www.domain.com\", \"www.\")");
+ assertEquals(StringUtils.removeStartIgnoreCase("domain.com", "www."), "domain.com", "removeStartIgnoreCase(\"domain.com\", \"www.\")");
+ assertEquals(StringUtils.removeStartIgnoreCase("domain.com", ""), "domain.com", "removeStartIgnoreCase(\"domain.com\", \"\")");
+ assertEquals(StringUtils.removeStartIgnoreCase("domain.com", null), "domain.com", "removeStartIgnoreCase(\"domain.com\", null)");
+
+ // Case-insensitive:
+ assertEquals(StringUtils.removeStartIgnoreCase("www.domain.com", "WWW."), "domain.com", "removeStartIgnoreCase(\"www.domain.com\", \"WWW.\")");
+ }
+
+ @Test
+ public void testRepeat_CharInt() {
+ assertEquals("zzz", StringUtils.repeat('z', 3));
+ assertEquals("", StringUtils.repeat('z', 0));
+ assertEquals("", StringUtils.repeat('z', -2));
+ }
+
+ @Test
+ public void testRepeat_StringInt() {
+ assertNull(StringUtils.repeat(null, 2));
+ assertEquals("", StringUtils.repeat("ab", 0));
+ assertEquals("", StringUtils.repeat("", 3));
+ assertEquals("aaa", StringUtils.repeat("a", 3));
+ assertEquals("", StringUtils.repeat("a", -2));
+ assertEquals("ababab", StringUtils.repeat("ab", 3));
+ assertEquals("abcabcabc", StringUtils.repeat("abc", 3));
+ final String str = StringUtils.repeat("a", 10000); // bigger than pad limit
+ assertEquals(10000, str.length());
+ assertTrue(StringUtils.containsOnly(str, 'a'));
+ }
+
+ @Test
+ public void testRepeat_StringStringInt() {
+ assertNull(StringUtils.repeat(null, null, 2));
+ assertNull(StringUtils.repeat(null, "x", 2));
+ assertEquals("", StringUtils.repeat("", null, 2));
+
+ assertEquals("", StringUtils.repeat("ab", "", 0));
+ assertEquals("", StringUtils.repeat("", "", 2));
+
+ assertEquals("xx", StringUtils.repeat("", "x", 3));
+
+ assertEquals("?, ?, ?", StringUtils.repeat("?", ", ", 3));
+ }
+
+ /**
+ * Test method for 'StringUtils.replaceEach(String, String[], String[])'
+ */
+ @Test
+ public void testReplace_StringStringArrayStringArray() {
+ //JAVADOC TESTS START
+ assertNull(StringUtils.replaceEach(null, new String[]{"a"}, new String[]{"b"}));
+ assertEquals(StringUtils.replaceEach("", new String[]{"a"}, new String[]{"b"}), "");
+ assertEquals(StringUtils.replaceEach("aba", null, null), "aba");
+ assertEquals(StringUtils.replaceEach("aba", new String[0], null), "aba");
+ assertEquals(StringUtils.replaceEach("aba", null, new String[0]), "aba");
+ assertEquals(StringUtils.replaceEach("aba", new String[]{"a"}, null), "aba");
+
+ assertEquals(StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}), "b");
+ assertEquals(StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}), "aba");
+ assertEquals(StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}), "wcte");
+ assertEquals(StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}), "dcte");
+ //JAVADOC TESTS END
+
+ assertEquals("bcc", StringUtils.replaceEach("abc", new String[]{"a", "b"}, new String[]{"b", "c"}));
+ assertEquals("q651.506bera", StringUtils.replaceEach("d216.102oren",
+ new String[]{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
+ "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D",
+ "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
+ "U", "V", "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9"},
+ new String[]{"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "a",
+ "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "N", "O", "P", "Q",
+ "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "A", "B", "C", "D", "E", "F", "G",
+ "H", "I", "J", "K", "L", "M", "5", "6", "7", "8", "9", "1", "2", "3", "4"}));
+
+ // Test null safety inside arrays - LANG-552
+ assertEquals(StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{null}), "aba");
+ assertEquals(StringUtils.replaceEach("aba", new String[]{"a", "b"}, new String[]{"c", null}), "cbc");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.replaceEach("abba", new String[]{"a"}, new String[]{"b", "a"}),
+ "StringUtils.replaceEach(String, String[], String[]) expecting IllegalArgumentException");
+ }
+
+ /**
+ * Test method for 'StringUtils.replaceEachRepeatedly(String, String[], String[])'
+ */
+ @Test
+ public void testReplace_StringStringArrayStringArrayBoolean() {
+ //JAVADOC TESTS START
+ assertNull(StringUtils.replaceEachRepeatedly(null, new String[]{"a"}, new String[]{"b"}));
+ assertEquals("", StringUtils.replaceEachRepeatedly("", new String[]{"a"}, new String[]{"b"}));
+ assertEquals("aba", StringUtils.replaceEachRepeatedly("aba", null, null));
+ assertEquals("aba", StringUtils.replaceEachRepeatedly("aba", new String[0], null));
+ assertEquals("aba", StringUtils.replaceEachRepeatedly("aba", null, new String[0]));
+ assertEquals("aba", StringUtils.replaceEachRepeatedly("aba", new String[0], null));
+
+ assertEquals("b", StringUtils.replaceEachRepeatedly("aba", new String[]{"a"}, new String[]{""}));
+ assertEquals("aba", StringUtils.replaceEachRepeatedly("aba", new String[]{null}, new String[]{"a"}));
+ assertEquals("wcte", StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}));
+ assertEquals("tcte", StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}));
+ assertEquals("blaan", StringUtils.replaceEachRepeatedly("blllaan", new String[]{"llaan"}, new String[]{"laan"}) );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}),
+ "Should be a circular reference");
+
+ //JAVADOC TESTS END
+ }
+
+ @Test
+ public void testReplace_StringStringString() {
+ assertNull(StringUtils.replace(null, null, null));
+ assertNull(StringUtils.replace(null, null, "any"));
+ assertNull(StringUtils.replace(null, "any", null));
+ assertNull(StringUtils.replace(null, "any", "any"));
+
+ assertEquals("", StringUtils.replace("", null, null));
+ assertEquals("", StringUtils.replace("", null, "any"));
+ assertEquals("", StringUtils.replace("", "any", null));
+ assertEquals("", StringUtils.replace("", "any", "any"));
+
+ assertEquals("FOO", StringUtils.replace("FOO", "", "any"));
+ assertEquals("FOO", StringUtils.replace("FOO", null, "any"));
+ assertEquals("FOO", StringUtils.replace("FOO", "F", null));
+ assertEquals("FOO", StringUtils.replace("FOO", null, null));
+
+ assertEquals("", StringUtils.replace("foofoofoo", "foo", ""));
+ assertEquals("barbarbar", StringUtils.replace("foofoofoo", "foo", "bar"));
+ assertEquals("farfarfar", StringUtils.replace("foofoofoo", "oo", "ar"));
+ }
+
+ @Test
+ public void testReplace_StringStringStringInt() {
+ assertNull(StringUtils.replace(null, null, null, 2));
+ assertNull(StringUtils.replace(null, null, "any", 2));
+ assertNull(StringUtils.replace(null, "any", null, 2));
+ assertNull(StringUtils.replace(null, "any", "any", 2));
+
+ assertEquals("", StringUtils.replace("", null, null, 2));
+ assertEquals("", StringUtils.replace("", null, "any", 2));
+ assertEquals("", StringUtils.replace("", "any", null, 2));
+ assertEquals("", StringUtils.replace("", "any", "any", 2));
+
+ final String str = new String(new char[]{'o', 'o', 'f', 'o', 'o'});
+ assertSame(str, StringUtils.replace(str, "x", "", -1));
+
+ assertEquals("f", StringUtils.replace("oofoo", "o", "", -1));
+ assertEquals("oofoo", StringUtils.replace("oofoo", "o", "", 0));
+ assertEquals("ofoo", StringUtils.replace("oofoo", "o", "", 1));
+ assertEquals("foo", StringUtils.replace("oofoo", "o", "", 2));
+ assertEquals("fo", StringUtils.replace("oofoo", "o", "", 3));
+ assertEquals("f", StringUtils.replace("oofoo", "o", "", 4));
+
+ assertEquals("f", StringUtils.replace("oofoo", "o", "", -5));
+ assertEquals("f", StringUtils.replace("oofoo", "o", "", 1000));
+ }
+
+ @Test
+ public void testReplaceAll_StringStringString() {
+ assertNull(StringUtils.replaceAll(null, "", ""));
+
+ assertEquals("any", StringUtils.replaceAll("any", null, ""));
+ assertEquals("any", StringUtils.replaceAll("any", "", null));
+
+ assertEquals("zzz", StringUtils.replaceAll("", "", "zzz"));
+ assertEquals("zzz", StringUtils.replaceAll("", ".*", "zzz"));
+ assertEquals("", StringUtils.replaceAll("", ".+", "zzz"));
+ assertEquals("ZZaZZbZZcZZ", StringUtils.replaceAll("abc", "", "ZZ"));
+
+ assertEquals("z\nz", StringUtils.replaceAll("<__>\n<__>", "<.*>", "z"));
+ assertEquals("z", StringUtils.replaceAll("<__>\n<__>", "(?s)<.*>", "z"));
+
+ assertEquals("ABC___123", StringUtils.replaceAll("ABCabc123", "[a-z]", "_"));
+ assertEquals("ABC_123", StringUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", "_"));
+ assertEquals("ABC123", StringUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", ""));
+ assertEquals("Lorem_ipsum_dolor_sit",
+ StringUtils.replaceAll("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2"));
+
+ assertThrows(
+ PatternSyntaxException.class,
+ () -> StringUtils.replaceAll("any", "{badRegexSyntax}", ""),
+ "StringUtils.replaceAll expecting PatternSyntaxException");
+ }
+
+ @Test
+ public void testReplaceChars_StringCharChar() {
+ assertNull(StringUtils.replaceChars(null, 'b', 'z'));
+ assertEquals("", StringUtils.replaceChars("", 'b', 'z'));
+ assertEquals("azcza", StringUtils.replaceChars("abcba", 'b', 'z'));
+ assertEquals("abcba", StringUtils.replaceChars("abcba", 'x', 'z'));
+ }
+
+ @Test
+ public void testReplaceChars_StringStringString() {
+ assertNull(StringUtils.replaceChars(null, null, null));
+ assertNull(StringUtils.replaceChars(null, "", null));
+ assertNull(StringUtils.replaceChars(null, "a", null));
+ assertNull(StringUtils.replaceChars(null, null, ""));
+ assertNull(StringUtils.replaceChars(null, null, "x"));
+
+ assertEquals("", StringUtils.replaceChars("", null, null));
+ assertEquals("", StringUtils.replaceChars("", "", null));
+ assertEquals("", StringUtils.replaceChars("", "a", null));
+ assertEquals("", StringUtils.replaceChars("", null, ""));
+ assertEquals("", StringUtils.replaceChars("", null, "x"));
+
+ assertEquals("abc", StringUtils.replaceChars("abc", null, null));
+ assertEquals("abc", StringUtils.replaceChars("abc", null, ""));
+ assertEquals("abc", StringUtils.replaceChars("abc", null, "x"));
+
+ assertEquals("abc", StringUtils.replaceChars("abc", "", null));
+ assertEquals("abc", StringUtils.replaceChars("abc", "", ""));
+ assertEquals("abc", StringUtils.replaceChars("abc", "", "x"));
+
+ assertEquals("ac", StringUtils.replaceChars("abc", "b", null));
+ assertEquals("ac", StringUtils.replaceChars("abc", "b", ""));
+ assertEquals("axc", StringUtils.replaceChars("abc", "b", "x"));
+
+ assertEquals("ayzya", StringUtils.replaceChars("abcba", "bc", "yz"));
+ assertEquals("ayya", StringUtils.replaceChars("abcba", "bc", "y"));
+ assertEquals("ayzya", StringUtils.replaceChars("abcba", "bc", "yzx"));
+
+ assertEquals("abcba", StringUtils.replaceChars("abcba", "z", "w"));
+ assertSame("abcba", StringUtils.replaceChars("abcba", "z", "w"));
+
+ // Javadoc examples:
+ assertEquals("jelly", StringUtils.replaceChars("hello", "ho", "jy"));
+ assertEquals("ayzya", StringUtils.replaceChars("abcba", "bc", "yz"));
+ assertEquals("ayya", StringUtils.replaceChars("abcba", "bc", "y"));
+ assertEquals("ayzya", StringUtils.replaceChars("abcba", "bc", "yzx"));
+
+ // From https://issues.apache.org/bugzilla/show_bug.cgi?id=25454
+ assertEquals("bcc", StringUtils.replaceChars("abc", "ab", "bc"));
+ assertEquals("q651.506bera", StringUtils.replaceChars("d216.102oren",
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789",
+ "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM567891234"));
+ }
+
+ @Test
+ public void testReplaceFirst_StringStringString() {
+ assertNull(StringUtils.replaceFirst(null, "", ""));
+
+ assertEquals("any", StringUtils.replaceFirst("any", null, ""));
+ assertEquals("any", StringUtils.replaceFirst("any", "", null));
+
+ assertEquals("zzz", StringUtils.replaceFirst("", "", "zzz"));
+ assertEquals("zzz", StringUtils.replaceFirst("", ".*", "zzz"));
+ assertEquals("", StringUtils.replaceFirst("", ".+", "zzz"));
+ assertEquals("ZZabc", StringUtils.replaceFirst("abc", "", "ZZ"));
+
+ assertEquals("z\n<__>", StringUtils.replaceFirst("<__>\n<__>", "<.*>", "z"));
+ assertEquals("z", StringUtils.replaceFirst("<__>\n<__>", "(?s)<.*>", "z"));
+
+ assertEquals("ABC_bc123", StringUtils.replaceFirst("ABCabc123", "[a-z]", "_"));
+ assertEquals("ABC_123abc", StringUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", "_"));
+ assertEquals("ABC123abc", StringUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", ""));
+ assertEquals("Lorem_ipsum dolor sit",
+ StringUtils.replaceFirst("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2"));
+
+ assertThrows(
+ PatternSyntaxException.class,
+ () -> StringUtils.replaceFirst("any", "{badRegexSyntax}", ""),
+ "StringUtils.replaceFirst expecting PatternSyntaxException");
+ }
+
+ @Test
+ public void testReplaceIgnoreCase_StringStringString() {
+ assertNull(StringUtils.replaceIgnoreCase(null, null, null));
+ assertNull(StringUtils.replaceIgnoreCase(null, null, "any"));
+ assertNull(StringUtils.replaceIgnoreCase(null, "any", null));
+ assertNull(StringUtils.replaceIgnoreCase(null, "any", "any"));
+
+ assertEquals("", StringUtils.replaceIgnoreCase("", null, null));
+ assertEquals("", StringUtils.replaceIgnoreCase("", null, "any"));
+ assertEquals("", StringUtils.replaceIgnoreCase("", "any", null));
+ assertEquals("", StringUtils.replaceIgnoreCase("", "any", "any"));
+
+ assertEquals("FOO", StringUtils.replaceIgnoreCase("FOO", "", "any"));
+ assertEquals("FOO", StringUtils.replaceIgnoreCase("FOO", null, "any"));
+ assertEquals("FOO", StringUtils.replaceIgnoreCase("FOO", "F", null));
+ assertEquals("FOO", StringUtils.replaceIgnoreCase("FOO", null, null));
+
+ assertEquals("", StringUtils.replaceIgnoreCase("foofoofoo", "foo", ""));
+ assertEquals("barbarbar", StringUtils.replaceIgnoreCase("foofoofoo", "foo", "bar"));
+ assertEquals("farfarfar", StringUtils.replaceIgnoreCase("foofoofoo", "oo", "ar"));
+
+ // IgnoreCase
+ assertEquals("", StringUtils.replaceIgnoreCase("foofoofoo", "FOO", ""));
+ assertEquals("barbarbar", StringUtils.replaceIgnoreCase("fooFOOfoo", "foo", "bar"));
+ assertEquals("farfarfar", StringUtils.replaceIgnoreCase("foofOOfoo", "OO", "ar"));
+ }
+
+ @Test
+ public void testReplaceIgnoreCase_StringStringStringInt() {
+ assertNull(StringUtils.replaceIgnoreCase(null, null, null, 2));
+ assertNull(StringUtils.replaceIgnoreCase(null, null, "any", 2));
+ assertNull(StringUtils.replaceIgnoreCase(null, "any", null, 2));
+ assertNull(StringUtils.replaceIgnoreCase(null, "any", "any", 2));
+
+ assertEquals("", StringUtils.replaceIgnoreCase("", null, null, 2));
+ assertEquals("", StringUtils.replaceIgnoreCase("", null, "any", 2));
+ assertEquals("", StringUtils.replaceIgnoreCase("", "any", null, 2));
+ assertEquals("", StringUtils.replaceIgnoreCase("", "any", "any", 2));
+
+ final String str = new String(new char[] { 'o', 'o', 'f', 'o', 'o' });
+ assertSame(str, StringUtils.replaceIgnoreCase(str, "x", "", -1));
+
+ assertEquals("f", StringUtils.replaceIgnoreCase("oofoo", "o", "", -1));
+ assertEquals("oofoo", StringUtils.replaceIgnoreCase("oofoo", "o", "", 0));
+ assertEquals("ofoo", StringUtils.replaceIgnoreCase("oofoo", "o", "", 1));
+ assertEquals("foo", StringUtils.replaceIgnoreCase("oofoo", "o", "", 2));
+ assertEquals("fo", StringUtils.replaceIgnoreCase("oofoo", "o", "", 3));
+ assertEquals("f", StringUtils.replaceIgnoreCase("oofoo", "o", "", 4));
+
+ assertEquals("f", StringUtils.replaceIgnoreCase("oofoo", "o", "", -5));
+ assertEquals("f", StringUtils.replaceIgnoreCase("oofoo", "o", "", 1000));
+
+ // IgnoreCase
+ assertEquals("f", StringUtils.replaceIgnoreCase("oofoo", "O", "", -1));
+ assertEquals("oofoo", StringUtils.replaceIgnoreCase("oofoo", "O", "", 0));
+ assertEquals("ofoo", StringUtils.replaceIgnoreCase("oofoo", "O", "", 1));
+ assertEquals("foo", StringUtils.replaceIgnoreCase("oofoo", "O", "", 2));
+ assertEquals("fo", StringUtils.replaceIgnoreCase("oofoo", "O", "", 3));
+ assertEquals("f", StringUtils.replaceIgnoreCase("oofoo", "O", "", 4));
+
+ assertEquals("f", StringUtils.replaceIgnoreCase("oofoo", "O", "", -5));
+ assertEquals("f", StringUtils.replaceIgnoreCase("oofoo", "O", "", 1000));
+ }
+
+ @Test
+ public void testReplaceOnce_StringStringString() {
+ assertNull(StringUtils.replaceOnce(null, null, null));
+ assertNull(StringUtils.replaceOnce(null, null, "any"));
+ assertNull(StringUtils.replaceOnce(null, "any", null));
+ assertNull(StringUtils.replaceOnce(null, "any", "any"));
+
+ assertEquals("", StringUtils.replaceOnce("", null, null));
+ assertEquals("", StringUtils.replaceOnce("", null, "any"));
+ assertEquals("", StringUtils.replaceOnce("", "any", null));
+ assertEquals("", StringUtils.replaceOnce("", "any", "any"));
+
+ assertEquals("FOO", StringUtils.replaceOnce("FOO", "", "any"));
+ assertEquals("FOO", StringUtils.replaceOnce("FOO", null, "any"));
+ assertEquals("FOO", StringUtils.replaceOnce("FOO", "F", null));
+ assertEquals("FOO", StringUtils.replaceOnce("FOO", null, null));
+
+ assertEquals("foofoo", StringUtils.replaceOnce("foofoofoo", "foo", ""));
+ }
+
+ @Test
+ public void testReplaceOnceIgnoreCase_StringStringString() {
+ assertNull(StringUtils.replaceOnceIgnoreCase(null, null, null));
+ assertNull(StringUtils.replaceOnceIgnoreCase(null, null, "any"));
+ assertNull(StringUtils.replaceOnceIgnoreCase(null, "any", null));
+ assertNull(StringUtils.replaceOnceIgnoreCase(null, "any", "any"));
+
+ assertEquals("", StringUtils.replaceOnceIgnoreCase("", null, null));
+ assertEquals("", StringUtils.replaceOnceIgnoreCase("", null, "any"));
+ assertEquals("", StringUtils.replaceOnceIgnoreCase("", "any", null));
+ assertEquals("", StringUtils.replaceOnceIgnoreCase("", "any", "any"));
+
+ assertEquals("FOO", StringUtils.replaceOnceIgnoreCase("FOO", "", "any"));
+ assertEquals("FOO", StringUtils.replaceOnceIgnoreCase("FOO", null, "any"));
+ assertEquals("FOO", StringUtils.replaceOnceIgnoreCase("FOO", "F", null));
+ assertEquals("FOO", StringUtils.replaceOnceIgnoreCase("FOO", null, null));
+
+ assertEquals("foofoo", StringUtils.replaceOnceIgnoreCase("foofoofoo", "foo", ""));
+
+ // Ignore Case
+ assertEquals("Foofoo", StringUtils.replaceOnceIgnoreCase("FoOFoofoo", "foo", ""));
+ }
+
+ @Test
+ public void testReplacePattern_StringStringString() {
+ assertNull(StringUtils.replacePattern(null, "", ""));
+ assertEquals("any", StringUtils.replacePattern("any", null, ""));
+ assertEquals("any", StringUtils.replacePattern("any", "", null));
+
+ assertEquals("zzz", StringUtils.replacePattern("", "", "zzz"));
+ assertEquals("zzz", StringUtils.replacePattern("", ".*", "zzz"));
+ assertEquals("", StringUtils.replacePattern("", ".+", "zzz"));
+
+ assertEquals("z", StringUtils.replacePattern("<__>\n<__>", "<.*>", "z"));
+ assertEquals("z", StringUtils.replacePattern("<__>\\n<__>", "<.*>", "z"));
+ assertEquals("X", StringUtils.replacePattern("<A>\nxy\n</A>", "<A>.*</A>", "X"));
+
+ assertEquals("ABC___123", StringUtils.replacePattern("ABCabc123", "[a-z]", "_"));
+ assertEquals("ABC_123", StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "_"));
+ assertEquals("ABC123", StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", ""));
+ assertEquals("Lorem_ipsum_dolor_sit",
+ StringUtils.replacePattern("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2"));
+ }
+
+ @Test
+ public void testReverse_String() {
+ assertNull(StringUtils.reverse(null));
+ assertEquals("", StringUtils.reverse(""));
+ assertEquals("sdrawkcab", StringUtils.reverse("backwards"));
+ }
+
+ @Test
+ public void testReverseDelimited_StringChar() {
+ assertNull(StringUtils.reverseDelimited(null, '.'));
+ assertEquals("", StringUtils.reverseDelimited("", '.'));
+ assertEquals("c.b.a", StringUtils.reverseDelimited("a.b.c", '.'));
+ assertEquals("a b c", StringUtils.reverseDelimited("a b c", '.'));
+ assertEquals("", StringUtils.reverseDelimited("", '.'));
+ }
+
+ @Test
+ public void testRightPad_StringInt() {
+ assertNull(StringUtils.rightPad(null, 5));
+ assertEquals(" ", StringUtils.rightPad("", 5));
+ assertEquals("abc ", StringUtils.rightPad("abc", 5));
+ assertEquals("abc", StringUtils.rightPad("abc", 2));
+ assertEquals("abc", StringUtils.rightPad("abc", -1));
+ }
+
+ @Test
+ public void testRightPad_StringIntChar() {
+ assertNull(StringUtils.rightPad(null, 5, ' '));
+ assertEquals(" ", StringUtils.rightPad("", 5, ' '));
+ assertEquals("abc ", StringUtils.rightPad("abc", 5, ' '));
+ assertEquals("abc", StringUtils.rightPad("abc", 2, ' '));
+ assertEquals("abc", StringUtils.rightPad("abc", -1, ' '));
+ assertEquals("abcxx", StringUtils.rightPad("abc", 5, 'x'));
+ final String str = StringUtils.rightPad("aaa", 10000, 'a'); // bigger than pad length
+ assertEquals(10000, str.length());
+ assertTrue(StringUtils.containsOnly(str, 'a'));
+ }
+
+ @Test
+ public void testRightPad_StringIntString() {
+ assertNull(StringUtils.rightPad(null, 5, "-+"));
+ assertEquals(" ", StringUtils.rightPad("", 5, " "));
+ assertNull(StringUtils.rightPad(null, 8, null));
+ assertEquals("abc-+-+", StringUtils.rightPad("abc", 7, "-+"));
+ assertEquals("abc-+~", StringUtils.rightPad("abc", 6, "-+~"));
+ assertEquals("abc-+", StringUtils.rightPad("abc", 5, "-+~"));
+ assertEquals("abc", StringUtils.rightPad("abc", 2, " "));
+ assertEquals("abc", StringUtils.rightPad("abc", -1, " "));
+ assertEquals("abc ", StringUtils.rightPad("abc", 5, null));
+ assertEquals("abc ", StringUtils.rightPad("abc", 5, ""));
+ }
+
+ @Test
+ public void testRotate_StringInt() {
+ assertNull(StringUtils.rotate(null, 1));
+ assertEquals("", StringUtils.rotate("", 1));
+ assertEquals("abcdefg", StringUtils.rotate("abcdefg", 0));
+ assertEquals("fgabcde", StringUtils.rotate("abcdefg", 2));
+ assertEquals("cdefgab", StringUtils.rotate("abcdefg", -2));
+ assertEquals("abcdefg", StringUtils.rotate("abcdefg", 7));
+ assertEquals("abcdefg", StringUtils.rotate("abcdefg", -7));
+ assertEquals("fgabcde", StringUtils.rotate("abcdefg", 9));
+ assertEquals("cdefgab", StringUtils.rotate("abcdefg", -9));
+ assertEquals("efgabcd", StringUtils.rotate("abcdefg", 17));
+ assertEquals("defgabc", StringUtils.rotate("abcdefg", -17));
+ }
+
+ @Test
+ public void testSplit_String() {
+ assertNull(StringUtils.split(null));
+ assertEquals(0, StringUtils.split("").length);
+
+ String str = "a b .c";
+ String[] res = StringUtils.split(str);
+ assertEquals(3, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals(".c", res[2]);
+
+ str = " a ";
+ res = StringUtils.split(str);
+ assertEquals(1, res.length);
+ assertEquals("a", res[0]);
+
+ str = "a" + WHITESPACE + "b" + NON_WHITESPACE + "c";
+ res = StringUtils.split(str);
+ assertEquals(2, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b" + NON_WHITESPACE + "c", res[1]);
+ }
+
+ @Test
+ public void testSplit_StringChar() {
+ assertNull(StringUtils.split(null, '.'));
+ assertEquals(0, StringUtils.split("", '.').length);
+
+ String str = "a.b.. c";
+ String[] res = StringUtils.split(str, '.');
+ assertEquals(3, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals(" c", res[2]);
+
+ str = ".a.";
+ res = StringUtils.split(str, '.');
+ assertEquals(1, res.length);
+ assertEquals("a", res[0]);
+
+ str = "a b c";
+ res = StringUtils.split(str, ' ');
+ assertEquals(3, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals("c", res[2]);
+ }
+
+ @Test
+ public void testSplit_StringString_StringStringInt() {
+ assertNull(StringUtils.split(null, "."));
+ assertNull(StringUtils.split(null, ".", 3));
+
+ assertEquals(0, StringUtils.split("", ".").length);
+ assertEquals(0, StringUtils.split("", ".", 3).length);
+
+ innerTestSplit('.', ".", ' ');
+ innerTestSplit('.', ".", ',');
+ innerTestSplit('.', ".,", 'x');
+ for (int i = 0; i < WHITESPACE.length(); i++) {
+ for (int j = 0; j < NON_WHITESPACE.length(); j++) {
+ innerTestSplit(WHITESPACE.charAt(i), null, NON_WHITESPACE.charAt(j));
+ innerTestSplit(WHITESPACE.charAt(i), String.valueOf(WHITESPACE.charAt(i)), NON_WHITESPACE.charAt(j));
+ }
+ }
+
+ String[] results;
+ final String[] expectedResults = {"ab", "de fg"};
+ results = StringUtils.split("ab de fg", null, 2);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+
+ final String[] expectedResults2 = {"ab", "cd:ef"};
+ results = StringUtils.split("ab:cd:ef", ":", 2);
+ assertEquals(expectedResults2.length, results.length);
+ for (int i = 0; i < expectedResults2.length; i++) {
+ assertEquals(expectedResults2[i], results[i]);
+ }
+ }
+
+ @Test
+ public void testSplitByCharacterType() {
+ assertNull(StringUtils.splitByCharacterType(null));
+ assertEquals(0, StringUtils.splitByCharacterType("").length);
+
+ assertTrue(Objects.deepEquals(new String[]{"ab", " ", "de", " ",
+ "fg"}, StringUtils.splitByCharacterType("ab de fg")));
+
+ assertTrue(Objects.deepEquals(new String[]{"ab", " ", "de", " ",
+ "fg"}, StringUtils.splitByCharacterType("ab de fg")));
+
+ assertTrue(Objects.deepEquals(new String[]{"ab", ":", "cd", ":",
+ "ef"}, StringUtils.splitByCharacterType("ab:cd:ef")));
+
+ assertTrue(Objects.deepEquals(new String[]{"number", "5"},
+ StringUtils.splitByCharacterType("number5")));
+
+ assertTrue(Objects.deepEquals(new String[]{"foo", "B", "ar"},
+ StringUtils.splitByCharacterType("fooBar")));
+
+ assertTrue(Objects.deepEquals(new String[]{"foo", "200", "B", "ar"},
+ StringUtils.splitByCharacterType("foo200Bar")));
+
+ assertTrue(Objects.deepEquals(new String[]{"ASFR", "ules"},
+ StringUtils.splitByCharacterType("ASFRules")));
+ }
+
+ @Test
+ public void testSplitByCharacterTypeCamelCase() {
+ assertNull(StringUtils.splitByCharacterTypeCamelCase(null));
+ assertEquals(0, StringUtils.splitByCharacterTypeCamelCase("").length);
+
+ assertTrue(Objects.deepEquals(new String[]{"ab", " ", "de", " ",
+ "fg"}, StringUtils.splitByCharacterTypeCamelCase("ab de fg")));
+
+ assertTrue(Objects.deepEquals(new String[]{"ab", " ", "de", " ",
+ "fg"}, StringUtils.splitByCharacterTypeCamelCase("ab de fg")));
+
+ assertTrue(Objects.deepEquals(new String[]{"ab", ":", "cd", ":",
+ "ef"}, StringUtils.splitByCharacterTypeCamelCase("ab:cd:ef")));
+
+ assertTrue(Objects.deepEquals(new String[]{"number", "5"},
+ StringUtils.splitByCharacterTypeCamelCase("number5")));
+
+ assertTrue(Objects.deepEquals(new String[]{"foo", "Bar"},
+ StringUtils.splitByCharacterTypeCamelCase("fooBar")));
+
+ assertTrue(Objects.deepEquals(new String[]{"foo", "200", "Bar"},
+ StringUtils.splitByCharacterTypeCamelCase("foo200Bar")));
+
+ assertTrue(Objects.deepEquals(new String[]{"ASF", "Rules"},
+ StringUtils.splitByCharacterTypeCamelCase("ASFRules")));
+ }
+
+ @Test
+ public void testSplitByWholeSeparatorPreserveAllTokens_StringString() {
+ assertArrayEquals(null, StringUtils.splitByWholeSeparatorPreserveAllTokens(null, "."));
+
+ assertEquals(0, StringUtils.splitByWholeSeparatorPreserveAllTokens("", ".").length);
+
+ // test whitespace
+ String input = "ab de fg";
+ String[] expected = {"ab", "", "", "de", "fg"};
+
+ String[] actual = StringUtils.splitByWholeSeparatorPreserveAllTokens(input, null);
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < actual.length; i += 1) {
+ assertEquals(expected[i], actual[i]);
+ }
+
+ // test delimiter singlechar
+ input = "1::2:::3::::4";
+ expected = new String[]{"1", "", "2", "", "", "3", "", "", "", "4"};
+
+ actual = StringUtils.splitByWholeSeparatorPreserveAllTokens(input, ":");
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < actual.length; i += 1) {
+ assertEquals(expected[i], actual[i]);
+ }
+
+ // test delimiter multichar
+ input = "1::2:::3::::4";
+ expected = new String[]{"1", "2", ":3", "", "4"};
+
+ actual = StringUtils.splitByWholeSeparatorPreserveAllTokens(input, "::");
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < actual.length; i += 1) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ @Test
+ public void testSplitByWholeSeparatorPreserveAllTokens_StringStringInt() {
+ assertArrayEquals(null, StringUtils.splitByWholeSeparatorPreserveAllTokens(null, ".", -1));
+
+ assertEquals(0, StringUtils.splitByWholeSeparatorPreserveAllTokens("", ".", -1).length);
+
+ // test whitespace
+ String input = "ab de fg";
+ String[] expected = {"ab", "", "", "de", "fg"};
+
+ String[] actual = StringUtils.splitByWholeSeparatorPreserveAllTokens(input, null, -1);
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < actual.length; i += 1) {
+ assertEquals(expected[i], actual[i]);
+ }
+
+ // test delimiter singlechar
+ input = "1::2:::3::::4";
+ expected = new String[]{"1", "", "2", "", "", "3", "", "", "", "4"};
+
+ actual = StringUtils.splitByWholeSeparatorPreserveAllTokens(input, ":", -1);
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < actual.length; i += 1) {
+ assertEquals(expected[i], actual[i]);
+ }
+
+ // test delimiter multichar
+ input = "1::2:::3::::4";
+ expected = new String[]{"1", "2", ":3", "", "4"};
+
+ actual = StringUtils.splitByWholeSeparatorPreserveAllTokens(input, "::", -1);
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < actual.length; i += 1) {
+ assertEquals(expected[i], actual[i]);
+ }
+
+ // test delimiter char with max
+ input = "1::2::3:4";
+ expected = new String[]{"1", "", "2", ":3:4"};
+
+ actual = StringUtils.splitByWholeSeparatorPreserveAllTokens(input, ":", 4);
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < actual.length; i += 1) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ @Test
+ public void testSplitByWholeString_StringStringBoolean() {
+ assertArrayEquals(null, StringUtils.splitByWholeSeparator(null, "."));
+
+ assertEquals(0, StringUtils.splitByWholeSeparator("", ".").length);
+
+ final String stringToSplitOnNulls = "ab de fg";
+ final String[] splitOnNullExpectedResults = {"ab", "de", "fg"};
+
+ final String[] splitOnNullResults = StringUtils.splitByWholeSeparator(stringToSplitOnNulls, null);
+ assertEquals(splitOnNullExpectedResults.length, splitOnNullResults.length);
+ for (int i = 0; i < splitOnNullExpectedResults.length; i += 1) {
+ assertEquals(splitOnNullExpectedResults[i], splitOnNullResults[i]);
+ }
+
+ final String stringToSplitOnCharactersAndString = "abstemiouslyaeiouyabstemiously";
+
+ final String[] splitOnStringExpectedResults = {"abstemiously", "abstemiously"};
+ final String[] splitOnStringResults = StringUtils.splitByWholeSeparator(stringToSplitOnCharactersAndString, "aeiouy");
+ assertEquals(splitOnStringExpectedResults.length, splitOnStringResults.length);
+ for (int i = 0; i < splitOnStringExpectedResults.length; i += 1) {
+ assertEquals(splitOnStringExpectedResults[i], splitOnStringResults[i]);
+ }
+
+ final String[] splitWithMultipleSeparatorExpectedResults = {"ab", "cd", "ef"};
+ final String[] splitWithMultipleSeparator = StringUtils.splitByWholeSeparator("ab:cd::ef", ":");
+ assertEquals(splitWithMultipleSeparatorExpectedResults.length, splitWithMultipleSeparator.length);
+ for (int i = 0; i < splitWithMultipleSeparatorExpectedResults.length; i++) {
+ assertEquals(splitWithMultipleSeparatorExpectedResults[i], splitWithMultipleSeparator[i]);
+ }
+ }
+
+ @Test
+ public void testSplitByWholeString_StringStringBooleanInt() {
+ assertArrayEquals(null, StringUtils.splitByWholeSeparator(null, ".", 3));
+
+ assertEquals(0, StringUtils.splitByWholeSeparator("", ".", 3).length);
+
+ final String stringToSplitOnNulls = "ab de fg";
+ final String[] splitOnNullExpectedResults = {"ab", "de fg"};
+ //String[] splitOnNullExpectedResults = { "ab", "de" } ;
+
+ final String[] splitOnNullResults = StringUtils.splitByWholeSeparator(stringToSplitOnNulls, null, 2);
+ assertEquals(splitOnNullExpectedResults.length, splitOnNullResults.length);
+ for (int i = 0; i < splitOnNullExpectedResults.length; i += 1) {
+ assertEquals(splitOnNullExpectedResults[i], splitOnNullResults[i]);
+ }
+
+ final String stringToSplitOnCharactersAndString = "abstemiouslyaeiouyabstemiouslyaeiouyabstemiously";
+
+ final String[] splitOnStringExpectedResults = {"abstemiously", "abstemiouslyaeiouyabstemiously"};
+ //String[] splitOnStringExpectedResults = { "abstemiously", "abstemiously" } ;
+ final String[] splitOnStringResults = StringUtils.splitByWholeSeparator(stringToSplitOnCharactersAndString, "aeiouy", 2);
+ assertEquals(splitOnStringExpectedResults.length, splitOnStringResults.length);
+ for (int i = 0; i < splitOnStringExpectedResults.length; i++) {
+ assertEquals(splitOnStringExpectedResults[i], splitOnStringResults[i]);
+ }
+ }
+
+ @Test
+ public void testSplitPreserveAllTokens_String() {
+ assertNull(StringUtils.splitPreserveAllTokens(null));
+ assertEquals(0, StringUtils.splitPreserveAllTokens("").length);
+
+ String str = "abc def";
+ String[] res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(2, res.length);
+ assertEquals("abc", res[0]);
+ assertEquals("def", res[1]);
+
+ str = "abc def";
+ res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(3, res.length);
+ assertEquals("abc", res[0]);
+ assertEquals("", res[1]);
+ assertEquals("def", res[2]);
+
+ str = " abc ";
+ res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(3, res.length);
+ assertEquals("", res[0]);
+ assertEquals("abc", res[1]);
+ assertEquals("", res[2]);
+
+ str = "a b .c";
+ res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(3, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals(".c", res[2]);
+
+ str = " a b .c";
+ res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(4, res.length);
+ assertEquals("", res[0]);
+ assertEquals("a", res[1]);
+ assertEquals("b", res[2]);
+ assertEquals(".c", res[3]);
+
+ str = "a b .c";
+ res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(5, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("", res[1]);
+ assertEquals("b", res[2]);
+ assertEquals("", res[3]);
+ assertEquals(".c", res[4]);
+
+ str = " a ";
+ res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(4, res.length);
+ assertEquals("", res[0]);
+ assertEquals("a", res[1]);
+ assertEquals("", res[2]);
+ assertEquals("", res[3]);
+
+ str = " a b";
+ res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(4, res.length);
+ assertEquals("", res[0]);
+ assertEquals("a", res[1]);
+ assertEquals("", res[2]);
+ assertEquals("b", res[3]);
+
+ str = "a" + WHITESPACE + "b" + NON_WHITESPACE + "c";
+ res = StringUtils.splitPreserveAllTokens(str);
+ assertEquals(WHITESPACE.length() + 1, res.length);
+ assertEquals("a", res[0]);
+ for (int i = 1; i < WHITESPACE.length() - 1; i++) {
+ assertEquals("", res[i]);
+ }
+ assertEquals("b" + NON_WHITESPACE + "c", res[WHITESPACE.length()]);
+ }
+
+ @Test
+ public void testSplitPreserveAllTokens_StringChar() {
+ assertNull(StringUtils.splitPreserveAllTokens(null, '.'));
+ assertEquals(0, StringUtils.splitPreserveAllTokens("", '.').length);
+
+ String str = "a.b. c";
+ String[] res = StringUtils.splitPreserveAllTokens(str, '.');
+ assertEquals(3, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals(" c", res[2]);
+
+ str = "a.b.. c";
+ res = StringUtils.splitPreserveAllTokens(str, '.');
+ assertEquals(4, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals("", res[2]);
+ assertEquals(" c", res[3]);
+
+ str = ".a.";
+ res = StringUtils.splitPreserveAllTokens(str, '.');
+ assertEquals(3, res.length);
+ assertEquals("", res[0]);
+ assertEquals("a", res[1]);
+ assertEquals("", res[2]);
+
+ str = ".a..";
+ res = StringUtils.splitPreserveAllTokens(str, '.');
+ assertEquals(4, res.length);
+ assertEquals("", res[0]);
+ assertEquals("a", res[1]);
+ assertEquals("", res[2]);
+ assertEquals("", res[3]);
+
+ str = "..a.";
+ res = StringUtils.splitPreserveAllTokens(str, '.');
+ assertEquals(4, res.length);
+ assertEquals("", res[0]);
+ assertEquals("", res[1]);
+ assertEquals("a", res[2]);
+ assertEquals("", res[3]);
+
+ str = "..a";
+ res = StringUtils.splitPreserveAllTokens(str, '.');
+ assertEquals(3, res.length);
+ assertEquals("", res[0]);
+ assertEquals("", res[1]);
+ assertEquals("a", res[2]);
+
+ str = "a b c";
+ res = StringUtils.splitPreserveAllTokens(str, ' ');
+ assertEquals(3, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals("c", res[2]);
+
+ str = "a b c";
+ res = StringUtils.splitPreserveAllTokens(str, ' ');
+ assertEquals(5, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("", res[1]);
+ assertEquals("b", res[2]);
+ assertEquals("", res[3]);
+ assertEquals("c", res[4]);
+
+ str = " a b c";
+ res = StringUtils.splitPreserveAllTokens(str, ' ');
+ assertEquals(4, res.length);
+ assertEquals("", res[0]);
+ assertEquals("a", res[1]);
+ assertEquals("b", res[2]);
+ assertEquals("c", res[3]);
+
+ str = " a b c";
+ res = StringUtils.splitPreserveAllTokens(str, ' ');
+ assertEquals(5, res.length);
+ assertEquals("", res[0]);
+ assertEquals("", res[1]);
+ assertEquals("a", res[2]);
+ assertEquals("b", res[3]);
+ assertEquals("c", res[4]);
+
+ str = "a b c ";
+ res = StringUtils.splitPreserveAllTokens(str, ' ');
+ assertEquals(4, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals("c", res[2]);
+ assertEquals("", res[3]);
+
+ str = "a b c ";
+ res = StringUtils.splitPreserveAllTokens(str, ' ');
+ assertEquals(5, res.length);
+ assertEquals("a", res[0]);
+ assertEquals("b", res[1]);
+ assertEquals("c", res[2]);
+ assertEquals("", res[3]);
+ assertEquals("", res[4]);
+
+ // Match example in javadoc
+ {
+ final String[] results;
+ final String[] expectedResults = {"a", "", "b", "c"};
+ results = StringUtils.splitPreserveAllTokens("a..b.c", '.');
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+ }
+
+ @Test
+ public void testSplitPreserveAllTokens_StringString_StringStringInt() {
+ assertNull(StringUtils.splitPreserveAllTokens(null, "."));
+ assertNull(StringUtils.splitPreserveAllTokens(null, ".", 3));
+
+ assertEquals(0, StringUtils.splitPreserveAllTokens("", ".").length);
+ assertEquals(0, StringUtils.splitPreserveAllTokens("", ".", 3).length);
+
+ innerTestSplitPreserveAllTokens('.', ".", ' ');
+ innerTestSplitPreserveAllTokens('.', ".", ',');
+ innerTestSplitPreserveAllTokens('.', ".,", 'x');
+ for (int i = 0; i < WHITESPACE.length(); i++) {
+ for (int j = 0; j < NON_WHITESPACE.length(); j++) {
+ innerTestSplitPreserveAllTokens(WHITESPACE.charAt(i), null, NON_WHITESPACE.charAt(j));
+ innerTestSplitPreserveAllTokens(WHITESPACE.charAt(i), String.valueOf(WHITESPACE.charAt(i)), NON_WHITESPACE.charAt(j));
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"ab", "de fg"};
+ results = StringUtils.splitPreserveAllTokens("ab de fg", null, 2);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"ab", " de fg"};
+ results = StringUtils.splitPreserveAllTokens("ab de fg", null, 2);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"ab", "::de:fg"};
+ results = StringUtils.splitPreserveAllTokens("ab:::de:fg", ":", 2);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"ab", "", " de fg"};
+ results = StringUtils.splitPreserveAllTokens("ab de fg", null, 3);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"ab", "", "", "de fg"};
+ results = StringUtils.splitPreserveAllTokens("ab de fg", null, 4);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] expectedResults = {"ab", "cd:ef"};
+ final String[] results;
+ results = StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 2);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"ab", ":cd:ef"};
+ results = StringUtils.splitPreserveAllTokens("ab::cd:ef", ":", 2);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"ab", "", ":cd:ef"};
+ results = StringUtils.splitPreserveAllTokens("ab:::cd:ef", ":", 3);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"ab", "", "", "cd:ef"};
+ results = StringUtils.splitPreserveAllTokens("ab:::cd:ef", ":", 4);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"", "ab", "", "", "cd:ef"};
+ results = StringUtils.splitPreserveAllTokens(":ab:::cd:ef", ":", 5);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ {
+ final String[] results;
+ final String[] expectedResults = {"", "", "ab", "", "", "cd:ef"};
+ results = StringUtils.splitPreserveAllTokens("::ab:::cd:ef", ":", 6);
+ assertEquals(expectedResults.length, results.length);
+ for (int i = 0; i < expectedResults.length; i++) {
+ assertEquals(expectedResults[i], results[i]);
+ }
+ }
+
+ }
+
+ // Methods on StringUtils that are immutable in spirit (i.e. calculate the length)
+ // should take a CharSequence parameter. Methods that are mutable in spirit (i.e. capitalize)
+ // should take a String or String[] parameter and return String or String[].
+ // This test enforces that this is done.
+ @Test
+ public void testStringUtilsCharSequenceContract() {
+ final Class<StringUtils> c = StringUtils.class;
+ // Methods that are expressly excluded from testStringUtilsCharSequenceContract()
+ final String[] excludeMethods = {
+ "public static int org.apache.commons.lang3.StringUtils.compare(java.lang.String,java.lang.String)",
+ "public static int org.apache.commons.lang3.StringUtils.compare(java.lang.String,java.lang.String,boolean)",
+ "public static int org.apache.commons.lang3.StringUtils.compareIgnoreCase(java.lang.String,java.lang.String)",
+ "public static int org.apache.commons.lang3.StringUtils.compareIgnoreCase(java.lang.String,java.lang.String,boolean)",
+ "public static byte[] org.apache.commons.lang3.StringUtils.getBytes(java.lang.String,java.nio.charset.Charset)",
+ "public static byte[] org.apache.commons.lang3.StringUtils.getBytes(java.lang.String,java.lang.String) throws java.io.UnsupportedEncodingException"
+ };
+ final Method[] methods = c.getMethods();
+
+ for (final Method m : methods) {
+ final String methodStr = m.toString();
+ if (m.getReturnType() == String.class || m.getReturnType() == String[].class) {
+ // Assume this is mutable and ensure the first parameter is not CharSequence.
+ // It may be String or it may be something else (String[], Object, Object[]) so
+ // don't actively test for that.
+ final Class<?>[] params = m.getParameterTypes();
+ if (params.length > 0 && (params[0] == CharSequence.class || params[0] == CharSequence[].class)) {
+ assertFalse(ArrayUtils.contains(excludeMethods, methodStr), "The method \"" + methodStr + "\" appears to be mutable in spirit and therefore must not accept a CharSequence");
+ }
+ } else {
+ // Assume this is immutable in spirit and ensure the first parameter is not String.
+ // As above, it may be something other than CharSequence.
+ final Class<?>[] params = m.getParameterTypes();
+ if (params.length > 0 && (params[0] == String.class || params[0] == String[].class)) {
+ assertTrue(ArrayUtils.contains(excludeMethods, methodStr),
+ "The method \"" + methodStr + "\" appears to be immutable in spirit and therefore must not accept a String");
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testSwapCase_String() {
+ assertNull(StringUtils.swapCase(null));
+ assertEquals("", StringUtils.swapCase(""));
+ assertEquals(" ", StringUtils.swapCase(" "));
+
+ assertEquals("i", WordUtils.swapCase("I"));
+ assertEquals("I", WordUtils.swapCase("i"));
+ assertEquals("I AM HERE 123", StringUtils.swapCase("i am here 123"));
+ assertEquals("i aM hERE 123", StringUtils.swapCase("I Am Here 123"));
+ assertEquals("I AM here 123", StringUtils.swapCase("i am HERE 123"));
+ assertEquals("i am here 123", StringUtils.swapCase("I AM HERE 123"));
+
+ final String test = "This String contains a TitleCase character: \u01C8";
+ final String expect = "tHIS sTRING CONTAINS A tITLEcASE CHARACTER: \u01C9";
+ assertEquals(expect, WordUtils.swapCase(test));
+ assertEquals(expect, StringUtils.swapCase(test));
+ }
+
+ @Test
+ public void testToCodePoints() {
+ final int orphanedHighSurrogate = 0xD801;
+ final int orphanedLowSurrogate = 0xDC00;
+ final int supplementary = 0x2070E;
+
+ final int[] codePoints = {'a', orphanedHighSurrogate, 'b', 'c', supplementary,
+ 'd', orphanedLowSurrogate, 'e'};
+ final String s = new String(codePoints, 0, codePoints.length);
+ assertArrayEquals(codePoints, StringUtils.toCodePoints(s));
+
+ assertNull(StringUtils.toCodePoints(null));
+ assertArrayEquals(ArrayUtils.EMPTY_INT_ARRAY, StringUtils.toCodePoints(""));
+ }
+
+ /**
+ * Tests {@link StringUtils#toEncodedString(byte[], Charset)}
+ *
+ * @see StringUtils#toEncodedString(byte[], Charset)
+ */
+ @Test
+ public void testToEncodedString() {
+ final String expectedString = "The quick brown fox jumps over the lazy dog.";
+ String encoding = SystemUtils.FILE_ENCODING;
+ byte[] expectedBytes = expectedString.getBytes(Charset.defaultCharset());
+ // sanity check start
+ assertArrayEquals(expectedBytes, expectedString.getBytes());
+ // sanity check end
+ assertEquals(expectedString, StringUtils.toEncodedString(expectedBytes, Charset.defaultCharset()));
+ assertEquals(expectedString, StringUtils.toEncodedString(expectedBytes, Charset.forName(encoding)));
+ encoding = "UTF-16";
+ expectedBytes = expectedString.getBytes(Charset.forName(encoding));
+ assertEquals(expectedString, StringUtils.toEncodedString(expectedBytes, Charset.forName(encoding)));
+ }
+
+ /**
+ * Tests {@link StringUtils#toString(byte[], String)}
+ *
+ * @throws java.io.UnsupportedEncodingException because the method under test max throw it
+ * @see StringUtils#toString(byte[], String)
+ */
+ @Test
+ public void testToString() throws UnsupportedEncodingException {
+ final String expectedString = "The quick brown fox jumps over the lazy dog.";
+ byte[] expectedBytes = expectedString.getBytes(Charset.defaultCharset());
+ // sanity check start
+ assertArrayEquals(expectedBytes, expectedString.getBytes());
+ // sanity check end
+ assertEquals(expectedString, StringUtils.toString(expectedBytes, null));
+ assertEquals(expectedString, StringUtils.toString(expectedBytes, SystemUtils.FILE_ENCODING));
+ final String encoding = "UTF-16";
+ expectedBytes = expectedString.getBytes(Charset.forName(encoding));
+ assertEquals(expectedString, StringUtils.toString(expectedBytes, encoding));
+ }
+
+ @Test
+ public void testTruncate_StringInt() {
+ assertNull(StringUtils.truncate(null, 12));
+ assertThrows(
+ IllegalArgumentException.class, () -> StringUtils.truncate(null, -1), "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class, () -> StringUtils.truncate(null, -10), "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate(null, Integer.MIN_VALUE),
+ "maxWith cannot be negative");
+ assertEquals("", StringUtils.truncate("", 10));
+ assertEquals("abc", StringUtils.truncate("abcdefghij", 3));
+ assertEquals("abcdef", StringUtils.truncate("abcdefghij", 6));
+ assertEquals("", StringUtils.truncate("abcdefghij", 0));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", -1),
+ "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", -100),
+ "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", Integer.MIN_VALUE),
+ "maxWith cannot be negative");
+ assertEquals("abcdefghij", StringUtils.truncate("abcdefghijklmno", 10));
+ assertEquals("abcdefghijklmno", StringUtils.truncate("abcdefghijklmno", Integer.MAX_VALUE));
+ assertEquals("abcde", StringUtils.truncate("abcdefghijklmno", 5));
+ assertEquals("abc", StringUtils.truncate("abcdefghijklmno", 3));
+ }
+
+ @Test
+ public void testTruncate_StringIntInt() {
+ assertNull(StringUtils.truncate(null, 0, 12));
+ assertThrows(
+ IllegalArgumentException.class, () -> StringUtils.truncate(null, -1, 0), "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate(null, -10, -4),
+ "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate(null, Integer.MIN_VALUE, Integer.MIN_VALUE),
+ "offset cannot be negative");
+ assertNull(StringUtils.truncate(null, 10, 12));
+ assertEquals("", StringUtils.truncate("", 0, 10));
+ assertEquals("", StringUtils.truncate("", 2, 10));
+ assertEquals("abc", StringUtils.truncate("abcdefghij", 0, 3));
+ assertEquals("fghij", StringUtils.truncate("abcdefghij", 5, 6));
+ assertEquals("", StringUtils.truncate("abcdefghij", 0, 0));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", 0, -1),
+ "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", 0, -10),
+ "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", 0, -100),
+ "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", 1, -100),
+ "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", 0, Integer.MIN_VALUE),
+ "maxWith cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", -1, 0),
+ "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", -10, 0),
+ "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", -100, 1),
+ "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", Integer.MIN_VALUE, 0),
+ "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", -1, -1),
+ "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", -10, -10),
+ "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", -100, -100),
+ "offset cannot be negative");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> StringUtils.truncate("abcdefghij", Integer.MIN_VALUE, Integer.MIN_VALUE),
+ "offset cannot be negative");
+ final String raspberry = "raspberry peach";
+ assertEquals("peach", StringUtils.truncate(raspberry, 10, 15));
+ assertEquals("abcdefghij", StringUtils.truncate("abcdefghijklmno", 0, 10));
+ assertEquals("abcdefghijklmno", StringUtils.truncate("abcdefghijklmno", 0, Integer.MAX_VALUE));
+ assertEquals("bcdefghijk", StringUtils.truncate("abcdefghijklmno", 1, 10));
+ assertEquals("cdefghijkl", StringUtils.truncate("abcdefghijklmno", 2, 10));
+ assertEquals("defghijklm", StringUtils.truncate("abcdefghijklmno", 3, 10));
+ assertEquals("efghijklmn", StringUtils.truncate("abcdefghijklmno", 4, 10));
+ assertEquals("fghijklmno", StringUtils.truncate("abcdefghijklmno", 5, 10));
+ assertEquals("fghij", StringUtils.truncate("abcdefghijklmno", 5, 5));
+ assertEquals("fgh", StringUtils.truncate("abcdefghijklmno", 5, 3));
+ assertEquals("klm", StringUtils.truncate("abcdefghijklmno", 10, 3));
+ assertEquals("klmno", StringUtils.truncate("abcdefghijklmno", 10, Integer.MAX_VALUE));
+ assertEquals("n", StringUtils.truncate("abcdefghijklmno", 13, 1));
+ assertEquals("no", StringUtils.truncate("abcdefghijklmno", 13, Integer.MAX_VALUE));
+ assertEquals("o", StringUtils.truncate("abcdefghijklmno", 14, 1));
+ assertEquals("o", StringUtils.truncate("abcdefghijklmno", 14, Integer.MAX_VALUE));
+ assertEquals("", StringUtils.truncate("abcdefghijklmno", 15, 1));
+ assertEquals("", StringUtils.truncate("abcdefghijklmno", 15, Integer.MAX_VALUE));
+ assertEquals("", StringUtils.truncate("abcdefghijklmno", Integer.MAX_VALUE, Integer.MAX_VALUE));
+ }
+
+ @Test
+ public void testUnCapitalize() {
+ assertNull(StringUtils.uncapitalize(null));
+
+ assertEquals(FOO_UNCAP, StringUtils.uncapitalize(FOO_CAP), "uncapitalize(String) failed");
+ assertEquals(FOO_UNCAP, StringUtils.uncapitalize(FOO_UNCAP), "uncapitalize(string) failed");
+ assertEquals("", StringUtils.uncapitalize(""), "uncapitalize(empty-string) failed");
+ assertEquals("x", StringUtils.uncapitalize("X"), "uncapitalize(single-char-string) failed");
+
+ // Examples from uncapitalize Javadoc
+ assertEquals("cat", StringUtils.uncapitalize("cat"));
+ assertEquals("cat", StringUtils.uncapitalize("Cat"));
+ assertEquals("cAT", StringUtils.uncapitalize("CAT"));
+ }
+
+ @Test
+ public void testUnescapeSurrogatePairs() {
+ assertEquals("\uD83D\uDE30", StringEscapeUtils.unescapeCsv("\uD83D\uDE30"));
+ // Examples from https://en.wikipedia.org/wiki/UTF-16
+ assertEquals("\uD800\uDC00", StringEscapeUtils.unescapeCsv("\uD800\uDC00"));
+ assertEquals("\uD834\uDD1E", StringEscapeUtils.unescapeCsv("\uD834\uDD1E"));
+ assertEquals("\uDBFF\uDFFD", StringEscapeUtils.unescapeCsv("\uDBFF\uDFFD"));
+ assertEquals("\uDBFF\uDFFD", StringEscapeUtils.unescapeHtml3("\uDBFF\uDFFD"));
+ assertEquals("\uDBFF\uDFFD", StringEscapeUtils.unescapeHtml4("\uDBFF\uDFFD"));
+ }
+
+ @Test
+ public void testUnwrap_StringChar() {
+ assertNull(StringUtils.unwrap(null, null));
+ assertNull(StringUtils.unwrap(null, CharUtils.NUL));
+ assertNull(StringUtils.unwrap(null, '1'));
+
+ assertEquals("abc", StringUtils.unwrap("abc", null));
+ assertEquals("a", StringUtils.unwrap("a", "a"));
+ assertEquals("", StringUtils.unwrap("aa", "a"));
+ assertEquals("abc", StringUtils.unwrap("\'abc\'", '\''));
+ assertEquals("abc", StringUtils.unwrap("AabcA", 'A'));
+ assertEquals("AabcA", StringUtils.unwrap("AAabcAA", 'A'));
+ assertEquals("abc", StringUtils.unwrap("abc", 'b'));
+ assertEquals("#A", StringUtils.unwrap("#A", '#'));
+ assertEquals("A#", StringUtils.unwrap("A#", '#'));
+ assertEquals("ABA", StringUtils.unwrap("AABAA", 'A'));
+ }
+
+ @Test
+ public void testUnwrap_StringString() {
+ assertNull(StringUtils.unwrap(null, null));
+ assertNull(StringUtils.unwrap(null, ""));
+ assertNull(StringUtils.unwrap(null, "1"));
+
+ assertEquals("abc", StringUtils.unwrap("abc", null));
+ assertEquals("abc", StringUtils.unwrap("abc", ""));
+ assertEquals("a", StringUtils.unwrap("a", "a"));
+ assertEquals("ababa", StringUtils.unwrap("ababa", "aba"));
+ assertEquals("", StringUtils.unwrap("aa", "a"));
+ assertEquals("abc", StringUtils.unwrap("\'abc\'", "\'"));
+ assertEquals("abc", StringUtils.unwrap("\"abc\"", "\""));
+ assertEquals("abc\"xyz", StringUtils.unwrap("\"abc\"xyz\"", "\""));
+ assertEquals("abc\"xyz\"", StringUtils.unwrap("\"abc\"xyz\"\"", "\""));
+ assertEquals("abc\'xyz\'", StringUtils.unwrap("\"abc\'xyz\'\"", "\""));
+ assertEquals("\"abc\'xyz\'\"", StringUtils.unwrap("AA\"abc\'xyz\'\"AA", "AA"));
+ assertEquals("\"abc\'xyz\'\"", StringUtils.unwrap("123\"abc\'xyz\'\"123", "123"));
+ assertEquals("AA\"abc\'xyz\'\"", StringUtils.unwrap("AA\"abc\'xyz\'\"", "AA"));
+ assertEquals("AA\"abc\'xyz\'\"AA", StringUtils.unwrap("AAA\"abc\'xyz\'\"AAA", "A"));
+ assertEquals("\"abc\'xyz\'\"AA", StringUtils.unwrap("\"abc\'xyz\'\"AA", "AA"));
+ }
+
+ @Test
+ public void testUpperCase() {
+ assertNull(StringUtils.upperCase(null));
+ assertNull(StringUtils.upperCase(null, Locale.ENGLISH));
+ assertEquals("FOO TEST THING", StringUtils.upperCase("fOo test THING"), "upperCase(String) failed");
+ assertEquals("", StringUtils.upperCase(""), "upperCase(empty-string) failed");
+ assertEquals("FOO TEST THING", StringUtils.upperCase("fOo test THING", Locale.ENGLISH),
+ "upperCase(String, Locale) failed");
+ assertEquals("", StringUtils.upperCase("", Locale.ENGLISH),
+ "upperCase(empty-string, Locale) failed");
+ }
+
+ @Test
+ public void testWrap_StringChar() {
+ assertNull(StringUtils.wrap(null, CharUtils.NUL));
+ assertNull(StringUtils.wrap(null, '1'));
+
+ assertEquals("", StringUtils.wrap("", CharUtils.NUL));
+ assertEquals("xabx", StringUtils.wrap("ab", 'x'));
+ assertEquals("\"ab\"", StringUtils.wrap("ab", '\"'));
+ assertEquals("\"\"ab\"\"", StringUtils.wrap("\"ab\"", '\"'));
+ assertEquals("'ab'", StringUtils.wrap("ab", '\''));
+ assertEquals("''abcd''", StringUtils.wrap("'abcd'", '\''));
+ assertEquals("'\"abcd\"'", StringUtils.wrap("\"abcd\"", '\''));
+ assertEquals("\"'abcd'\"", StringUtils.wrap("'abcd'", '\"'));
+ }
+
+ @Test
+ public void testWrap_StringString() {
+ assertNull(StringUtils.wrap(null, null));
+ assertNull(StringUtils.wrap(null, ""));
+ assertNull(StringUtils.wrap(null, "1"));
+
+ assertNull(StringUtils.wrap(null, null));
+ assertEquals("", StringUtils.wrap("", ""));
+ assertEquals("ab", StringUtils.wrap("ab", null));
+ assertEquals("xabx", StringUtils.wrap("ab", "x"));
+ assertEquals("\"ab\"", StringUtils.wrap("ab", "\""));
+ assertEquals("\"\"ab\"\"", StringUtils.wrap("\"ab\"", "\""));
+ assertEquals("'ab'", StringUtils.wrap("ab", "'"));
+ assertEquals("''abcd''", StringUtils.wrap("'abcd'", "'"));
+ assertEquals("'\"abcd\"'", StringUtils.wrap("\"abcd\"", "'"));
+ assertEquals("\"'abcd'\"", StringUtils.wrap("'abcd'", "\""));
+ }
+
+ @Test
+ public void testWrapIfMissing_StringChar() {
+ assertNull(StringUtils.wrapIfMissing(null, CharUtils.NUL));
+ assertNull(StringUtils.wrapIfMissing(null, '1'));
+
+ assertEquals("", StringUtils.wrapIfMissing("", CharUtils.NUL));
+ assertEquals("xabx", StringUtils.wrapIfMissing("ab", 'x'));
+ assertEquals("\"ab\"", StringUtils.wrapIfMissing("ab", '\"'));
+ assertEquals("\"ab\"", StringUtils.wrapIfMissing("\"ab\"", '\"'));
+ assertEquals("'ab'", StringUtils.wrapIfMissing("ab", '\''));
+ assertEquals("'abcd'", StringUtils.wrapIfMissing("'abcd'", '\''));
+ assertEquals("'\"abcd\"'", StringUtils.wrapIfMissing("\"abcd\"", '\''));
+ assertEquals("\"'abcd'\"", StringUtils.wrapIfMissing("'abcd'", '\"'));
+ assertEquals("/x/", StringUtils.wrapIfMissing("x", '/'));
+ assertEquals("/x/y/z/", StringUtils.wrapIfMissing("x/y/z", '/'));
+ assertEquals("/x/y/z/", StringUtils.wrapIfMissing("/x/y/z", '/'));
+ assertEquals("/x/y/z/", StringUtils.wrapIfMissing("x/y/z/", '/'));
+
+ assertSame("/", StringUtils.wrapIfMissing("/", '/'));
+ assertSame("/x/", StringUtils.wrapIfMissing("/x/", '/'));
+ }
+
+ @Test
+ public void testWrapIfMissing_StringString() {
+ assertNull(StringUtils.wrapIfMissing(null, "\0"));
+ assertNull(StringUtils.wrapIfMissing(null, "1"));
+
+ assertEquals("", StringUtils.wrapIfMissing("", "\0"));
+ assertEquals("xabx", StringUtils.wrapIfMissing("ab", "x"));
+ assertEquals("\"ab\"", StringUtils.wrapIfMissing("ab", "\""));
+ assertEquals("\"ab\"", StringUtils.wrapIfMissing("\"ab\"", "\""));
+ assertEquals("'ab'", StringUtils.wrapIfMissing("ab", "\'"));
+ assertEquals("'abcd'", StringUtils.wrapIfMissing("'abcd'", "\'"));
+ assertEquals("'\"abcd\"'", StringUtils.wrapIfMissing("\"abcd\"", "\'"));
+ assertEquals("\"'abcd'\"", StringUtils.wrapIfMissing("'abcd'", "\""));
+ assertEquals("/x/", StringUtils.wrapIfMissing("x", "/"));
+ assertEquals("/x/y/z/", StringUtils.wrapIfMissing("x/y/z", "/"));
+ assertEquals("/x/y/z/", StringUtils.wrapIfMissing("/x/y/z", "/"));
+ assertEquals("/x/y/z/", StringUtils.wrapIfMissing("x/y/z/", "/"));
+ assertEquals("/", StringUtils.wrapIfMissing("/", "/"));
+ assertEquals("ab/ab", StringUtils.wrapIfMissing("/", "ab"));
+
+ assertSame("ab/ab", StringUtils.wrapIfMissing("ab/ab", "ab"));
+ assertSame("//x//", StringUtils.wrapIfMissing("//x//", "//"));
+ }
+
+ @Test
+ public void testToRootLowerCase() {
+ assertNull(StringUtils.toRootLowerCase(null));
+ assertEquals("a", StringUtils.toRootLowerCase("A"));
+ assertEquals("a", StringUtils.toRootLowerCase("a"));
+ final Locale TURKISH = Locale.forLanguageTag("tr");
+ // Sanity checks:
+ assertNotEquals("title", "TITLE".toLowerCase(TURKISH));
+ assertEquals("title", "TITLE".toLowerCase(Locale.ROOT));
+ assertEquals("title", StringUtils.toRootLowerCase("TITLE"));
+ // Make sure we are not using the default Locale:
+ final Locale defaultLocale = Locale.getDefault();
+ try {
+ Locale.setDefault(TURKISH);
+ assertEquals("title", StringUtils.toRootLowerCase("TITLE"));
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ @Test
+ public void testToRootUpperCase() {
+ assertNull(StringUtils.toRootUpperCase(null));
+ assertEquals("A", StringUtils.toRootUpperCase("a"));
+ assertEquals("A", StringUtils.toRootUpperCase("A"));
+ final Locale TURKISH = Locale.forLanguageTag("tr");
+ // Sanity checks:
+ assertNotEquals("TITLE", "title".toUpperCase(TURKISH));
+ assertEquals("TITLE", "title".toUpperCase(Locale.ROOT));
+ assertEquals("TITLE", StringUtils.toRootUpperCase("title"));
+ // Make sure we are not using the default Locale:
+ final Locale defaultLocale = Locale.getDefault();
+ try {
+ Locale.setDefault(TURKISH);
+ assertEquals("TITLE", StringUtils.toRootUpperCase("title"));
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ @Test
+ public void testGeorgianSample() {
+ final char[] arrayI = {
+ //Latin Small Letter dotless I
+ (char) 0x0131,
+ //Greek Capital Letter Theta
+ (char) 0x03F4
+ };
+ final char[] arrayJ = {
+ //Latin Capital Letter I with dot above
+ (char) 0x0130,
+ //Greek Theta Symbol
+ (char) 0x03D1
+ };
+ for (final char i : arrayI) {
+ for (final char j : arrayJ) {
+ final String si = String.valueOf(i);
+ final String sj = String.valueOf(j);
+ final boolean res1 = si.equalsIgnoreCase(sj);
+ final CharSequence ci = new StringBuilder(si);
+ final CharSequence cj = new StringBuilder(sj);
+ boolean res2 = StringUtils.startsWithIgnoreCase(ci, cj);
+ assertEquals(res1, res2, "si : " + si + " sj : " + sj);
+ res2 = StringUtils.endsWithIgnoreCase(ci, cj);
+ assertEquals(res1, res2, "si : " + si + " sj : " + sj);
+ res2 = StringUtils.compareIgnoreCase(ci.toString(), cj.toString()) == 0;
+ assertEquals(res1, res2, "si : " + si + " sj : " + sj);
+ res2 = StringUtils.indexOfIgnoreCase(ci.toString(), cj.toString()) == 0;
+ assertEquals(res1, res2, "si : " + si + " sj : " + sj);
+ res2 = StringUtils.lastIndexOfIgnoreCase(ci.toString(), cj.toString()) == 0;
+ assertEquals(res1, res2, "si : " + si + " sj : " + sj);
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsTrimStripTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsTrimStripTest.java
new file mode 100644
index 000000000..79bff3f1e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsTrimStripTest.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.StringUtils} - Trim/Strip methods
+ */
+public class StringUtilsTrimStripTest extends AbstractLangTest {
+ private static final String FOO = "foo";
+
+ @Test
+ public void testTrim() {
+ assertEquals(FOO, StringUtils.trim(FOO + " "));
+ assertEquals(FOO, StringUtils.trim(" " + FOO + " "));
+ assertEquals(FOO, StringUtils.trim(" " + FOO));
+ assertEquals(FOO, StringUtils.trim(FOO + ""));
+ assertEquals("", StringUtils.trim(" \t\r\n\b "));
+ assertEquals("", StringUtils.trim(StringUtilsTest.TRIMMABLE));
+ assertEquals(StringUtilsTest.NON_TRIMMABLE, StringUtils.trim(StringUtilsTest.NON_TRIMMABLE));
+ assertEquals("", StringUtils.trim(""));
+ assertNull(StringUtils.trim(null));
+ }
+
+ @Test
+ public void testTrimToNull() {
+ assertEquals(FOO, StringUtils.trimToNull(FOO + " "));
+ assertEquals(FOO, StringUtils.trimToNull(" " + FOO + " "));
+ assertEquals(FOO, StringUtils.trimToNull(" " + FOO));
+ assertEquals(FOO, StringUtils.trimToNull(FOO + ""));
+ assertNull(StringUtils.trimToNull(" \t\r\n\b "));
+ assertNull(StringUtils.trimToNull(StringUtilsTest.TRIMMABLE));
+ assertEquals(StringUtilsTest.NON_TRIMMABLE, StringUtils.trimToNull(StringUtilsTest.NON_TRIMMABLE));
+ assertNull(StringUtils.trimToNull(""));
+ assertNull(StringUtils.trimToNull(null));
+ }
+
+ @Test
+ public void testTrimToEmpty() {
+ assertEquals(FOO, StringUtils.trimToEmpty(FOO + " "));
+ assertEquals(FOO, StringUtils.trimToEmpty(" " + FOO + " "));
+ assertEquals(FOO, StringUtils.trimToEmpty(" " + FOO));
+ assertEquals(FOO, StringUtils.trimToEmpty(FOO + ""));
+ assertEquals("", StringUtils.trimToEmpty(" \t\r\n\b "));
+ assertEquals("", StringUtils.trimToEmpty(StringUtilsTest.TRIMMABLE));
+ assertEquals(StringUtilsTest.NON_TRIMMABLE, StringUtils.trimToEmpty(StringUtilsTest.NON_TRIMMABLE));
+ assertEquals("", StringUtils.trimToEmpty(""));
+ assertEquals("", StringUtils.trimToEmpty(null));
+ }
+
+ @Test
+ public void testStrip_String() {
+ assertNull(StringUtils.strip(null));
+ assertEquals("", StringUtils.strip(""));
+ assertEquals("", StringUtils.strip(" "));
+ assertEquals("abc", StringUtils.strip(" abc "));
+ assertEquals(StringUtilsTest.NON_WHITESPACE,
+ StringUtils.strip(StringUtilsTest.WHITESPACE + StringUtilsTest.NON_WHITESPACE + StringUtilsTest.WHITESPACE));
+ }
+
+ @Test
+ public void testStripToNull_String() {
+ assertNull(StringUtils.stripToNull(null));
+ assertNull(StringUtils.stripToNull(""));
+ assertNull(StringUtils.stripToNull(" "));
+ assertNull(StringUtils.stripToNull(StringUtilsTest.WHITESPACE));
+ assertEquals("ab c", StringUtils.stripToNull(" ab c "));
+ assertEquals(StringUtilsTest.NON_WHITESPACE,
+ StringUtils.stripToNull(StringUtilsTest.WHITESPACE + StringUtilsTest.NON_WHITESPACE + StringUtilsTest.WHITESPACE));
+ }
+
+ @Test
+ public void testStripToEmpty_String() {
+ assertEquals("", StringUtils.stripToEmpty(null));
+ assertEquals("", StringUtils.stripToEmpty(""));
+ assertEquals("", StringUtils.stripToEmpty(" "));
+ assertEquals("", StringUtils.stripToEmpty(StringUtilsTest.WHITESPACE));
+ assertEquals("ab c", StringUtils.stripToEmpty(" ab c "));
+ assertEquals(StringUtilsTest.NON_WHITESPACE,
+ StringUtils.stripToEmpty(StringUtilsTest.WHITESPACE + StringUtilsTest.NON_WHITESPACE + StringUtilsTest.WHITESPACE));
+ }
+
+ @Test
+ public void testStrip_StringString() {
+ // null strip
+ assertNull(StringUtils.strip(null, null));
+ assertEquals("", StringUtils.strip("", null));
+ assertEquals("", StringUtils.strip(" ", null));
+ assertEquals("abc", StringUtils.strip(" abc ", null));
+ assertEquals(StringUtilsTest.NON_WHITESPACE,
+ StringUtils.strip(StringUtilsTest.WHITESPACE + StringUtilsTest.NON_WHITESPACE + StringUtilsTest.WHITESPACE, null));
+
+ // "" strip
+ assertNull(StringUtils.strip(null, ""));
+ assertEquals("", StringUtils.strip("", ""));
+ assertEquals(" ", StringUtils.strip(" ", ""));
+ assertEquals(" abc ", StringUtils.strip(" abc ", ""));
+ assertEquals(StringUtilsTest.WHITESPACE, StringUtils.strip(StringUtilsTest.WHITESPACE, ""));
+
+ // " " strip
+ assertNull(StringUtils.strip(null, " "));
+ assertEquals("", StringUtils.strip("", " "));
+ assertEquals("", StringUtils.strip(" ", " "));
+ assertEquals("abc", StringUtils.strip(" abc ", " "));
+
+ // "ab" strip
+ assertNull(StringUtils.strip(null, "ab"));
+ assertEquals("", StringUtils.strip("", "ab"));
+ assertEquals(" ", StringUtils.strip(" ", "ab"));
+ assertEquals(" abc ", StringUtils.strip(" abc ", "ab"));
+ assertEquals("c", StringUtils.strip("abcabab", "ab"));
+ assertEquals(StringUtilsTest.WHITESPACE, StringUtils.strip(StringUtilsTest.WHITESPACE, ""));
+ }
+
+ @Test
+ public void testStripStart_StringString() {
+ // null stripStart
+ assertNull(StringUtils.stripStart(null, null));
+ assertEquals("", StringUtils.stripStart("", null));
+ assertEquals("", StringUtils.stripStart(" ", null));
+ assertEquals("abc ", StringUtils.stripStart(" abc ", null));
+ assertEquals(StringUtilsTest.NON_WHITESPACE + StringUtilsTest.WHITESPACE,
+ StringUtils.stripStart(StringUtilsTest.WHITESPACE + StringUtilsTest.NON_WHITESPACE + StringUtilsTest.WHITESPACE, null));
+
+ // "" stripStart
+ assertNull(StringUtils.stripStart(null, ""));
+ assertEquals("", StringUtils.stripStart("", ""));
+ assertEquals(" ", StringUtils.stripStart(" ", ""));
+ assertEquals(" abc ", StringUtils.stripStart(" abc ", ""));
+ assertEquals(StringUtilsTest.WHITESPACE, StringUtils.stripStart(StringUtilsTest.WHITESPACE, ""));
+
+ // " " stripStart
+ assertNull(StringUtils.stripStart(null, " "));
+ assertEquals("", StringUtils.stripStart("", " "));
+ assertEquals("", StringUtils.stripStart(" ", " "));
+ assertEquals("abc ", StringUtils.stripStart(" abc ", " "));
+
+ // "ab" stripStart
+ assertNull(StringUtils.stripStart(null, "ab"));
+ assertEquals("", StringUtils.stripStart("", "ab"));
+ assertEquals(" ", StringUtils.stripStart(" ", "ab"));
+ assertEquals(" abc ", StringUtils.stripStart(" abc ", "ab"));
+ assertEquals("cabab", StringUtils.stripStart("abcabab", "ab"));
+ assertEquals(StringUtilsTest.WHITESPACE, StringUtils.stripStart(StringUtilsTest.WHITESPACE, ""));
+ }
+
+ @Test
+ public void testStripEnd_StringString() {
+ // null stripEnd
+ assertNull(StringUtils.stripEnd(null, null));
+ assertEquals("", StringUtils.stripEnd("", null));
+ assertEquals("", StringUtils.stripEnd(" ", null));
+ assertEquals(" abc", StringUtils.stripEnd(" abc ", null));
+ assertEquals(StringUtilsTest.WHITESPACE + StringUtilsTest.NON_WHITESPACE,
+ StringUtils.stripEnd(StringUtilsTest.WHITESPACE + StringUtilsTest.NON_WHITESPACE + StringUtilsTest.WHITESPACE, null));
+
+ // "" stripEnd
+ assertNull(StringUtils.stripEnd(null, ""));
+ assertEquals("", StringUtils.stripEnd("", ""));
+ assertEquals(" ", StringUtils.stripEnd(" ", ""));
+ assertEquals(" abc ", StringUtils.stripEnd(" abc ", ""));
+ assertEquals(StringUtilsTest.WHITESPACE, StringUtils.stripEnd(StringUtilsTest.WHITESPACE, ""));
+
+ // " " stripEnd
+ assertNull(StringUtils.stripEnd(null, " "));
+ assertEquals("", StringUtils.stripEnd("", " "));
+ assertEquals("", StringUtils.stripEnd(" ", " "));
+ assertEquals(" abc", StringUtils.stripEnd(" abc ", " "));
+
+ // "ab" stripEnd
+ assertNull(StringUtils.stripEnd(null, "ab"));
+ assertEquals("", StringUtils.stripEnd("", "ab"));
+ assertEquals(" ", StringUtils.stripEnd(" ", "ab"));
+ assertEquals(" abc ", StringUtils.stripEnd(" abc ", "ab"));
+ assertEquals("abc", StringUtils.stripEnd("abcabab", "ab"));
+ assertEquals(StringUtilsTest.WHITESPACE, StringUtils.stripEnd(StringUtilsTest.WHITESPACE, ""));
+ }
+
+ @Test
+ public void testStripAll() {
+ // test stripAll method, merely an array version of the above strip
+ final String[] empty = {};
+ final String[] fooSpace = { " "+FOO+" ", " "+FOO, FOO+" " };
+ final String[] fooDots = { ".."+FOO+"..", ".."+FOO, FOO+".." };
+ final String[] foo = { FOO, FOO, FOO };
+
+ assertNull(StringUtils.stripAll((String[]) null));
+ // Additional varargs tests
+ assertArrayEquals(empty, StringUtils.stripAll()); // empty array
+ assertArrayEquals(new String[]{null}, StringUtils.stripAll((String) null)); // == new String[]{null}
+
+ assertArrayEquals(empty, StringUtils.stripAll(empty));
+ assertArrayEquals(foo, StringUtils.stripAll(fooSpace));
+
+ assertNull(StringUtils.stripAll(null, null));
+ assertArrayEquals(foo, StringUtils.stripAll(fooSpace, null));
+ assertArrayEquals(foo, StringUtils.stripAll(fooDots, "."));
+ }
+
+ @Test
+ public void testStripAccents() {
+ final String cue = "\u00C7\u00FA\u00EA";
+ assertEquals("Cue", StringUtils.stripAccents(cue), "Failed to strip accents from " + cue);
+
+ final String lots = "\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C7\u00C8\u00C9" +
+ "\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D1\u00D2\u00D3" +
+ "\u00D4\u00D5\u00D6\u00D9\u00DA\u00DB\u00DC\u00DD";
+ assertEquals("AAAAAACEEEEIIIINOOOOOUUUUY",
+ StringUtils.stripAccents(lots),
+ "Failed to strip accents from " + lots);
+
+ assertNull(StringUtils.stripAccents(null), "Failed null safety");
+ assertEquals("", StringUtils.stripAccents(""), "Failed empty String");
+ assertEquals("control", StringUtils.stripAccents("control"), "Failed to handle non-accented text");
+ assertEquals("eclair", StringUtils.stripAccents("\u00E9clair"), "Failed to handle easy example");
+ assertEquals("ALOSZZCN aloszzcn", StringUtils.stripAccents("\u0104\u0141\u00D3\u015A\u017B\u0179\u0106\u0143 "
+ + "\u0105\u0142\u00F3\u015B\u017C\u017A\u0107\u0144"));
+ }
+
+ @Test
+ @Disabled
+ public void testStripAccents_Korean() {
+ // LANG-1655
+ final String input = "잊지마 넌 흐린 어둠사이 왼손으로 그린 별 하나";
+ assertEquals(input, StringUtils.stripAccents(input), "Failed to handle non-accented text");
+
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsValueOfTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsValueOfTest.java
new file mode 100644
index 000000000..1f7dd7af8
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/StringUtilsValueOfTest.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;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link StringUtils}'s valueOf() methods.
+ *
+ * @since 3.9
+ */
+public class StringUtilsValueOfTest extends AbstractLangTest {
+
+ @Test
+ public void testValueOfChar() {
+ Assertions.assertEquals("ABC", StringUtils.valueOf(new char[] {'A', 'B', 'C' }));
+ }
+
+ @Test
+ public void testValueOfCharEmpty() {
+ Assertions.assertEquals(StringUtils.EMPTY, StringUtils.valueOf(ArrayUtils.EMPTY_CHAR_ARRAY));
+ }
+
+ @Test
+ public void testValueOfCharNull() {
+ Assertions.assertNull(StringUtils.valueOf(null));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/Supplementary.java b/src/test/java/org/apache/commons/lang3/Supplementary.java
new file mode 100644
index 000000000..92310b187
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/Supplementary.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;
+
+/**
+ * Supplementary character test fixtures.
+ *
+ * See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+public class Supplementary {
+
+ /**
+ * Supplementary character U+20000 See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ static final String CharU20000 = "\uD840\uDC00";
+
+ /**
+ * Supplementary character U+20001 See https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ static final String CharU20001 = "\uD840\uDC01";
+
+ /**
+ * Incomplete supplementary character U+20000, high surrogate only. See
+ * https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ static final String CharUSuppCharHigh = "\uDC00";
+
+ /**
+ * Incomplete supplementary character U+20000, low surrogate only. See
+ * https://www.oracle.com/technical-resources/articles/javase/supplementary.html
+ */
+ static final String CharUSuppCharLow = "\uD840";
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/SystemPropertiesTest.java b/src/test/java/org/apache/commons/lang3/SystemPropertiesTest.java
new file mode 100644
index 000000000..46b282995
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/SystemPropertiesTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import org.junit.jupiter.api.Test;
+
+public class SystemPropertiesTest {
+
+ private boolean isJava11OrGreater() {
+ return SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_11);
+ }
+
+ @Test
+ public void testGetAwtToolkit() {
+ assertDoesNotThrow(SystemProperties::getAwtToolkit);
+ }
+
+ @Test
+ public void testGetFileEncoding() {
+ assertNotNull(SystemProperties.getFileEncoding());
+ }
+
+ @Test
+ public void testGetFileSeparator() {
+ assertNotNull(SystemProperties.getFileSeparator());
+ }
+
+ @Test
+ public void testGetJavaAwtFonts() {
+ assertNull(SystemProperties.getJavaAwtFonts());
+ }
+
+ @Test
+ public void testGetJavaAwtGraphicsenv() {
+ assertDoesNotThrow(SystemProperties::getJavaAwtGraphicsenv);
+ }
+
+ @Test
+ public void testGetJavaAwtHeadless() {
+ assertNull(SystemProperties.getJavaAwtHeadless());
+ }
+
+ @Test
+ public void testGetJavaAwtPrinterjob() {
+ assertDoesNotThrow(SystemProperties::getJavaAwtPrinterjob);
+ }
+
+ @Test
+ public void testGetJavaClassPath() {
+ assertNotNull(SystemProperties.getJavaClassPath());
+ }
+
+ @Test
+ public void testGetJavaClassVersion() {
+ assertNotNull(SystemProperties.getJavaClassVersion());
+ }
+
+ @Test
+ public void testGetJavaCompiler() {
+ if (SystemUtils.IS_JAVA_14) {
+ // Not in Java 11
+ assertNotNull(SystemProperties.getJavaCompiler());
+ }
+ }
+
+ @Test
+ public void testGetJavaEndorsedDirs() {
+ if (isJava11OrGreater()) {
+ // Not in Java 11
+ assertNull(SystemProperties.getJavaEndorsedDirs());
+ } else {
+ assertNotNull(SystemProperties.getJavaExtDirs());
+ }
+ }
+
+ @Test
+ public void testGetJavaExtDirs() {
+ if (isJava11OrGreater()) {
+ // Not in Java 11
+ assertNull(SystemProperties.getJavaExtDirs());
+ } else {
+ assertNotNull(SystemProperties.getJavaExtDirs());
+ }
+ }
+
+ @Test
+ public void testGetJavaHome() {
+ assertNotNull(SystemProperties.getJavaHome());
+ }
+
+ @Test
+ public void testGetJavaIoTmpdir() {
+ assertNotNull(SystemProperties.getJavaIoTmpdir());
+ }
+
+ @Test
+ public void testGetJavaLibraryPath() {
+ assertNotNull(SystemProperties.getJavaLibraryPath());
+ }
+
+ @Test
+ public void testGetJavaRuntimeName() {
+ assertNotNull(SystemProperties.getJavaRuntimeName());
+ }
+
+ @Test
+ public void testGetJavaRuntimeVersion() {
+ assertNotNull(SystemProperties.getJavaRuntimeVersion());
+ }
+
+ @Test
+ public void testGetJavaSpecificationName() {
+ assertNotNull(SystemProperties.getJavaSpecificationName());
+ }
+
+ @Test
+ public void testGetJavaSpecificationVendor() {
+ assertNotNull(SystemProperties.getJavaSpecificationVendor());
+ }
+
+ @Test
+ public void testGetJavaSpecificationVersion() {
+ assertNotNull(SystemProperties.getJavaSpecificationVersion());
+ }
+
+ @Test
+ public void testGetJavaUtilPrefsPreferencesFactory() {
+ assertNull(SystemProperties.getJavaUtilPrefsPreferencesFactory());
+ }
+
+ @Test
+ public void testGetJavaVendor() {
+ assertNotNull(SystemProperties.getJavaVendor());
+ }
+
+ @Test
+ public void testGetJavaVendorUrl() {
+ assertNotNull(SystemProperties.getJavaVendorUrl());
+ }
+
+ @Test
+ public void testGetJavaVersion() {
+ assertNotNull(SystemProperties.getJavaVersion());
+ }
+
+ @Test
+ public void testGetJavaVmInfo() {
+ assertNotNull(SystemProperties.getJavaVmInfo());
+ }
+
+ @Test
+ public void testGetJavaVmName() {
+ assertNotNull(SystemProperties.getJavaVmName());
+ }
+
+ @Test
+ public void testGetJavaVmSpecificationName() {
+ assertNotNull(SystemProperties.getJavaVmSpecificationName());
+ }
+
+ @Test
+ public void testGetJavaVmSpecificationVendor() {
+ assertNotNull(SystemProperties.getJavaVmSpecificationVendor());
+ }
+
+ @Test
+ public void testGetJavaVmSpecificationVersion() {
+ assertNotNull(SystemProperties.getJavaVmSpecificationVersion());
+ }
+
+ @Test
+ public void testGetJavaVmVendor() {
+ assertNotNull(SystemProperties.getJavaVmVendor());
+ }
+
+ @Test
+ public void testGetJavaVmVersion() {
+ assertNotNull(SystemProperties.getJavaVmVersion());
+ }
+
+ @Test
+ public void testGetLineSeparator() {
+ assertNotNull(SystemProperties.getLineSeparator());
+ }
+
+ @Test
+ public void testGetOsArch() {
+ assertNotNull(SystemProperties.getOsArch());
+ }
+
+ @Test
+ public void testGetOsName() {
+ assertNotNull(SystemProperties.getOsName());
+ }
+
+ @Test
+ public void testGetOsVersion() {
+ assertNotNull(SystemProperties.getOsVersion());
+ }
+
+ @Test
+ public void testGetPathSeparator() {
+ assertNotNull(SystemProperties.getPathSeparator());
+ }
+
+ @Test
+ public void testGetUserCountry() {
+ assertDoesNotThrow(SystemProperties::getUserCountry);
+ }
+
+ @Test
+ public void testGetUserDir() {
+ assertNotNull(SystemProperties.getUserDir());
+ }
+
+ @Test
+ public void testGetUserHome() {
+ assertNotNull(SystemProperties.getUserHome());
+ }
+
+ @Test
+ public void testGetUserLanguage() {
+ assertNotNull(SystemProperties.getUserLanguage());
+ }
+
+ @Test
+ public void testGetUserName() {
+ assertNotNull(SystemProperties.getUserName());
+ }
+
+ @Test
+ public void testGetUserTimezone() {
+ assertDoesNotThrow(SystemProperties::getUserTimezone);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/SystemUtilsTest.java b/src/test/java/org/apache/commons/lang3/SystemUtilsTest.java
new file mode 100644
index 000000000..753de4dab
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/SystemUtilsTest.java
@@ -0,0 +1,885 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 static org.apache.commons.lang3.JavaVersion.JAVA_10;
+import static org.apache.commons.lang3.JavaVersion.JAVA_11;
+import static org.apache.commons.lang3.JavaVersion.JAVA_12;
+import static org.apache.commons.lang3.JavaVersion.JAVA_13;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_1;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_2;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_3;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_4;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_5;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_6;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_7;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.Locale;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.SystemUtils}.
+ *
+ * Only limited testing can be performed.
+ */
+public class SystemUtilsTest extends AbstractLangTest {
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void test_IS_JAVA() {
+ final String javaVersion = SystemUtils.JAVA_VERSION;
+ if (javaVersion == null) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("1.8")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertTrue(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("9")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertTrue(SystemUtils.IS_JAVA_1_9);
+ assertTrue(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("10")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertTrue(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("11")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertTrue(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("12")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertTrue(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("13")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertTrue(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("14")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertTrue(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("15")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertTrue(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("16")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertTrue(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("17")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertTrue(SystemUtils.IS_JAVA_17);
+ assertFalse(SystemUtils.IS_JAVA_18);
+ } else if (javaVersion.startsWith("18")) {
+ assertFalse(SystemUtils.IS_JAVA_1_1);
+ assertFalse(SystemUtils.IS_JAVA_1_2);
+ assertFalse(SystemUtils.IS_JAVA_1_3);
+ assertFalse(SystemUtils.IS_JAVA_1_4);
+ assertFalse(SystemUtils.IS_JAVA_1_5);
+ assertFalse(SystemUtils.IS_JAVA_1_6);
+ assertFalse(SystemUtils.IS_JAVA_1_7);
+ assertFalse(SystemUtils.IS_JAVA_1_8);
+ assertFalse(SystemUtils.IS_JAVA_1_9);
+ assertFalse(SystemUtils.IS_JAVA_9);
+ assertFalse(SystemUtils.IS_JAVA_10);
+ assertFalse(SystemUtils.IS_JAVA_11);
+ assertFalse(SystemUtils.IS_JAVA_12);
+ assertFalse(SystemUtils.IS_JAVA_13);
+ assertFalse(SystemUtils.IS_JAVA_14);
+ assertFalse(SystemUtils.IS_JAVA_15);
+ assertFalse(SystemUtils.IS_JAVA_16);
+ assertFalse(SystemUtils.IS_JAVA_17);
+ assertTrue(SystemUtils.IS_JAVA_18);
+ } else {
+ System.out.println("Can't test IS_JAVA value: " + javaVersion);
+ }
+ }
+
+ @Test
+ public void test_IS_OS() {
+ final String osName = System.getProperty("os.name");
+ if (osName == null) {
+ assertFalse(SystemUtils.IS_OS_WINDOWS);
+ assertFalse(SystemUtils.IS_OS_UNIX);
+ assertFalse(SystemUtils.IS_OS_SOLARIS);
+ assertFalse(SystemUtils.IS_OS_LINUX);
+ assertFalse(SystemUtils.IS_OS_MAC_OSX);
+ } else if (osName.startsWith("Windows")) {
+ assertFalse(SystemUtils.IS_OS_UNIX);
+ assertTrue(SystemUtils.IS_OS_WINDOWS);
+ } else if (osName.startsWith("Solaris")) {
+ assertTrue(SystemUtils.IS_OS_SOLARIS);
+ assertTrue(SystemUtils.IS_OS_UNIX);
+ assertFalse(SystemUtils.IS_OS_WINDOWS);
+ } else if (osName.toLowerCase(Locale.ENGLISH).startsWith("linux")) {
+ assertTrue(SystemUtils.IS_OS_LINUX);
+ assertTrue(SystemUtils.IS_OS_UNIX);
+ assertFalse(SystemUtils.IS_OS_WINDOWS);
+ } else if (osName.startsWith("Mac OS X")) {
+ assertTrue(SystemUtils.IS_OS_MAC_OSX);
+ assertTrue(SystemUtils.IS_OS_UNIX);
+ assertFalse(SystemUtils.IS_OS_WINDOWS);
+ } else if (osName.startsWith("OS/2")) {
+ assertTrue(SystemUtils.IS_OS_OS2);
+ assertFalse(SystemUtils.IS_OS_UNIX);
+ assertFalse(SystemUtils.IS_OS_WINDOWS);
+ } else if (osName.startsWith("SunOS")) {
+ assertTrue(SystemUtils.IS_OS_SUN_OS);
+ assertTrue(SystemUtils.IS_OS_UNIX);
+ assertFalse(SystemUtils.IS_OS_WINDOWS);
+ } else if (osName.startsWith("FreeBSD")) {
+ assertTrue(SystemUtils.IS_OS_FREE_BSD);
+ assertTrue(SystemUtils.IS_OS_UNIX);
+ assertFalse(SystemUtils.IS_OS_WINDOWS);
+ } else {
+ System.out.println("Can't test IS_OS value: " + osName);
+ }
+ }
+
+ @Test
+ public void test_IS_zOS() {
+ final String osName = System.getProperty("os.name");
+ if (osName == null) {
+ assertFalse(SystemUtils.IS_OS_ZOS);
+ } else if (osName.contains("z/OS")) {
+ assertFalse(SystemUtils.IS_OS_WINDOWS);
+ assertTrue(SystemUtils.IS_OS_ZOS);
+ }
+ }
+
+ /**
+ * Assumes no security manager exists.
+ */
+ @Test
+ public void test_USER_NAME() {
+ assertEquals(System.getProperty("user.name"), SystemUtils.USER_NAME);
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new SystemUtils());
+ final Constructor<?>[] cons = SystemUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(SystemUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(SystemUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testGetEnvironmentVariableAbsent() {
+ final String name = "THIS_ENV_VAR_SHOULD_NOT_EXIST_FOR_THIS_TEST_TO_PASS";
+ final String expected = System.getenv(name);
+ assertNull(expected);
+ final String value = SystemUtils.getEnvironmentVariable(name, "DEFAULT");
+ assertEquals("DEFAULT", value);
+ }
+
+ @Test
+ public void testGetEnvironmentVariablePresent() {
+ final String name = "PATH";
+ final String expected = System.getenv(name);
+ final String value = SystemUtils.getEnvironmentVariable(name, null);
+ assertEquals(expected, value);
+ }
+
+ @Test
+ public void testGetHostName() {
+ final String hostName = SystemUtils.getHostName();
+ final String expected = SystemUtils.IS_OS_WINDOWS ? System.getenv("COMPUTERNAME") : System.getenv("HOSTNAME");
+ assertEquals(expected, hostName);
+ }
+
+ /**
+ * Assumes no security manager exists.
+ */
+ @Test
+ public void testGetJavaHome() {
+ final File dir = SystemUtils.getJavaHome();
+ assertNotNull(dir);
+ assertTrue(dir.exists());
+ }
+
+ /**
+ * Assumes no security manager exists.
+ */
+ @Test
+ public void testGetJavaIoTmpDir() {
+ final File dir = SystemUtils.getJavaIoTmpDir();
+ assertNotNull(dir);
+ assertTrue(dir.exists());
+ }
+
+ /**
+ * Assumes no security manager exists.
+ */
+ @Test
+ public void testGetUserDir() {
+ final File dir = SystemUtils.getUserDir();
+ assertNotNull(dir);
+ assertTrue(dir.exists());
+ }
+
+ /**
+ * Assumes no security manager exists.
+ */
+ @Test
+ public void testGetUserHome() {
+ final File dir = SystemUtils.getUserHome();
+ assertNotNull(dir);
+ assertTrue(dir.exists());
+ }
+
+ /**
+ * Assumes no security manager exists.
+ */
+ @Test
+ public void testGetUserName() {
+ assertEquals(System.getProperty("user.name"), SystemUtils.getUserName());
+ // Don't overwrite the system property in this test in case something goes awfully wrong.
+ assertEquals(System.getProperty("user.name", "foo"), SystemUtils.getUserName("foo"));
+ }
+
+ @Test
+ public void testIsJavaVersionAtLeast() {
+ if (SystemUtils.IS_JAVA_1_8) {
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_1));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_2));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_3));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_4));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_5));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_6));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_7));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_8));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_9));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_10));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_11));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_12));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_9) {
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_1));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_2));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_3));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_4));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_5));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_6));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_7));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_8));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_9));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_10));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_11));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_12));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_10) {
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_1));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_2));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_3));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_4));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_5));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_6));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_7));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_8));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_9));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_10));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_11));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_12));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_11) {
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_1));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_2));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_3));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_4));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_5));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_6));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_7));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_8));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_9));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_10));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_11));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_12));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_12) {
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_1));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_2));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_3));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_4));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_5));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_6));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_7));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_8));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_9));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_10));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_11));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_12));
+ assertFalse(SystemUtils.isJavaVersionAtLeast(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_13) {
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_1));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_2));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_3));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_4));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_5));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_6));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_7));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_1_8));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_9));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_10));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_11));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_12));
+ assertTrue(SystemUtils.isJavaVersionAtLeast(JAVA_13));
+ }
+ }
+
+ @Test
+ public void testIsJavaVersionAtMost() {
+ if (SystemUtils.IS_JAVA_1_8) {
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_1));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_2));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_3));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_4));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_5));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_6));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_7));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_1_8));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_9));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_10));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_11));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_12));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_9) {
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_1));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_2));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_3));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_4));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_5));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_6));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_7));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_8));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_9));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_10));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_11));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_12));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_10) {
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_1));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_2));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_3));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_4));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_5));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_6));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_7));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_8));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_9));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_10));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_11));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_12));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_11) {
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_1));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_2));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_3));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_4));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_5));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_6));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_7));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_8));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_9));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_10));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_11));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_12));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_12) {
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_1));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_2));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_3));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_4));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_5));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_6));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_7));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_8));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_9));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_10));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_11));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_12));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_13));
+ } else if (SystemUtils.IS_JAVA_13) {
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_1));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_2));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_3));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_4));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_5));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_6));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_7));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_1_8));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_9));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_10));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_11));
+ assertFalse(SystemUtils.isJavaVersionAtMost(JAVA_12));
+ assertTrue(SystemUtils.isJavaVersionAtMost(JAVA_13));
+ }
+ }
+
+ @Test
+ public void testJavaAwtHeadless() {
+ final String expectedStringValue = System.getProperty("java.awt.headless");
+ final String expectedStringValueWithDefault = System.getProperty("java.awt.headless", "false");
+ assertNotNull(expectedStringValueWithDefault);
+ final boolean expectedValue = Boolean.parseBoolean(expectedStringValue);
+ if (expectedStringValue != null) {
+ assertEquals(expectedStringValue, SystemUtils.JAVA_AWT_HEADLESS);
+ }
+ assertEquals(expectedValue, SystemUtils.isJavaAwtHeadless());
+ assertEquals(expectedStringValueWithDefault, "" + SystemUtils.isJavaAwtHeadless());
+ }
+
+ @Test
+ public void testJavaVersionMatches() {
+ String javaVersion = null;
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.0";
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.1";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.2";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.3.0";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.3.1";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.4.0";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.4.1";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.4.2";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.5.0";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.6.0";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.7.0";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "1.8.0";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ javaVersion = "9";
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.0"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.1"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.2"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.3"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.4"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.5"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.6"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.7"));
+ assertFalse(SystemUtils.isJavaVersionMatch(javaVersion, "1.8"));
+ assertTrue(SystemUtils.isJavaVersionMatch(javaVersion, "9"));
+ }
+
+ @Test
+ public void testOSMatchesName() {
+ String osName = null;
+ assertFalse(SystemUtils.isOSNameMatch(osName, "Windows"));
+ osName = "";
+ assertFalse(SystemUtils.isOSNameMatch(osName, "Windows"));
+ osName = "Windows 95";
+ assertTrue(SystemUtils.isOSNameMatch(osName, "Windows"));
+ osName = "Windows NT";
+ assertTrue(SystemUtils.isOSNameMatch(osName, "Windows"));
+ osName = "OS/2";
+ assertFalse(SystemUtils.isOSNameMatch(osName, "Windows"));
+ }
+
+ @Test
+ public void testOSMatchesNameAndVersion() {
+ String osName = null;
+ String osVersion = null;
+ assertFalse(SystemUtils.isOSMatch(osName, osVersion, "Windows 9", "4.1"));
+ osName = "";
+ osVersion = "";
+ assertFalse(SystemUtils.isOSMatch(osName, osVersion, "Windows 9", "4.1"));
+ osName = "Windows 95";
+ osVersion = "4.0";
+ assertFalse(SystemUtils.isOSMatch(osName, osVersion, "Windows 9", "4.1"));
+ osName = "Windows 95";
+ osVersion = "4.1";
+ assertTrue(SystemUtils.isOSMatch(osName, osVersion, "Windows 9", "4.1"));
+ osName = "Windows 98";
+ osVersion = "4.1";
+ assertTrue(SystemUtils.isOSMatch(osName, osVersion, "Windows 9", "4.1"));
+ osName = "Windows NT";
+ osVersion = "4.0";
+ assertFalse(SystemUtils.isOSMatch(osName, osVersion, "Windows 9", "4.1"));
+ osName = "OS/2";
+ osVersion = "4.0";
+ assertFalse(SystemUtils.isOSMatch(osName, osVersion, "Windows 9", "4.1"));
+ }
+
+ @Test
+ public void testOsVersionMatches() {
+ String osVersion = null;
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.1"));
+
+ osVersion = "";
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.1"));
+
+ osVersion = "10";
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.1"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.1.1"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.10"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.10.1"));
+
+ osVersion = "10.1";
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.1"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.1.1"));
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.10"));
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.10.1"));
+
+ osVersion = "10.1.1";
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.1"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.1.1"));
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.10"));
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.10.1"));
+
+ osVersion = "10.10";
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.1"));
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.1.1"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.10"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.10.1"));
+
+ osVersion = "10.10.1";
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.1"));
+ assertFalse(SystemUtils.isOSVersionMatch(osVersion, "10.1.1"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.10"));
+ assertTrue(SystemUtils.isOSVersionMatch(osVersion, "10.10.1"));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ThreadUtilsTest.java b/src/test/java/org/apache/commons/lang3/ThreadUtilsTest.java
new file mode 100644
index 000000000..ac4d72a15
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ThreadUtilsTest.java
@@ -0,0 +1,391 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
+
+import org.apache.commons.lang3.ThreadUtils.ThreadGroupPredicate;
+import org.apache.commons.lang3.ThreadUtils.ThreadPredicate;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.ThreadUtils}.
+ */
+public class ThreadUtilsTest extends AbstractLangTest {
+
+ private static class TestThread extends Thread {
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ TestThread(final String name) {
+ super(name);
+ }
+
+ TestThread(final ThreadGroup group, final String name) {
+ super(group, name);
+ }
+
+ @Override
+ public void run() {
+ latch.countDown();
+ try {
+ synchronized (this) {
+ this.wait();
+ }
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ public synchronized void start() {
+ super.start();
+ try {
+ latch.await();
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ @Test
+ public void testAtLeastOneThreadExists() {
+ assertFalse(ThreadUtils.getAllThreads().isEmpty());
+ }
+
+ @Test
+ public void testAtLeastOneThreadGroupsExists() {
+ assertFalse(ThreadUtils.getAllThreadGroups().isEmpty());
+ }
+
+ @Test
+ public void testComplexThreadGroups() throws Exception {
+ final ThreadGroup threadGroup1 = new ThreadGroup("thread_group_1__");
+ final ThreadGroup threadGroup2 = new ThreadGroup("thread_group_2__");
+ final ThreadGroup threadGroup3 = new ThreadGroup(threadGroup2, "thread_group_3__");
+ final ThreadGroup threadGroup4 = new ThreadGroup(threadGroup2, "thread_group_4__");
+ final ThreadGroup threadGroup5 = new ThreadGroup(threadGroup1, "thread_group_5__");
+ final ThreadGroup threadGroup6 = new ThreadGroup(threadGroup4, "thread_group_6__");
+ final ThreadGroup threadGroup7 = new ThreadGroup(threadGroup4, "thread_group_7__");
+ final ThreadGroup threadGroup7Doubled = new ThreadGroup(threadGroup4, "thread_group_7__");
+ final List<ThreadGroup> threadGroups = Arrays.asList(threadGroup1, threadGroup2, threadGroup3, threadGroup4, threadGroup5, threadGroup6, threadGroup7,
+ threadGroup7Doubled);
+
+ final Thread t1 = new TestThread("thread1_X__");
+ final Thread t2 = new TestThread(threadGroup1, "thread2_X__");
+ final Thread t3 = new TestThread(threadGroup2, "thread3_X__");
+ final Thread t4 = new TestThread(threadGroup3, "thread4_X__");
+ final Thread t5 = new TestThread(threadGroup4, "thread5_X__");
+ final Thread t6 = new TestThread(threadGroup5, "thread6_X__");
+ final Thread t7 = new TestThread(threadGroup6, "thread7_X__");
+ final Thread t8 = new TestThread(threadGroup4, "thread8_X__");
+ final Thread t9 = new TestThread(threadGroup6, "thread9_X__");
+ final Thread t10 = new TestThread(threadGroup3, "thread10_X__");
+ final Thread t11 = new TestThread(threadGroup7, "thread11_X__");
+ final Thread t11Doubled = new TestThread(threadGroup7Doubled, "thread11_X__");
+ final List<Thread> threads = Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t11Doubled);
+
+ try {
+ for (final Thread thread : threads) {
+ thread.start();
+ }
+ assertTrue(ThreadUtils.getAllThreadGroups().size() >= 7);
+ assertTrue(ThreadUtils.getAllThreads().size() >= 11);
+ assertTrue(ThreadUtils.findThreads(ThreadUtils.ALWAYS_TRUE_PREDICATE).size() >= 11);
+ assertEquals(1, ThreadUtils.findThreadsByName(t4.getName(), threadGroup3.getName()).size());
+ assertEquals(0, ThreadUtils.findThreadsByName(t4.getName(), threadGroup2.getName()).size());
+ assertEquals(2, ThreadUtils.findThreadsByName(t11.getName(), threadGroup7.getName()).size());
+ } finally {
+ for (final Thread thread : threads) {
+ thread.interrupt();
+ thread.join();
+ }
+ for (final ThreadGroup threadGroup : threadGroups) {
+ if (!threadGroup.isDestroyed()) {
+ threadGroup.destroy();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new ThreadUtils());
+ final Constructor<?>[] cons = ThreadUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(ThreadUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(ThreadUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testInvalidThreadId() {
+ assertThrows(IllegalArgumentException.class, () -> ThreadUtils.findThreadById(-5L));
+ }
+
+ @Test
+ public void testJoinDuration() throws InterruptedException {
+ ThreadUtils.join(new Thread(), Duration.ZERO);
+ ThreadUtils.join(new Thread(), Duration.ofMillis(1));
+ }
+
+ @Test
+ public void testNoThread() {
+ assertEquals(0, ThreadUtils.findThreadsByName("some_thread_which_does_not_exist_18762ZucTT").size());
+ }
+
+ @Test
+ public void testNoThreadGroup() {
+ assertEquals(0, ThreadUtils.findThreadGroupsByName("some_thread_group_which_does_not_exist_18762ZucTTII").size());
+ }
+
+ @Test
+ public void testNullThreadGroupName() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadGroupsByName(null));
+ }
+
+ @Test
+ public void testNullThreadName() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadsByName(null));
+ }
+
+ @Test
+ public void testNullThreadThreadGroup1() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadsByName("tname", (ThreadGroup) null));
+ }
+
+ @Test
+ public void testNullThreadThreadGroup2() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadById(1L, (ThreadGroup) null));
+ }
+
+ @Test
+ public void testNullThreadThreadGroup3() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadsByName(null, (ThreadGroup) null));
+ }
+
+ @Test
+ public void testNullThreadThreadGroupName1() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadsByName(null, "tgname"));
+ }
+
+ @Test
+ public void testNullThreadThreadGroupName2() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadsByName("tname", (String) null));
+ }
+
+ @Test
+ public void testNullThreadThreadGroupName3() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadsByName(null, (String) null));
+ }
+
+ @Test
+ public void testSleepDuration() throws InterruptedException {
+ ThreadUtils.sleep(Duration.ZERO);
+ ThreadUtils.sleep(Duration.ofMillis(1));
+ }
+
+ @Test
+ public void testSystemThreadGroupExists() {
+ final ThreadGroup systemThreadGroup = ThreadUtils.getSystemThreadGroup();
+ assertNotNull(systemThreadGroup);
+ assertNull(systemThreadGroup.getParent());
+ assertEquals("system", systemThreadGroup.getName());
+ }
+
+ @Test
+ public void testThreadGroups() throws InterruptedException {
+ final ThreadGroup threadGroup = new ThreadGroup("thread_group_DDZZ99__");
+ final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__");
+ final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__");
+
+ try {
+ t1.start();
+ t2.start();
+ assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__").size());
+ assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__", "thread_group_DDZZ99__").size());
+ assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOPP__", "thread_group_DDZZ99__").size());
+ assertEquals(0, ThreadUtils.findThreadsByName("thread1_XXOOPP__", "non_existent_thread_group_JJHHZZ__").size());
+ assertEquals(0, ThreadUtils.findThreadsByName("non_existent_thread_BBDDWW__", "thread_group_DDZZ99__").size());
+ assertEquals(1, ThreadUtils.findThreadGroupsByName("thread_group_DDZZ99__").size());
+ assertEquals(0, ThreadUtils.findThreadGroupsByName("non_existent_thread_group_JJHHZZ__").size());
+ assertNotNull(ThreadUtils.findThreadById(t1.getId(), threadGroup));
+ } finally {
+ t1.interrupt();
+ t2.interrupt();
+ t1.join();
+ t2.join();
+ threadGroup.destroy();
+ }
+ }
+
+ @Test
+ public void testThreadGroupsById() throws InterruptedException {
+ final ThreadGroup threadGroup = new ThreadGroup("thread_group_DDZZ99__");
+ final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__");
+ final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__");
+ final long nonExistingId = t1.getId() + t2.getId();
+
+ try {
+ t1.start();
+ t2.start();
+ assertSame(t1, ThreadUtils.findThreadById(t1.getId(), "thread_group_DDZZ99__"));
+ assertSame(t2, ThreadUtils.findThreadById(t2.getId(), "thread_group_DDZZ99__"));
+ assertNull(ThreadUtils.findThreadById(nonExistingId, "non_existent_thread_group_JJHHZZ__"));
+ assertNull(ThreadUtils.findThreadById(nonExistingId, "thread_group_DDZZ99__"));
+ } finally {
+ t1.interrupt();
+ t2.interrupt();
+ t1.join();
+ t2.join();
+ threadGroup.destroy();
+ }
+ }
+
+ @Test
+ public void testThreadGroupsByIdFail() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadById(Thread.currentThread().getId(), (String) null));
+ }
+
+ @Test
+ public void testThreadGroupsNullParent() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadGroups(null, true, ThreadUtils.ALWAYS_TRUE_PREDICATE));
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadGroups(null, false, ThreadUtils.ALWAYS_TRUE_PREDICATE));
+ }
+
+ @Test
+ public void testThreadGroupsNullPredicate() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadGroups((ThreadGroupPredicate) null));
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadGroups((Predicate<ThreadGroup>) null));
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreadGroups((Predicate) null));
+ }
+
+ @Test
+ public void testThreadGroupsRef() throws InterruptedException {
+ final ThreadGroup threadGroup = new ThreadGroup("thread_group_DDZZ99__");
+ final ThreadGroup deadThreadGroup = new ThreadGroup("dead_thread_group_MMQQSS__");
+ deadThreadGroup.destroy();
+ final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__");
+ final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__");
+
+ try {
+ t1.start();
+ t2.start();
+ assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__").size());
+ assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__", threadGroup).size());
+ assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOPP__", threadGroup).size());
+ assertEquals(0, ThreadUtils.findThreadsByName("thread1_XXOOPP__", deadThreadGroup).size());
+ } finally {
+ t1.interrupt();
+ t2.interrupt();
+ t1.join();
+ t2.join();
+ threadGroup.destroy();
+ assertEquals(0, ThreadUtils.findThreadsByName("thread2_XXOOPP__", threadGroup).size());
+ }
+ }
+
+ @Test
+ public void testThreads() throws InterruptedException {
+ final Thread t1 = new TestThread("thread1_XXOOLL__");
+ final Thread t2 = new TestThread("thread2_XXOOLL__");
+
+ try {
+ t1.start();
+ t2.start();
+ assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOLL__").size());
+ } finally {
+ t1.interrupt();
+ t2.interrupt();
+ t1.join();
+ t2.join();
+ }
+ }
+
+ @Test
+ public void testThreadsById() throws InterruptedException {
+ final Thread t1 = new TestThread("thread1_XXOOLL__");
+ final Thread t2 = new TestThread("thread2_XXOOLL__");
+
+ try {
+ t1.start();
+ t2.start();
+ assertSame(t1, ThreadUtils.findThreadById(t1.getId()));
+ assertSame(t2, ThreadUtils.findThreadById(t2.getId()));
+ } finally {
+ t1.interrupt();
+ t2.interrupt();
+ t1.join();
+ t2.join();
+ }
+ }
+
+ @Test
+ public void testThreadsByIdWrongGroup() throws InterruptedException {
+ final Thread t1 = new TestThread("thread1_XXOOLL__");
+ final ThreadGroup tg = new ThreadGroup("tg__HHEE22");
+
+ try {
+ t1.start();
+ assertNull(ThreadUtils.findThreadById(t1.getId(), tg));
+ } finally {
+ t1.interrupt();
+ t1.join();
+ tg.destroy();
+ }
+ }
+
+ @Test
+ public void testThreadsNullPredicate() {
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreads((ThreadPredicate) null));
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreads((Predicate<Thread>) null));
+ assertThrows(NullPointerException.class, () -> ThreadUtils.findThreads((Predicate) null));
+ }
+
+ @Test
+ public void testThreadsSameName() throws InterruptedException {
+ final Thread t1 = new TestThread("thread1_XXOOLL__");
+ final Thread alsot1 = new TestThread("thread1_XXOOLL__");
+
+ try {
+ t1.start();
+ alsot1.start();
+ assertEquals(2, ThreadUtils.findThreadsByName("thread1_XXOOLL__").size());
+ } finally {
+ t1.interrupt();
+ alsot1.interrupt();
+ t1.join();
+ alsot1.join();
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/ValidateTest.java b/src/test/java/org/apache/commons/lang3/ValidateTest.java
new file mode 100644
index 000000000..1c9c0922b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ValidateTest.java
@@ -0,0 +1,1626 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link Validate}.
+ */
+public class ValidateTest extends AbstractLangTest {
+
+ @Nested
+ class IsTrue {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowForTrueExpression() {
+ Validate.isTrue(true);
+ }
+
+ @Test
+ void shouldThrowExceptionWithDefaultMessageForFalseExpression() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isTrue(false));
+ assertEquals("The validated expression is false", ex.getMessage());
+ }
+
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowForTrueExpression() {
+ Validate.isTrue(true, "MSG");
+ }
+
+ @Test
+ void shouldThrowExceptionWithGivenMessageForFalseExpression() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isTrue(false, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowExceptionWithGivenMessageContainingSpecialCharacterForFalseExpression() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isTrue(false, "%"));
+ assertEquals("%", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithLongTemplate {
+
+ @Test
+ void shouldNotThrowForTrueExpression() {
+ Validate.isTrue(true, "MSG", 6);
+ }
+
+ @Test
+ void shouldThrowExceptionWithLongInsertedIntoTemplateMessageForFalseExpression() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isTrue(false, "MSG %s", 6));
+ assertEquals("MSG 6", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithDoubleTemplate {
+
+ @Test
+ void shouldNotThrowForTrueExpression() {
+ Validate.isTrue(true, "MSG", 7.4d);
+ }
+
+ @Test
+ void shouldThrowExceptionWithDoubleInsertedIntoTemplateMessageForFalseExpression() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isTrue(false, "MSG %s", 7.4d));
+ assertEquals("MSG 7.4", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithObjectTemplate {
+
+ @Test
+ void shouldNotThrowForTrueExpression() {
+ Validate.isTrue(true, "MSG", "Object 1", "Object 2");
+ }
+
+ @Test
+ void shouldThrowExceptionWithDoubleInsertedIntoTemplateMessageForFalseExpression() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.isTrue(false, "MSG %s %s", "Object 1", "Object 2"));
+ assertEquals("MSG Object 1 Object 2", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class NotNull {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowForNonNullReference() {
+ Validate.notNull(new Object());
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ assertSame("Hi", Validate.notNull("Hi"));
+ }
+
+ @Test
+ void shouldThrowExceptionWithDefaultMessageForNullReference() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notNull(null));
+ assertEquals("The validated object is null", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowForNonNullReference() {
+ Validate.notNull(new Object(), "MSG");
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ assertSame("Hi", Validate.notNull("Hi", "MSG"));
+ }
+
+ @Test
+ void shouldThrowExceptionWithGivenMessageForNullReference() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notNull(null, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class NotEmpty {
+
+ @Nested
+ class WithArray {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForArrayContainingNullReference() {
+ Validate.notEmpty(new Object[] {null});
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ final String[] expected = new String[] {"hi"};
+ assertSame(expected, Validate.notEmpty(expected));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullArray() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notEmpty((Object[]) null));
+ assertEquals("The validated array is empty", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForEmptyArray() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notEmpty(new Object[0]));
+ assertEquals("The validated array is empty", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForArrayContainingNullReference() {
+ Validate.notEmpty(new Object[] {null}, "MSG");
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ final String[] expected = new String[] {"hi"};
+ assertSame(expected, Validate.notEmpty(expected, "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithGivenMessageForNullArray() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notEmpty((Object[]) null, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForEmptyArray() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notEmpty(new Object[0], "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithCollection {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForCollectionContainingNullReference() {
+ Validate.notEmpty(Collections.singleton(null));
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ final Set<String> singleton = Collections.singleton("Hi");
+ assertSame(singleton, Validate.notEmpty(singleton));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullCollection() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notEmpty((Collection<?>) null));
+ assertEquals("The validated collection is empty", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForEmptyCollection() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notEmpty(Collections.emptySet()));
+ assertEquals("The validated collection is empty", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForCollectionContainingNullReference() {
+ Validate.notEmpty(Collections.singleton(null), "MSG");
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ final Set<String> singleton = Collections.singleton("Hi");
+ assertSame(singleton, Validate.notEmpty(singleton, "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithGivenMessageForNullCollection() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notEmpty((Collection<?>) null, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForEmptyCollection() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notEmpty(Collections.emptySet(), "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithMap {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForMapContainingNullMapping() {
+ Validate.notEmpty(Collections.singletonMap("key", null));
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ final Map<String, String> singletonMap = Collections.singletonMap("key", "value");
+ assertSame(singletonMap, Validate.notEmpty(singletonMap));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullMap() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notEmpty((Map<?, ?>) null));
+ assertEquals("The validated map is empty", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForEmptyMap() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notEmpty(Collections.emptyMap()));
+ assertEquals("The validated map is empty", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForMapContainingNullMapping() {
+ Validate.notEmpty(Collections.singletonMap("key", null), "MSG");
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ final Map<String, String> singletonMap = Collections.singletonMap("key", "value");
+ assertSame(singletonMap, Validate.notEmpty(singletonMap, "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithGivenMessageForNullMap() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notEmpty((Map<?, ?>) null, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForEmptyMap() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notEmpty(Collections.emptyMap(), "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithCharSequence {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyString() {
+ Validate.notEmpty("Hi");
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ assertSame("Hi", Validate.notEmpty("Hi"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullCharSequence() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notEmpty((CharSequence) null));
+ assertEquals("The validated character sequence is empty", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForEmptyString() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notEmpty(""));
+ assertEquals("The validated character sequence is empty", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyString() {
+ Validate.notEmpty("Hi", "MSG");
+ }
+
+ @Test
+ void shouldReturnTheSameInstance() {
+ assertSame("Hi", Validate.notEmpty("Hi", "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithGivenMessageForNullCharSequence() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notEmpty((CharSequence) null, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForEmptyString() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notEmpty("", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+ }
+
+ @Nested
+ class NotBlank {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyString() {
+ Validate.notBlank("abc");
+ }
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyStringContainingSpaces() {
+ Validate.notBlank(" abc ");
+ }
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyStringContainingWhitespaceChars() {
+ Validate.notBlank(" \n \t abc \r \n ");
+ }
+
+ @Test
+ void shouldReturnNonBlankValue() {
+ assertSame("abc", Validate.notBlank("abc"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullString() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notBlank(null));
+ assertEquals("The validated character sequence is blank", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForEmptyString() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notBlank(""));
+ assertEquals("The validated character sequence is blank", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForBlankString() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notBlank(" "));
+ assertEquals("The validated character sequence is blank", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForStringContainingOnlyWhitespaceChars() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notBlank(" \n \t \r \n "));
+ assertEquals("The validated character sequence is blank", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyString() {
+ Validate.notBlank("abc", "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyStringContainingSpaces() {
+ Validate.notBlank(" abc ", "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyStringContainingWhitespaceChars() {
+ Validate.notBlank(" \n \t abc \r \n ", "MSG");
+ }
+
+ @Test
+ void shouldReturnNonBlankValue() {
+ assertSame("abc", Validate.notBlank("abc", "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithGivenMessageForNullString() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.notBlank(null, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForEmptyString() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notBlank("", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForBlankString() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notBlank(" ", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForStringContainingOnlyWhitespaceChars() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notBlank(" \n \t \r \n ", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class NoNullElements {
+
+ @Nested
+ class WithArray {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyArray() {
+ Validate.noNullElements(new String[] {"a", "b"});
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final String[] expected = new String[] {"a", "b"};
+ assertSame(expected, Validate.noNullElements(expected));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullArray() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.noNullElements((Object[]) null));
+ assertEquals("array", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForArrayWithNullElement() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.noNullElements(new String[] {"a", null}));
+ assertEquals("The validated array contains null element at index: 1", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyArray() {
+ Validate.noNullElements(new String[] {"a", "b"}, "MSG");
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final String[] array = {"a", "b"};
+ assertSame(array, Validate.noNullElements(array, "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullArray() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.noNullElements((Object[]) null, "MSG"));
+ assertEquals("array", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForArrayWithNullElement() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.noNullElements(new String[] {"a", null}, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithCollection {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyCollection() {
+ Validate.noNullElements(Collections.singleton("a"));
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final Set<String> col = Collections.singleton("a");
+ assertSame(col, Validate.noNullElements(col));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullCollection() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.noNullElements((Collection<?>) null));
+ assertEquals("iterable", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForCollectionWithNullElement() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.noNullElements(Collections.singleton(null)));
+ assertEquals("The validated collection contains null element at index: 0", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNonEmptyCollection() {
+ Validate.noNullElements(Collections.singleton("a"), "MSG");
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final Set<String> col = Collections.singleton("a");
+ assertSame(col, Validate.noNullElements(col, "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullCollection() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.noNullElements((Collection<?>) null, "MSG"));
+ assertEquals("iterable", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForCollectionWithNullElement() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.noNullElements(Collections.singleton(null), "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+ }
+
+ @Nested
+ class ValidState {
+
+ @Nested
+ class WitMessage {
+ @Test
+ void shouldNotThrowExceptionForValidIndex() {
+ Validate.validState(true, "The Message");
+ }
+
+ @Test
+ void shouldThrowExceptionForTrueExpression() {
+ assertThrows(IllegalStateException.class, () -> Validate.validState(false, "The Message"));
+ }
+
+ }
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForTrueExpression() {
+ Validate.validState(true);
+ }
+
+ @Test
+ void shouldThrowExceptionForTrueExpression() {
+ assertThrows(IllegalStateException.class, () -> Validate.validState(false));
+ }
+
+ }
+ }
+
+ @Nested
+ class ValidIndex {
+
+ @Nested
+ class WithArray {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForValidIndex() {
+ Validate.validIndex(new String[] {"a"}, 0);
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final String[] array = {"a"};
+ assertSame(array, Validate.validIndex(array, 0));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultForNullArray() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.validIndex((Object[]) null, 1));
+ assertEquals("array", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithDefaultMessageForNegativeIndex() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> Validate.validIndex(new String[] {"a"}, -1));
+ assertEquals("The validated array index is invalid: -1", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithDefaultMessageForIndexOutOfBounds() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> Validate.validIndex(new String[] {"a"}, 1));
+ assertEquals("The validated array index is invalid: 1", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForValidIndex() {
+ Validate.validIndex(new String[] {"a"}, 0, "MSG");
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final String[] array = {"a"};
+ assertSame(array, Validate.validIndex(array, 0, "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullArray() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.validIndex((Object[]) null, 1, "MSG"));
+ assertEquals("array", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithGivenMessageForNegativeIndex() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class,
+ () -> Validate.validIndex(new String[] {"a"}, -1, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithGivenMessageForIndexOutOfBounds() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> Validate.validIndex(new String[] {"a"}, 1, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithCollection {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForValidIndex() {
+ Validate.validIndex(Collections.singleton("a"), 0);
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final Set<String> col = Collections.singleton("a");
+ assertSame(col, Validate.validIndex(col, 0));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultForNullCollection() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.validIndex((Collection<?>) null, 1));
+ assertEquals("collection", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithDefaultMessageForNegativeIndex() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class,
+ () -> Validate.validIndex(Collections.singleton("a"), -1));
+ assertEquals("The validated collection index is invalid: -1", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithDefaultMessageForIndexOutOfBounds() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class,
+ () -> Validate.validIndex(Collections.singleton("a"), 1));
+ assertEquals("The validated collection index is invalid: 1", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForValidIndex() {
+ Validate.validIndex(Collections.singleton("a"), 0, "MSG");
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final Set<String> col = Collections.singleton("a");
+ assertSame(col, Validate.validIndex(col, 0, "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullCollection() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.validIndex((Collection<?>) null, 1, "MSG"));
+ assertEquals("collection", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithGivenMessageForNegativeIndex() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class,
+ () -> Validate.validIndex(Collections.singleton("a"), -1, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithGivenMessageForIndexOutOfBounds() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> Validate.validIndex(Collections.singleton("a"), 1, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithCharSequence {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForValidIndex() {
+ Validate.validIndex("a", 0);
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final String str = "a";
+ assertSame(str, Validate.validIndex(str, 0));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultForNullString() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.validIndex((String) null, 1));
+ assertEquals("chars", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithDefaultMessageForNegativeIndex() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> Validate.validIndex("a", -1));
+ assertEquals("The validated character sequence index is invalid: -1", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithDefaultMessageForIndexOutOfBounds() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> Validate.validIndex("a", 1));
+ assertEquals("The validated character sequence index is invalid: 1", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForValidIndex() {
+ Validate.validIndex("a", 0, "MSG");
+ }
+
+ @Test
+ void shouldReturnSameInstance() {
+ final String str = "a";
+ assertSame(str, Validate.validIndex(str, 0, "MSG"));
+ }
+
+ @Test
+ void shouldThrowNullPointerExceptionWithDefaultMessageForNullStr() {
+ final NullPointerException ex = assertThrows(NullPointerException.class, () -> Validate.validIndex((String) null, 1, "MSG"));
+ assertEquals("chars", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithGivenMessageForNegativeIndex() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> Validate.validIndex("a", -1, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIndexOutOfBoundsExceptionWithGivenMessageForIndexOutOfBounds() {
+ final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> Validate.validIndex("a", 1, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+ }
+
+ @Nested
+ class MatchesPattern {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenStringMatchesPattern() {
+ Validate.matchesPattern("hi", "[a-z]*");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenStringDoesNotMatchPattern() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.matchesPattern("hi", "[0-9]*"));
+ assertEquals("The string hi does not match the pattern [0-9]*", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenStringMatchesPattern() {
+ Validate.matchesPattern("hi", "[a-z]*", "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWhenStringDoesNotMatchPattern() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.matchesPattern("hi", "[0-9]*", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class NotNaN {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNumber() {
+ Validate.notNaN(0.0);
+ }
+
+ @Test
+ void shouldNotThrowExceptionForPositiveInfinity() {
+ Validate.notNaN(Double.POSITIVE_INFINITY);
+ }
+
+ @Test
+ void shouldNotThrowExceptionForNegativeInfinity() {
+ Validate.notNaN(Double.NEGATIVE_INFINITY);
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForNaN() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notNaN(Double.NaN));
+ assertEquals("The validated value is not a number", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForNumber() {
+ Validate.notNaN(0.0, "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionForPositiveInfinity() {
+ Validate.notNaN(Double.POSITIVE_INFINITY, "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionForNegativeInfinity() {
+ Validate.notNaN(Double.NEGATIVE_INFINITY, "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageForNaN() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.notNaN(Double.NaN, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class Finite {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionForFiniteValue() {
+ Validate.finite(0.0);
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForPositiveInfinity() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.finite(Double.POSITIVE_INFINITY));
+ assertEquals("The value is invalid: Infinity", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForNegativeInfinity() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.finite(Double.NEGATIVE_INFINITY));
+ assertEquals("The value is invalid: -Infinity", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForNaN() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.finite(Double.NaN));
+ assertEquals("The value is invalid: NaN", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionForFiniteValue() {
+ Validate.finite(0.0, "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForPositiveInfinity() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.finite(Double.POSITIVE_INFINITY, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForNegativeInfinity() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.finite(Double.NEGATIVE_INFINITY, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageForNaN() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.finite(Double.NaN, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class InclusiveBetween {
+
+ @Nested
+ class WithComparable {
+
+ private static final String LOWER_BOUND = "1";
+ private static final String UPPER_BOUND = "3";
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, "2");
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsLowerBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND);
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsUpperBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND);
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, "0"));
+ assertEquals("The value 0 is not in the specified inclusive range of 1 to 3", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, "4"));
+ assertEquals("The value 4 is not in the specified inclusive range of 1 to 3", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, "2", "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsLowerBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND, "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsUpperBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND, "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, "0", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, "4", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithLong {
+
+ private static final long LOWER_BOUND = 1;
+ private static final long UPPER_BOUND = 3;
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 2);
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsLowerBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND);
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsUpperBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND);
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 0));
+ assertEquals("The value 0 is not in the specified inclusive range of 1 to 3", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 4));
+ assertEquals("The value 4 is not in the specified inclusive range of 1 to 3", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 2, "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsLowerBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND, "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsUpperBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND, "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 0, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 4, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithDouble {
+
+ private static final double LOWER_BOUND = 0.1;
+ private static final double UPPER_BOUND = 3.1;
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 2.1);
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsLowerBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND);
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsUpperBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND);
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 0.01));
+ assertEquals("The value 0.01 is not in the specified inclusive range of 0.1 to 3.1", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 4.1));
+ assertEquals("The value 4.1 is not in the specified inclusive range of 0.1 to 3.1", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 2.1, "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsLowerBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND, "MSG");
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsUpperBound() {
+ Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND, "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 0.01, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.inclusiveBetween(LOWER_BOUND, UPPER_BOUND, 4.1, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+ }
+
+ @Nested
+ class ExclusiveBetween {
+
+ @Nested
+ class WithComparable {
+
+ private static final String LOWER_BOUND = "1";
+ private static final String UPPER_BOUND = "3";
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, "2");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND));
+ assertEquals("The value 1 is not in the specified exclusive range of 1 to 3", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND));
+ assertEquals("The value 3 is not in the specified exclusive range of 1 to 3", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, "0"));
+ assertEquals("The value 0 is not in the specified exclusive range of 1 to 3", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, "4"));
+ assertEquals("The value 4 is not in the specified exclusive range of 1 to 3", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, "2", "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, "0", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, "4", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithLong {
+
+ private static final long LOWER_BOUND = 1;
+ private static final long UPPER_BOUND = 3;
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 2);
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND));
+ assertEquals("The value 1 is not in the specified exclusive range of 1 to 3", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND));
+ assertEquals("The value 3 is not in the specified exclusive range of 1 to 3", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 0));
+ assertEquals("The value 0 is not in the specified exclusive range of 1 to 3", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 4));
+ assertEquals("The value 4 is not in the specified exclusive range of 1 to 3", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 2, "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 0, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 4, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class WithDouble {
+
+ private static final double LOWER_BOUND = 0.1;
+ private static final double UPPER_BOUND = 3.1;
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 2.1);
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExcdeptionWhenValueIsLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND));
+ assertEquals("The value 0.1 is not in the specified exclusive range of 0.1 to 3.1", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExcdeptionWhenValueIsUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND));
+ assertEquals("The value 3.1 is not in the specified exclusive range of 0.1 to 3.1", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 0.01));
+ assertEquals("The value 0.01 is not in the specified exclusive range of 0.1 to 3.1", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 4.1));
+ assertEquals("The value 4.1 is not in the specified exclusive range of 0.1 to 3.1", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsBetweenBounds() {
+ Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 2.1, "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExcdeptionWhenValueIsLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, LOWER_BOUND, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExcdeptionWhenValueIsUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, UPPER_BOUND, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsBelowLowerBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 0.01, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsAboveUpperBound() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.exclusiveBetween(LOWER_BOUND, UPPER_BOUND, 4.1, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+ }
+
+ @Nested
+ class IsInstanceOf {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsInstanceOfClass() {
+ Validate.isInstanceOf(String.class, "hi");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenValueIsNotInstanceOfClass() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isInstanceOf(List.class, "hi"));
+ assertEquals("Expected type: java.util.List, actual: java.lang.String", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsInstanceOfClass() {
+ Validate.isInstanceOf(String.class, "hi", "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsNotInstanceOfClass() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isInstanceOf(List.class, "hi", "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessageTemplate {
+
+ @Test
+ void shouldNotThrowExceptionWhenValueIsInstanceOfClass() {
+ Validate.isInstanceOf(String.class, "hi", "Error %s=%s", "Name", "Value");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGivenMessageWhenValueIsNotInstanceOfClass() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.isInstanceOf(List.class, "hi", "Error %s=%s", "Name", "Value"));
+ assertEquals("Error Name=Value", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class IsAssignable {
+
+ @Nested
+ class WithoutMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenClassIsAssignable() {
+ Validate.isAssignableFrom(CharSequence.class, String.class);
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithDefaultMessageWhenClassIsNotAssignable() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isAssignableFrom(List.class, String.class));
+ assertEquals("Cannot assign a java.lang.String to a java.util.List", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithNullSuperType() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isAssignableFrom(null, String.class));
+ assertEquals("Cannot assign a java.lang.String to a null type", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithNullType() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isAssignableFrom(List.class, null));
+ assertEquals("Cannot assign a null type to a java.util.List", ex.getMessage());
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithNullTypes() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Validate.isAssignableFrom(null, null));
+ assertEquals("Cannot assign a null type to a null type", ex.getMessage());
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @Test
+ void shouldNotThrowExceptionWhenClassIsAssignable() {
+ Validate.isAssignableFrom(CharSequence.class, String.class, "MSG");
+ }
+
+ @Test
+ void shouldThrowIllegalArgumentExceptionWithGiventMessageWhenClassIsNotAssignable() {
+ final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> Validate.isAssignableFrom(List.class, String.class, "MSG"));
+ assertEquals("MSG", ex.getMessage());
+ }
+ }
+ }
+
+ @Nested
+ class UtilClassConventions {
+
+ @Test
+ void instancesCanBeConstrcuted() {
+ assertNotNull(new Validate());
+ }
+
+ @Test
+ void hasOnlyOnePublicConstructor() {
+ final Constructor<?>[] cons = Validate.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ }
+
+ @Test
+ void isPublicClass() {
+ assertTrue(Modifier.isPublic(Validate.class.getModifiers()));
+ }
+
+ @Test
+ void isNonFinalClass() {
+ assertFalse(Modifier.isFinal(Validate.class.getModifiers()));
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/CompareToBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/CompareToBuilderTest.java
new file mode 100644
index 000000000..e2cc1b45c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/CompareToBuilderTest.java
@@ -0,0 +1,1166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.math.BigInteger;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.CompareToBuilder}.
+ */
+public class CompareToBuilderTest extends AbstractLangTest {
+
+
+ static class TestObject implements Comparable<TestObject> {
+ private int a;
+ TestObject(final int a) {
+ this.a = a;
+ }
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof TestObject)) {
+ return false;
+ }
+ final TestObject rhs = (TestObject) o;
+ return a == rhs.a;
+ }
+
+ @Override
+ public int hashCode() {
+ return a;
+ }
+
+ public void setA(final int a) {
+ this.a = a;
+ }
+
+ public int getA() {
+ return a;
+ }
+ @Override
+ public int compareTo(final TestObject rhs) {
+ return Integer.compare(a, rhs.a);
+ }
+ }
+
+ static class TestSubObject extends TestObject {
+ private int b;
+ TestSubObject() {
+ super(0);
+ }
+ TestSubObject(final int a, final int b) {
+ super(a);
+ this.b = b;
+ }
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof TestSubObject)) {
+ return false;
+ }
+ final TestSubObject rhs = (TestSubObject) o;
+ return super.equals(o) && b == rhs.b;
+ }
+ }
+
+ static class TestTransientSubObject extends TestObject {
+ @SuppressWarnings("unused")
+ private final transient int t;
+ TestTransientSubObject(final int a, final int t) {
+ super(a);
+ this.t = t;
+ }
+ }
+
+ @Test
+ public void testReflectionCompare() {
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(4);
+ assertEquals(0, CompareToBuilder.reflectionCompare(o1, o1));
+ assertEquals(0, CompareToBuilder.reflectionCompare(o1, o2));
+ o2.setA(5);
+ assertTrue(CompareToBuilder.reflectionCompare(o1, o2) < 0);
+ assertTrue(CompareToBuilder.reflectionCompare(o2, o1) > 0);
+ }
+
+ @Test
+ public void testReflectionCompareEx1() {
+ final TestObject o1 = new TestObject(4);
+ assertThrows(NullPointerException.class, () -> CompareToBuilder.reflectionCompare(o1, null));
+ }
+
+ @Test
+ public void testReflectionCompareEx2() {
+ final TestObject o1 = new TestObject(4);
+ final Object o2 = new Object();
+ assertThrows(ClassCastException.class, () -> CompareToBuilder.reflectionCompare(o1, o2));
+ }
+
+ @Test
+ public void testReflectionHierarchyCompare() {
+ testReflectionHierarchyCompare(false, null);
+ }
+
+ @Test
+ public void testReflectionHierarchyCompareExcludeFields() {
+ final String[] excludeFields = { "b" };
+ testReflectionHierarchyCompare(true, excludeFields);
+
+ TestSubObject x;
+ TestSubObject y;
+ TestSubObject z;
+
+ x = new TestSubObject(1, 1);
+ y = new TestSubObject(2, 1);
+ z = new TestSubObject(3, 1);
+ assertXYZCompareOrder(x, y, z, true, excludeFields);
+
+ x = new TestSubObject(1, 3);
+ y = new TestSubObject(2, 2);
+ z = new TestSubObject(3, 1);
+ assertXYZCompareOrder(x, y, z, true, excludeFields);
+ }
+
+ @Test
+ public void testReflectionHierarchyCompareTransients() {
+ testReflectionHierarchyCompare(true, null);
+
+ TestTransientSubObject x;
+ TestTransientSubObject y;
+ TestTransientSubObject z;
+
+ x = new TestTransientSubObject(1, 1);
+ y = new TestTransientSubObject(2, 2);
+ z = new TestTransientSubObject(3, 3);
+ assertXYZCompareOrder(x, y, z, true, null);
+
+ x = new TestTransientSubObject(1, 1);
+ y = new TestTransientSubObject(1, 2);
+ z = new TestTransientSubObject(1, 3);
+ assertXYZCompareOrder(x, y, z, true, null);
+ }
+
+ private void assertXYZCompareOrder(final Object x, final Object y, final Object z, final boolean testTransients, final String[] excludeFields) {
+ assertEquals(0, CompareToBuilder.reflectionCompare(x, x, testTransients, null, excludeFields));
+ assertEquals(0, CompareToBuilder.reflectionCompare(y, y, testTransients, null, excludeFields));
+ assertEquals(0, CompareToBuilder.reflectionCompare(z, z, testTransients, null, excludeFields));
+
+ assertTrue(0 > CompareToBuilder.reflectionCompare(x, y, testTransients, null, excludeFields));
+ assertTrue(0 > CompareToBuilder.reflectionCompare(x, z, testTransients, null, excludeFields));
+ assertTrue(0 > CompareToBuilder.reflectionCompare(y, z, testTransients, null, excludeFields));
+
+ assertTrue(0 < CompareToBuilder.reflectionCompare(y, x, testTransients, null, excludeFields));
+ assertTrue(0 < CompareToBuilder.reflectionCompare(z, x, testTransients, null, excludeFields));
+ assertTrue(0 < CompareToBuilder.reflectionCompare(z, y, testTransients, null, excludeFields));
+ }
+
+ private void testReflectionHierarchyCompare(final boolean testTransients, final String[] excludeFields) {
+ final TestObject to1 = new TestObject(1);
+ final TestObject to2 = new TestObject(2);
+ final TestObject to3 = new TestObject(3);
+ final TestSubObject tso1 = new TestSubObject(1, 1);
+ final TestSubObject tso2 = new TestSubObject(2, 2);
+ final TestSubObject tso3 = new TestSubObject(3, 3);
+
+ assertReflectionCompareContract(to1, to1, to1, false, excludeFields);
+ assertReflectionCompareContract(to1, to2, to3, false, excludeFields);
+ assertReflectionCompareContract(tso1, tso1, tso1, false, excludeFields);
+ assertReflectionCompareContract(tso1, tso2, tso3, false, excludeFields);
+ assertReflectionCompareContract("1", "2", "3", false, excludeFields);
+
+ assertTrue(0 != CompareToBuilder.reflectionCompare(tso1, new TestSubObject(1, 0), testTransients));
+ assertTrue(0 != CompareToBuilder.reflectionCompare(tso1, new TestSubObject(0, 1), testTransients));
+
+ // root class
+ assertXYZCompareOrder(to1, to2, to3, true, null);
+ // subclass
+ assertXYZCompareOrder(tso1, tso2, tso3, true, null);
+ }
+
+ /**
+ * See "Effective Java" under "Consider Implementing Comparable".
+ *
+ * @param x an object to compare
+ * @param y an object to compare
+ * @param z an object to compare
+ * @param testTransients Whether to include transients in the comparison
+ * @param excludeFields fields to exclude
+ */
+ private void assertReflectionCompareContract(final Object x, final Object y, final Object z, final boolean testTransients, final String[] excludeFields) {
+
+ // signum
+ assertEquals(reflectionCompareSignum(x, y, testTransients, excludeFields), -reflectionCompareSignum(y, x, testTransients, excludeFields));
+
+ // transitive
+ if (CompareToBuilder.reflectionCompare(x, y, testTransients, null, excludeFields) > 0
+ && CompareToBuilder.reflectionCompare(y, z, testTransients, null, excludeFields) > 0) {
+ assertTrue(CompareToBuilder.reflectionCompare(x, z, testTransients, null, excludeFields) > 0);
+ }
+
+ // un-named
+ if (CompareToBuilder.reflectionCompare(x, y, testTransients, null, excludeFields) == 0) {
+ assertEquals(reflectionCompareSignum(x, z, testTransients, excludeFields), -reflectionCompareSignum(y, z, testTransients, excludeFields));
+ }
+
+ // strongly recommended but not strictly required
+ assertTrue(CompareToBuilder.reflectionCompare(x, y, testTransients) ==0 == EqualsBuilder.reflectionEquals(x, y, testTransients));
+ }
+
+ /**
+ * Returns the signum of the result of comparing x and y with
+ * {@code CompareToBuilder.reflectionCompare}
+ *
+ * @param lhs The "left-hand-side" of the comparison.
+ * @param rhs The "right-hand-side" of the comparison.
+ * @param testTransients Whether to include transients in the comparison
+ * @param excludeFields fields to exclude
+ * @return int The signum
+ */
+ private int reflectionCompareSignum(final Object lhs, final Object rhs, final boolean testTransients, final String[] excludeFields) {
+ return BigInteger.valueOf(CompareToBuilder.reflectionCompare(lhs, rhs, testTransients)).signum();
+ }
+
+ @Test
+ public void testAppendSuper() {
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(5);
+ assertEquals(0, new CompareToBuilder().appendSuper(0).append(o1, o1).toComparison());
+ assertTrue(new CompareToBuilder().appendSuper(0).append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().appendSuper(0).append(o2, o1).toComparison() > 0);
+
+ assertTrue(new CompareToBuilder().appendSuper(-1).append(o1, o1).toComparison() < 0);
+ assertTrue(new CompareToBuilder().appendSuper(-1).append(o1, o2).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().appendSuper(1).append(o1, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().appendSuper(1).append(o1, o2).toComparison() > 0);
+ }
+
+ @Test
+ public void testObject() {
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(4);
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(o1, o2).toComparison());
+ o2.setA(5);
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() > 0);
+
+ assertTrue(new CompareToBuilder().append(o1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((Object) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, o1).toComparison() < 0);
+ }
+
+ @Test
+ public void testObjectBuild() {
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(4);
+ assertEquals(Integer.valueOf(0), new CompareToBuilder().append(o1, o1).build());
+ assertEquals(Integer.valueOf(0), new CompareToBuilder().append(o1, o2).build());
+ o2.setA(5);
+ assertTrue(new CompareToBuilder().append(o1, o2).build().intValue() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).build().intValue() > 0);
+
+ assertTrue(new CompareToBuilder().append(o1, null).build().intValue() > 0);
+ assertEquals(Integer.valueOf(0), new CompareToBuilder().append((Object) null, null).build());
+ assertTrue(new CompareToBuilder().append(null, o1).build().intValue() < 0);
+ }
+
+ @Test
+ public void testObjectEx2() {
+ final TestObject o1 = new TestObject(4);
+ final Object o2 = new Object();
+ assertThrows(ClassCastException.class, () -> new CompareToBuilder().append(o1, o2));
+ }
+
+ @Test
+ public void testObjectComparator() {
+ final String o1 = "Fred";
+ String o2 = "Fred";
+ assertEquals(0, new CompareToBuilder().append(o1, o1, String.CASE_INSENSITIVE_ORDER).toComparison());
+ assertEquals(0, new CompareToBuilder().append(o1, o2, String.CASE_INSENSITIVE_ORDER).toComparison());
+ o2 = "FRED";
+ assertEquals(0, new CompareToBuilder().append(o1, o2, String.CASE_INSENSITIVE_ORDER).toComparison());
+ assertEquals(0, new CompareToBuilder().append(o2, o1, String.CASE_INSENSITIVE_ORDER).toComparison());
+ o2 = "FREDA";
+ assertTrue(new CompareToBuilder().append(o1, o2, String.CASE_INSENSITIVE_ORDER).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1, String.CASE_INSENSITIVE_ORDER).toComparison() > 0);
+
+ assertTrue(new CompareToBuilder().append(o1, null, String.CASE_INSENSITIVE_ORDER).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append(null, null, String.CASE_INSENSITIVE_ORDER).toComparison());
+ assertTrue(new CompareToBuilder().append(null, o1, String.CASE_INSENSITIVE_ORDER).toComparison() < 0);
+ }
+
+ @Test
+ public void testObjectComparatorNull() {
+ final String o1 = "Fred";
+ String o2 = "Fred";
+ assertEquals(0, new CompareToBuilder().append(o1, o1, null).toComparison());
+ assertEquals(0, new CompareToBuilder().append(o1, o2, null).toComparison());
+ o2 = "Zebra";
+ assertTrue(new CompareToBuilder().append(o1, o2, null).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1, null).toComparison() > 0);
+
+ assertTrue(new CompareToBuilder().append(o1, null, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append(null, null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, o1, null).toComparison() < 0);
+ }
+
+ @Test
+ public void testLong() {
+ final long o1 = 1L;
+ final long o2 = 2L;
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Long.MAX_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Long.MAX_VALUE, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Long.MIN_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Long.MIN_VALUE, o1).toComparison() < 0);
+ }
+
+ @Test
+ public void testInt() {
+ final int o1 = 1;
+ final int o2 = 2;
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Integer.MAX_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Integer.MAX_VALUE, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Integer.MIN_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Integer.MIN_VALUE, o1).toComparison() < 0);
+ }
+
+ @Test
+ public void testShort() {
+ final short o1 = 1;
+ final short o2 = 2;
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Short.MAX_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Short.MAX_VALUE, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Short.MIN_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Short.MIN_VALUE, o1).toComparison() < 0);
+ }
+
+ @Test
+ public void testChar() {
+ final char o1 = 1;
+ final char o2 = 2;
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Character.MAX_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Character.MAX_VALUE, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Character.MIN_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Character.MIN_VALUE, o1).toComparison() < 0);
+ }
+
+ @Test
+ public void testByte() {
+ final byte o1 = 1;
+ final byte o2 = 2;
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Byte.MAX_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Byte.MAX_VALUE, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Byte.MIN_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Byte.MIN_VALUE, o1).toComparison() < 0);
+ }
+
+ @Test
+ public void testDouble() {
+ final double o1 = 1;
+ final double o2 = 2;
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Double.MAX_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Double.MAX_VALUE, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Double.MIN_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Double.MIN_VALUE, o1).toComparison() < 0);
+ assertEquals(0, new CompareToBuilder().append(Double.NaN, Double.NaN).toComparison());
+ assertTrue(new CompareToBuilder().append(Double.NaN, Double.MAX_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Double.POSITIVE_INFINITY, Double.MAX_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Double.NEGATIVE_INFINITY, Double.MIN_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o1, Double.NaN).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Double.NaN, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(-0.0, 0.0).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(0.0, -0.0).toComparison() > 0);
+ }
+
+ @Test
+ public void testFloat() {
+ final float o1 = 1;
+ final float o2 = 2;
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Float.MAX_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Float.MAX_VALUE, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o1, Float.MIN_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Float.MIN_VALUE, o1).toComparison() < 0);
+ assertEquals(0, new CompareToBuilder().append(Float.NaN, Float.NaN).toComparison());
+ assertTrue(new CompareToBuilder().append(Float.NaN, Float.MAX_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Float.POSITIVE_INFINITY, Float.MAX_VALUE).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(Float.NEGATIVE_INFINITY, Float.MIN_VALUE).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(o1, Float.NaN).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(Float.NaN, o1).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(-0.0, 0.0).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(0.0, -0.0).toComparison() > 0);
+ }
+
+ @Test
+ public void testBoolean() {
+ final boolean o1 = true;
+ final boolean o2 = false;
+ assertEquals(0, new CompareToBuilder().append(o1, o1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(o2, o2).toComparison());
+ assertTrue(new CompareToBuilder().append(o1, o2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(o2, o1).toComparison() < 0);
+ }
+
+ @Test
+ public void testObjectArray() {
+ final TestObject[] obj1 = new TestObject[2];
+ obj1[0] = new TestObject(4);
+ obj1[1] = new TestObject(5);
+ final TestObject[] obj2 = new TestObject[2];
+ obj2[0] = new TestObject(4);
+ obj2[1] = new TestObject(5);
+ final TestObject[] obj3 = new TestObject[3];
+ obj3[0] = new TestObject(4);
+ obj3[1] = new TestObject(5);
+ obj3[2] = new TestObject(6);
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = new TestObject(7);
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((Object[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testLongArray() {
+ final long[] obj1 = new long[2];
+ obj1[0] = 5L;
+ obj1[1] = 6L;
+ final long[] obj2 = new long[2];
+ obj2[0] = 5L;
+ obj2[1] = 6L;
+ final long[] obj3 = new long[3];
+ obj3[0] = 5L;
+ obj3[1] = 6L;
+ obj3[2] = 7L;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((long[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testIntArray() {
+ final int[] obj1 = new int[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ final int[] obj2 = new int[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ final int[] obj3 = new int[3];
+ obj3[0] = 5;
+ obj3[1] = 6;
+ obj3[2] = 7;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((int[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testShortArray() {
+ final short[] obj1 = new short[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ final short[] obj2 = new short[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ final short[] obj3 = new short[3];
+ obj3[0] = 5;
+ obj3[1] = 6;
+ obj3[2] = 7;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((short[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testCharArray() {
+ final char[] obj1 = new char[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ final char[] obj2 = new char[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ final char[] obj3 = new char[3];
+ obj3[0] = 5;
+ obj3[1] = 6;
+ obj3[2] = 7;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((char[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testByteArray() {
+ final byte[] obj1 = new byte[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ final byte[] obj2 = new byte[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ final byte[] obj3 = new byte[3];
+ obj3[0] = 5;
+ obj3[1] = 6;
+ obj3[2] = 7;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((byte[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testDoubleArray() {
+ final double[] obj1 = new double[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ final double[] obj2 = new double[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ final double[] obj3 = new double[3];
+ obj3[0] = 5;
+ obj3[1] = 6;
+ obj3[2] = 7;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((double[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testFloatArray() {
+ final float[] obj1 = new float[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ final float[] obj2 = new float[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ final float[] obj3 = new float[3];
+ obj3[0] = 5;
+ obj3[1] = 6;
+ obj3[2] = 7;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((float[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testBooleanArray() {
+ final boolean[] obj1 = new boolean[2];
+ obj1[0] = true;
+ obj1[1] = false;
+ final boolean[] obj2 = new boolean[2];
+ obj2[0] = true;
+ obj2[1] = false;
+ final boolean[] obj3 = new boolean[3];
+ obj3[0] = true;
+ obj3[1] = false;
+ obj3[2] = true;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ obj1[1] = true;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+
+ assertTrue(new CompareToBuilder().append(obj1, null).toComparison() > 0);
+ assertEquals(0, new CompareToBuilder().append((boolean[]) null, null).toComparison());
+ assertTrue(new CompareToBuilder().append(null, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMultiLongArray() {
+ final long[][] array1 = new long[2][2];
+ final long[][] array2 = new long[2][2];
+ final long[][] array3 = new long[2][3];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ array3[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ array3[1][2] = 100;
+ array3[1][2] = 100;
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = 200;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMultiIntArray() {
+ final int[][] array1 = new int[2][2];
+ final int[][] array2 = new int[2][2];
+ final int[][] array3 = new int[2][3];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ array3[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ array3[1][2] = 100;
+ array3[1][2] = 100;
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = 200;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMultiShortArray() {
+ final short[][] array1 = new short[2][2];
+ final short[][] array2 = new short[2][2];
+ final short[][] array3 = new short[2][3];
+ for (short i = 0; i < array1.length; ++i) {
+ for (short j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (short) ((i + 1) * (j + 1));
+ array2[i][j] = (short) ((i + 1) * (j + 1));
+ array3[i][j] = (short) ((i + 1) * (j + 1));
+ }
+ }
+ array3[1][2] = 100;
+ array3[1][2] = 100;
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = 200;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMultiCharArray() {
+ final char[][] array1 = new char[2][2];
+ final char[][] array2 = new char[2][2];
+ final char[][] array3 = new char[2][3];
+ for (short i = 0; i < array1.length; ++i) {
+ for (short j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (char) ((i + 1) * (j + 1));
+ array2[i][j] = (char) ((i + 1) * (j + 1));
+ array3[i][j] = (char) ((i + 1) * (j + 1));
+ }
+ }
+ array3[1][2] = 100;
+ array3[1][2] = 100;
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = 200;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMultiByteArray() {
+ final byte[][] array1 = new byte[2][2];
+ final byte[][] array2 = new byte[2][2];
+ final byte[][] array3 = new byte[2][3];
+ for (byte i = 0; i < array1.length; ++i) {
+ for (byte j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (byte) ((i + 1) * (j + 1));
+ array2[i][j] = (byte) ((i + 1) * (j + 1));
+ array3[i][j] = (byte) ((i + 1) * (j + 1));
+ }
+ }
+ array3[1][2] = 100;
+ array3[1][2] = 100;
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = 127;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMultiFloatArray() {
+ final float[][] array1 = new float[2][2];
+ final float[][] array2 = new float[2][2];
+ final float[][] array3 = new float[2][3];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ array3[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ array3[1][2] = 100;
+ array3[1][2] = 100;
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = 127;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMultiDoubleArray() {
+ final double[][] array1 = new double[2][2];
+ final double[][] array2 = new double[2][2];
+ final double[][] array3 = new double[2][3];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ array3[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ array3[1][2] = 100;
+ array3[1][2] = 100;
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = 127;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMultiBooleanArray() {
+ final boolean[][] array1 = new boolean[2][2];
+ final boolean[][] array2 = new boolean[2][2];
+ final boolean[][] array3 = new boolean[2][3];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = i == 1 ^ j == 1;
+ array2[i][j] = i == 1 ^ j == 1;
+ array3[i][j] = i == 1 ^ j == 1;
+ }
+ }
+ array3[1][2] = false;
+ array3[1][2] = false;
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = true;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testRaggedArray() {
+ final long[][] array1 = new long[2][];
+ final long[][] array2 = new long[2][];
+ final long[][] array3 = new long[3][];
+ for (int i = 0; i < array1.length; ++i) {
+ array1[i] = new long[2];
+ array2[i] = new long[2];
+ array3[i] = new long[3];
+ for (int j = 0; j < array1[i].length; ++j) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ array3[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ array3[1][2] = 100;
+ array3[1][2] = 100;
+
+
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ array1[1][1] = 200;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testMixedArray() {
+ final Object[] array1 = new Object[2];
+ final Object[] array2 = new Object[2];
+ final Object[] array3 = new Object[2];
+ for (int i = 0; i < array1.length; ++i) {
+ array1[i] = new long[2];
+ array2[i] = new long[2];
+ array3[i] = new long[3];
+ for (int j = 0; j < 2; ++j) {
+ ((long[]) array1[i])[j] = (i + 1) * (j + 1);
+ ((long[]) array2[i])[j] = (i + 1) * (j + 1);
+ ((long[]) array3[i])[j] = (i + 1) * (j + 1);
+ }
+ }
+ ((long[]) array3[0])[2] = 1;
+ ((long[]) array3[1])[2] = 1;
+ assertEquals(0, new CompareToBuilder().append(array1, array1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(array1, array2).toComparison());
+ assertTrue(new CompareToBuilder().append(array1, array3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(array3, array1).toComparison() > 0);
+ ((long[]) array1[1])[1] = 200;
+ assertTrue(new CompareToBuilder().append(array1, array2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(array2, array1).toComparison() < 0);
+ }
+
+ @Test
+ public void testObjectArrayHiddenByObject() {
+ final TestObject[] array1 = new TestObject[2];
+ array1[0] = new TestObject(4);
+ array1[1] = new TestObject(5);
+ final TestObject[] array2 = new TestObject[2];
+ array2[0] = new TestObject(4);
+ array2[1] = new TestObject(5);
+ final TestObject[] array3 = new TestObject[3];
+ array3[0] = new TestObject(4);
+ array3[1] = new TestObject(5);
+ array3[2] = new TestObject(6);
+
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = new TestObject(7);
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testLongArrayHiddenByObject() {
+ final long[] array1 = new long[2];
+ array1[0] = 5L;
+ array1[1] = 6L;
+ final long[] array2 = new long[2];
+ array2[0] = 5L;
+ array2[1] = 6L;
+ final long[] array3 = new long[3];
+ array3[0] = 5L;
+ array3[1] = 6L;
+ array3[2] = 7L;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testIntArrayHiddenByObject() {
+ final int[] array1 = new int[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final int[] array2 = new int[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final int[] array3 = new int[3];
+ array3[0] = 5;
+ array3[1] = 6;
+ array3[2] = 7;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testShortArrayHiddenByObject() {
+ final short[] array1 = new short[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final short[] array2 = new short[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final short[] array3 = new short[3];
+ array3[0] = 5;
+ array3[1] = 6;
+ array3[2] = 7;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testCharArrayHiddenByObject() {
+ final char[] array1 = new char[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final char[] array2 = new char[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final char[] array3 = new char[3];
+ array3[0] = 5;
+ array3[1] = 6;
+ array3[2] = 7;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testByteArrayHiddenByObject() {
+ final byte[] array1 = new byte[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final byte[] array2 = new byte[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final byte[] array3 = new byte[3];
+ array3[0] = 5;
+ array3[1] = 6;
+ array3[2] = 7;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testDoubleArrayHiddenByObject() {
+ final double[] array1 = new double[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final double[] array2 = new double[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final double[] array3 = new double[3];
+ array3[0] = 5;
+ array3[1] = 6;
+ array3[2] = 7;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testFloatArrayHiddenByObject() {
+ final float[] array1 = new float[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final float[] array2 = new float[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final float[] array3 = new float[3];
+ array3[0] = 5;
+ array3[1] = 6;
+ array3[2] = 7;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = 7;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ @Test
+ public void testBooleanArrayHiddenByObject() {
+ final boolean[] array1 = new boolean[2];
+ array1[0] = true;
+ array1[1] = false;
+ final boolean[] array2 = new boolean[2];
+ array2[0] = true;
+ array2[1] = false;
+ final boolean[] array3 = new boolean[3];
+ array3[0] = true;
+ array3[1] = false;
+ array3[2] = true;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ final Object obj3 = array3;
+ assertEquals(0, new CompareToBuilder().append(obj1, obj1).toComparison());
+ assertEquals(0, new CompareToBuilder().append(obj1, obj2).toComparison());
+ assertTrue(new CompareToBuilder().append(obj1, obj3).toComparison() < 0);
+ assertTrue(new CompareToBuilder().append(obj3, obj1).toComparison() > 0);
+
+ array1[1] = true;
+ assertTrue(new CompareToBuilder().append(obj1, obj2).toComparison() > 0);
+ assertTrue(new CompareToBuilder().append(obj2, obj1).toComparison() < 0);
+ }
+
+ }
diff --git a/src/test/java/org/apache/commons/lang3/builder/DefaultToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/DefaultToStringStyleTest.java
new file mode 100644
index 000000000..a5f50211b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/DefaultToStringStyleTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.DefaultToStringStyleTest}.
+ */
+public class DefaultToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+ private final String baseStr = base.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(base));
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").toString());
+
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString());
+ assertEquals(baseStr + "[<null>,a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").append("a", "hello").toString());
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).toString());
+ assertEquals(baseStr + "[a=<null>]", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals(baseStr + "[a=<Integer>]", new ToStringBuilder(base).append("a", i3, false).toString());
+ }
+
+ @Test
+ public void testCollection() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", Collections.emptyList(), false).toString());
+ assertEquals(baseStr + "[a=[]]", new ToStringBuilder(base).append("a", Collections.emptyList(), true).toString());
+ assertEquals(baseStr + "[a=<size=1>]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), false).toString());
+ assertEquals(baseStr + "[a=[3]]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), true).toString());
+ assertEquals(baseStr + "[a=<size=2>]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), false).toString());
+ assertEquals(baseStr + "[a=[3, 4]]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), true).toString());
+ }
+
+ @Test
+ public void testMap() {
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", Collections.emptyMap(), false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", Collections.emptyMap(), true).toString());
+ assertEquals(baseStr + "[a=<size=1>]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), false).toString());
+ assertEquals(baseStr + "[a={k=v}]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), true).toString());
+ }
+
+ @Test
+ public void testArray() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", (Object) new Integer[0], false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", (Object) new Integer[0], true).toString());
+ assertEquals(baseStr + "[a=<size=1>]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, false).toString());
+ assertEquals(baseStr + "[a={3}]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, true).toString());
+ assertEquals(baseStr + "[a=<size=2>]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, false).toString());
+ assertEquals(baseStr + "[a={3,4}]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, true).toString());
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "John Doe";
+ p.age = 33;
+ p.smoker = false;
+ final String pBaseStr = p.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p));
+ assertEquals(pBaseStr + "[name=John Doe,age=33,smoker=false]", new ToStringBuilder(p).append("name", p.name).append("age", p.age).append("smoker", p.smoker).toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3L).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/DiffBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/DiffBuilderTest.java
new file mode 100644
index 000000000..705fee26f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/DiffBuilderTest.java
@@ -0,0 +1,498 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Unit tests {@link DiffBuilder}.
+ */
+public class DiffBuilderTest extends AbstractLangTest {
+
+ private static class TypeTestClass implements Diffable<TypeTestClass> {
+ private ToStringStyle style = SHORT_STYLE;
+ private boolean booleanField = true;
+ private boolean[] booleanArrayField = {true};
+ private byte byteField = (byte) 0xFF;
+ private byte[] byteArrayField = {(byte) 0xFF};
+ private char charField = 'a';
+ private char[] charArrayField = {'a'};
+ private double doubleField = 1.0;
+ private double[] doubleArrayField = {1.0};
+ private float floatField = 1.0f;
+ private float[] floatArrayField = {1.0f};
+ private int intField = 1;
+ private int[] intArrayField = {1};
+ private long longField = 1L;
+ private long[] longArrayField = {1L};
+ private short shortField = 1;
+ private short[] shortArrayField = {1};
+ private Object objectField = null;
+ private Object[] objectArrayField = {null};
+
+ @Override
+ public DiffResult<TypeTestClass> diff(final TypeTestClass obj) {
+ return new DiffBuilder<>(this, obj, style)
+ .append("boolean", booleanField, obj.booleanField)
+ .append("booleanArray", booleanArrayField, obj.booleanArrayField)
+ .append("byte", byteField, obj.byteField)
+ .append("byteArray", byteArrayField, obj.byteArrayField)
+ .append("char", charField, obj.charField)
+ .append("charArray", charArrayField, obj.charArrayField)
+ .append("double", doubleField, obj.doubleField)
+ .append("doubleArray", doubleArrayField, obj.doubleArrayField)
+ .append("float", floatField, obj.floatField)
+ .append("floatArray", floatArrayField, obj.floatArrayField)
+ .append("int", intField, obj.intField)
+ .append("intArray", intArrayField, obj.intArrayField)
+ .append("long", longField, obj.longField)
+ .append("longArray", longArrayField, obj.longArrayField)
+ .append("short", shortField, obj.shortField)
+ .append("shortArray", shortArrayField, obj.shortArrayField)
+ .append("objectField", objectField, obj.objectField)
+ .append("objectArrayField", objectArrayField, obj.objectArrayField)
+ .build();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj, false);
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this, false);
+ }
+ }
+
+ private static final ToStringStyle SHORT_STYLE = ToStringStyle.SHORT_PREFIX_STYLE;
+
+ @Test
+ public void testBoolean() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.booleanField = false;
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(Boolean.class, diff.getType());
+ assertEquals(Boolean.TRUE, diff.getLeft());
+ assertEquals(Boolean.FALSE, diff.getRight());
+ }
+
+ @Test
+ public void testBooleanArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.booleanArrayField = new boolean[] {false, false};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(ArrayUtils.toObject(class1.booleanArrayField),
+ (Object[]) diff.getLeft());
+ assertArrayEquals(ArrayUtils.toObject(class2.booleanArrayField),
+ (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testByte() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.byteField = 0x01;
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(Byte.valueOf(class1.byteField), diff.getLeft());
+ assertEquals(Byte.valueOf(class2.byteField), diff.getRight());
+ }
+
+ @Test
+ public void testByteArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.byteArrayField= new byte[] {0x01, 0x02};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(ArrayUtils.toObject(class1.byteArrayField),
+ (Object[]) diff.getLeft());
+ assertArrayEquals(ArrayUtils.toObject(class2.byteArrayField),
+ (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testByteArrayEqualAsObject() {
+ final DiffResult<String> list = new DiffBuilder<>("String1", "String2", SHORT_STYLE)
+ .append("foo", new boolean[] {false}, new boolean[] {false})
+ .append("foo", new byte[] {0x01}, new byte[] {0x01})
+ .append("foo", new char[] {'a'}, new char[] {'a'})
+ .append("foo", new double[] {1.0}, new double[] {1.0})
+ .append("foo", new float[] {1.0F}, new float[] {1.0F})
+ .append("foo", new int[] {1}, new int[] {1})
+ .append("foo", new long[] {1L}, new long[] {1L})
+ .append("foo", new short[] {1}, new short[] {1})
+ .append("foo", new Object[] {1, "two"}, new Object[] {1, "two"})
+ .build();
+
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testChar() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.charField = 'z';
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(Character.valueOf(class1.charField), diff.getLeft());
+ assertEquals(Character.valueOf(class2.charField), diff.getRight());
+ }
+
+ @Test
+ public void testCharArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.charArrayField = new char[] {'f', 'o', 'o'};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(ArrayUtils.toObject(class1.charArrayField),
+ (Object[]) diff.getLeft());
+ assertArrayEquals(ArrayUtils.toObject(class2.charArrayField),
+ (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testDiffResult() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.intField = 2;
+
+ final DiffResult<TypeTestClass> list = new DiffBuilder<>(class1, class2, SHORT_STYLE)
+ .append("prop1", class1.diff(class2))
+ .build();
+ assertEquals(1, list.getNumberOfDiffs());
+ assertEquals("prop1.int", list.getDiffs().get(0).getFieldName());
+ }
+
+ @Test
+ public void testDouble() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.doubleField = 99.99;
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(Double.valueOf(class1.doubleField), diff.getLeft());
+ assertEquals(Double.valueOf(class2.doubleField), diff.getRight());
+ }
+
+ @Test
+ public void testDoubleArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.doubleArrayField = new double[] {3.0, 2.9, 2.8};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(ArrayUtils.toObject(class1.doubleArrayField),
+ (Object[]) diff.getLeft());
+ assertArrayEquals(ArrayUtils.toObject(class2.doubleArrayField),
+ (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testFloat() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.floatField = 99.99F;
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(Float.valueOf(class1.floatField), diff.getLeft());
+ assertEquals(Float.valueOf(class2.floatField), diff.getRight());
+ }
+
+ @Test
+ public void testFloatArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.floatArrayField = new float[] {3.0F, 2.9F, 2.8F};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(ArrayUtils.toObject(class1.floatArrayField),
+ (Object[]) diff.getLeft());
+ assertArrayEquals(ArrayUtils.toObject(class2.floatArrayField),
+ (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testInt() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.intField = 42;
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(Integer.valueOf(class1.intField), diff.getLeft());
+ assertEquals(Integer.valueOf(class2.intField), diff.getRight());
+ }
+
+ @Test
+ public void testIntArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.intArrayField = new int[] {3, 2, 1};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(ArrayUtils.toObject(class1.intArrayField),
+ (Object[]) diff.getLeft());
+ assertArrayEquals(ArrayUtils.toObject(class2.intArrayField),
+ (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testLong() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.longField = 42L;
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(Long.valueOf(class1.longField), diff.getLeft());
+ assertEquals(Long.valueOf(class2.longField), diff.getRight());
+ }
+
+ @Test
+ public void testLongArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.longArrayField = new long[] {3L, 2L, 1L};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(ArrayUtils.toObject(class1.longArrayField),
+ (Object[]) diff.getLeft());
+ assertArrayEquals(ArrayUtils.toObject(class2.longArrayField),
+ (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testNullLhs() {
+ assertThrows(NullPointerException.class, () -> new DiffBuilder<>(null, this, ToStringStyle.DEFAULT_STYLE));
+ }
+
+ @Test
+ public void testNullRhs() {
+ assertThrows(NullPointerException.class, () -> new DiffBuilder<>(this, null, ToStringStyle.DEFAULT_STYLE));
+ }
+
+ @Test
+ public void testObject() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.objectField = "Some string";
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(class1.objectField, diff.getLeft());
+ assertEquals(class2.objectField, diff.getRight());
+ }
+
+ @Test
+ public void testObjectArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.objectArrayField = new Object[] {"string", 1, 2};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(class1.objectArrayField, (Object[]) diff.getLeft());
+ assertArrayEquals(class2.objectArrayField, (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testObjectArrayEqual() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class1.objectArrayField = new Object[] {"string", 1, 2};
+ class2.objectArrayField = new Object[] {"string", 1, 2};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ /**
+ * Test that "left" and "right" are the same instance but are equal.
+ */
+ @Test
+ public void testObjectsNotSameButEqual() {
+ final TypeTestClass left = new TypeTestClass();
+ left.objectField = Integer.valueOf(1000);
+ final TypeTestClass right = new TypeTestClass();
+ right.objectField = Integer.valueOf(1000);
+ assertNotSame(left.objectField, right.objectField);
+ assertEquals(left.objectField, right.objectField);
+
+ final DiffResult<TypeTestClass> list = left.diff(right);
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ /**
+ * Test that "left" and "right" are not the same instance and are not equal.
+ */
+ @Test
+ public void testObjectsNotSameNorEqual() {
+ final TypeTestClass left = new TypeTestClass();
+ left.objectField = 4;
+ final TypeTestClass right = new TypeTestClass();
+ right.objectField = 100;
+ assertNotSame(left.objectField, right.objectField);
+ assertNotEquals(left.objectField, right.objectField);
+
+ final DiffResult<TypeTestClass> list = left.diff(right);
+ assertEquals(1, list.getNumberOfDiffs());
+ }
+
+ /**
+ * Test that "left" and "right" are the same instance and are equal.
+ */
+ @Test
+ public void testObjectsSameAndEqual() {
+ final Integer sameObject = 1;
+ final TypeTestClass left = new TypeTestClass();
+ left.objectField = sameObject;
+ final TypeTestClass right = new TypeTestClass();
+ right.objectField = sameObject;
+ assertSame(left.objectField, right.objectField);
+ assertEquals(left.objectField, right.objectField);
+
+ final DiffResult<TypeTestClass> list = left.diff(right);
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testSameObjectIgnoresAppends() {
+ final TypeTestClass testClass = new TypeTestClass();
+ final DiffResult<TypeTestClass> list = new DiffBuilder<>(testClass, testClass, SHORT_STYLE)
+ .append("ignored", false, true)
+ .build();
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testShort() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.shortField = 42;
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertEquals(Short.valueOf(class1.shortField), diff.getLeft());
+ assertEquals(Short.valueOf(class2.shortField), diff.getRight());
+ }
+
+ @Test
+ public void testShortArray() {
+ final TypeTestClass class1 = new TypeTestClass();
+ final TypeTestClass class2 = new TypeTestClass();
+ class2.shortArrayField = new short[] {3, 2, 1};
+ final DiffResult<TypeTestClass> list = class1.diff(class2);
+ assertEquals(1, list.getNumberOfDiffs());
+ final Diff<?> diff = list.getDiffs().get(0);
+ assertArrayEquals(ArrayUtils.toObject(class1.shortArrayField),
+ (Object[]) diff.getLeft());
+ assertArrayEquals(ArrayUtils.toObject(class2.shortArrayField),
+ (Object[]) diff.getRight());
+ }
+
+ @Test
+ public void testSimilarObjectIgnoresAppends() {
+ final TypeTestClass testClass1 = new TypeTestClass();
+ final TypeTestClass testClass2 = new TypeTestClass();
+ final DiffResult<TypeTestClass> list = new DiffBuilder<>(testClass1, testClass2, SHORT_STYLE)
+ .append("ignored", false, true)
+ .build();
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testStylePassedToDiffResult() {
+ final TypeTestClass class1 = new TypeTestClass();
+ DiffResult<TypeTestClass> list = class1.diff(class1);
+ assertEquals(SHORT_STYLE, list.getToStringStyle());
+
+ class1.style = ToStringStyle.MULTI_LINE_STYLE;
+ list = class1.diff(class1);
+ assertEquals(ToStringStyle.MULTI_LINE_STYLE, list.getToStringStyle());
+ }
+
+ @Test
+ public void testTriviallyEqualTestDisabled() {
+ final Matcher<Integer> equalToOne = equalTo(1);
+
+ // Constructor's arguments are not trivially equal, but not testing for that.
+ final DiffBuilder<Integer> explicitTestAndNotEqual1 = new DiffBuilder<>(1, 2, null, false);
+ explicitTestAndNotEqual1.append("letter", "X", "Y");
+ assertThat(explicitTestAndNotEqual1.build().getNumberOfDiffs(), equalToOne);
+
+ // Constructor's arguments are trivially equal, but not testing for that.
+ final DiffBuilder<Integer> explicitTestAndNotEqual2 = new DiffBuilder<>(1, 1, null, false);
+ // This append(f, l, r) will not abort early.
+ explicitTestAndNotEqual2.append("letter", "X", "Y");
+ assertThat(explicitTestAndNotEqual2.build().getNumberOfDiffs(), equalToOne);
+ }
+
+ @Test
+ public void testTriviallyEqualTestEnabled() {
+ final Matcher<Integer> equalToZero = equalTo(0);
+ final Matcher<Integer> equalToOne = equalTo(1);
+
+ // The option to test if trivially equal is enabled by default.
+ final DiffBuilder<Integer> implicitTestAndEqual = new DiffBuilder<>(1, 1, null);
+ // This append(f, l, r) will abort without creating a Diff for letter.
+ implicitTestAndEqual.append("letter", "X", "Y");
+ assertThat(implicitTestAndEqual.build().getNumberOfDiffs(), equalToZero);
+
+ final DiffBuilder<Integer> implicitTestAndNotEqual = new DiffBuilder<>(1, 2, null);
+ // This append(f, l, r) will not abort early
+ // because the constructor's arguments were not trivially equal.
+ implicitTestAndNotEqual.append("letter", "X", "Y");
+ assertThat(implicitTestAndNotEqual.build().getNumberOfDiffs(), equalToOne);
+
+ // This is explicitly enabling the trivially equal test.
+ final DiffBuilder<Integer> explicitTestAndEqual = new DiffBuilder<>(1, 1, null, true);
+ explicitTestAndEqual.append("letter", "X", "Y");
+ assertThat(explicitTestAndEqual.build().getNumberOfDiffs(), equalToZero);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/DiffResultTest.java b/src/test/java/org/apache/commons/lang3/builder/DiffResultTest.java
new file mode 100644
index 000000000..a9a9cc19f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/DiffResultTest.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link DiffResult}.
+ */
+public class DiffResultTest extends AbstractLangTest {
+
+ private static final SimpleClass SIMPLE_FALSE = new SimpleClass(false);
+ private static final SimpleClass SIMPLE_TRUE = new SimpleClass(true);
+ private static final ToStringStyle SHORT_STYLE = ToStringStyle.SHORT_PREFIX_STYLE;
+
+ private static class SimpleClass implements Diffable<SimpleClass> {
+ private final boolean booleanField;
+
+ SimpleClass(final boolean booleanField) {
+ this.booleanField = booleanField;
+ }
+
+ static String getFieldName() {
+ return "booleanField";
+ }
+
+ @Override
+ public DiffResult<SimpleClass> diff(final SimpleClass obj) {
+ return new DiffBuilder<>(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append(getFieldName(), booleanField, obj.booleanField)
+ .build();
+ }
+ }
+
+ private static class EmptyClass {
+ // empty
+ }
+
+ @Test
+ public void testListIsNonModifiable() {
+ final SimpleClass lhs = new SimpleClass(true);
+ final SimpleClass rhs = new SimpleClass(false);
+
+ final List<Diff<?>> diffs = lhs.diff(rhs).getDiffs();
+
+ final DiffResult<SimpleClass> list = new DiffResult<>(lhs, rhs, diffs, SHORT_STYLE);
+ assertEquals(diffs, list.getDiffs());
+ assertEquals(1, list.getNumberOfDiffs());
+ assertThrows(UnsupportedOperationException.class, () -> list.getDiffs().remove(0));
+ }
+
+ @Test
+ public void testIterator() {
+ final SimpleClass lhs = new SimpleClass(true);
+ final SimpleClass rhs = new SimpleClass(false);
+
+ final List<Diff<?>> diffs = lhs.diff(rhs).getDiffs();
+ final Iterator<Diff<?>> expectedIterator = diffs.iterator();
+
+ final DiffResult<SimpleClass> list = new DiffResult<>(lhs, rhs, diffs, SHORT_STYLE);
+ final Iterator<Diff<?>> iterator = list.iterator();
+
+ while (iterator.hasNext()) {
+ assertTrue(expectedIterator.hasNext());
+ assertEquals(expectedIterator.next(), iterator.next());
+ }
+ }
+
+ @Test
+ public void testToStringOutput() {
+ final DiffResult<EmptyClass> list = new DiffBuilder<>(new EmptyClass(), new EmptyClass(),
+ ToStringStyle.SHORT_PREFIX_STYLE).append("test", false, true)
+ .build();
+ assertEquals(
+ "DiffResultTest.EmptyClass[test=false] differs from DiffResultTest.EmptyClass[test=true]",
+ list.toString());
+ }
+
+ @Test
+ public void testToStringSpecifyStyleOutput() {
+ final DiffResult<SimpleClass> list = SIMPLE_FALSE.diff(SIMPLE_TRUE);
+ assertEquals(list.getToStringStyle(), SHORT_STYLE);
+
+ final String lhsString = new ToStringBuilder(SIMPLE_FALSE,
+ ToStringStyle.MULTI_LINE_STYLE).append(
+ SimpleClass.getFieldName(), SIMPLE_FALSE.booleanField).build();
+
+ final String rhsString = new ToStringBuilder(SIMPLE_TRUE,
+ ToStringStyle.MULTI_LINE_STYLE).append(
+ SimpleClass.getFieldName(), SIMPLE_TRUE.booleanField).build();
+
+ final String expectedOutput = String.format("%s differs from %s", lhsString,
+ rhsString);
+ assertEquals(expectedOutput,
+ list.toString(ToStringStyle.MULTI_LINE_STYLE));
+ }
+
+ @Test
+ public void testNullLhs() {
+ assertThrows(NullPointerException.class,
+ () -> new DiffResult<>(null, SIMPLE_FALSE, SIMPLE_TRUE.diff(SIMPLE_FALSE).getDiffs(), SHORT_STYLE));
+ }
+
+ @Test
+ public void testNullRhs() {
+ assertThrows(NullPointerException.class,
+ () -> new DiffResult<>(SIMPLE_TRUE, null, SIMPLE_TRUE.diff(SIMPLE_FALSE).getDiffs(), SHORT_STYLE));
+ }
+
+ @Test
+ public void testNullList() {
+ assertThrows(NullPointerException.class,
+ () -> new DiffResult<>(SIMPLE_TRUE, SIMPLE_FALSE, null, SHORT_STYLE));
+ }
+
+ @Test
+ public void testNullStyle() {
+ final DiffResult<SimpleClass> diffResult = new DiffResult<>(SIMPLE_TRUE, SIMPLE_FALSE, SIMPLE_TRUE
+ .diff(SIMPLE_FALSE).getDiffs(), null);
+ assertEquals(ToStringStyle.DEFAULT_STYLE, diffResult.getToStringStyle());
+ }
+
+ @Test
+ public void testNoDifferencesString() {
+ final DiffResult<SimpleClass> diffResult = new DiffBuilder<>(SIMPLE_TRUE, SIMPLE_TRUE,
+ SHORT_STYLE).build();
+ assertEquals(DiffResult.OBJECTS_SAME_STRING, diffResult.toString());
+ }
+
+ @Test
+ public void testLeftAndRightGetters() {
+ final SimpleClass left = new SimpleClass(true);
+ final SimpleClass right = new SimpleClass(false);
+
+ final List<Diff<?>> diffs = left.diff(right).getDiffs();
+ final DiffResult diffResult = new DiffResult(left, right, diffs, SHORT_STYLE);
+
+ assertEquals(left, diffResult.getLeft());
+ assertEquals(right, diffResult.getRight());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/DiffTest.java b/src/test/java/org/apache/commons/lang3/builder/DiffTest.java
new file mode 100644
index 000000000..796699395
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/DiffTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Unit tests {@link Diff}.
+ */
+public class DiffTest extends AbstractLangTest {
+
+ private static final String FIELD_NAME = "field";
+ private static final Diff<Boolean> booleanDiff = new BooleanDiff(FIELD_NAME);
+
+ private static class BooleanDiff extends Diff<Boolean> {
+ private static final long serialVersionUID = 1L;
+
+ protected BooleanDiff(final String fieldName) {
+ super(fieldName);
+ }
+
+ @Override
+ public Boolean getLeft() {
+ return Boolean.TRUE;
+ }
+
+ @Override
+ public Boolean getRight() {
+ return Boolean.FALSE;
+ }
+ }
+
+ @Test
+ public void testCannotModify() {
+ assertThrows(UnsupportedOperationException.class, () -> booleanDiff.setValue(Boolean.FALSE));
+ }
+
+ @Test
+ public void testGetFieldName() {
+ assertEquals(FIELD_NAME, booleanDiff.getFieldName());
+ }
+
+ @Test
+ public void testGetType() {
+ assertEquals(Boolean.class, booleanDiff.getType());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals(String.format("[%s: %s, %s]", FIELD_NAME, booleanDiff.getLeft(),
+ booleanDiff.getRight()), booleanDiff.toString());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/EqualsBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/EqualsBuilderTest.java
new file mode 100644
index 000000000..eed05f414
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/EqualsBuilderTest.java
@@ -0,0 +1,1422 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.reflect.MethodUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.EqualsBuilder}.
+ */
+public class EqualsBuilderTest extends AbstractLangTest {
+
+
+ static class TestObject {
+ private int a;
+
+ TestObject() {
+ }
+
+ TestObject(final int a) {
+ this.a = a;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == null) {
+ return false;
+ }
+ if (o == this) {
+ return true;
+ }
+ if (o.getClass() != getClass()) {
+ return false;
+ }
+
+ final TestObject rhs = (TestObject) o;
+ return a == rhs.a;
+ }
+
+ @Override
+ public int hashCode() {
+ return a;
+ }
+
+ public void setA(final int a) {
+ this.a = a;
+ }
+
+ public int getA() {
+ return a;
+ }
+ }
+
+ static class TestSubObject extends TestObject {
+ private int b;
+
+ TestSubObject() {
+ super(0);
+ }
+
+ TestSubObject(final int a, final int b) {
+ super(a);
+ this.b = b;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == null) {
+ return false;
+ }
+ if (o == this) {
+ return true;
+ }
+ if (o.getClass() != getClass()) {
+ return false;
+ }
+
+ final TestSubObject rhs = (TestSubObject) o;
+ return super.equals(o) && b == rhs.b;
+ }
+
+ @Override
+ public int hashCode() {
+ return b * 17 + super.hashCode();
+ }
+
+ public void setB(final int b) {
+ this.b = b;
+ }
+
+ public int getB() {
+ return b;
+ }
+ }
+
+ static class TestEmptySubObject extends TestObject {
+ TestEmptySubObject(final int a) {
+ super(a);
+ }
+ }
+
+ static class TestTSubObject extends TestObject {
+ @SuppressWarnings("unused")
+ private final transient int t;
+
+ TestTSubObject(final int a, final int t) {
+ super(a);
+ this.t = t;
+ }
+ }
+
+ static class TestTTSubObject extends TestTSubObject {
+ @SuppressWarnings("unused")
+ private final transient int tt;
+
+ TestTTSubObject(final int a, final int t, final int tt) {
+ super(a, t);
+ this.tt = tt;
+ }
+ }
+
+ static class TestTTLeafObject extends TestTTSubObject {
+ @SuppressWarnings("unused")
+ private final int leafValue;
+
+ TestTTLeafObject(final int a, final int t, final int tt, final int leafValue) {
+ super(a, t, tt);
+ this.leafValue = leafValue;
+ }
+ }
+
+ static class TestTSubObject2 extends TestObject {
+ private transient int t;
+
+ TestTSubObject2(final int a, final int t) {
+ super(a);
+ }
+
+ public int getT() {
+ return t;
+ }
+
+ public void setT(final int t) {
+ this.t = t;
+ }
+ }
+
+ static class TestRecursiveGenericObject<T> {
+
+ private final T a;
+
+ TestRecursiveGenericObject(final T a) {
+ this.a = a;
+ }
+
+ public T getA() {
+ return a;
+ }
+ }
+
+ static class TestRecursiveObject {
+ private final TestRecursiveInnerObject a;
+ private final TestRecursiveInnerObject b;
+ private int z;
+
+ TestRecursiveObject(final TestRecursiveInnerObject a,
+ final TestRecursiveInnerObject b, final int z) {
+ this.a = a;
+ this.b = b;
+ }
+
+ public TestRecursiveInnerObject getA() {
+ return a;
+ }
+
+ public TestRecursiveInnerObject getB() {
+ return b;
+ }
+
+ public int getZ() {
+ return z;
+ }
+
+ }
+
+ static class TestRecursiveInnerObject {
+ private final int n;
+
+ TestRecursiveInnerObject(final int n) {
+ this.n = n;
+ }
+
+ public int getN() {
+ return n;
+ }
+ }
+
+ static class TestRecursiveCycleObject {
+ private TestRecursiveCycleObject cycle;
+ private final int n;
+
+ TestRecursiveCycleObject(final int n) {
+ this.n = n;
+ this.cycle = this;
+ }
+
+ TestRecursiveCycleObject(final TestRecursiveCycleObject cycle, final int n) {
+ this.n = n;
+ this.cycle = cycle;
+ }
+
+ public int getN() {
+ return n;
+ }
+
+ public TestRecursiveCycleObject getCycle() {
+ return cycle;
+ }
+
+ public void setCycle(final TestRecursiveCycleObject cycle) {
+ this.cycle = cycle;
+ }
+ }
+
+ @Test
+ public void testReflectionEquals() {
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(5);
+ assertTrue(EqualsBuilder.reflectionEquals(o1, o1));
+ assertFalse(EqualsBuilder.reflectionEquals(o1, o2));
+ o2.setA(4);
+ assertTrue(EqualsBuilder.reflectionEquals(o1, o2));
+
+ assertFalse(EqualsBuilder.reflectionEquals(o1, this));
+
+ assertFalse(EqualsBuilder.reflectionEquals(o1, null));
+ assertFalse(EqualsBuilder.reflectionEquals(null, o2));
+ assertTrue(EqualsBuilder.reflectionEquals(null, null));
+ }
+
+ @Test
+ public void testReflectionHierarchyEquals() {
+ testReflectionHierarchyEquals(false);
+ testReflectionHierarchyEquals(true);
+ // Transients
+ assertTrue(EqualsBuilder.reflectionEquals(new TestTTLeafObject(1, 2, 3, 4), new TestTTLeafObject(1, 2, 3, 4), true));
+ assertTrue(EqualsBuilder.reflectionEquals(new TestTTLeafObject(1, 2, 3, 4), new TestTTLeafObject(1, 2, 3, 4), false));
+ assertFalse(EqualsBuilder.reflectionEquals(new TestTTLeafObject(1, 0, 0, 4), new TestTTLeafObject(1, 2, 3, 4), true));
+ assertFalse(EqualsBuilder.reflectionEquals(new TestTTLeafObject(1, 2, 3, 4), new TestTTLeafObject(1, 2, 3, 0), true));
+ assertFalse(EqualsBuilder.reflectionEquals(new TestTTLeafObject(0, 2, 3, 4), new TestTTLeafObject(1, 2, 3, 4), true));
+ }
+
+ private void testReflectionHierarchyEquals(final boolean testTransients) {
+ final TestObject to1 = new TestObject(4);
+ final TestObject to1Bis = new TestObject(4);
+ final TestObject to1Ter = new TestObject(4);
+ final TestObject to2 = new TestObject(5);
+ final TestEmptySubObject teso = new TestEmptySubObject(4);
+ final TestTSubObject ttso = new TestTSubObject(4, 1);
+ final TestTTSubObject tttso = new TestTTSubObject(4, 1, 2);
+ final TestTTLeafObject ttlo = new TestTTLeafObject(4, 1, 2, 3);
+ final TestSubObject tso1 = new TestSubObject(1, 4);
+ final TestSubObject tso1bis = new TestSubObject(1, 4);
+ final TestSubObject tso1ter = new TestSubObject(1, 4);
+ final TestSubObject tso2 = new TestSubObject(2, 5);
+
+ testReflectionEqualsEquivalenceRelationship(to1, to1Bis, to1Ter, to2, new TestObject(), testTransients);
+ testReflectionEqualsEquivalenceRelationship(tso1, tso1bis, tso1ter, tso2, new TestSubObject(), testTransients);
+
+ // More sanity checks:
+
+ // same values
+ assertTrue(EqualsBuilder.reflectionEquals(ttlo, ttlo, testTransients));
+ assertTrue(EqualsBuilder.reflectionEquals(new TestSubObject(1, 10), new TestSubObject(1, 10), testTransients));
+ // same super values, diff sub values
+ assertFalse(EqualsBuilder.reflectionEquals(new TestSubObject(1, 10), new TestSubObject(1, 11), testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(new TestSubObject(1, 11), new TestSubObject(1, 10), testTransients));
+ // diff super values, same sub values
+ assertFalse(EqualsBuilder.reflectionEquals(new TestSubObject(0, 10), new TestSubObject(1, 10), testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(new TestSubObject(1, 10), new TestSubObject(0, 10), testTransients));
+
+ // mix super and sub types: equals
+ assertTrue(EqualsBuilder.reflectionEquals(to1, teso, testTransients));
+ assertTrue(EqualsBuilder.reflectionEquals(teso, to1, testTransients));
+
+ assertTrue(EqualsBuilder.reflectionEquals(to1, ttso, false)); // Force testTransients = false for this assert
+ assertTrue(EqualsBuilder.reflectionEquals(ttso, to1, false)); // Force testTransients = false for this assert
+
+ assertTrue(EqualsBuilder.reflectionEquals(to1, tttso, false)); // Force testTransients = false for this assert
+ assertTrue(EqualsBuilder.reflectionEquals(tttso, to1, false)); // Force testTransients = false for this assert
+
+ assertTrue(EqualsBuilder.reflectionEquals(ttso, tttso, false)); // Force testTransients = false for this assert
+ assertTrue(EqualsBuilder.reflectionEquals(tttso, ttso, false)); // Force testTransients = false for this assert
+
+ // mix super and sub types: NOT equals
+ assertFalse(EqualsBuilder.reflectionEquals(new TestObject(0), new TestEmptySubObject(1), testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(new TestEmptySubObject(1), new TestObject(0), testTransients));
+
+ assertFalse(EqualsBuilder.reflectionEquals(new TestObject(0), new TestTSubObject(1, 1), testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(new TestTSubObject(1, 1), new TestObject(0), testTransients));
+
+ assertFalse(EqualsBuilder.reflectionEquals(new TestObject(1), new TestSubObject(0, 10), testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(new TestSubObject(0, 10), new TestObject(1), testTransients));
+
+ assertFalse(EqualsBuilder.reflectionEquals(to1, ttlo));
+ assertFalse(EqualsBuilder.reflectionEquals(tso1, this));
+ }
+
+ /**
+ * Equivalence relationship tests inspired by "Effective Java":
+ * <ul>
+ * <li>reflection</li>
+ * <li>symmetry</li>
+ * <li>transitive</li>
+ * <li>consistency</li>
+ * <li>non-null reference</li>
+ * </ul>
+ *
+ * @param to a TestObject
+ * @param toBis a TestObject, equal to to and toTer
+ * @param toTer left-hand side, equal to to and toBis
+ * @param to2 a different TestObject
+ * @param oToChange a TestObject that will be changed
+ * @param testTransients whether to test transient instance variables
+ */
+ private void testReflectionEqualsEquivalenceRelationship(
+ final TestObject to,
+ final TestObject toBis,
+ final TestObject toTer,
+ final TestObject to2,
+ final TestObject oToChange,
+ final boolean testTransients) {
+
+ // reflection test
+ assertTrue(EqualsBuilder.reflectionEquals(to, to, testTransients));
+ assertTrue(EqualsBuilder.reflectionEquals(to2, to2, testTransients));
+
+ // symmetry test
+ assertTrue(EqualsBuilder.reflectionEquals(to, toBis, testTransients) && EqualsBuilder.reflectionEquals(toBis, to, testTransients));
+
+ // transitive test
+ assertTrue(
+ EqualsBuilder.reflectionEquals(to, toBis, testTransients)
+ && EqualsBuilder.reflectionEquals(toBis, toTer, testTransients)
+ && EqualsBuilder.reflectionEquals(to, toTer, testTransients));
+
+ // consistency test
+ oToChange.setA(to.getA());
+ if (oToChange instanceof TestSubObject) {
+ ((TestSubObject) oToChange).setB(((TestSubObject) to).getB());
+ }
+ assertTrue(EqualsBuilder.reflectionEquals(oToChange, to, testTransients));
+ assertTrue(EqualsBuilder.reflectionEquals(oToChange, to, testTransients));
+ oToChange.setA(to.getA() + 1);
+ if (oToChange instanceof TestSubObject) {
+ ((TestSubObject) oToChange).setB(((TestSubObject) to).getB() + 1);
+ }
+ assertFalse(EqualsBuilder.reflectionEquals(oToChange, to, testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(oToChange, to, testTransients));
+
+ // non-null reference test
+ assertFalse(EqualsBuilder.reflectionEquals(to, null, testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(to2, null, testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(null, to, testTransients));
+ assertFalse(EqualsBuilder.reflectionEquals(null, to2, testTransients));
+ assertTrue(EqualsBuilder.reflectionEquals(null, null, testTransients));
+ }
+
+ @Test
+ public void testSuper() {
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(5);
+ assertTrue(new EqualsBuilder().appendSuper(true).append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().appendSuper(false).append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().appendSuper(true).append(o1, o2).isEquals());
+ assertFalse(new EqualsBuilder().appendSuper(false).append(o1, o2).isEquals());
+ }
+
+ @Test
+ public void testObject() {
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(5);
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ o2.setA(4);
+ assertTrue(new EqualsBuilder().append(o1, o2).isEquals());
+
+ assertFalse(new EqualsBuilder().append(o1, this).isEquals());
+
+ assertFalse(new EqualsBuilder().append(o1, null).isEquals());
+ assertFalse(new EqualsBuilder().append(null, o2).isEquals());
+ assertTrue(new EqualsBuilder().append((Object) null, null).isEquals());
+ }
+
+ @Test
+ public void testObjectBuild() {
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(5);
+ assertEquals(Boolean.TRUE, new EqualsBuilder().append(o1, o1).build());
+ assertEquals(Boolean.FALSE, new EqualsBuilder().append(o1, o2).build());
+ o2.setA(4);
+ assertEquals(Boolean.TRUE, new EqualsBuilder().append(o1, o2).build());
+
+ assertEquals(Boolean.FALSE, new EqualsBuilder().append(o1, this).build());
+
+ assertEquals(Boolean.FALSE, new EqualsBuilder().append(o1, null).build());
+ assertEquals(Boolean.FALSE, new EqualsBuilder().append(null, o2).build());
+ assertEquals(Boolean.TRUE, new EqualsBuilder().append((Object) null, null).build());
+ }
+
+ @Test
+ public void testObjectRecursiveGenericInteger() {
+ final TestRecursiveGenericObject<Integer> o1_a = new TestRecursiveGenericObject<>(1);
+ final TestRecursiveGenericObject<Integer> o1_b = new TestRecursiveGenericObject<>(1);
+ final TestRecursiveGenericObject<Integer> o2 = new TestRecursiveGenericObject<>(2);
+
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_a, o1_b).isEquals());
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_b, o1_a).isEquals());
+
+ assertFalse(new EqualsBuilder().setTestRecursive(true).append(o1_b, o2).isEquals());
+ }
+
+ @Test
+ public void testObjectsBypassReflectionClasses() {
+ final List<Class<?>> bypassReflectionClasses = new ArrayList<>();
+ bypassReflectionClasses.add(List.class);
+ bypassReflectionClasses.add(Boolean.class);
+ assertTrue(new EqualsBuilder().setBypassReflectionClasses(bypassReflectionClasses).isEquals());
+ }
+
+ @Test
+ public void testObjectRecursiveGenericString() {
+ // Note: Do not use literals, because string literals are always mapped by same object (internal() of String))!
+ final String s1_a = String.valueOf(1);
+ final TestRecursiveGenericObject<String> o1_a = new TestRecursiveGenericObject<>(s1_a);
+ final TestRecursiveGenericObject<String> o1_b = new TestRecursiveGenericObject<>(String.valueOf(1));
+ final TestRecursiveGenericObject<String> o2 = new TestRecursiveGenericObject<>(String.valueOf(2));
+
+ // To trigger bug reported in LANG-1356, call hashCode only on string in instance o1_a
+ s1_a.hashCode();
+
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_a, o1_b).isEquals());
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_b, o1_a).isEquals());
+
+ assertFalse(new EqualsBuilder().setTestRecursive(true).append(o1_b, o2).isEquals());
+ }
+
+ @Test
+ public void testObjectRecursive() {
+ final TestRecursiveInnerObject i1_1 = new TestRecursiveInnerObject(1);
+ final TestRecursiveInnerObject i1_2 = new TestRecursiveInnerObject(1);
+ final TestRecursiveInnerObject i2_1 = new TestRecursiveInnerObject(2);
+ final TestRecursiveInnerObject i2_2 = new TestRecursiveInnerObject(2);
+ final TestRecursiveInnerObject i3 = new TestRecursiveInnerObject(3);
+ final TestRecursiveInnerObject i4 = new TestRecursiveInnerObject(4);
+
+ final TestRecursiveObject o1_a = new TestRecursiveObject(i1_1, i2_1, 1);
+ final TestRecursiveObject o1_b = new TestRecursiveObject(i1_2, i2_2, 1);
+ final TestRecursiveObject o2 = new TestRecursiveObject(i3, i4, 2);
+ final TestRecursiveObject oNull = new TestRecursiveObject(null, null, 2);
+
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_a, o1_a).isEquals());
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_a, o1_b).isEquals());
+
+ assertFalse(new EqualsBuilder().setTestRecursive(true).append(o1_a, o2).isEquals());
+
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(oNull, oNull).isEquals());
+ assertFalse(new EqualsBuilder().setTestRecursive(true).append(o1_a, oNull).isEquals());
+ }
+
+ @Test
+ public void testObjectRecursiveCycleSelfreference() {
+ final TestRecursiveCycleObject o1_a = new TestRecursiveCycleObject(1);
+ final TestRecursiveCycleObject o1_b = new TestRecursiveCycleObject(1);
+ final TestRecursiveCycleObject o2 = new TestRecursiveCycleObject(2);
+
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_a, o1_a).isEquals());
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_a, o1_b).isEquals());
+ assertFalse(new EqualsBuilder().setTestRecursive(true).append(o1_a, o2).isEquals());
+ }
+
+ @Test
+ public void testObjectRecursiveCycle() {
+ final TestRecursiveCycleObject o1_a = new TestRecursiveCycleObject(1);
+ final TestRecursiveCycleObject i1_a = new TestRecursiveCycleObject(o1_a, 100);
+ o1_a.setCycle(i1_a);
+
+ final TestRecursiveCycleObject o1_b = new TestRecursiveCycleObject(1);
+ final TestRecursiveCycleObject i1_b = new TestRecursiveCycleObject(o1_b, 100);
+ o1_b.setCycle(i1_b);
+
+ final TestRecursiveCycleObject o2 = new TestRecursiveCycleObject(2);
+ final TestRecursiveCycleObject i2 = new TestRecursiveCycleObject(o1_b, 200);
+ o2.setCycle(i2);
+
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_a, o1_a).isEquals());
+ assertTrue(new EqualsBuilder().setTestRecursive(true).append(o1_a, o1_b).isEquals());
+ assertFalse(new EqualsBuilder().setTestRecursive(true).append(o1_a, o2).isEquals());
+
+ assertTrue(EqualsBuilder.reflectionEquals(o1_a, o1_b, false, null, true));
+ assertFalse(EqualsBuilder.reflectionEquals(o1_a, o2, false, null, true));
+ }
+
+ @Test
+ public void testLong() {
+ final long o1 = 1L;
+ final long o2 = 2L;
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ }
+
+ @Test
+ public void testInt() {
+ final int o1 = 1;
+ final int o2 = 2;
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ }
+
+ @Test
+ public void testShort() {
+ final short o1 = 1;
+ final short o2 = 2;
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ }
+
+ @Test
+ public void testChar() {
+ final char o1 = 1;
+ final char o2 = 2;
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ }
+
+ @Test
+ public void testByte() {
+ final byte o1 = 1;
+ final byte o2 = 2;
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ }
+
+ @Test
+ public void testDouble() {
+ final double o1 = 1;
+ final double o2 = 2;
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, Double.NaN).isEquals());
+ assertTrue(new EqualsBuilder().append(Double.NaN, Double.NaN).isEquals());
+ assertTrue(new EqualsBuilder().append(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY).isEquals());
+ }
+
+ @Test
+ public void testFloat() {
+ final float o1 = 1;
+ final float o2 = 2;
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, Float.NaN).isEquals());
+ assertTrue(new EqualsBuilder().append(Float.NaN, Float.NaN).isEquals());
+ assertTrue(new EqualsBuilder().append(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY).isEquals());
+ }
+
+ @Test
+ public void testAccessors() {
+ final EqualsBuilder equalsBuilder = new EqualsBuilder();
+ assertTrue(equalsBuilder.isEquals());
+ equalsBuilder.setEquals(true);
+ assertTrue(equalsBuilder.isEquals());
+ equalsBuilder.setEquals(false);
+ assertFalse(equalsBuilder.isEquals());
+ }
+
+ @Test
+ public void testReset() {
+ final EqualsBuilder equalsBuilder = new EqualsBuilder();
+ assertTrue(equalsBuilder.isEquals());
+ equalsBuilder.setEquals(false);
+ assertFalse(equalsBuilder.isEquals());
+ equalsBuilder.reset();
+ assertTrue(equalsBuilder.isEquals());
+ }
+
+ @Test
+ public void testBoolean() {
+ final boolean o1 = true;
+ final boolean o2 = false;
+ assertTrue(new EqualsBuilder().append(o1, o1).isEquals());
+ assertFalse(new EqualsBuilder().append(o1, o2).isEquals());
+ }
+
+ @Test
+ public void testObjectArray() {
+ TestObject[] obj1 = new TestObject[3];
+ obj1[0] = new TestObject(4);
+ obj1[1] = new TestObject(5);
+ obj1[2] = null;
+ TestObject[] obj2 = new TestObject[3];
+ obj2[0] = new TestObject(4);
+ obj2[1] = new TestObject(5);
+ obj2[2] = null;
+
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj2, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1].setA(6);
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1].setA(5);
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[2] = obj1[1];
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[2] = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] obj1 = new long[2];
+ obj1[0] = 5L;
+ obj1[1] = 6L;
+ long[] obj2 = new long[2];
+ obj2[0] = 5L;
+ obj2[1] = 6L;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testIntArray() {
+ int[] obj1 = new int[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ int[] obj2 = new int[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testShortArray() {
+ short[] obj1 = new short[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ short[] obj2 = new short[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testCharArray() {
+ char[] obj1 = new char[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ char[] obj2 = new char[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testByteArray() {
+ byte[] obj1 = new byte[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ byte[] obj2 = new byte[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testDoubleArray() {
+ double[] obj1 = new double[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ double[] obj2 = new double[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testFloatArray() {
+ float[] obj1 = new float[2];
+ obj1[0] = 5;
+ obj1[1] = 6;
+ float[] obj2 = new float[2];
+ obj2[0] = 5;
+ obj2[1] = 6;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testBooleanArray() {
+ boolean[] obj1 = new boolean[2];
+ obj1[0] = true;
+ obj1[1] = false;
+ boolean[] obj2 = new boolean[2];
+ obj2[0] = true;
+ obj2[1] = false;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1[1] = true;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+
+ obj2 = null;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ obj1 = null;
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testMultiLongArray() {
+ final long[][] array1 = new long[2][2];
+ final long[][] array2 = new long[2][2];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testMultiIntArray() {
+ final int[][] array1 = new int[2][2];
+ final int[][] array2 = new int[2][2];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testMultiShortArray() {
+ final short[][] array1 = new short[2][2];
+ final short[][] array2 = new short[2][2];
+ for (short i = 0; i < array1.length; ++i) {
+ for (short j = 0; j < array1[0].length; j++) {
+ array1[i][j] = i;
+ array2[i][j] = i;
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testMultiCharArray() {
+ final char[][] array1 = new char[2][2];
+ final char[][] array2 = new char[2][2];
+ for (char i = 0; i < array1.length; ++i) {
+ for (char j = 0; j < array1[0].length; j++) {
+ array1[i][j] = i;
+ array2[i][j] = i;
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testMultiByteArray() {
+ final byte[][] array1 = new byte[2][2];
+ final byte[][] array2 = new byte[2][2];
+ for (byte i = 0; i < array1.length; ++i) {
+ for (byte j = 0; j < array1[0].length; j++) {
+ array1[i][j] = i;
+ array2[i][j] = i;
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testMultiFloatArray() {
+ final float[][] array1 = new float[2][2];
+ final float[][] array2 = new float[2][2];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testMultiDoubleArray() {
+ final double[][] array1 = new double[2][2];
+ final double[][] array2 = new double[2][2];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testMultiBooleanArray() {
+ final boolean[][] array1 = new boolean[2][2];
+ final boolean[][] array2 = new boolean[2][2];
+ for (int i = 0; i < array1.length; ++i) {
+ for (int j = 0; j < array1[0].length; j++) {
+ array1[i][j] = i == 1 || j == 1;
+ array2[i][j] = i == 1 || j == 1;
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = false;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+
+ // compare 1 dim to 2.
+ final boolean[] array3 = {true, true};
+ assertFalse(new EqualsBuilder().append(array1, array3).isEquals());
+ assertFalse(new EqualsBuilder().append(array3, array1).isEquals());
+ assertFalse(new EqualsBuilder().append(array2, array3).isEquals());
+ assertFalse(new EqualsBuilder().append(array3, array2).isEquals());
+ }
+
+ @Test
+ public void testRaggedArray() {
+ final long[][] array1 = new long[2][];
+ final long[][] array2 = new long[2][];
+ for (int i = 0; i < array1.length; ++i) {
+ array1[i] = new long[2];
+ array2[i] = new long[2];
+ for (int j = 0; j < array1[i].length; ++j) {
+ array1[i][j] = (i + 1) * (j + 1);
+ array2[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ array1[1][1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testMixedArray() {
+ final Object[] array1 = new Object[2];
+ final Object[] array2 = new Object[2];
+ for (int i = 0; i < array1.length; ++i) {
+ array1[i] = new long[2];
+ array2[i] = new long[2];
+ for (int j = 0; j < 2; ++j) {
+ ((long[]) array1[i])[j] = (i + 1) * (j + 1);
+ ((long[]) array2[i])[j] = (i + 1) * (j + 1);
+ }
+ }
+ assertTrue(new EqualsBuilder().append(array1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(array1, array2).isEquals());
+ ((long[]) array1[1])[1] = 0;
+ assertFalse(new EqualsBuilder().append(array1, array2).isEquals());
+ }
+
+ @Test
+ public void testObjectArrayHiddenByObject() {
+ final TestObject[] array1 = new TestObject[2];
+ array1[0] = new TestObject(4);
+ array1[1] = new TestObject(5);
+ final TestObject[] array2 = new TestObject[2];
+ array2[0] = new TestObject(4);
+ array2[1] = new TestObject(5);
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1].setA(6);
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testLongArrayHiddenByObject() {
+ final long[] array1 = new long[2];
+ array1[0] = 5L;
+ array1[1] = 6L;
+ final long[] array2 = new long[2];
+ array2[0] = 5L;
+ array2[1] = 6L;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testIntArrayHiddenByObject() {
+ final int[] array1 = new int[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final int[] array2 = new int[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testShortArrayHiddenByObject() {
+ final short[] array1 = new short[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final short[] array2 = new short[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testCharArrayHiddenByObject() {
+ final char[] array1 = new char[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final char[] array2 = new char[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testByteArrayHiddenByObject() {
+ final byte[] array1 = new byte[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final byte[] array2 = new byte[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testDoubleArrayHiddenByObject() {
+ final double[] array1 = new double[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final double[] array2 = new double[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testFloatArrayHiddenByObject() {
+ final float[] array1 = new float[2];
+ array1[0] = 5;
+ array1[1] = 6;
+ final float[] array2 = new float[2];
+ array2[0] = 5;
+ array2[1] = 6;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1] = 7;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ @Test
+ public void testBooleanArrayHiddenByObject() {
+ final boolean[] array1 = new boolean[2];
+ array1[0] = true;
+ array1[1] = false;
+ final boolean[] array2 = new boolean[2];
+ array2[0] = true;
+ array2[1] = false;
+ final Object obj1 = array1;
+ final Object obj2 = array2;
+ assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array1).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals());
+ assertTrue(new EqualsBuilder().append(obj1, array2).isEquals());
+ array1[1] = true;
+ assertFalse(new EqualsBuilder().append(obj1, obj2).isEquals());
+ }
+
+ public static class TestACanEqualB {
+ private final int a;
+
+ public TestACanEqualB(final int a) {
+ this.a = a;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof TestACanEqualB) {
+ return this.a == ((TestACanEqualB) o).getA();
+ }
+ if (o instanceof TestBCanEqualA) {
+ return this.a == ((TestBCanEqualA) o).getB();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return a;
+ }
+
+ public int getA() {
+ return this.a;
+ }
+ }
+
+ public static class TestBCanEqualA {
+ private final int b;
+
+ public TestBCanEqualA(final int b) {
+ this.b = b;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof TestACanEqualB) {
+ return this.b == ((TestACanEqualB) o).getA();
+ }
+ if (o instanceof TestBCanEqualA) {
+ return this.b == ((TestBCanEqualA) o).getB();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return b;
+ }
+
+ public int getB() {
+ return this.b;
+ }
+ }
+
+ /**
+ * Tests two instances of classes that can be equal and that are not "related". The two classes are not subclasses
+ * of each other and do not share a parent aside from Object.
+ * See https://issues.apache.org/jira/browse/LANG-6
+ */
+ @Test
+ public void testUnrelatedClasses() {
+ final Object[] x = {new TestACanEqualB(1)};
+ final Object[] y = {new TestBCanEqualA(1)};
+
+ // sanity checks:
+ assertArrayEquals(x, x);
+ assertArrayEquals(y, y);
+ assertArrayEquals(x, y);
+ assertArrayEquals(y, x);
+ // real tests:
+ assertEquals(x[0], x[0]);
+ assertEquals(y[0], y[0]);
+ assertEquals(x[0], y[0]);
+ assertEquals(y[0], x[0]);
+ assertTrue(new EqualsBuilder().append(x, x).isEquals());
+ assertTrue(new EqualsBuilder().append(y, y).isEquals());
+ assertTrue(new EqualsBuilder().append(x, y).isEquals());
+ assertTrue(new EqualsBuilder().append(y, x).isEquals());
+ }
+
+ /**
+ * Test from https://issues.apache.org/jira/browse/LANG-42
+ */
+ @Test
+ public void testNpeForNullElement() {
+ final Object[] x1 = {Integer.valueOf(1), null, Integer.valueOf(3)};
+ final Object[] x2 = {Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)};
+
+ // causes an NPE in 2.0 according to:
+ // https://issues.apache.org/jira/browse/LANG-42
+ new EqualsBuilder().append(x1, x2);
+ }
+
+ @Test
+ public void testReflectionEqualsExcludeFields() {
+ final TestObjectWithMultipleFields x1 = new TestObjectWithMultipleFields(1, 2, 3);
+ final TestObjectWithMultipleFields x2 = new TestObjectWithMultipleFields(1, 3, 4);
+
+ // not equal when including all fields
+ assertFalse(EqualsBuilder.reflectionEquals(x1, x2));
+
+ // doesn't barf on null, empty array, or non-existent field, but still tests as not equal
+ assertFalse(EqualsBuilder.reflectionEquals(x1, x2, (String[]) null));
+ assertFalse(EqualsBuilder.reflectionEquals(x1, x2));
+ assertFalse(EqualsBuilder.reflectionEquals(x1, x2, "xxx"));
+
+ // not equal if only one of the differing fields excluded
+ assertFalse(EqualsBuilder.reflectionEquals(x1, x2, "two"));
+ assertFalse(EqualsBuilder.reflectionEquals(x1, x2, "three"));
+
+ // equal if both differing fields excluded
+ assertTrue(EqualsBuilder.reflectionEquals(x1, x2, "two", "three"));
+
+ // still equal as long as both differing fields are among excluded
+ assertTrue(EqualsBuilder.reflectionEquals(x1, x2, "one", "two", "three"));
+ assertTrue(EqualsBuilder.reflectionEquals(x1, x2, "one", "two", "three", "xxx"));
+
+ // still equal as long as both differing fields are among excluded
+ assertTrue(EqualsBuilder.reflectionEquals(x1, x2, Arrays.asList("one", "two", "three")));
+ assertTrue(EqualsBuilder.reflectionEquals(x1, x2, Arrays.asList("one", "two", "three", "xxx")));
+
+ }
+
+ static class TestObjectWithMultipleFields {
+ @SuppressWarnings("unused")
+ private final TestObject one;
+ @SuppressWarnings("unused")
+ private final TestObject two;
+ @SuppressWarnings("unused")
+ private final TestObject three;
+
+ TestObjectWithMultipleFields(final int one, final int two, final int three) {
+ this.one = new TestObject(one);
+ this.two = new TestObject(two);
+ this.three = new TestObject(three);
+ }
+ }
+
+ /**
+ * Test cyclical object references which cause a StackOverflowException if
+ * not handled properly. s. LANG-606
+ */
+ @Test
+ public void testCyclicalObjectReferences() {
+ final TestObjectReference refX1 = new TestObjectReference(1);
+ final TestObjectReference x1 = new TestObjectReference(1);
+ x1.setObjectReference(refX1);
+ refX1.setObjectReference(x1);
+
+ final TestObjectReference refX2 = new TestObjectReference(1);
+ final TestObjectReference x2 = new TestObjectReference(1);
+ x2.setObjectReference(refX2);
+ refX2.setObjectReference(x2);
+
+ final TestObjectReference refX3 = new TestObjectReference(2);
+ final TestObjectReference x3 = new TestObjectReference(2);
+ x3.setObjectReference(refX3);
+ refX3.setObjectReference(x3);
+
+ assertEquals(x1, x2);
+ assertNull(EqualsBuilder.getRegistry());
+ assertNotEquals(x1, x3);
+ assertNull(EqualsBuilder.getRegistry());
+ assertNotEquals(x2, x3);
+ assertNull(EqualsBuilder.getRegistry());
+ }
+
+ static class TestObjectReference {
+ @SuppressWarnings("unused")
+ private TestObjectReference reference;
+ @SuppressWarnings("unused")
+ private final TestObject one;
+
+ TestObjectReference(final int one) {
+ this.one = new TestObject(one);
+ }
+
+ public void setObjectReference(final TestObjectReference reference) {
+ this.reference = reference;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
+ }
+
+ @Test
+ public void testReflectionArrays() {
+
+ final TestObject one = new TestObject(1);
+ final TestObject two = new TestObject(2);
+
+ final Object[] o1 = {one};
+ final Object[] o2 = {two};
+ final Object[] o3 = {one};
+
+ assertFalse(EqualsBuilder.reflectionEquals(o1, o2));
+ assertTrue(EqualsBuilder.reflectionEquals(o1, o1));
+ assertTrue(EqualsBuilder.reflectionEquals(o1, o3));
+
+ final double[] d1 = {0, 1};
+ final double[] d2 = {2, 3};
+ final double[] d3 = {0, 1};
+
+ assertFalse(EqualsBuilder.reflectionEquals(d1, d2));
+ assertTrue(EqualsBuilder.reflectionEquals(d1, d1));
+ assertTrue(EqualsBuilder.reflectionEquals(d1, d3));
+ }
+
+ static class TestObjectEqualsExclude {
+ @EqualsExclude
+ private final int a;
+ private final int b;
+
+ TestObjectEqualsExclude(final int a, final int b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ public int getA() {
+ return a;
+ }
+
+ public int getB() {
+ return b;
+ }
+ }
+
+ @Test
+ public void testToEqualsExclude() {
+ TestObjectEqualsExclude one = new TestObjectEqualsExclude(1, 2);
+ TestObjectEqualsExclude two = new TestObjectEqualsExclude(1, 3);
+
+ assertFalse(EqualsBuilder.reflectionEquals(one, two));
+
+ one = new TestObjectEqualsExclude(1, 2);
+ two = new TestObjectEqualsExclude(2, 2);
+
+ assertTrue(EqualsBuilder.reflectionEquals(one, two));
+ }
+
+ @Test
+ public void testReflectionAppend() {
+ assertTrue(EqualsBuilder.reflectionEquals(null, null));
+
+ final TestObject o1 = new TestObject(4);
+ final TestObject o2 = new TestObject(5);
+ assertTrue(new EqualsBuilder().reflectionAppend(o1, o1).build());
+ assertFalse(new EqualsBuilder().reflectionAppend(o1, o2).build());
+
+ o2.setA(4);
+ assertTrue(new EqualsBuilder().reflectionAppend(o1, o2).build());
+
+ assertFalse(new EqualsBuilder().reflectionAppend(o1, this).build());
+
+ assertFalse(new EqualsBuilder().reflectionAppend(o1, null).build());
+ assertFalse(new EqualsBuilder().reflectionAppend(null, o2).build());
+ }
+
+ @Test
+ public void testIsRegistered() throws Exception {
+ final Object firstObject = new Object();
+ final Object secondObject = new Object();
+
+ try {
+ final Method registerMethod = MethodUtils.getMatchingMethod(EqualsBuilder.class, "register", Object.class, Object.class);
+ registerMethod.setAccessible(true);
+ registerMethod.invoke(null, firstObject, secondObject);
+
+ assertTrue(EqualsBuilder.isRegistered(firstObject, secondObject));
+ assertTrue(EqualsBuilder.isRegistered(secondObject, firstObject)); // LANG-1349
+ } finally {
+ final Method unregisterMethod = MethodUtils.getMatchingMethod(EqualsBuilder.class, "unregister", Object.class, Object.class);
+ unregisterMethod.setAccessible(true);
+ unregisterMethod.invoke(null, firstObject, secondObject);
+ }
+ }
+}
+
diff --git a/src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderAndEqualsBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderAndEqualsBuilderTest.java
new file mode 100644
index 000000000..b12f61e22
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderAndEqualsBuilderTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link org.apache.commons.lang3.builder.HashCodeBuilder} and
+ * {@link org.apache.commons.lang3.builder.EqualsBuilderTest} to ensure that equal
+ * objects must have equal hash codes.
+ */
+public class HashCodeBuilderAndEqualsBuilderTest extends AbstractLangTest {
+
+
+ private void testInteger(final boolean testTransients) {
+ final Integer i1 = Integer.valueOf(12345);
+ final Integer i2 = Integer.valueOf(12345);
+ assertEqualsAndHashCodeContract(i1, i2, testTransients);
+ }
+
+ @Test
+ public void testInteger() {
+ testInteger(false);
+ }
+
+ @Test
+ public void testIntegerWithTransients() {
+ testInteger(true);
+ }
+
+ @Test
+ public void testFixture() {
+ testFixture(false);
+ }
+
+ @Test
+ public void testFixtureWithTransients() {
+ testFixture(true);
+ }
+
+ private void testFixture(final boolean testTransients) {
+ assertEqualsAndHashCodeContract(new TestFixture(2, 'c', "Test", (short) 2), new TestFixture(2, 'c', "Test", (short) 2), testTransients);
+ assertEqualsAndHashCodeContract(
+ new AllTransientFixture(2, 'c', "Test", (short) 2),
+ new AllTransientFixture(2, 'c', "Test", (short) 2),
+ testTransients);
+ assertEqualsAndHashCodeContract(
+ new SubTestFixture(2, 'c', "Test", (short) 2, "Same"),
+ new SubTestFixture(2, 'c', "Test", (short) 2, "Same"),
+ testTransients);
+ assertEqualsAndHashCodeContract(
+ new SubAllTransientFixture(2, 'c', "Test", (short) 2, "Same"),
+ new SubAllTransientFixture(2, 'c', "Test", (short) 2, "Same"),
+ testTransients);
+ }
+
+ /**
+ * Asserts that if {@code lhs} equals {@code rhs}
+ * then their hash codes MUST be identical.
+ *
+ * @param lhs The Left-Hand-Side of the equals test
+ * @param rhs The Right-Hand-Side of the equals test
+ * @param testTransients whether to test transient fields
+ */
+ private void assertEqualsAndHashCodeContract(final Object lhs, final Object rhs, final boolean testTransients) {
+ if (EqualsBuilder.reflectionEquals(lhs, rhs, testTransients)) {
+ // test a couple of times for consistency.
+ assertEquals(HashCodeBuilder.reflectionHashCode(lhs, testTransients), HashCodeBuilder.reflectionHashCode(rhs, testTransients));
+ assertEquals(HashCodeBuilder.reflectionHashCode(lhs, testTransients), HashCodeBuilder.reflectionHashCode(rhs, testTransients));
+ assertEquals(HashCodeBuilder.reflectionHashCode(lhs, testTransients), HashCodeBuilder.reflectionHashCode(rhs, testTransients));
+ }
+ }
+
+ static class TestFixture {
+ int i;
+ char c;
+ String string;
+ short s;
+
+ TestFixture(final int i, final char c, final String string, final short s) {
+ this.i = i;
+ this.c = c;
+ this.string = string;
+ this.s = s;
+ }
+ }
+
+ static class SubTestFixture extends TestFixture {
+ transient String tString;
+
+ SubTestFixture(final int i, final char c, final String string, final short s, final String tString) {
+ super(i, c, string, s);
+ this.tString = tString;
+ }
+ }
+
+ static class AllTransientFixture {
+ transient int i;
+ transient char c;
+ transient String string;
+ transient short s;
+
+ AllTransientFixture(final int i, final char c, final String string, final short s) {
+ this.i = i;
+ this.c = c;
+ this.string = string;
+ this.s = s;
+ }
+ }
+
+ static class SubAllTransientFixture extends AllTransientFixture {
+ transient String tString;
+
+ SubAllTransientFixture(final int i, final char c, final String string, final short s, final String tString) {
+ super(i, c, string, s);
+ this.tString = tString;
+ }
+ }
+
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderTest.java
new file mode 100644
index 000000000..cf045e946
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderTest.java
@@ -0,0 +1,623 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.HashCodeBuilder}.
+ */
+public class HashCodeBuilderTest extends AbstractLangTest {
+
+ /**
+ * A reflection test fixture.
+ */
+ static class ReflectionTestCycleA {
+ ReflectionTestCycleB b;
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+ }
+
+ /**
+ * A reflection test fixture.
+ */
+ static class ReflectionTestCycleB {
+ ReflectionTestCycleA a;
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+ }
+
+ static class TestObject {
+ private int a;
+
+ TestObject(final int a) {
+ this.a = a;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof TestObject)) {
+ return false;
+ }
+ final TestObject rhs = (TestObject) o;
+ return a == rhs.a;
+ }
+
+ public int getA() {
+ return a;
+ }
+
+ @Override
+ public int hashCode() {
+ return a;
+ }
+
+ public void setA(final int a) {
+ this.a = a;
+ }
+ }
+
+ static class TestObjectHashCodeExclude {
+ @HashCodeExclude
+ private final int a;
+ private final int b;
+
+ TestObjectHashCodeExclude(final int a, final int b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ public int getA() {
+ return a;
+ }
+
+ public int getB() {
+ return b;
+ }
+ }
+
+ static class TestObjectHashCodeExclude2 {
+ @HashCodeExclude
+ private final int a;
+ @HashCodeExclude
+ private final int b;
+
+ TestObjectHashCodeExclude2(final int a, final int b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ public int getA() {
+ return a;
+ }
+
+ public int getB() {
+ return b;
+ }
+ }
+
+ static class TestObjectWithMultipleFields {
+ @SuppressWarnings("unused")
+ private int one = 0;
+
+ @SuppressWarnings("unused")
+ private int two = 0;
+
+ @SuppressWarnings("unused")
+ private int three = 0;
+
+ TestObjectWithMultipleFields(final int one, final int two, final int three) {
+ this.one = one;
+ this.two = two;
+ this.three = three;
+ }
+ }
+
+ static class TestSubObject extends TestObject {
+ private int b;
+
+ @SuppressWarnings("unused")
+ private transient int t;
+
+ TestSubObject() {
+ super(0);
+ }
+
+ TestSubObject(final int a, final int b, final int t) {
+ super(a);
+ this.b = b;
+ this.t = t;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof TestSubObject)) {
+ return false;
+ }
+ final TestSubObject rhs = (TestSubObject) o;
+ return super.equals(o) && b == rhs.b;
+ }
+
+ @Override
+ public int hashCode() {
+ return b*17 + super.hashCode();
+ }
+
+ }
+
+ @Test
+ public void testBoolean() {
+ assertEquals(17 * 37 + 0, new HashCodeBuilder(17, 37).append(true).toHashCode());
+ assertEquals(17 * 37 + 1, new HashCodeBuilder(17, 37).append(false).toHashCode());
+ }
+
+ @Test
+ public void testBooleanArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((boolean[]) null).toHashCode());
+ final boolean[] obj = new boolean[2];
+ assertEquals((17 * 37 + 1) * 37 + 1, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = true;
+ assertEquals((17 * 37 + 0) * 37 + 1, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = false;
+ assertEquals((17 * 37 + 0) * 37 + 1, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testBooleanArrayAsObject() {
+ final boolean[] obj = new boolean[2];
+ assertEquals((17 * 37 + 1) * 37 + 1, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = true;
+ assertEquals((17 * 37 + 0) * 37 + 1, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = false;
+ assertEquals((17 * 37 + 0) * 37 + 1, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testBooleanMultiArray() {
+ final boolean[][] obj = new boolean[2][];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = new boolean[0];
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = new boolean[1];
+ assertEquals((17 * 37 + 1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = new boolean[2];
+ assertEquals(((17 * 37 + 1) * 37 + 1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0][0] = true;
+ assertEquals(((17 * 37 + 0) * 37 + 1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = new boolean[1];
+ assertEquals(((17 * 37 + 0) * 37 + 1) * 37 + 1, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testByte() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((byte) 0).toHashCode());
+ assertEquals(17 * 37 + 123, new HashCodeBuilder(17, 37).append((byte) 123).toHashCode());
+ }
+
+ @Test
+ public void testByteArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((byte[]) null).toHashCode());
+ final byte[] obj = new byte[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = (byte) 5;
+ assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = (byte) 6;
+ assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testByteArrayAsObject() {
+ final byte[] obj = new byte[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = (byte) 5;
+ assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = (byte) 6;
+ assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testChar() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((char) 0).toHashCode());
+ assertEquals(17 * 37 + 1234, new HashCodeBuilder(17, 37).append((char) 1234).toHashCode());
+ }
+
+ @Test
+ public void testCharArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((char[]) null).toHashCode());
+ final char[] obj = new char[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = (char) 5;
+ assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = (char) 6;
+ assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testCharArrayAsObject() {
+ final char[] obj = new char[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = (char) 5;
+ assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = (char) 6;
+ assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testConstructorExEvenFirst() {
+ assertThrows(IllegalArgumentException.class, () -> new HashCodeBuilder(2, 3));
+ }
+
+ @Test
+ public void testConstructorExEvenNegative() {
+ assertThrows(IllegalArgumentException.class, () -> new HashCodeBuilder(-2, -2));
+ }
+
+ @Test
+ public void testConstructorExEvenSecond() {
+ assertThrows(IllegalArgumentException.class, () -> new HashCodeBuilder(3, 2));
+ }
+
+ @Test
+ public void testConstructorExZero() {
+ assertThrows(IllegalArgumentException.class, () -> new HashCodeBuilder(0, 0));
+ }
+
+ @Test
+ public void testDouble() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(0d).toHashCode());
+ final double d = 1234567.89;
+ final long l = Double.doubleToLongBits(d);
+ assertEquals(17 * 37 + (int) (l ^ l >> 32), new HashCodeBuilder(17, 37).append(d).toHashCode());
+ }
+
+ @Test
+ public void testDoubleArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((double[]) null).toHashCode());
+ final double[] obj = new double[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = 5.4d;
+ final long l1 = Double.doubleToLongBits(5.4d);
+ final int h1 = (int) (l1 ^ l1 >> 32);
+ assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = 6.3d;
+ final long l2 = Double.doubleToLongBits(6.3d);
+ final int h2 = (int) (l2 ^ l2 >> 32);
+ assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testDoubleArrayAsObject() {
+ final double[] obj = new double[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = 5.4d;
+ final long l1 = Double.doubleToLongBits(5.4d);
+ final int h1 = (int) (l1 ^ l1 >> 32);
+ assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = 6.3d;
+ final long l2 = Double.doubleToLongBits(6.3d);
+ final int h2 = (int) (l2 ^ l2 >> 32);
+ assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testEquals() {
+ final HashCodeBuilder hcb1 = new HashCodeBuilder(17, 37).append(1).append('a');
+ final HashCodeBuilder hcb2 = new HashCodeBuilder(17, 37).append(1).append('a');
+ final HashCodeBuilder hcb3 = new HashCodeBuilder(17, 37).append(2).append('c');
+ assertEquals(hcb1, hcb1);
+ assertEquals(hcb1, hcb2);
+ assertEquals(hcb2, hcb1);
+ assertNotEquals(hcb1, hcb3);
+ assertNotEquals(hcb2, hcb3);
+ }
+
+ @Test
+ public void testFloat() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(0f).toHashCode());
+ final float f = 1234.89f;
+ final int i = Float.floatToIntBits(f);
+ assertEquals(17 * 37 + i, new HashCodeBuilder(17, 37).append(f).toHashCode());
+ }
+
+ @Test
+ public void testFloatArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((float[]) null).toHashCode());
+ final float[] obj = new float[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = 5.4f;
+ final int h1 = Float.floatToIntBits(5.4f);
+ assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = 6.3f;
+ final int h2 = Float.floatToIntBits(6.3f);
+ assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testFloatArrayAsObject() {
+ final float[] obj = new float[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = 5.4f;
+ final int h1 = Float.floatToIntBits(5.4f);
+ assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = 6.3f;
+ final int h2 = Float.floatToIntBits(6.3f);
+ assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testInt() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(0).toHashCode());
+ assertEquals(17 * 37 + 123456, new HashCodeBuilder(17, 37).append(123456).toHashCode());
+ }
+
+ @Test
+ public void testIntArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((int[]) null).toHashCode());
+ final int[] obj = new int[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = 5;
+ assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = 6;
+ assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testIntArrayAsObject() {
+ final int[] obj = new int[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = 5;
+ assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = 6;
+ assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(0L).toHashCode());
+ assertEquals(17 * 37 + (int) (123456789L ^ 123456789L >> 32), new HashCodeBuilder(17, 37).append(
+ 123456789L).toHashCode());
+ }
+
+ @Test
+ public void testLongArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((long[]) null).toHashCode());
+ final long[] obj = new long[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = 5L;
+ final int h1 = (int) (5L ^ 5L >> 32);
+ assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = 6L;
+ final int h2 = (int) (6L ^ 6L >> 32);
+ assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testLongArrayAsObject() {
+ final long[] obj = new long[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = 5L;
+ final int h1 = (int) (5L ^ 5L >> 32);
+ assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = 6L;
+ final int h2 = (int) (6L ^ 6L >> 32);
+ assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testObject() {
+ Object obj = null;
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj = new Object();
+ assertEquals(17 * 37 + obj.hashCode(), new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testObjectArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((Object[]) null).toHashCode());
+ final Object[] obj = new Object[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = new Object();
+ assertEquals((17 * 37 + obj[0].hashCode()) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = new Object();
+ assertEquals((17 * 37 + obj[0].hashCode()) * 37 + obj[1].hashCode(), new HashCodeBuilder(17, 37).append(obj)
+ .toHashCode());
+ }
+
+ @Test
+ public void testObjectArrayAsObject() {
+ final Object[] obj = new Object[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = new Object();
+ assertEquals((17 * 37 + obj[0].hashCode()) * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = new Object();
+ assertEquals((17 * 37 + obj[0].hashCode()) * 37 + obj[1].hashCode(), new HashCodeBuilder(17, 37).append(
+ (Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testObjectBuild() {
+ Object obj = null;
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(obj).build().intValue());
+ obj = new Object();
+ assertEquals(17 * 37 + obj.hashCode(), new HashCodeBuilder(17, 37).append(obj).build().intValue());
+ }
+
+ @Test
+ public void testReflectionHashCode() {
+ assertEquals(17 * 37, HashCodeBuilder.reflectionHashCode(new TestObject(0)));
+ assertEquals(17 * 37 + 123456, HashCodeBuilder.reflectionHashCode(new TestObject(123456)));
+ }
+
+ @Test
+ public void testReflectionHashCodeEx1() {
+ assertThrows(IllegalArgumentException.class, () -> HashCodeBuilder.reflectionHashCode(0, 0, new TestObject(0), true));
+ }
+
+ @Test
+ public void testReflectionHashCodeEx2() {
+ assertThrows(IllegalArgumentException.class, () -> HashCodeBuilder.reflectionHashCode(2, 2, new TestObject(0), true));
+ }
+
+ @Test
+ public void testReflectionHashCodeEx3() {
+ assertThrows(NullPointerException.class, () -> HashCodeBuilder.reflectionHashCode(13, 19, null, true));
+ }
+
+ @Test
+ public void testReflectionHashCodeExcludeFields() {
+ final TestObjectWithMultipleFields x = new TestObjectWithMultipleFields(1, 2, 3);
+
+ assertEquals(((17 * 37 + 1) * 37 + 3) * 37 + 2, HashCodeBuilder.reflectionHashCode(x));
+
+ assertEquals(((17 * 37 + 1) * 37 + 3) * 37 + 2, HashCodeBuilder.reflectionHashCode(x, (String[]) null));
+ assertEquals(((17 * 37 + 1) * 37 + 3) * 37 + 2, HashCodeBuilder.reflectionHashCode(x));
+ assertEquals(((17 * 37 + 1) * 37 + 3) * 37 + 2, HashCodeBuilder.reflectionHashCode(x, "xxx"));
+
+ assertEquals((17 * 37 + 1) * 37 + 3, HashCodeBuilder.reflectionHashCode(x, "two"));
+ assertEquals((17 * 37 + 1) * 37 + 2, HashCodeBuilder.reflectionHashCode(x, "three"));
+
+ assertEquals(17 * 37 + 1, HashCodeBuilder.reflectionHashCode(x, "two", "three"));
+
+ assertEquals(17, HashCodeBuilder.reflectionHashCode(x, "one", "two", "three"));
+ assertEquals(17, HashCodeBuilder.reflectionHashCode(x, "one", "two", "three", "xxx"));
+ }
+
+ @Test
+ public void testReflectionHierarchyHashCode() {
+ assertEquals(17 * 37 * 37, HashCodeBuilder.reflectionHashCode(new TestSubObject(0, 0, 0)));
+ assertEquals(17 * 37 * 37 * 37, HashCodeBuilder.reflectionHashCode(new TestSubObject(0, 0, 0), true));
+ assertEquals((17 * 37 + 7890) * 37 + 123456, HashCodeBuilder.reflectionHashCode(new TestSubObject(123456, 7890,
+ 0)));
+ assertEquals(((17 * 37 + 7890) * 37 + 0) * 37 + 123456, HashCodeBuilder.reflectionHashCode(new TestSubObject(
+ 123456, 7890, 0), true));
+ }
+
+ @Test
+ public void testReflectionHierarchyHashCodeEx1() {
+ assertThrows(IllegalArgumentException.class, () -> HashCodeBuilder.reflectionHashCode(0, 0, new TestSubObject(0, 0, 0), true));
+ }
+
+ @Test
+ public void testReflectionHierarchyHashCodeEx2() {
+ assertThrows(IllegalArgumentException.class, () -> HashCodeBuilder.reflectionHashCode(2, 2, new TestSubObject(0, 0, 0), true));
+ }
+
+ /**
+ * Test Objects pointing to each other.
+ */
+ @Test
+ public void testReflectionObjectCycle() {
+ final ReflectionTestCycleA a = new ReflectionTestCycleA();
+ final ReflectionTestCycleB b = new ReflectionTestCycleB();
+ a.b = b;
+ b.a = a;
+
+ // Used to caused:
+ // java.lang.StackOverflowError
+ // at java.lang.ClassLoader.getCallerClassLoader(Native Method)
+ // at java.lang.Class.getDeclaredFields(Class.java:992)
+ // at org.apache.commons.lang.builder.HashCodeBuilder.reflectionAppend(HashCodeBuilder.java:373)
+ // at org.apache.commons.lang.builder.HashCodeBuilder.reflectionHashCode(HashCodeBuilder.java:349)
+ // at org.apache.commons.lang.builder.HashCodeBuilder.reflectionHashCode(HashCodeBuilder.java:155)
+ // at
+ // org.apache.commons.lang.builder.HashCodeBuilderTest$ReflectionTestCycleB.hashCode(HashCodeBuilderTest.java:53)
+ // at org.apache.commons.lang.builder.HashCodeBuilder.append(HashCodeBuilder.java:422)
+ // at org.apache.commons.lang.builder.HashCodeBuilder.reflectionAppend(HashCodeBuilder.java:383)
+ // at org.apache.commons.lang.builder.HashCodeBuilder.reflectionHashCode(HashCodeBuilder.java:349)
+ // at org.apache.commons.lang.builder.HashCodeBuilder.reflectionHashCode(HashCodeBuilder.java:155)
+ // at
+ // org.apache.commons.lang.builder.HashCodeBuilderTest$ReflectionTestCycleA.hashCode(HashCodeBuilderTest.java:42)
+ // at org.apache.commons.lang.builder.HashCodeBuilder.append(HashCodeBuilder.java:422)
+
+ a.hashCode();
+ assertNull(HashCodeBuilder.getRegistry());
+ b.hashCode();
+ assertNull(HashCodeBuilder.getRegistry());
+ }
+
+ @Test
+ public void testShort() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((short) 0).toHashCode());
+ assertEquals(17 * 37 + 12345, new HashCodeBuilder(17, 37).append((short) 12345).toHashCode());
+ }
+
+ @Test
+ public void testShortArray() {
+ assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((short[]) null).toHashCode());
+ final short[] obj = new short[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[0] = (short) 5;
+ assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ obj[1] = (short) 6;
+ assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append(obj).toHashCode());
+ }
+
+ @Test
+ public void testShortArrayAsObject() {
+ final short[] obj = new short[2];
+ assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[0] = (short) 5;
+ assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ obj[1] = (short) 6;
+ assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append((Object) obj).toHashCode());
+ }
+
+ @Test
+ public void testSuper() {
+ final Object obj = new Object();
+ assertEquals(17 * 37 + 19 * 41 + obj.hashCode(), new HashCodeBuilder(17, 37).appendSuper(
+ new HashCodeBuilder(19, 41).append(obj).toHashCode()).toHashCode());
+ }
+
+ /**
+ * Ensures LANG-520 remains true
+ */
+ @Test
+ public void testToHashCodeEqualsHashCode() {
+ final HashCodeBuilder hcb = new HashCodeBuilder(17, 37).append(new Object()).append('a');
+ assertEquals(hcb.toHashCode(), hcb.hashCode(),
+ "hashCode() is no longer returning the same value as toHashCode() - see LANG-520");
+ }
+
+ @Test
+ public void testToHashCodeExclude() {
+ final TestObjectHashCodeExclude one = new TestObjectHashCodeExclude(1, 2);
+ final TestObjectHashCodeExclude2 two = new TestObjectHashCodeExclude2(1, 2);
+ assertEquals(17 * 37 + 2, HashCodeBuilder.reflectionHashCode(one));
+ assertEquals(17, HashCodeBuilder.reflectionHashCode(two));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/JsonToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/JsonToStringStyleTest.java
new file mode 100644
index 000000000..56eef75c5
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/JsonToStringStyleTest.java
@@ -0,0 +1,698 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.JsonToStringStyleTest}.
+ */
+public class JsonToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.JSON_STYLE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testNull() {
+ assertEquals("null", new ToStringBuilder(null).toString());
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals("{}", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals(
+ "{}",
+ new ToStringBuilder(base).appendSuper(
+ "Integer@8888[" + System.lineSeparator() + "]")
+ .toString());
+ assertEquals(
+ "{}",
+ new ToStringBuilder(base).appendSuper(
+ "Integer@8888[" + System.lineSeparator() + " null"
+ + System.lineSeparator() + "]").toString());
+ assertEquals(
+ "{\"a\":\"hello\"}",
+ new ToStringBuilder(base)
+ .appendSuper(
+ "Integer@8888[" + System.lineSeparator()
+ + "]").append("a", "hello").toString());
+ assertEquals(
+ "{\"a\":\"hello\"}",
+ new ToStringBuilder(base)
+ .appendSuper(
+ "Integer@8888[" + System.lineSeparator()
+ + " null" + System.lineSeparator()
+ + "]").append("a", "hello").toString());
+ assertEquals("{\"a\":\"hello\"}", new ToStringBuilder(base)
+ .appendSuper(null).append("a", "hello").toString());
+
+ assertEquals("{\"a\":\"hello\",\"b\":\"world\"}", new ToStringBuilder(base)
+ .appendSuper("{\"a\":\"hello\"}").append("b", "world").toString());
+ }
+
+ @Test
+ public void testChar() {
+ assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append('A').toString());
+
+ assertEquals("{\"a\":\"A\"}", new ToStringBuilder(base).append("a", 'A')
+ .toString());
+ assertEquals("{\"a\":\"A\",\"b\":\"B\"}", new ToStringBuilder(base).append("a", 'A').append("b", 'B')
+ .toString());
+ }
+
+ @Test
+ public void testDate() {
+ final Date now = new Date();
+ final Date afterNow = new Date(System.currentTimeMillis() + 1);
+
+ assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append(now).toString());
+
+ assertEquals("{\"now\":\"" + now.toString() +"\"}", new ToStringBuilder(base).append("now", now)
+ .toString());
+ assertEquals("{\"now\":\"" + now.toString() +"\",\"after\":\"" + afterNow.toString() + "\"}", new ToStringBuilder(base).append("now", now).append("after", afterNow)
+ .toString());
+ }
+
+ @Test
+ public void testObject() {
+
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> new ToStringBuilder(base).append((Object) null).toString());
+
+ assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append(i3).toString());
+
+ assertEquals("{\"a\":null}",
+ new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals("{\"a\":3}", new ToStringBuilder(base).append("a", i3)
+ .toString());
+ assertEquals("{\"a\":3,\"b\":4}",
+ new ToStringBuilder(base).append("a", i3).append("b", i4)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> new ToStringBuilder(base).append("a", i3, false).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> new ToStringBuilder(base).append("a", new ArrayList<>(), false).toString());
+
+ assertEquals(
+ "{\"a\":[]}",
+ new ToStringBuilder(base).append("a", new ArrayList<>(),
+ true).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> new ToStringBuilder(base).append("a", new HashMap<>(), false).toString());
+
+ assertEquals(
+ "{\"a\":{}}",
+ new ToStringBuilder(base).append("a",
+ new HashMap<>(), true).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> new ToStringBuilder(base).append("a", (Object) new String[0], false).toString());
+
+ assertEquals(
+ "{\"a\":[]}",
+ new ToStringBuilder(base).append("a", (Object) new String[0],
+ true).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> new ToStringBuilder(base).append("a", (Object) new int[]{1, 2, 3}, false).toString());
+
+ assertEquals(
+ "{\"a\":[1,2,3]}",
+ new ToStringBuilder(base).append("a",
+ (Object) new int[]{1, 2, 3}, true).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> new ToStringBuilder(base).append("a", (Object) new String[]{"v", "x", "y", "z"}, false).toString());
+
+ assertEquals(
+ "{\"a\":[\"v\",\"x\",\"y\",\"z\"]}",
+ new ToStringBuilder(base).append("a",
+ (Object) new String[]{"v", "x", "y", "z"}, true)
+ .toString());
+ }
+
+ @Test
+ public void testList() {
+ final Student student = new Student();
+ final ArrayList<Hobby> objects = new ArrayList<>();
+
+ objects.add(Hobby.BOOK);
+ objects.add(Hobby.SPORT);
+ objects.add(Hobby.MUSIC);
+
+ student.setHobbies(objects);
+
+ assertEquals(student.toString(), "{\"hobbies\":[\"BOOK\",\"SPORT\",\"MUSIC\"]}");
+ student.setHobbies(new ArrayList<>());
+ assertEquals(student.toString(), "{\"hobbies\":[]}");
+ student.setHobbies(null);
+ assertEquals(student.toString(), "{\"hobbies\":null}");
+ }
+
+ @Test
+ public void testArrayEnum() {
+ final Teacher teacher = new Teacher();
+ final Hobby[] hobbies = new Hobby[3];
+ hobbies[0] = Hobby.BOOK;
+ hobbies[1] = Hobby.SPORT;
+ hobbies[2] = Hobby.MUSIC;
+
+ teacher.setHobbies(hobbies);
+
+ assertEquals(teacher.toString(), "{\"hobbies\":[\"BOOK\",\"SPORT\",\"MUSIC\"]}");
+ teacher.setHobbies(new Hobby[0]);
+ assertEquals(teacher.toString(), "{\"hobbies\":[]}");
+ teacher.setHobbies(null);
+ assertEquals(teacher.toString(), "{\"hobbies\":null}");
+ }
+
+ @Test
+ public void testCombineListAndEnum() {
+ final Teacher teacher = new Teacher();
+
+ final Hobby[] teacherHobbies = new Hobby[3];
+ teacherHobbies[0] = Hobby.BOOK;
+ teacherHobbies[1] = Hobby.SPORT;
+ teacherHobbies[2] = Hobby.MUSIC;
+
+ teacher.setHobbies(teacherHobbies);
+
+ final Student john = new Student();
+ john.setHobbies(Arrays.asList(Hobby.BOOK, Hobby.MUSIC));
+
+ final Student alice = new Student();
+ alice.setHobbies(new ArrayList<>());
+
+ final Student bob = new Student();
+ bob.setHobbies(Collections.singletonList(Hobby.BOOK));
+
+ final ArrayList<Student> students = new ArrayList<>();
+ students.add(john);
+ students.add(alice);
+ students.add(bob);
+
+ final AcademyClass academyClass = new AcademyClass();
+ academyClass.setStudents(students);
+ academyClass.setTeacher(teacher);
+
+ assertEquals(academyClass.toString(), "{\"students\":[{\"hobbies\":[\"BOOK\",\"MUSIC\"]},{\"hobbies\":[]},{\"hobbies\":[\"BOOK\"]}],\"teacher\":{\"hobbies\":[\"BOOK\",\"SPORT\",\"MUSIC\"]}}");
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "Jane Doe";
+ p.age = 25;
+ p.smoker = true;
+
+ assertEquals(
+ "{\"name\":\"Jane Doe\",\"age\":25,\"smoker\":true}",
+ new ToStringBuilder(p).append("name", p.name)
+ .append("age", p.age).append("smoker", p.smoker)
+ .toString());
+ }
+
+ @Test
+ public void testNestingPerson() {
+ final Person p = new Person() {
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).append("name", this.name)
+ .append("age", this.age).append("smoker", this.smoker)
+ .toString();
+ }
+ };
+ p.name = "Jane Doe";
+ p.age = 25;
+ p.smoker = true;
+
+ final NestingPerson nestP = new NestingPerson();
+ nestP.pid="#1@Jane";
+ nestP.person = p;
+
+ assertEquals(
+ "{\"pid\":\"#1@Jane\",\"person\":{\"name\":\"Jane Doe\",\"age\":25,\"smoker\":true}}",
+ new ToStringBuilder(nestP).append("pid", nestP.pid)
+ .append("person", nestP.person)
+ .toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append(3L).toString());
+
+ assertEquals("{\"a\":3}", new ToStringBuilder(base).append("a", 3L)
+ .toString());
+ assertEquals("{\"a\":3,\"b\":4}",
+ new ToStringBuilder(base).append("a", 3L).append("b", 4L)
+ .toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ final Object[] array = {null, base, new int[]{3, 6}};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"objectArray\":[null,5,[3,6]]}", toStringBuilder.append("objectArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ final long[] array = {1, 2, -3, 4};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"longArray\":[1,2,-3,4]}", toStringBuilder.append("longArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testIntArray() {
+ final int[] array = {1, 2, -3, 4};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"intArray\":[1,2,-3,4]}", toStringBuilder.append("intArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testByteArray() {
+ final byte[] array = {1, 2, -3, 4};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"byteArray\":[1,2,-3,4]}", toStringBuilder.append("byteArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testShortArray() {
+ final short[] array = {1, 2, -3, 4};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"shortArray\":[1,2,-3,4]}", toStringBuilder.append("shortArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testDoubleArray() {
+ final double[] array = {1, 2, -3, 4};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"doubleArray\":[1.0,2.0,-3.0,4.0]}", toStringBuilder.append("doubleArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testFloatArray() {
+ final float[] array = {1, 2, -3, 4};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"floatArray\":[1.0,2.0,-3.0,4.0]}", toStringBuilder.append("floatArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testCharArray() {
+ final char[] array = {'1', '2', '3', '4'};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"charArray\":[\"1\",\"2\",\"3\",\"4\"]}", toStringBuilder.append("charArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testBooleanArray() {
+ final boolean[] array = {true, false};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertEquals("{\"booleanArray\":[true,false]}", toStringBuilder.append("booleanArray", array)
+ .toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ final long[][] array = {{1, 2}, null, {5}};
+
+ final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
+ assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((long[][]) null).toString());
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
+ }
+
+ @Test
+ public void testArray() {
+ final Person p = new Person();
+ p.name = "Jane Doe";
+ p.age = 25;
+ p.smoker = true;
+
+ assertEquals(
+ "{\"name\":\"Jane Doe\",\"age\":25,\"smoker\":true,\"groups\":['admin', 'manager', 'user']}",
+ new ToStringBuilder(p).append("name", p.name)
+ .append("age", p.age).append("smoker", p.smoker)
+ .append("groups", new Object() {
+ @Override
+ public String toString() {
+ return "['admin', 'manager', 'user']";
+ }
+ })
+ .toString());
+ }
+
+ @Test
+ public void testLANG1395() {
+ assertEquals("{\"name\":\"value\"}", new ToStringBuilder(base).append("name", "value").toString());
+ assertEquals("{\"name\":\"\"}", new ToStringBuilder(base).append("name", "").toString());
+ assertEquals("{\"name\":\"\\\"\"}", new ToStringBuilder(base).append("name", '"').toString());
+ assertEquals("{\"name\":\"\\\\\"}", new ToStringBuilder(base).append("name", '\\').toString());
+ assertEquals("{\"name\":\"Let's \\\"quote\\\" this\"}", new ToStringBuilder(base).append("name", "Let's \"quote\" this").toString());
+ }
+
+ @Test
+ public void testLANG1396() {
+ assertEquals("{\"Let's \\\"quote\\\" this\":\"value\"}", new ToStringBuilder(base).append("Let's \"quote\" this", "value").toString());
+ }
+
+ @Test
+ public void testRootMap() {
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("k1", "v1");
+ map.put("k2", 2);
+
+ assertEquals("{\"map\":{\"k1\":\"v1\",\"k2\":2}}",
+ new ToStringBuilder(base).append("map", map).toString());
+ }
+
+ @Test
+ public void testObjectWithInnerMap() {
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("k1", "value1");
+ map.put("k2", 2);
+
+ final InnerMapObject object = new InnerMapObject(){
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).append("pid", this.pid)
+ .append("map", this.map).toString();
+ }
+ };
+ object.pid = "dummy-text";
+ object.map = map;
+
+ assertEquals("{\"object\":{\"pid\":\"dummy-text\",\"map\":{\"k1\":\"value1\",\"k2\":2}}}",
+ new ToStringBuilder(base).append("object", object).toString());
+ }
+
+ @Test
+ public void testNestedMaps() {
+ final Map<String, Object> innerMap = new LinkedHashMap<>();
+ innerMap.put("k2.1", "v2.1");
+ innerMap.put("k2.2", "v2.2");
+ final Map<String, Object> baseMap = new LinkedHashMap<>();
+ baseMap.put("k1", "v1");
+ baseMap.put("k2", innerMap);
+
+ final InnerMapObject object = new InnerMapObject(){
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).append("pid", this.pid)
+ .append("map", this.map).toString();
+ }
+ };
+ object.pid = "dummy-text";
+ object.map = baseMap;
+
+ assertEquals("{\"object\":{\"pid\":\"dummy-text\",\"map\":{\"k1\":\"v1\"," +
+ "\"k2\":{\"k2.1\":\"v2.1\",\"k2.2\":\"v2.2\"}}}}",
+ new ToStringBuilder(base).append("object", object).toString());
+ }
+
+ @Test
+ public void testMapSkipNullKey() {
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("k1", "v1");
+ map.put(null, "v2");
+
+ assertEquals("{\"map\":{\"k1\":\"v1\"}}",
+ new ToStringBuilder(base).append("map", map).toString());
+ }
+
+ /**
+ * An object with nested object structures used to test {@code ToStringStyle.JsonToStringStyle}.
+ */
+ static class NestingPerson {
+ /**
+ * Test String field.
+ */
+ String pid;
+
+ /**
+ * Test nested object field.
+ */
+ Person person;
+ }
+
+ enum Hobby {
+ SPORT,
+ BOOK,
+ MUSIC
+ }
+
+ enum EmptyEnum {
+ }
+
+ static class Student {
+ List<Hobby> hobbies;
+
+ public List<Hobby> getHobbies() {
+ return hobbies;
+ }
+
+ public void setHobbies(final List<Hobby> hobbies) {
+ this.hobbies = hobbies;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+ static class Teacher {
+ Hobby[] hobbies;
+
+ public Hobby[] getHobbies() {
+ return hobbies;
+ }
+
+ public void setHobbies(final Hobby[] hobbies) {
+ this.hobbies = hobbies;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+ static class AcademyClass {
+ Teacher teacher;
+ List<Student> students;
+
+ public void setTeacher(final Teacher teacher) {
+ this.teacher = teacher;
+ }
+
+ public void setStudents(final List<Student> students) {
+ this.students = students;
+ }
+
+ public Teacher getTeacher() {
+ return teacher;
+ }
+
+ public List<Student> getStudents() {
+ return students;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+ /**
+ * An object with a Map field used to test {@code ToStringStyle.JsonToStringStyle}.
+ */
+ static class InnerMapObject {
+ /**
+ * Test String field.
+ */
+ String pid;
+
+ /**
+ * Test inner map field.
+ */
+ Map<String, Object> map;
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/MultiLineToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/MultiLineToStringStyleTest.java
new file mode 100644
index 000000000..ae9e30db6
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/MultiLineToStringStyleTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.MultiLineToStringStyleTest}.
+ */
+public class MultiLineToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+ private final String baseStr = base.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(base));
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.MULTI_LINE_STYLE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals(baseStr + "[" + System.lineSeparator() + "]", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals(baseStr + "[" + System.lineSeparator() + "]", new ToStringBuilder(base).appendSuper("Integer@8888[" + System.lineSeparator() + "]").toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]", new ToStringBuilder(base).appendSuper("Integer@8888[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]").toString());
+
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=hello" + System.lineSeparator() + "]", new ToStringBuilder(base).appendSuper("Integer@8888[" + System.lineSeparator() + "]").append("a", "hello").toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + " a=hello" + System.lineSeparator() + "]", new ToStringBuilder(base).appendSuper("Integer@8888[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]").append("a", "hello").toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=hello" + System.lineSeparator() + "]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " 3" + System.lineSeparator() + "]", new ToStringBuilder(base).append(i3).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<null>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=3" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=3" + System.lineSeparator() + " b=4" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<Integer>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", i3, false).toString());
+ }
+
+ @Test
+ public void testCollection() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<size=0>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Collections.emptyList(), false).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=[]" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Collections.emptyList(), true).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<size=1>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), false).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=[3]" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), true).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<size=2>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), false).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=[3, 4]" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), true).toString());
+ }
+
+ @Test
+ public void testMap() {
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<size=0>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Collections.emptyMap(), false).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a={}" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Collections.emptyMap(), true).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<size=1>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), false).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a={k=v}" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), true).toString());
+ }
+
+ @Test
+ public void testArray() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<size=0>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", (Object) new Integer[0], false).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a={}" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", (Object) new Integer[0], true).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<size=1>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, false).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a={3}" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, true).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=<size=2>" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, false).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a={3,4}" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, true).toString());
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "Jane Doe";
+ p.age = 25;
+ p.smoker = true;
+ final String pBaseStr = p.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p));
+ assertEquals(pBaseStr + "[" + System.lineSeparator() + " name=Jane Doe" + System.lineSeparator() + " age=25" + System.lineSeparator() + " smoker=true" + System.lineSeparator() + "]", new ToStringBuilder(p).append("name", p.name).append("age", p.age).append("smoker", p.smoker).toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals(baseStr + "[" + System.lineSeparator() + " 3" + System.lineSeparator() + "]", new ToStringBuilder(base).append(3L).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=3" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " a=3" + System.lineSeparator() + " b=4" + System.lineSeparator() + "]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals(baseStr + "[" + System.lineSeparator() + " {<null>,5,{3,6}}" + System.lineSeparator() + "]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " {<null>,5,{3,6}}" + System.lineSeparator() + "]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[" + System.lineSeparator() + " {1,2,-3,4}" + System.lineSeparator() + "]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " {1,2,-3,4}" + System.lineSeparator() + "]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[" + System.lineSeparator() + " {{1,2},<null>,{5}}" + System.lineSeparator() + "]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " {{1,2},<null>,{5}}" + System.lineSeparator() + "]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[" + System.lineSeparator() + " <null>" + System.lineSeparator() + "]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyleTest.java
new file mode 100644
index 000000000..0353a2fc0
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyleTest.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ */
+public class MultilineRecursiveToStringStyleTest extends AbstractLangTest {
+
+ private static final String LS = System.lineSeparator();
+ private static final String BASE_WITH_ARRAYS_TO_STRING = "[" + LS
+ + " boolArray=#BOOLEAN#," + LS
+ + " byteArray=#BYTE#," + LS
+ + " charArray=#CHAR#," + LS
+ + " doubleArray=#DOUBLE#," + LS
+ + " floatArray=#FLOAT#," + LS
+ + " intArray=#INT#," + LS
+ + " longArray=#LONG#," + LS
+ + " shortArray=#SHORT#," + LS
+ + " stringArray=#STRING#" + LS
+ + "]";
+
+ @Test
+ public void simpleObject() {
+ final Transaction tx = new Transaction("2014.10.15", 100);
+ final String expected = getClassPrefix(tx) + "[" + LS
+ + " amount=100.0," + LS
+ + " date=2014.10.15" + LS
+ + "]";
+ assertEquals(expected, toString(tx));
+ }
+
+ @Test
+ public void nestedElements() {
+ final Customer customer = new Customer("Douglas Adams");
+ final Bank bank = new Bank("ASF Bank");
+ customer.bank = bank;
+ final String exp = getClassPrefix(customer) + "[" + LS
+ + " accounts=<null>," + LS
+ + " bank=" + getClassPrefix(bank) + "[" + LS
+ + " name=ASF Bank" + LS
+ + " ]," + LS
+ + " name=Douglas Adams" + LS
+ + "]";
+ assertEquals(exp, toString(customer));
+ }
+
+ @Test
+ public void nestedAndArray() {
+ final Account acc = new Account();
+ final Transaction tx1 = new Transaction("2014.10.14", 100);
+ final Transaction tx2 = new Transaction("2014.10.15", 50);
+ acc.transactions.add(tx1);
+ acc.transactions.add(tx2);
+ final String expected = getClassPrefix(acc) + "[" + LS
+ + " owner=<null>," + LS
+ + " transactions=" + getClassPrefix(acc.transactions) + "{" + LS
+ + " " + getClassPrefix(tx1) + "[" + LS
+ + " amount=100.0," + LS
+ + " date=2014.10.14" + LS
+ + " ]," + LS
+ + " " + getClassPrefix(tx2) + "[" + LS
+ + " amount=50.0," + LS
+ + " date=2014.10.15" + LS
+ + " ]" + LS
+ + " }" + LS
+ + "]";
+ assertEquals(expected, toString(acc));
+ }
+
+ @Test
+ public void noArray() {
+ final WithArrays wa = new WithArrays();
+ final String exp = getExpectedToString(wa, WithArraysTestType.NONE, "");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void boolArray() {
+ final WithArrays wa = new WithArrays();
+ wa.boolArray = new boolean[] { true, false, true };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.BOOLEAN,
+ "{" + LS
+ + " true," + LS
+ + " false," + LS
+ + " true" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void byteArray() {
+ final WithArrays wa = new WithArrays();
+ wa.byteArray = new byte[] { 1, 2 };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.BYTE,
+ "{" + LS
+ + " 1," + LS
+ + " 2" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void charArray() {
+ final WithArrays wa = new WithArrays();
+ wa.charArray = new char[] { 'a', 'A' };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.CHAR,
+ "{" + LS
+ + " a," + LS
+ + " A" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void intArray() {
+ final WithArrays wa = new WithArrays();
+ wa.intArray = new int[] { 1, 2 };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.INT,
+ "{" + LS
+ + " 1," + LS
+ + " 2" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void doubleArray() {
+ final WithArrays wa = new WithArrays();
+ wa.doubleArray = new double[] { 1, 2 };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.DOUBLE,
+ "{" + LS
+ + " 1.0," + LS
+ + " 2.0" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void floatArray() {
+ final WithArrays wa = new WithArrays();
+ wa.floatArray = new float[] { 1f, 2f };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.FLOAT,
+ "{" + LS
+ + " 1.0," + LS
+ + " 2.0" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void longArray() {
+ final WithArrays wa = new WithArrays();
+ wa.longArray = new long[] { 1L, 2L };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.LONG,
+ "{" + LS
+ + " 1," + LS
+ + " 2" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void stringArray() {
+ final WithArrays wa = new WithArrays();
+ wa.stringArray = new String[] { "a", "A" };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.STRING,
+ "{" + LS
+ + " a," + LS
+ + " A" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void shortArray() {
+ final WithArrays wa = new WithArrays();
+ wa.shortArray = new short[] { 1, 2 };
+ final String exp = getExpectedToString(
+ wa, WithArraysTestType.SHORT,
+ "{" + LS
+ + " 1," + LS
+ + " 2" + LS
+ + " }");
+ assertEquals(exp, toString(wa));
+ }
+
+ @Test
+ public void testLANG1319() {
+ final String[] stringArray = {"1", "2"};
+
+ final String exp = getClassPrefix(stringArray) + "[" + LS
+ + " {" + LS
+ + " 1," + LS
+ + " 2" + LS
+ + " }" + LS
+ + "]";
+ assertEquals(exp, toString(stringArray));
+ }
+
+ private String getClassPrefix(final Object object) {
+ return object.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(object));
+ }
+
+ private String toString(final Object object) {
+ return new ReflectionToStringBuilder(object, new MultilineRecursiveToStringStyle()).toString();
+ }
+
+ static class WithArrays {
+ boolean[] boolArray;
+ byte[] byteArray;
+ char[] charArray;
+ double[] doubleArray;
+ float[] floatArray;
+ int[] intArray;
+ long[] longArray;
+ short[] shortArray;
+ String[] stringArray;
+ }
+
+ /**
+ * Create an expected to String for the given WithArraysInstance
+ * @param wa Instance
+ * @param arrayType Type - empty used to indicate expect all nulls
+ * @param expectedArrayValue Expected value for the array type
+ * @return expected toString
+ */
+ private String getExpectedToString(final WithArrays wa, final WithArraysTestType arrayType, final String expectedArrayValue) {
+ return getClassPrefix(wa)
+ + BASE_WITH_ARRAYS_TO_STRING
+ .replace("#" + arrayType + "#", expectedArrayValue)
+ .replaceAll("#[A-Z]+#", "<null>");
+ }
+
+ private enum WithArraysTestType {
+ NONE, BOOLEAN, BYTE, CHAR, DOUBLE, FLOAT, INT, LONG, SHORT, STRING
+ }
+
+ static class Bank {
+ String name;
+
+ Bank(final String name) {
+ this.name = name;
+ }
+ }
+
+ static class Customer {
+ String name;
+ Bank bank;
+ List<Account> accounts;
+
+ Customer(final String name) {
+ this.name = name;
+ }
+ }
+
+ static class Account {
+ Customer owner;
+ List<Transaction> transactions = new ArrayList<>();
+
+ public double getBalance() {
+ double balance = 0;
+ for (final Transaction tx : transactions) {
+ balance += tx.amount;
+ }
+ return balance;
+ }
+ }
+
+ static class Transaction {
+ double amount;
+ String date;
+
+ Transaction(final String datum, final double betrag) {
+ this.date = datum;
+ this.amount = betrag;
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/NoClassNameToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/NoClassNameToStringStyleTest.java
new file mode 100644
index 000000000..d165773f3
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/NoClassNameToStringStyleTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link ToStringStyle#NO_CLASS_NAME_STYLE}.
+ */
+public class NoClassNameToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.NO_CLASS_NAME_STYLE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals("[]", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals("[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());
+ assertEquals("[<null>]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").toString());
+
+ assertEquals("[a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString());
+ assertEquals("[<null>,a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").append("a", "hello").toString());
+ assertEquals("[a=hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals("[<null>]", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals("[3]", new ToStringBuilder(base).append(i3).toString());
+ assertEquals("[a=<null>]", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals("[a=3]", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals("[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals("[a=<Integer>]", new ToStringBuilder(base).append("a", i3, false).toString());
+ }
+
+ @Test
+ public void testCollection() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals("[a=<size=0>]", new ToStringBuilder(base).append("a", Collections.emptyList(), false).toString());
+ assertEquals("[a=[]]", new ToStringBuilder(base).append("a", Collections.emptyList(), true).toString());
+ assertEquals("[a=<size=1>]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), false).toString());
+ assertEquals("[a=[3]]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), true).toString());
+ assertEquals("[a=<size=2>]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), false).toString());
+ assertEquals("[a=[3, 4]]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), true).toString());
+ }
+
+ @Test
+ public void testMap() {
+ assertEquals("[a=<size=0>]", new ToStringBuilder(base).append("a", Collections.emptyMap(), false).toString());
+ assertEquals("[a={}]", new ToStringBuilder(base).append("a", Collections.emptyMap(), true).toString());
+ assertEquals("[a=<size=1>]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), false).toString());
+ assertEquals("[a={k=v}]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), true).toString());
+ }
+
+ @Test
+ public void testArray() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals("[a=<size=0>]", new ToStringBuilder(base).append("a", (Object) new Integer[0], false).toString());
+ assertEquals("[a={}]", new ToStringBuilder(base).append("a", (Object) new Integer[0], true).toString());
+ assertEquals("[a=<size=1>]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, false).toString());
+ assertEquals("[a={3}]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, true).toString());
+ assertEquals("[a=<size=2>]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, false).toString());
+ assertEquals("[a={3,4}]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, true).toString());
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "John Q. Public";
+ p.age = 45;
+ p.smoker = true;
+ assertEquals("[name=John Q. Public,age=45,smoker=true]", new ToStringBuilder(p).append("name", p.name).append("age", p.age).append("smoker", p.smoker).toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals("[3]", new ToStringBuilder(base).append(3L).toString());
+ assertEquals("[a=3]", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals("[a=3,b=4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals("[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals("[{<null>,5,{3,6}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals("[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals("[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals("[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals("[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals("[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals("[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals("[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals("[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals("[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals("[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/NoFieldNamesToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/NoFieldNamesToStringStyleTest.java
new file mode 100644
index 000000000..47680fcfc
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/NoFieldNamesToStringStyleTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.NoFieldNamesToStringStyleTest}.
+ */
+public class NoFieldNamesToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+ private final String baseStr = base.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(base));
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.NO_FIELD_NAMES_STYLE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").toString());
+
+ assertEquals(baseStr + "[hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString());
+ assertEquals(baseStr + "[<null>,hello]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").append("a", "hello").toString());
+ assertEquals(baseStr + "[hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals(baseStr + "[3,4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals(baseStr + "[<Integer>]", new ToStringBuilder(base).append("a", i3, false).toString());
+ }
+
+ @Test
+ public void testCollection() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[<size=0>]", new ToStringBuilder(base).append("a", Collections.emptyList(), false).toString());
+ assertEquals(baseStr + "[[]]", new ToStringBuilder(base).append("a", Collections.emptyList(), true).toString());
+ assertEquals(baseStr + "[<size=1>]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), false).toString());
+ assertEquals(baseStr + "[[3]]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), true).toString());
+ assertEquals(baseStr + "[<size=2>]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), false).toString());
+ assertEquals(baseStr + "[[3, 4]]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), true).toString());
+ }
+
+ @Test
+ public void testMap() {
+ assertEquals(baseStr + "[<size=0>]", new ToStringBuilder(base).append("a", Collections.emptyMap(), false).toString());
+ assertEquals(baseStr + "[{}]", new ToStringBuilder(base).append("a", Collections.emptyMap(), true).toString());
+ assertEquals(baseStr + "[<size=1>]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), false).toString());
+ assertEquals(baseStr + "[{k=v}]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), true).toString());
+ }
+
+ @Test
+ public void testArray() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[<size=0>]", new ToStringBuilder(base).append("a", (Object) new Integer[0], false).toString());
+ assertEquals(baseStr + "[{}]", new ToStringBuilder(base).append("a", (Object) new Integer[0], true).toString());
+ assertEquals(baseStr + "[<size=1>]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, false).toString());
+ assertEquals(baseStr + "[{3}]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, true).toString());
+ assertEquals(baseStr + "[<size=2>]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, false).toString());
+ assertEquals(baseStr + "[{3,4}]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, true).toString());
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "Ron Paul";
+ p.age = 72;
+ p.smoker = false;
+ final String pBaseStr = p.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p));
+ assertEquals(pBaseStr + "[Ron Paul,72,false]", new ToStringBuilder(p).append("name", p.name).append("age", p.age).append("smoker", p.smoker).toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3L).toString());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals(baseStr + "[3,4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/RecursiveToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/RecursiveToStringStyleTest.java
new file mode 100644
index 000000000..77faf230a
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/RecursiveToStringStyleTest.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.RecursiveToStringStyleTest}.
+ */
+public class RecursiveToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+ private final String baseStr = base.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(base));
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(new RecursiveToStringStyle());
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").toString());
+
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString());
+ assertEquals(baseStr + "[<null>,a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").append("a", "hello").toString());
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ final ArrayList<Object> emptyList = new ArrayList<>();
+
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).toString());
+ assertEquals(baseStr + "[a=<null>]", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals(baseStr + "[a=<Integer>]", new ToStringBuilder(base).append("a", i3, false).toString());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", emptyList, false).toString());
+ assertEquals(baseStr + "[a=java.util.ArrayList@" + Integer.toHexString(System.identityHashCode(emptyList)) + "{}]",
+ new ToStringBuilder(base).append("a", emptyList, true).toString());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", new HashMap<>(), false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", new HashMap<>(), true).toString());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", (Object) new String[0], false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", (Object) new String[0], true).toString());
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "John Doe";
+ p.age = 33;
+ p.smoker = false;
+ p.job = new Job();
+ p.job.title = "Manager";
+ final String pBaseStr = p.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p));
+ final String pJobStr = p.job.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p.job));
+ assertEquals(pBaseStr + "[age=33,job=" + pJobStr + "[title=Manager],name=John Doe,smoker=false]",
+ new ReflectionToStringBuilder(p, new RecursiveToStringStyle()).toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3L).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ static class Person {
+ /**
+ * Test String field.
+ */
+ String name;
+
+ /**
+ * Test integer field.
+ */
+ int age;
+
+ /**
+ * Test boolean field.
+ */
+ boolean smoker;
+
+ /**
+ * Test Object field.
+ */
+ Job job;
+ }
+
+ static class Job {
+ /**
+ * Test String field.
+ */
+ String title;
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java
new file mode 100644
index 000000000..8fc3a1d43
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+public class ReflectionDiffBuilderTest extends AbstractLangTest {
+
+ private static final ToStringStyle SHORT_STYLE = ToStringStyle.SHORT_PREFIX_STYLE;
+
+ @SuppressWarnings("unused")
+ private static class TypeTestClass implements Diffable<TypeTestClass> {
+ private final ToStringStyle style = SHORT_STYLE;
+ private final boolean booleanField = true;
+ private final boolean[] booleanArrayField = {true};
+ private final byte byteField = (byte) 0xFF;
+ private final byte[] byteArrayField = {(byte) 0xFF};
+ private char charField = 'a';
+ private char[] charArrayField = {'a'};
+ private final double doubleField = 1.0;
+ private final double[] doubleArrayField = {1.0};
+ private final float floatField = 1.0f;
+ private final float[] floatArrayField = {1.0f};
+ int intField = 1;
+ private final int[] intArrayField = {1};
+ private final long longField = 1L;
+ private final long[] longArrayField = {1L};
+ private final short shortField = 1;
+ private final short[] shortArrayField = {1};
+ private final Object objectField = null;
+ private final Object[] objectArrayField = {null};
+ private static int staticField;
+ private transient String transientField;
+
+ @Override
+ public DiffResult diff(final TypeTestClass obj) {
+ return new ReflectionDiffBuilder(this, obj, style).build();
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this, false);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj, false);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class TypeTestChildClass extends TypeTestClass {
+ String field = "a";
+ }
+
+ @Test
+ public void test_no_differences() {
+ final TypeTestClass firstObject = new TypeTestClass();
+ final TypeTestClass secondObject = new TypeTestClass();
+
+ final DiffResult list = firstObject.diff(secondObject);
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void test_primitive_difference() {
+ final TypeTestClass firstObject = new TypeTestClass();
+ firstObject.charField = 'c';
+ final TypeTestClass secondObject = new TypeTestClass();
+
+ final DiffResult list = firstObject.diff(secondObject);
+ assertEquals(1, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void test_array_difference() {
+ final TypeTestClass firstObject = new TypeTestClass();
+ firstObject.charArrayField = new char[] { 'c' };
+ final TypeTestClass secondObject = new TypeTestClass();
+
+ final DiffResult list = firstObject.diff(secondObject);
+ assertEquals(1, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void test_transient_field_difference() {
+ final TypeTestClass firstObject = new TypeTestClass();
+ firstObject.transientField = "a";
+ final TypeTestClass secondObject = new TypeTestClass();
+ firstObject.transientField = "b";
+
+ final DiffResult list = firstObject.diff(secondObject);
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void test_no_differences_inheritance() {
+ final TypeTestChildClass firstObject = new TypeTestChildClass();
+ final TypeTestChildClass secondObject = new TypeTestChildClass();
+
+ final DiffResult list = firstObject.diff(secondObject);
+ assertEquals(0, list.getNumberOfDiffs());
+ }
+
+ @Test
+ public void test_difference_in_inherited_field() {
+ final TypeTestChildClass firstObject = new TypeTestChildClass();
+ firstObject.intField = 99;
+ final TypeTestChildClass secondObject = new TypeTestChildClass();
+
+ final DiffResult list = firstObject.diff(secondObject);
+ assertEquals(1, list.getNumberOfDiffs());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderConcurrencyTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderConcurrencyTest.java
new file mode 100644
index 000000000..8de0c863f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderConcurrencyTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.concurrent.UncheckedFuture;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests concurrent access for {@link ReflectionToStringBuilder}.
+ * <p>
+ * The {@link ToStringStyle} class includes a registry to avoid infinite loops for objects with circular references. We
+ * want to make sure that we do not get concurrency exceptions accessing this registry.
+ * </p>
+ * <p>
+ * The tests on the non-thread-safe collections do not pass.
+ * </p>
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/LANG-762">[LANG-762] Handle or document ReflectionToStringBuilder
+ * and ToStringBuilder for collections that are not thread safe</a>
+ * @since 3.1
+ */
+public class ReflectionToStringBuilderConcurrencyTest extends AbstractLangTest {
+
+ static class CollectionHolder<T extends Collection<?>> {
+ T collection;
+
+ CollectionHolder(final T collection) {
+ this.collection = collection;
+ }
+ }
+
+ private static final int DATA_SIZE = 100000;
+ private static final int REPEAT = 100;
+
+ @Test
+ @Disabled
+ public void testLinkedList() throws InterruptedException {
+ this.testConcurrency(new CollectionHolder<>(new LinkedList<>()));
+ }
+
+ @Test
+ @Disabled
+ public void testArrayList() throws InterruptedException {
+ this.testConcurrency(new CollectionHolder<>(new ArrayList<>()));
+ }
+
+ @Test
+ @Disabled
+ public void testCopyOnWriteArrayList() throws InterruptedException {
+ this.testConcurrency(new CollectionHolder<>(new CopyOnWriteArrayList<>()));
+ }
+
+ private void testConcurrency(final CollectionHolder<List<Integer>> holder) throws InterruptedException {
+ final List<Integer> list = holder.collection;
+ // make a big array that takes a long time to toString()
+ for (int i = 0; i < DATA_SIZE; i++) {
+ list.add(Integer.valueOf(i));
+ }
+ // Create a thread pool with two threads to cause the most contention on the underlying resource.
+ final ExecutorService threadPool = Executors.newFixedThreadPool(2);
+ try {
+ // Consumes toStrings
+ final Callable<Integer> consumer = () -> {
+ for (int i = 0; i < REPEAT; i++) {
+ final String s = ReflectionToStringBuilder.toString(holder);
+ assertNotNull(s);
+ }
+ return Integer.valueOf(REPEAT);
+ };
+ // Produces changes in the list
+ final Callable<Integer> producer = () -> {
+ for (int i = 0; i < DATA_SIZE; i++) {
+ list.remove(list.get(0));
+ }
+ return Integer.valueOf(REPEAT);
+ };
+ final Collection<Callable<Integer>> tasks = new ArrayList<>();
+ tasks.add(consumer);
+ tasks.add(producer);
+ final List<Future<Integer>> futures = threadPool.invokeAll(tasks);
+ UncheckedFuture.on(futures).forEach(f -> assertEquals(REPEAT, f.get().intValue()));
+ } finally {
+ threadPool.shutdown();
+ threadPool.awaitTermination(1, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeNullValuesTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeNullValuesTest.java
new file mode 100644
index 000000000..450da9295
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeNullValuesTest.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+public class ReflectionToStringBuilderExcludeNullValuesTest extends AbstractLangTest {
+
+ static class TestFixture {
+ @SuppressWarnings("unused")
+ private final Integer testIntegerField;
+ @SuppressWarnings("unused")
+ private final String testStringField;
+
+ TestFixture(final Integer a, final String b) {
+ this.testIntegerField = a;
+ this.testStringField = b;
+ }
+ }
+
+ private static final String INTEGER_FIELD_NAME = "testIntegerField";
+ private static final String STRING_FIELD_NAME = "testStringField";
+ private final TestFixture BOTH_NON_NULL = new TestFixture(0, "str");
+ private final TestFixture FIRST_NULL = new TestFixture(null, "str");
+ private final TestFixture SECOND_NULL = new TestFixture(0, null);
+ private final TestFixture BOTH_NULL = new TestFixture(null, null);
+
+ @Test
+ public void test_NonExclude() {
+ //normal case=
+ String toString = ReflectionToStringBuilder.toString(BOTH_NON_NULL, null, false, false, false, null);
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+
+ //make one null
+ toString = ReflectionToStringBuilder.toString(FIRST_NULL, null, false, false, false, null);
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+
+ //other one null
+ toString = ReflectionToStringBuilder.toString(SECOND_NULL, null, false, false, false, null);
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+
+ //make the both null
+ toString = ReflectionToStringBuilder.toString(BOTH_NULL, null, false, false, false, null);
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+ }
+
+ @Test
+ public void test_excludeNull() {
+
+ //test normal case
+ String toString = ReflectionToStringBuilder.toString(BOTH_NON_NULL, null, false, false, true, null);
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+
+ //make one null
+ toString = ReflectionToStringBuilder.toString(FIRST_NULL, null, false, false, true, null);
+ assertFalse(toString.contains(INTEGER_FIELD_NAME));
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+
+ //other one null
+ toString = ReflectionToStringBuilder.toString(SECOND_NULL, null, false, false, true, null);
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ assertFalse(toString.contains(STRING_FIELD_NAME));
+
+ //both null
+ toString = ReflectionToStringBuilder.toString(BOTH_NULL, null, false, false, true, null);
+ assertFalse(toString.contains(INTEGER_FIELD_NAME));
+ assertFalse(toString.contains(STRING_FIELD_NAME));
+ }
+
+ @Test
+ public void test_ConstructorOption() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(BOTH_NON_NULL, null, null, null, false, false, true);
+ assertTrue(builder.isExcludeNullValues());
+ String toString = builder.toString();
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+
+ builder = new ReflectionToStringBuilder(FIRST_NULL, null, null, null, false, false, true);
+ toString = builder.toString();
+ assertFalse(toString.contains(INTEGER_FIELD_NAME));
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+
+ builder = new ReflectionToStringBuilder(SECOND_NULL, null, null, null, false, false, true);
+ toString = builder.toString();
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ assertFalse(toString.contains(STRING_FIELD_NAME));
+
+ builder = new ReflectionToStringBuilder(BOTH_NULL, null, null, null, false, false, true);
+ toString = builder.toString();
+ assertFalse(toString.contains(INTEGER_FIELD_NAME));
+ assertFalse(toString.contains(STRING_FIELD_NAME));
+ }
+
+ @Test
+ public void test_ConstructorOptionNormal() {
+ final ReflectionToStringBuilder builder = new ReflectionToStringBuilder(BOTH_NULL, null, null, null, false, false, false);
+ assertFalse(builder.isExcludeNullValues());
+ String toString = builder.toString();
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+
+ //regression test older constructors
+ ReflectionToStringBuilder oldBuilder = new ReflectionToStringBuilder(BOTH_NULL);
+ toString = oldBuilder.toString();
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+
+ oldBuilder = new ReflectionToStringBuilder(BOTH_NULL, null, null, null, false, false);
+ toString = oldBuilder.toString();
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+
+ oldBuilder = new ReflectionToStringBuilder(BOTH_NULL, null, null);
+ toString = oldBuilder.toString();
+ assertTrue(toString.contains(STRING_FIELD_NAME));
+ assertTrue(toString.contains(INTEGER_FIELD_NAME));
+ }
+
+ @Test
+ public void test_ConstructorOption_ExcludeNull() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(BOTH_NULL, null, null, null, false, false, false);
+ builder.setExcludeNullValues(true);
+ assertTrue(builder.isExcludeNullValues());
+ String toString = builder.toString();
+ assertFalse(toString.contains(STRING_FIELD_NAME));
+ assertFalse(toString.contains(INTEGER_FIELD_NAME));
+
+ builder = new ReflectionToStringBuilder(BOTH_NULL, null, null, null, false, false, true);
+ toString = builder.toString();
+ assertFalse(toString.contains(STRING_FIELD_NAME));
+ assertFalse(toString.contains(INTEGER_FIELD_NAME));
+
+ final ReflectionToStringBuilder oldBuilder = new ReflectionToStringBuilder(BOTH_NULL);
+ oldBuilder.setExcludeNullValues(true);
+ assertTrue(oldBuilder.isExcludeNullValues());
+ toString = oldBuilder.toString();
+ assertFalse(toString.contains(STRING_FIELD_NAME));
+ assertFalse(toString.contains(INTEGER_FIELD_NAME));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeTest.java
new file mode 100644
index 000000000..47ab6c5b9
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ */
+public class ReflectionToStringBuilderExcludeTest extends AbstractLangTest {
+
+ class TestFixture {
+ @SuppressWarnings("unused")
+ private final String secretField = SECRET_VALUE;
+
+ @SuppressWarnings("unused")
+ private final String showField = NOT_SECRET_VALUE;
+ }
+
+ private static final String NOT_SECRET_FIELD = "showField";
+
+ private static final String NOT_SECRET_VALUE = "Hello World!";
+
+ private static final String SECRET_FIELD = "secretField";
+
+ private static final String SECRET_VALUE = "secret value";
+
+ @Test
+ public void test_toStringExclude() {
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), SECRET_FIELD);
+ this.validateSecretFieldAbsent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeArray() {
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), SECRET_FIELD);
+ this.validateSecretFieldAbsent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeArrayWithNull() {
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), new String[]{null});
+ this.validateSecretFieldPresent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeArrayWithNulls() {
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), null, null);
+ this.validateSecretFieldPresent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeCollection() {
+ final List<String> excludeList = new ArrayList<>();
+ excludeList.add(SECRET_FIELD);
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), excludeList);
+ this.validateSecretFieldAbsent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeCollectionWithNull() {
+ final List<String> excludeList = new ArrayList<>();
+ excludeList.add(null);
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), excludeList);
+ this.validateSecretFieldPresent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeCollectionWithNulls() {
+ final List<String> excludeList = new ArrayList<>();
+ excludeList.add(null);
+ excludeList.add(null);
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), excludeList);
+ this.validateSecretFieldPresent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeEmptyArray() {
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), ArrayUtils.EMPTY_STRING_ARRAY);
+ this.validateSecretFieldPresent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeEmptyCollection() {
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), new ArrayList<>());
+ this.validateSecretFieldPresent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeNullArray() {
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), (String[]) null);
+ this.validateSecretFieldPresent(toString);
+ }
+
+ @Test
+ public void test_toStringExcludeNullCollection() {
+ final String toString = ReflectionToStringBuilder.toStringExclude(new TestFixture(), (Collection<String>) null);
+ this.validateSecretFieldPresent(toString);
+ }
+
+ private void validateNonSecretField(final String toString) {
+ assertTrue(toString.contains(NOT_SECRET_FIELD));
+ assertTrue(toString.contains(NOT_SECRET_VALUE));
+ }
+
+ private void validateSecretFieldAbsent(final String toString) {
+ assertEquals(ArrayUtils.INDEX_NOT_FOUND, toString.indexOf(SECRET_VALUE));
+ this.validateNonSecretField(toString);
+ }
+
+ private void validateSecretFieldPresent(final String toString) {
+ assertTrue(toString.indexOf(SECRET_VALUE) > 0);
+ this.validateNonSecretField(toString);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeWithAnnotationTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeWithAnnotationTest.java
new file mode 100644
index 000000000..0a39b50f2
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeWithAnnotationTest.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.builder;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for ToStringExclude annotation
+ */
+public class ReflectionToStringBuilderExcludeWithAnnotationTest extends AbstractLangTest {
+
+ class TestFixture {
+ @ToStringExclude
+ private final String excludedField = EXCLUDED_FIELD_VALUE;
+
+ @SuppressWarnings("unused")
+ private final String includedField = INCLUDED_FIELD_VALUE;
+ }
+
+ private static final String INCLUDED_FIELD_NAME = "includedField";
+
+ private static final String INCLUDED_FIELD_VALUE = "Hello World!";
+
+ private static final String EXCLUDED_FIELD_NAME = "excludedField";
+
+ private static final String EXCLUDED_FIELD_VALUE = "excluded field value";
+
+ @Test
+ public void test_toStringExclude() {
+ final String toString = ReflectionToStringBuilder.toString(new TestFixture());
+
+ assertThat(toString, not(containsString(EXCLUDED_FIELD_NAME)));
+ assertThat(toString, not(containsString(EXCLUDED_FIELD_VALUE)));
+ assertThat(toString, containsString(INCLUDED_FIELD_NAME));
+ assertThat(toString, containsString(INCLUDED_FIELD_VALUE));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderIncludeTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderIncludeTest.java
new file mode 100644
index 000000000..e5141732e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderIncludeTest.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ReflectionToStringBuilderIncludeTest extends AbstractLangTest {
+
+ class TestFeature {
+ @SuppressWarnings("unused")
+ private final String field1 = VALUES[0];
+
+ @SuppressWarnings("unused")
+ private final String field2 = VALUES[1];
+
+ @SuppressWarnings("unused")
+ private final String field3 = VALUES[2];
+
+ @SuppressWarnings("unused")
+ private final String field4 = VALUES[3];
+
+ @SuppressWarnings("unused")
+ private final String field5 = VALUES[4];
+ }
+
+ private static final String[] FIELDS = {"field1", "field2", "field3", "field4", "field5"};
+
+ private static final String[] VALUES = {"value 1", "value 2", "value 3", "value 4", "value 5"};
+
+ private static final String SINGLE_FIELD_TO_SHOW = FIELDS[2];
+
+ private static final String SINGLE_FIELD_VALUE_TO_SHOW = VALUES[2];
+
+ private static final String[] FIELDS_TO_SHOW = {FIELDS[0], FIELDS[3]};
+
+ private static final String[] FIELDS_VALUES_TO_SHOW = {VALUES[0], VALUES[3]};
+
+ @Test
+ public void test_toStringInclude() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), SINGLE_FIELD_TO_SHOW);
+ this.validateIncludeFieldsPresent(toString, new String[]{ SINGLE_FIELD_TO_SHOW }, new String[]{ SINGLE_FIELD_VALUE_TO_SHOW });
+ }
+
+ @Test
+ public void test_toStringIncludeArray() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), FIELDS_TO_SHOW);
+ this.validateIncludeFieldsPresent(toString, FIELDS_TO_SHOW, FIELDS_VALUES_TO_SHOW);
+ }
+
+ @Test
+ public void test_toStringIncludeWithoutInformingFields() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature());
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeArrayWithNull() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), new String[]{null});
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeArrayWithNulls() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), null, null);
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeCollection() {
+ final List<String> includeList = new ArrayList<>();
+ includeList.add(SINGLE_FIELD_TO_SHOW);
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), includeList);
+ this.validateIncludeFieldsPresent(toString, new String[]{ SINGLE_FIELD_TO_SHOW }, new String[]{ SINGLE_FIELD_VALUE_TO_SHOW });
+ }
+
+ @Test
+ public void test_toStringIncludeCollectionWithNull() {
+ final List<String> includeList = new ArrayList<>();
+ includeList.add(null);
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), includeList);
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeCollectionWithNulls() {
+ final List<String> includeList = new ArrayList<>();
+ includeList.add(null);
+ includeList.add(null);
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), includeList);
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeEmptyArray() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), ArrayUtils.EMPTY_STRING_ARRAY);
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeEmptyCollection() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), new ArrayList<>());
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeNullArray() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), (String[]) null);
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeNullArrayMultiplesValues() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), new String[] {null, null, null, null});
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringIncludeNullCollection() {
+ final String toString = ReflectionToStringBuilder.toStringInclude(new TestFeature(), (Collection<String>) null);
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringDefaultBehavior() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(new TestFeature());
+ final String toString = builder.toString();
+ this.validateAllFieldsPresent(toString);
+ }
+
+ @Test
+ public void test_toStringSetIncludeAndExcludeWithoutIntersection() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(new TestFeature());
+ builder.setExcludeFieldNames(FIELDS[1], FIELDS[4]);
+ builder.setIncludeFieldNames(FIELDS_TO_SHOW);
+ final String toString = builder.toString();
+ this.validateIncludeFieldsPresent(toString, FIELDS_TO_SHOW, FIELDS_VALUES_TO_SHOW);
+ }
+
+ @Test
+ public void test_toStringSetIncludeAndExcludeWithIntersection() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(new TestFeature());
+ builder.setExcludeFieldNames(FIELDS[1], FIELDS[4]);
+ builder.setIncludeFieldNames(FIELDS[0], FIELDS[1]);
+ Assertions.assertThrows(IllegalStateException.class, () -> {
+ builder.toString();
+ });
+ }
+
+ @Test
+ public void test_toStringSetIncludeWithMultipleNullFields() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(new TestFeature());
+ builder.setExcludeFieldNames(FIELDS[1], FIELDS[4]);
+ builder.setIncludeFieldNames(null, null, null);
+ final String toString = builder.toString();
+ this.validateIncludeFieldsPresent(toString, new String[]{FIELDS[0], FIELDS[2], FIELDS[3]}, new String[]{VALUES[0], VALUES[2], VALUES[3]});
+ }
+
+ @Test
+ public void test_toStringSetIncludeWithArrayWithMultipleNullFields() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(new TestFeature());
+ builder.setExcludeFieldNames(new String[] {FIELDS[1], FIELDS[4]});
+ builder.setIncludeFieldNames(new String[] {null, null, null});
+ final String toString = builder.toString();
+ this.validateIncludeFieldsPresent(toString, new String[]{FIELDS[0], FIELDS[2], FIELDS[3]}, new String[]{VALUES[0], VALUES[2], VALUES[3]});
+ }
+
+ @Test
+ public void test_toStringSetIncludeAndExcludeWithRandomFieldsWithIntersection() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(new TestFeature());
+ builder.setExcludeFieldNames(FIELDS[1], "random1");
+ builder.setIncludeFieldNames("random1");
+ Assertions.assertThrows(IllegalStateException.class, () -> {
+ builder.toString();
+ });
+ }
+
+ @Test
+ public void test_toStringSetIncludeAndExcludeWithRandomFieldsWithoutIntersection() {
+ ReflectionToStringBuilder builder = new ReflectionToStringBuilder(new TestFeature());
+ builder.setExcludeFieldNames(FIELDS[1], "random1");
+ builder.setIncludeFieldNames("random2", FIELDS[2]);
+ final String toString = builder.toString();
+ this.validateIncludeFieldsPresent(toString, new String[]{FIELDS[2]}, new String[]{VALUES[2]});
+ }
+
+ private void validateAllFieldsPresent(String toString) {
+ validateIncludeFieldsPresent(toString, FIELDS, VALUES);
+ }
+
+ private void validateIncludeFieldsPresent(final String toString, final String[] fieldsToShow, final String[] valuesToShow) {
+ for (String includeField : fieldsToShow) {
+ assertTrue(toString.indexOf(includeField) > 0);
+ }
+
+ for (String includeValue : valuesToShow) {
+ assertTrue(toString.indexOf(includeValue) > 0);
+ }
+
+ this.validateNonIncludeFieldsAbsent(toString, fieldsToShow, valuesToShow);
+ }
+
+ private void validateNonIncludeFieldsAbsent(String toString, String[] IncludeFields, String[] IncludeFieldsValues) {
+ String[] nonIncludeFields = ArrayUtils.removeElements(FIELDS.clone(), IncludeFields);
+ String[] nonIncludeFieldsValues = ArrayUtils.removeElements(VALUES.clone(), IncludeFieldsValues);
+
+ for (String nonIncludeField : nonIncludeFields) {
+ assertEquals(ArrayUtils.INDEX_NOT_FOUND, toString.indexOf(nonIncludeField));
+ }
+
+ for (String nonIncludeValue : nonIncludeFieldsValues) {
+ assertEquals(ArrayUtils.INDEX_NOT_FOUND, toString.indexOf(nonIncludeValue));
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderMutateInspectConcurrencyTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderMutateInspectConcurrencyTest.java
new file mode 100644
index 000000000..97d350b3f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderMutateInspectConcurrencyTest.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.builder;
+
+import java.util.LinkedList;
+import java.util.Random;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests concurrent access for {@link ReflectionToStringBuilder}.
+ * <p>
+ * The {@link ToStringStyle} class includes a registry to avoid infinite loops for objects with circular references. We
+ * want to make sure that we do not get concurrency exceptions accessing this registry.
+ * </p>
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/LANG-762">[LANG-762] Handle or document ReflectionToStringBuilder
+ * and ToStringBuilder for collections that are not thread safe</a>
+ * @since 3.1
+ */
+public class ReflectionToStringBuilderMutateInspectConcurrencyTest extends AbstractLangTest {
+
+ class TestFixture {
+ private final LinkedList<Integer> listField = new LinkedList<>();
+ private final Random random = new Random();
+ private final int N = 100;
+
+ TestFixture() {
+ synchronized (this) {
+ for (int i = 0; i < N; i++) {
+ listField.add(Integer.valueOf(i));
+ }
+ }
+ }
+
+ public synchronized void add() {
+ listField.add(Integer.valueOf(random.nextInt(N)));
+ }
+
+ public synchronized void delete() {
+ listField.remove(Integer.valueOf(random.nextInt(N)));
+ }
+ }
+
+ class MutatingClient implements Runnable {
+ private final TestFixture testFixture;
+ private final Random random = new Random();
+
+ MutatingClient(final TestFixture testFixture) {
+ this.testFixture = testFixture;
+ }
+
+ @Override
+ public void run() {
+ if (random.nextBoolean()) {
+ testFixture.add();
+ } else {
+ testFixture.delete();
+ }
+ }
+ }
+
+ class InspectingClient implements Runnable {
+ private final TestFixture testFixture;
+
+ InspectingClient(final TestFixture testFixture) {
+ this.testFixture = testFixture;
+ }
+
+ @Override
+ public void run() {
+ ReflectionToStringBuilder.toString(testFixture);
+ }
+ }
+
+ @Test
+ @Disabled
+ public void testConcurrency() {
+ final TestFixture testFixture = new TestFixture();
+ final int numMutators = 10;
+ final int numIterations = 10;
+ for (int i = 0; i < numIterations; i++) {
+ for (int j = 0; j < numMutators; j++) {
+ final Thread t = new Thread(new MutatingClient(testFixture));
+ t.start();
+ final Thread s = new Thread(new InspectingClient(testFixture));
+ s.start();
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderSummaryTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderSummaryTest.java
new file mode 100644
index 000000000..0826bd8ca
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderSummaryTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+public class ReflectionToStringBuilderSummaryTest extends AbstractLangTest {
+
+ @SuppressWarnings("unused")
+ private final String stringField = "string";
+
+ @ToStringSummary
+ private final String summaryString = "summary";
+
+ @Test
+ public void testSummary() {
+ assertEquals("[stringField=string,summaryString=<String>]",
+ new ReflectionToStringBuilder(this, ToStringStyle.NO_CLASS_NAME_STYLE).build());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderTest.java
new file mode 100644
index 000000000..16a38fd02
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderTest.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+public class ReflectionToStringBuilderTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructorWithNullObject() {
+ assertThrows(NullPointerException.class,
+ () -> new ReflectionToStringBuilder(null, ToStringStyle.DEFAULT_STYLE, new StringBuffer()));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ShortPrefixToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/ShortPrefixToStringStyleTest.java
new file mode 100644
index 000000000..f07cdf470
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ShortPrefixToStringStyleTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.ToStringStyle#SHORT_PREFIX_STYLE}.
+ */
+public class ShortPrefixToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+ private final String baseStr = "Integer";
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").toString());
+
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString());
+ assertEquals(baseStr + "[<null>,a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").append("a", "hello").toString());
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).toString());
+ assertEquals(baseStr + "[a=<null>]", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals(baseStr + "[a=<Integer>]", new ToStringBuilder(base).append("a", i3, false).toString());
+ }
+
+ @Test
+ public void testCollection() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", Collections.emptyList(), false).toString());
+ assertEquals(baseStr + "[a=[]]", new ToStringBuilder(base).append("a", Collections.emptyList(), true).toString());
+ assertEquals(baseStr + "[a=<size=1>]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), false).toString());
+ assertEquals(baseStr + "[a=[3]]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), true).toString());
+ assertEquals(baseStr + "[a=<size=2>]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), false).toString());
+ assertEquals(baseStr + "[a=[3, 4]]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), true).toString());
+ }
+
+ @Test
+ public void testMap() {
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", Collections.emptyMap(), false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", Collections.emptyMap(), true).toString());
+ assertEquals(baseStr + "[a=<size=1>]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), false).toString());
+ assertEquals(baseStr + "[a={k=v}]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), true).toString());
+ }
+
+ @Test
+ public void testArray() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", (Object) new Integer[0], false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", (Object) new Integer[0], true).toString());
+ assertEquals(baseStr + "[a=<size=1>]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, false).toString());
+ assertEquals(baseStr + "[a={3}]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, true).toString());
+ assertEquals(baseStr + "[a=<size=2>]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, false).toString());
+ assertEquals(baseStr + "[a={3,4}]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, true).toString());
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "John Q. Public";
+ p.age = 45;
+ p.smoker = true;
+ final String pBaseStr = "ToStringStyleTest.Person";
+ assertEquals(pBaseStr + "[name=John Q. Public,age=45,smoker=true]", new ToStringBuilder(p).append("name", p.name).append("age", p.age).append("smoker", p.smoker).toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3L).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/SimpleToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/SimpleToStringStyleTest.java
new file mode 100644
index 000000000..2a546e68f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/SimpleToStringStyleTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.SimpleToStringStyleTest}.
+ */
+public class SimpleToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.SIMPLE_STYLE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals("", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals("", new ToStringBuilder(base).appendSuper("").toString());
+ assertEquals("<null>", new ToStringBuilder(base).appendSuper("<null>").toString());
+
+ assertEquals("hello", new ToStringBuilder(base).appendSuper("").append("a", "hello").toString());
+ assertEquals("<null>,hello", new ToStringBuilder(base).appendSuper("<null>").append("a", "hello").toString());
+ assertEquals("hello", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals("<null>", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals("3", new ToStringBuilder(base).append(i3).toString());
+ assertEquals("<null>", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals("3", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals("3,4", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals("<Integer>", new ToStringBuilder(base).append("a", i3, false).toString());
+ }
+
+ @Test
+ public void testCollection() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals("<size=0>", new ToStringBuilder(base).append("a", Collections.emptyList(), false).toString());
+ assertEquals("[]", new ToStringBuilder(base).append("a", Collections.emptyList(), true).toString());
+ assertEquals("<size=1>", new ToStringBuilder(base).append("a", Collections.singletonList(i3), false).toString());
+ assertEquals("[3]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), true).toString());
+ assertEquals("<size=2>", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), false).toString());
+ assertEquals("[3, 4]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), true).toString());
+ }
+
+ @Test
+ public void testMap() {
+ assertEquals("<size=0>", new ToStringBuilder(base).append("a", Collections.emptyMap(), false).toString());
+ assertEquals("{}", new ToStringBuilder(base).append("a", Collections.emptyMap(), true).toString());
+ assertEquals("<size=1>", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), false).toString());
+ assertEquals("{k=v}", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), true).toString());
+ }
+
+ @Test
+ public void testArray() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals("<size=0>", new ToStringBuilder(base).append("a", (Object) new Integer[0], false).toString());
+ assertEquals("{}", new ToStringBuilder(base).append("a", (Object) new Integer[0], true).toString());
+ assertEquals("<size=1>", new ToStringBuilder(base).append("a", (Object) new Integer[]{i3}, false).toString());
+ assertEquals("{3}", new ToStringBuilder(base).append("a", (Object) new Integer[]{i3}, true).toString());
+ assertEquals("<size=2>", new ToStringBuilder(base).append("a", (Object) new Integer[]{i3, i4}, false).toString());
+ assertEquals("{3,4}", new ToStringBuilder(base).append("a", (Object) new Integer[]{i3, i4}, true).toString());
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "Jane Q. Public";
+ p.age = 47;
+ p.smoker = false;
+ assertEquals("Jane Q. Public,47,false", new ToStringBuilder(p).append("name", p.name).append("age", p.age).append("smoker", p.smoker).toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals("3", new ToStringBuilder(base).append(3L).toString());
+ assertEquals("3", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals("3,4", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals("{<null>,5,{3,6}}", new ToStringBuilder(base).append(array).toString());
+ assertEquals("{<null>,5,{3,6}}", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals("<null>", new ToStringBuilder(base).append(array).toString());
+ assertEquals("<null>", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals("{1,2,-3,4}", new ToStringBuilder(base).append(array).toString());
+ assertEquals("{1,2,-3,4}", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals("<null>", new ToStringBuilder(base).append(array).toString());
+ assertEquals("<null>", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals("{{1,2},<null>,{5}}", new ToStringBuilder(base).append(array).toString());
+ assertEquals("{{1,2},<null>,{5}}", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals("<null>", new ToStringBuilder(base).append(array).toString());
+ assertEquals("<null>", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/StandardToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/StandardToStringStyleTest.java
new file mode 100644
index 000000000..fa183f513
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/StandardToStringStyleTest.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.builder.ToStringStyle}.
+ */
+public class StandardToStringStyleTest extends AbstractLangTest {
+
+ private final Integer base = Integer.valueOf(5);
+ private final String baseStr = "Integer";
+
+ private static final StandardToStringStyle STYLE = new StandardToStringStyle();
+
+ static {
+ STYLE.setUseShortClassName(true);
+ STYLE.setUseIdentityHashCode(false);
+ STYLE.setArrayStart("[");
+ STYLE.setArraySeparator(", ");
+ STYLE.setArrayEnd("]");
+ STYLE.setNullText("%NULL%");
+ STYLE.setSizeStartText("%SIZE=");
+ STYLE.setSizeEndText("%");
+ STYLE.setSummaryObjectStartText("%");
+ STYLE.setSummaryObjectEndText("%");
+ STYLE.setUseClassName(true);
+ STYLE.setUseFieldNames(true);
+ STYLE.setUseClassName(true);
+ STYLE.setUseFieldNames(true);
+ STYLE.setDefaultFullDetail(true);
+ STYLE.setArrayContentDetail(true);
+ STYLE.setFieldNameValueSeparator("=");
+ }
+
+ @BeforeEach
+ public void setUp() {
+ ToStringBuilder.setDefaultStyle(STYLE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).toString());
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());
+ assertEquals(baseStr + "[%NULL%]", new ToStringBuilder(base).appendSuper("Integer@8888[%NULL%]").toString());
+
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString());
+ assertEquals(baseStr + "[%NULL%,a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[%NULL%]").append("a", "hello").toString());
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[%NULL%]", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).toString());
+ assertEquals(baseStr + "[a=%NULL%]", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals(baseStr + "[a=%Integer%]", new ToStringBuilder(base).append("a", i3, false).toString());
+ }
+
+ @Test
+ public void testCollection() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[a=%SIZE=0%]", new ToStringBuilder(base).append("a", Collections.emptyList(), false).toString());
+ assertEquals(baseStr + "[a=[]]", new ToStringBuilder(base).append("a", Collections.emptyList(), true).toString());
+ assertEquals(baseStr + "[a=%SIZE=1%]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), false).toString());
+ assertEquals(baseStr + "[a=[3]]", new ToStringBuilder(base).append("a", Collections.singletonList(i3), true).toString());
+ assertEquals(baseStr + "[a=%SIZE=2%]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), false).toString());
+ assertEquals(baseStr + "[a=[3, 4]]", new ToStringBuilder(base).append("a", Arrays.asList(i3, i4), true).toString());
+ }
+
+ @Test
+ public void testMap() {
+ assertEquals(baseStr + "[a=%SIZE=0%]", new ToStringBuilder(base).append("a", Collections.emptyMap(), false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", Collections.emptyMap(), true).toString());
+ assertEquals(baseStr + "[a=%SIZE=1%]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), false).toString());
+ assertEquals(baseStr + "[a={k=v}]", new ToStringBuilder(base).append("a", Collections.singletonMap("k", "v"), true).toString());
+ }
+
+ @Test
+ public void testArray() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[a=%SIZE=0%]", new ToStringBuilder(base).append("a", (Object) new Integer[0], false).toString());
+ assertEquals(baseStr + "[a=[]]", new ToStringBuilder(base).append("a", (Object) new Integer[0], true).toString());
+ assertEquals(baseStr + "[a=%SIZE=1%]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, false).toString());
+ assertEquals(baseStr + "[a=[3]]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3}, true).toString());
+ assertEquals(baseStr + "[a=%SIZE=2%]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, false).toString());
+ assertEquals(baseStr + "[a=[3, 4]]", new ToStringBuilder(base).append("a", (Object) new Integer[] {i3, i4}, true).toString());
+ }
+
+ @Test
+ public void testPerson() {
+ final Person p = new Person();
+ p.name = "Suzy Queue";
+ p.age = 19;
+ p.smoker = false;
+ final String pBaseStr = "ToStringStyleTest.Person";
+ assertEquals(pBaseStr + "[name=Suzy Queue,age=19,smoker=false]", new ToStringBuilder(p).append("name", p.name).append("age", p.age).append("smoker", p.smoker).toString());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3L).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals(baseStr + "[[%NULL%, 5, [3, 6]]]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[[%NULL%, 5, [3, 6]]]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[%NULL%]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[%NULL%]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[[1, 2, -3, 4]]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[[1, 2, -3, 4]]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[%NULL%]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[%NULL%]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[[[1, 2], %NULL%, [5]]]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[[[1, 2], %NULL%, [5]]]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[%NULL%]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[%NULL%]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testDefaultValueOfUseClassName() {
+ assertTrue((new StandardToStringStyle()).isUseClassName());
+ }
+
+ @Test
+ public void testDefaultValueOfUseFieldNames() {
+ assertTrue((new StandardToStringStyle()).isUseFieldNames());
+ }
+
+ @Test
+ public void testDefaultValueOfUseShortClassName() {
+ assertFalse((new StandardToStringStyle()).isUseShortClassName());
+ }
+
+ @Test
+ public void testDefaultValueOfUseIdentityHashCode() {
+ assertTrue((new StandardToStringStyle()).isUseIdentityHashCode());
+ }
+
+ @Test
+ public void testDefaultValueOfFullDetail() {
+ assertTrue((new StandardToStringStyle()).isDefaultFullDetail());
+ }
+
+ @Test
+ public void testDefaultIsArrayContentDetail() {
+ assertTrue((new StandardToStringStyle()).isArrayContentDetail());
+ }
+
+ @Test
+ public void testDefaultIsFieldSeparatorAtStart() {
+ assertFalse((new StandardToStringStyle()).isFieldSeparatorAtStart());
+ }
+
+ @Test
+ public void testDefaultIsFieldSeparatorAtEnd() {
+ assertFalse((new StandardToStringStyle()).isFieldSeparatorAtEnd());
+ }
+
+ @Test
+ public void testDefaultGetter() {
+ assertEquals("[", STYLE.getContentStart());
+ assertEquals("]", STYLE.getContentEnd());
+ assertEquals("=", STYLE.getFieldNameValueSeparator());
+ assertEquals(",", STYLE.getFieldSeparator());
+ assertEquals("%NULL%", STYLE.getNullText());
+ assertEquals("%SIZE=", STYLE.getSizeStartText());
+ assertEquals("%", STYLE.getSizeEndText());
+ assertEquals("%", STYLE.getSummaryObjectStartText());
+ assertEquals("%", STYLE.getSummaryObjectEndText());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ToStringBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/ToStringBuilderTest.java
new file mode 100644
index 000000000..64a244764
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ToStringBuilderTest.java
@@ -0,0 +1,1291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.builder.ToStringBuilder}.
+ */
+public class ToStringBuilderTest extends AbstractLangTest {
+
+ // See LANG-1337 for more.
+ private static final int ARRAYLIST_INITIAL_CAPACITY = 10;
+ private final Integer base = Integer.valueOf(5);
+ private final String baseStr = base.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(base));
+
+ @Test
+ public void testConstructorEx1() {
+ assertEquals("<null>", new ToStringBuilder(null).toString());
+ }
+
+ @Test
+ public void testConstructorEx2() {
+ assertEquals("<null>", new ToStringBuilder(null, null).toString());
+ new ToStringBuilder(this.base, null).toString();
+ }
+
+ @Test
+ public void testConstructorEx3() {
+ assertEquals("<null>", new ToStringBuilder(null, null, null).toString());
+ new ToStringBuilder(this.base, null, null).toString();
+ new ToStringBuilder(this.base, ToStringStyle.DEFAULT_STYLE, null).toString();
+ }
+
+ @Test
+ public void testGetSetDefault() {
+ try {
+ ToStringBuilder.setDefaultStyle(ToStringStyle.NO_FIELD_NAMES_STYLE);
+ assertSame(ToStringStyle.NO_FIELD_NAMES_STYLE, ToStringBuilder.getDefaultStyle());
+ } finally {
+ // reset for other tests
+ ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
+ }
+ }
+
+ @Test
+ public void testSetDefaultEx() {
+ assertThrows(NullPointerException.class, () -> ToStringBuilder.setDefaultStyle(null));
+ }
+
+ @Test
+ public void testBlank() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).toString());
+ }
+
+ /**
+ * Test wrapper for int primitive.
+ */
+ @Test
+ public void testReflectionInteger() {
+ assertEquals(baseStr + "[value=5]", ToStringBuilder.reflectionToString(base));
+ }
+
+ /**
+ * Test wrapper for char primitive.
+ */
+ @Test
+ public void testReflectionCharacter() {
+ final Character c = 'A';
+ assertEquals(this.toBaseString(c) + "[value=A]", ToStringBuilder.reflectionToString(c));
+ }
+
+ /**
+ * Test wrapper for char boolean.
+ */
+ @Test
+ public void testReflectionBoolean() {
+ Boolean b;
+ b = Boolean.TRUE;
+ assertEquals(this.toBaseString(b) + "[value=true]", ToStringBuilder.reflectionToString(b));
+ b = Boolean.FALSE;
+ assertEquals(this.toBaseString(b) + "[value=false]", ToStringBuilder.reflectionToString(b));
+ }
+
+ /**
+ * Create the same toString() as Object.toString().
+ * @param o the object to create the string for.
+ * @return a String in the Object.toString format.
+ */
+ private String toBaseString(final Object o) {
+ return o.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(o));
+ }
+
+ // Reflection Array tests
+
+ //
+ // Note on the following line of code repeated in the reflection array tests.
+ //
+ // assertReflectionArray("<null>", array);
+ //
+ // The expected value is not baseStr + "[<null>]" since array==null and is typed as Object.
+ // The null array does not carry array type information.
+ // If we added a primitive array type constructor and pile of associated methods,
+ // then type declaring type information could be carried forward. IMHO, null is null.
+ //
+ // Gary Gregory - 2003-03-12 - ggregory@seagullsw.com
+ //
+
+ public void assertReflectionArray(final String expected, final Object actual) {
+ if (actual == null) {
+ // Until ToStringBuilder supports null objects.
+ return;
+ }
+ assertEquals(expected, ToStringBuilder.reflectionToString(actual));
+ assertEquals(expected, ToStringBuilder.reflectionToString(actual, null));
+ assertEquals(expected, ToStringBuilder.reflectionToString(actual, null, true));
+ assertEquals(expected, ToStringBuilder.reflectionToString(actual, null, false));
+ }
+
+ @Test
+ public void testReflectionObjectArray() {
+ Object[] array = { null, base, new int[] { 3, 6 } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{<null>,5,{3,6}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionLongArray() {
+ long[] array = { 1, 2, -3, 4 };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{1,2,-3,4}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionIntArray() {
+ int[] array = { 1, 2, -3, 4 };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{1,2,-3,4}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionShortArray() {
+ short[] array = { 1, 2, -3, 4 };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{1,2,-3,4}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionyteArray() {
+ byte[] array = { 1, 2, -3, 4 };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{1,2,-3,4}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionCharArray() {
+ char[] array = { 'A', '2', '_', 'D' };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{A,2,_,D}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionDoubleArray() {
+ double[] array = { 1.0, 2.9876, -3.00001, 4.3 };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{1.0,2.9876,-3.00001,4.3}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionFloatArray() {
+ float[] array = { 1.0f, 2.9876f, -3.00001f, 4.3f };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{1.0,2.9876,-3.00001,4.3}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionBooleanArray() {
+ boolean[] array = { true, false, false };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{true,false,false}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ // Reflection Array Array tests
+
+ @Test
+ public void testReflectionFloatArrayArray() {
+ float[][] array = { { 1.0f, 2.29686f }, null, { Float.NaN } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{{1.0,2.29686},<null>,{NaN}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+
+ @Test
+ public void testReflectionLongArrayArray() {
+ long[][] array = { { 1, 2 }, null, { 5 } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{{1,2},<null>,{5}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionIntArrayArray() {
+ int[][] array = { { 1, 2 }, null, { 5 } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{{1,2},<null>,{5}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionhortArrayArray() {
+ short[][] array = { { 1, 2 }, null, { 5 } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{{1,2},<null>,{5}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionByteArrayArray() {
+ byte[][] array = { { 1, 2 }, null, { 5 } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{{1,2},<null>,{5}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionCharArrayArray() {
+ char[][] array = { { 'A', 'B' }, null, { 'p' } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{{A,B},<null>,{p}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionDoubleArrayArray() {
+ double[][] array = { { 1.0, 2.29686 }, null, { Double.NaN } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{{1.0,2.29686},<null>,{NaN}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ @Test
+ public void testReflectionBooleanArrayArray() {
+ boolean[][] array = { { true, false }, null, { false } };
+ final String baseString = this.toBaseString(array);
+ assertEquals(baseString + "[{{true,false},<null>,{false}}]", ToStringBuilder.reflectionToString(array));
+ assertEquals(baseString + "[{{true,false},<null>,{false}}]", ToStringBuilder.reflectionToString(array));
+ array = null;
+ assertReflectionArray("<null>", array);
+ }
+
+ // Reflection hierarchy tests
+ @Test
+ public void testReflectionHierarchyArrayList() {
+ // LANG-1337 without this, the generated string can differ depending on the JVM version/vendor
+ final List<Object> list = new ArrayList<>(ARRAYLIST_INITIAL_CAPACITY);
+ final String baseString = this.toBaseString(list);
+ final String expectedWithTransients = baseString + "[elementData={<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>},size=0,modCount=0]";
+ final String toStringWithTransients = ToStringBuilder.reflectionToString(list, null, true);
+ if (!expectedWithTransients.equals(toStringWithTransients)) {
+ assertEquals(expectedWithTransients, toStringWithTransients);
+ }
+ final String expectedWithoutTransients = baseString + "[size=0]";
+ final String toStringWithoutTransients = ToStringBuilder.reflectionToString(list, null, false);
+ if (!expectedWithoutTransients.equals(toStringWithoutTransients)) {
+ assertEquals(expectedWithoutTransients, toStringWithoutTransients);
+ }
+ }
+
+ @Test
+ public void testReflectionHierarchy() {
+ final ReflectionTestFixtureA baseA = new ReflectionTestFixtureA();
+ String baseString = this.toBaseString(baseA);
+ assertEquals(baseString + "[a=a]", ToStringBuilder.reflectionToString(baseA));
+ assertEquals(baseString + "[a=a]", ToStringBuilder.reflectionToString(baseA, null));
+ assertEquals(baseString + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false));
+ assertEquals(baseString + "[a=a,transientA=t]", ToStringBuilder.reflectionToString(baseA, null, true));
+ assertEquals(baseString + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false, null));
+ assertEquals(baseString + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false, Object.class));
+ assertEquals(baseString + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false, ReflectionTestFixtureA.class));
+
+ final ReflectionTestFixtureB baseB = new ReflectionTestFixtureB();
+ baseString = this.toBaseString(baseB);
+ assertEquals(baseString + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB));
+ assertEquals(baseString + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB));
+ assertEquals(baseString + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null));
+ assertEquals(baseString + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false));
+ assertEquals(baseString + "[b=b,transientB=t,a=a,transientA=t]", ToStringBuilder.reflectionToString(baseB, null, true));
+ assertEquals(baseString + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false, null));
+ assertEquals(baseString + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false, Object.class));
+ assertEquals(baseString + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false, ReflectionTestFixtureA.class));
+ assertEquals(baseString + "[b=b]", ToStringBuilder.reflectionToString(baseB, null, false, ReflectionTestFixtureB.class));
+ }
+
+ static class ReflectionTestFixtureA {
+ @SuppressWarnings("unused")
+ private final char a='a';
+ @SuppressWarnings("unused")
+ private final transient char transientA='t';
+ }
+
+ static class ReflectionTestFixtureB extends ReflectionTestFixtureA {
+ @SuppressWarnings("unused")
+ private final char b='b';
+ @SuppressWarnings("unused")
+ private final transient char transientB='t';
+ }
+
+ @Test
+ public void testInnerClassReflection() {
+ final Outer outer = new Outer();
+ assertEquals(toBaseString(outer) + "[inner=" + toBaseString(outer.inner) + "[]]", outer.toString());
+ }
+
+ static class Outer {
+ Inner inner = new Inner();
+ class Inner {
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+ // Reflection cycle tests
+
+ /**
+ * Test an array element pointing to its container.
+ */
+ @Test
+ public void testReflectionArrayCycle() {
+ final Object[] objects = new Object[1];
+ objects[0] = objects;
+ assertEquals(
+ this.toBaseString(objects) + "[{" + this.toBaseString(objects) + "}]",
+ ToStringBuilder.reflectionToString(objects));
+ }
+
+ /**
+ * Test an array element pointing to its container.
+ */
+ @Test
+ public void testReflectionArrayCycleLevel2() {
+ final Object[] objects = new Object[1];
+ final Object[] objectsLevel2 = new Object[1];
+ objects[0] = objectsLevel2;
+ objectsLevel2[0] = objects;
+ assertEquals(
+ this.toBaseString(objects) + "[{{" + this.toBaseString(objects) + "}}]",
+ ToStringBuilder.reflectionToString(objects));
+ assertEquals(
+ this.toBaseString(objectsLevel2) + "[{{" + this.toBaseString(objectsLevel2) + "}}]",
+ ToStringBuilder.reflectionToString(objectsLevel2));
+ }
+
+ @Test
+ public void testReflectionArrayArrayCycle() {
+ final Object[][] objects = new Object[2][2];
+ objects[0][0] = objects;
+ objects[0][1] = objects;
+ objects[1][0] = objects;
+ objects[1][1] = objects;
+ final String basicToString = this.toBaseString(objects);
+ assertEquals(
+ basicToString
+ + "[{{"
+ + basicToString
+ + ","
+ + basicToString
+ + "},{"
+ + basicToString
+ + ","
+ + basicToString
+ + "}}]",
+ ToStringBuilder.reflectionToString(objects));
+ }
+
+ /**
+ * A reflection test fixture.
+ */
+ static class ReflectionTestCycleA {
+ ReflectionTestCycleB b;
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+ /**
+ * A reflection test fixture.
+ */
+ static class ReflectionTestCycleB {
+ ReflectionTestCycleA a;
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+ /**
+ * A reflection test fixture.
+ */
+ static class SimpleReflectionTestFixture {
+ Object o;
+
+ SimpleReflectionTestFixture() {
+ }
+
+ SimpleReflectionTestFixture(final Object o) {
+ this.o = o;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+ private static class SelfInstanceVarReflectionTestFixture {
+ @SuppressWarnings("unused")
+ private final SelfInstanceVarReflectionTestFixture typeIsSelf;
+
+ SelfInstanceVarReflectionTestFixture() {
+ this.typeIsSelf = this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+ private static class SelfInstanceTwoVarsReflectionTestFixture {
+ @SuppressWarnings("unused")
+ private final SelfInstanceTwoVarsReflectionTestFixture typeIsSelf;
+ private final String otherType = "The Other Type";
+
+ SelfInstanceTwoVarsReflectionTestFixture() {
+ this.typeIsSelf = this;
+ }
+
+ public String getOtherType() {
+ return this.otherType;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+ }
+
+
+ /**
+ * Test an Object pointing to itself, the simplest test.
+ */
+ @Test
+ public void testSimpleReflectionObjectCycle() {
+ final SimpleReflectionTestFixture simple = new SimpleReflectionTestFixture();
+ simple.o = simple;
+ assertEquals(this.toBaseString(simple) + "[o=" + this.toBaseString(simple) + "]", simple.toString());
+ }
+
+ /**
+ * Test a class that defines an ivar pointing to itself.
+ */
+ @Test
+ public void testSelfInstanceVarReflectionObjectCycle() {
+ final SelfInstanceVarReflectionTestFixture test = new SelfInstanceVarReflectionTestFixture();
+ assertEquals(this.toBaseString(test) + "[typeIsSelf=" + this.toBaseString(test) + "]", test.toString());
+ }
+
+ /**
+ * Test a class that defines an ivar pointing to itself. This test was
+ * created to show that handling cyclical object resulted in a missing endFieldSeparator call.
+ */
+ @Test
+ public void testSelfInstanceTwoVarsReflectionObjectCycle() {
+ final SelfInstanceTwoVarsReflectionTestFixture test = new SelfInstanceTwoVarsReflectionTestFixture();
+ assertEquals(this.toBaseString(test) + "[otherType=" + test.getOtherType().toString() + ",typeIsSelf=" + this.toBaseString(test) + "]", test.toString());
+ }
+
+
+ /**
+ * Test Objects pointing to each other.
+ */
+ @Test
+ public void testReflectionObjectCycle() {
+ final ReflectionTestCycleA a = new ReflectionTestCycleA();
+ final ReflectionTestCycleB b = new ReflectionTestCycleB();
+ a.b = b;
+ b.a = a;
+ assertEquals(
+ this.toBaseString(a) + "[b=" + this.toBaseString(b) + "[a=" + this.toBaseString(a) + "]]",
+ a.toString());
+ }
+
+ /**
+ * Test a nasty combination of arrays and Objects pointing to each other.
+ * objects[0] -&gt; SimpleReflectionTestFixture[ o -&gt; objects ]
+ */
+ @Test
+ public void testReflectionArrayAndObjectCycle() {
+ final Object[] objects = new Object[1];
+ final SimpleReflectionTestFixture simple = new SimpleReflectionTestFixture(objects);
+ objects[0] = simple;
+ assertEquals(
+ this.toBaseString(objects)
+ + "[{"
+ + this.toBaseString(simple)
+ + "[o="
+ + this.toBaseString(objects)
+ + "]"
+ + "}]",
+ ToStringBuilder.reflectionToString(objects));
+ assertEquals(
+ this.toBaseString(simple)
+ + "[o={"
+ + this.toBaseString(simple)
+ + "}]",
+ ToStringBuilder.reflectionToString(simple));
+ }
+
+ @Test
+ public void testAppendSuper() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").toString());
+
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString());
+ assertEquals(baseStr + "[<null>,a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").append("a", "hello").toString());
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testAppendToString() {
+ assertEquals(baseStr + "[]", new ToStringBuilder(base).appendToString("Integer@8888[]").toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).appendToString("Integer@8888[<null>]").toString());
+
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendToString("Integer@8888[]").append("a", "hello").toString());
+ assertEquals(baseStr + "[<null>,a=hello]", new ToStringBuilder(base).appendToString("Integer@8888[<null>]").append("a", "hello").toString());
+ assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendToString(null).append("a", "hello").toString());
+ }
+
+ @Test
+ public void testAppendAsObjectToString() {
+ final String objectToAppend1 = "";
+ final Boolean objectToAppend2 = Boolean.TRUE;
+ final Object objectToAppend3 = new Object();
+
+ assertEquals(baseStr + "[" + toBaseString(objectToAppend1) + "]",
+ new ToStringBuilder(base).appendAsObjectToString(objectToAppend1).toString());
+ assertEquals(baseStr + "[" + toBaseString(objectToAppend2) + "]",
+ new ToStringBuilder(base).appendAsObjectToString(objectToAppend2).toString());
+ assertEquals(baseStr + "[" + toBaseString(objectToAppend3) + "]",
+ new ToStringBuilder(base).appendAsObjectToString(objectToAppend3).toString());
+ }
+
+ @Test
+ public void testAppendAsObjectToStringNullPointerException() {
+ ToStringBuilder builder = new ToStringBuilder(1);
+ assertThrows(NullPointerException.class, () -> builder.appendAsObjectToString(null));
+ builder.toString();
+ }
+
+ @Test
+ public void testAppendBooleanArrayWithFieldName() {
+ final boolean[] array = { true, false, false };
+ assertEquals(baseStr + "[flags={true,false,false}]",
+ new ToStringBuilder(base).append("flags", array).toString());
+ assertEquals(baseStr + "[flags=<null>]",
+ new ToStringBuilder(base).append("flags", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{true,false,false}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendBooleanArrayWithFieldNameAndFullDetatil() {
+ final boolean[] array = { true, false, false };
+ assertEquals(baseStr + "[flags={true,false,false}]",
+ new ToStringBuilder(base).append("flags", array, true).toString());
+ assertEquals(baseStr + "[length=<size=3>]",
+ new ToStringBuilder(base).append("length", array, false).toString());
+ assertEquals(baseStr + "[flags=<null>]",
+ new ToStringBuilder(base).append("flags", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=3>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testAppendCharArrayWithFieldName() {
+ final char[] array = { 'A', '2', '_', 'D' };
+ assertEquals(baseStr + "[chars={A,2,_,D}]", new ToStringBuilder(base).append("chars", array).toString());
+ assertEquals(baseStr + "[letters={A,2,_,D}]", new ToStringBuilder(base).append("letters", array).toString());
+ assertEquals(baseStr + "[flags=<null>]",
+ new ToStringBuilder(base).append("flags", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{A,2,_,D}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendCharArrayWithFieldNameAndFullDetatil() {
+ final char[] array = { 'A', '2', '_', 'D' };
+ assertEquals(baseStr + "[chars={A,2,_,D}]", new ToStringBuilder(base).append("chars", array, true).toString());
+ assertEquals(baseStr + "[letters=<size=4>]",
+ new ToStringBuilder(base).append("letters", array, false).toString());
+ assertEquals(baseStr + "[flags=<null>]",
+ new ToStringBuilder(base).append("flags", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=4>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testAppendDoubleArrayWithFieldName() {
+ final double[] array = { 1.0, 2.9876, -3.00001, 4.3 };
+ assertEquals(baseStr + "[values={1.0,2.9876,-3.00001,4.3}]",
+ new ToStringBuilder(base).append("values", array).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{1.0,2.9876,-3.00001,4.3}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendDoubleArrayWithFieldNameAndFullDetatil() {
+ final double[] array = { 1.0, 2.9876, -3.00001, 4.3 };
+ assertEquals(baseStr + "[values={1.0,2.9876,-3.00001,4.3}]",
+ new ToStringBuilder(base).append("values", array, true).toString());
+ assertEquals(baseStr + "[length=<size=4>]",
+ new ToStringBuilder(base).append("length", array, false).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=4>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testAppendObjectArrayWithFieldName() {
+ final Object[] array = { null, base, new int[] { 3, 6 } };
+ assertEquals(baseStr + "[values={<null>,5,{3,6}}]",
+ new ToStringBuilder(base).append("values", array).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendObjectArrayWithFieldNameAndFullDetatil() {
+ final Object[] array = { null, base, new int[] { 3, 6 } };
+ assertEquals(baseStr + "[values={<null>,5,{3,6}}]",
+ new ToStringBuilder(base).append("values", array, true).toString());
+ assertEquals(baseStr + "[length=<size=3>]",
+ new ToStringBuilder(base).append("length", array, false).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=3>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testAppendLongArrayWithFieldName() {
+ final long[] array = { 1, 2, -3, 4 };
+ assertEquals(baseStr + "[values={1,2,-3,4}]", new ToStringBuilder(base).append("values", array).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendLongArrayWithFieldNameAndFullDetatil() {
+ final long[] array = { 1, 2, -3, 4 };
+ assertEquals(baseStr + "[values={1,2,-3,4}]",
+ new ToStringBuilder(base).append("values", array, true).toString());
+ assertEquals(baseStr + "[length=<size=4>]",
+ new ToStringBuilder(base).append("length", array, false).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=4>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testAppendIntArrayWithFieldName() {
+ final int[] array = { 1, 2, -3, 4 };
+ assertEquals(baseStr + "[values={1,2,-3,4}]", new ToStringBuilder(base).append("values", array).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendIntArrayWithFieldNameAndFullDetatil() {
+ final int[] array = { 1, 2, -3, 4 };
+ assertEquals(baseStr + "[values={1,2,-3,4}]",
+ new ToStringBuilder(base).append("values", array, true).toString());
+ assertEquals(baseStr + "[length=<size=4>]",
+ new ToStringBuilder(base).append("length", array, false).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=4>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testAppendShortArrayWithFieldName() {
+ final short[] array = { 1, 2, -3, 4 };
+ assertEquals(baseStr + "[values={1,2,-3,4}]", new ToStringBuilder(base).append("values", array).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendShortArrayWithFieldNameAndFullDetatil() {
+ final short[] array = { 1, 2, -3, 4 };
+ assertEquals(baseStr + "[values={1,2,-3,4}]",
+ new ToStringBuilder(base).append("values", array, true).toString());
+ assertEquals(baseStr + "[length=<size=4>]",
+ new ToStringBuilder(base).append("length", array, false).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=4>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testAppendByteArrayWithFieldName() {
+ final byte[] array = { 1, 2, -3, 4 };
+ assertEquals(baseStr + "[values={1,2,-3,4}]", new ToStringBuilder(base).append("values", array).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendByteArrayWithFieldNameAndFullDetatil() {
+ final byte[] array = { 1, 2, -3, 4 };
+ assertEquals(baseStr + "[values={1,2,-3,4}]",
+ new ToStringBuilder(base).append("values", array, true).toString());
+ assertEquals(baseStr + "[length=<size=4>]",
+ new ToStringBuilder(base).append("length", array, false).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=4>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testAppendFloatArrayWithFieldName() {
+ final float[] array = { 1.0f, 2.9876f, -3.00001f, 4.3f };
+ assertEquals(baseStr + "[values={1.0,2.9876,-3.00001,4.3}]",
+ new ToStringBuilder(base).append("values", array).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null).toString());
+ assertEquals(baseStr + "[{1.0,2.9876,-3.00001,4.3}]", new ToStringBuilder(base).append(null, array).toString());
+ }
+
+ @Test
+ public void testAppendFloatArrayWithFieldNameAndFullDetatil() {
+ final float[] array = { 1.0f, 2.9876f, -3.00001f, 4.3f };
+ assertEquals(baseStr + "[values={1.0,2.9876,-3.00001,4.3}]",
+ new ToStringBuilder(base).append("values", array, true).toString());
+ assertEquals(baseStr + "[length=<size=4>]",
+ new ToStringBuilder(base).append("length", array, false).toString());
+ assertEquals(baseStr + "[values=<null>]",
+ new ToStringBuilder(base).append("values", (boolean[]) null, true).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(null, (boolean[]) null, false).toString());
+ assertEquals(baseStr + "[<size=4>]", new ToStringBuilder(base).append(null, array, false).toString());
+ }
+
+ @Test
+ public void testConstructToStringBuilder() {
+ final ToStringBuilder stringBuilder1 = new ToStringBuilder(base, null, null);
+ final ToStringBuilder stringBuilder2 = new ToStringBuilder(base, ToStringStyle.DEFAULT_STYLE, new StringBuffer(1024));
+ assertEquals(ToStringStyle.DEFAULT_STYLE, stringBuilder1.getStyle());
+ assertNotNull(stringBuilder1.getStringBuffer());
+ assertNotNull(stringBuilder1.toString());
+ assertEquals(ToStringStyle.DEFAULT_STYLE, stringBuilder2.getStyle());
+ assertNotNull(stringBuilder2.getStringBuffer());
+ assertNotNull(stringBuilder2.toString());
+ }
+
+
+ @Test
+ public void testObject() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) null).toString());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).toString());
+ assertEquals(baseStr + "[a=<null>]", new ToStringBuilder(base).append("a", (Object) null).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", i3).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
+ assertEquals(baseStr + "[a=<Integer>]", new ToStringBuilder(base).append("a", i3, false).toString());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", new ArrayList<>(), false).toString());
+ assertEquals(baseStr + "[a=[]]", new ToStringBuilder(base).append("a", new ArrayList<>(), true).toString());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", new HashMap<>(), false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", new HashMap<>(), true).toString());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", (Object) new String[0], false).toString());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", (Object) new String[0], true).toString());
+ }
+
+ @Test
+ public void testObjectBuild() {
+ final Integer i3 = Integer.valueOf(3);
+ final Integer i4 = Integer.valueOf(4);
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) null).build());
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).build());
+ assertEquals(baseStr + "[a=<null>]", new ToStringBuilder(base).append("a", (Object) null).build());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", i3).build());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).build());
+ assertEquals(baseStr + "[a=<Integer>]", new ToStringBuilder(base).append("a", i3, false).build());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", new ArrayList<>(), false).build());
+ assertEquals(baseStr + "[a=[]]", new ToStringBuilder(base).append("a", new ArrayList<>(), true).build());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", new HashMap<>(), false).build());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", new HashMap<>(), true).build());
+ assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", (Object) new String[0], false).build());
+ assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", (Object) new String[0], true).build());
+ }
+
+ @Test
+ public void testLong() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3L).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", 3L).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
+ }
+
+ @Test
+ public void testInt() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", 3).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", 3).append("b", 4).toString());
+ }
+
+ @Test
+ public void testShort() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append((short) 3).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", (short) 3).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", (short) 3).append("b", (short) 4).toString());
+ }
+
+ @Test
+ public void testChar() {
+ assertEquals(baseStr + "[A]", new ToStringBuilder(base).append((char) 65).toString());
+ assertEquals(baseStr + "[a=A]", new ToStringBuilder(base).append("a", (char) 65).toString());
+ assertEquals(baseStr + "[a=A,b=B]", new ToStringBuilder(base).append("a", (char) 65).append("b", (char) 66).toString());
+ }
+
+ @Test
+ public void testByte() {
+ assertEquals(baseStr + "[3]", new ToStringBuilder(base).append((byte) 3).toString());
+ assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", (byte) 3).toString());
+ assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", (byte) 3).append("b", (byte) 4).toString());
+ }
+
+ @Test
+ public void testDouble() {
+ assertEquals(baseStr + "[3.2]", new ToStringBuilder(base).append(3.2).toString());
+ assertEquals(baseStr + "[a=3.2]", new ToStringBuilder(base).append("a", 3.2).toString());
+ assertEquals(baseStr + "[a=3.2,b=4.3]", new ToStringBuilder(base).append("a", 3.2).append("b", 4.3).toString());
+ }
+
+ @Test
+ public void testFloat() {
+ assertEquals(baseStr + "[3.2]", new ToStringBuilder(base).append((float) 3.2).toString());
+ assertEquals(baseStr + "[a=3.2]", new ToStringBuilder(base).append("a", (float) 3.2).toString());
+ assertEquals(baseStr + "[a=3.2,b=4.3]", new ToStringBuilder(base).append("a", (float) 3.2).append("b", (float) 4.3).toString());
+ }
+
+ @Test
+ public void testBoolean() {
+ assertEquals(baseStr + "[true]", new ToStringBuilder(base).append(true).toString());
+ assertEquals(baseStr + "[a=true]", new ToStringBuilder(base).append("a", true).toString());
+ assertEquals(baseStr + "[a=true,b=false]", new ToStringBuilder(base).append("a", true).append("b", false).toString());
+ }
+
+
+ @Test
+ public void testObjectArray() {
+ Object[] array = {null, base, new int[] {3, 6}};
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArray() {
+ long[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testIntArray() {
+ int[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testShortArray() {
+ short[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testByteArray() {
+ byte[] array = {1, 2, -3, 4};
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testCharArray() {
+ char[] array = {'A', '2', '_', 'D'};
+ assertEquals(baseStr + "[{A,2,_,D}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{A,2,_,D}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testDoubleArray() {
+ double[] array = {1.0, 2.9876, -3.00001, 4.3};
+ assertEquals(baseStr + "[{1.0,2.9876,-3.00001,4.3}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1.0,2.9876,-3.00001,4.3}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testFloatArray() {
+ float[] array = {1.0f, 2.9876f, -3.00001f, 4.3f};
+ assertEquals(baseStr + "[{1.0,2.9876,-3.00001,4.3}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{1.0,2.9876,-3.00001,4.3}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testBooleanArray() {
+ boolean[] array = {true, false, false};
+ assertEquals(baseStr + "[{true,false,false}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{true,false,false}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testLongArrayArray() {
+ long[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testIntArrayArray() {
+ int[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testShortArrayArray() {
+ short[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testByteArrayArray() {
+ byte[][] array = {{1, 2}, null, {5}};
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testCharArrayArray() {
+ char[][] array = {{'A', 'B'}, null, {'p'}};
+ assertEquals(baseStr + "[{{A,B},<null>,{p}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{A,B},<null>,{p}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testDoubleArrayArray() {
+ double[][] array = {{1.0, 2.29686}, null, {Double.NaN}};
+ assertEquals(baseStr + "[{{1.0,2.29686},<null>,{NaN}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1.0,2.29686},<null>,{NaN}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testFloatArrayArray() {
+ float[][] array = {{1.0f, 2.29686f}, null, {Float.NaN}};
+ assertEquals(baseStr + "[{{1.0,2.29686},<null>,{NaN}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{1.0,2.29686},<null>,{NaN}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testBooleanArrayArray() {
+ boolean[][] array = {{true, false}, null, {false}};
+ assertEquals(baseStr + "[{{true,false},<null>,{false}}]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[{{true,false},<null>,{false}}]", new ToStringBuilder(base).append((Object) array).toString());
+ array = null;
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
+ assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
+ }
+
+ @Test
+ public void testObjectCycle() {
+ final ObjectCycle a = new ObjectCycle();
+ final ObjectCycle b = new ObjectCycle();
+ a.obj = b;
+ b.obj = a;
+
+ final String expected = toBaseString(a) + "[" + toBaseString(b) + "[" + toBaseString(a) + "]]";
+ assertEquals(expected, a.toString());
+ }
+
+ static class ObjectCycle {
+ Object obj;
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).append(obj).toString();
+ }
+ }
+
+ @Test
+ public void testSimpleReflectionStatics() {
+ final SimpleReflectionStaticFieldsFixture instance1 = new SimpleReflectionStaticFieldsFixture();
+ assertEquals(
+ this.toBaseString(instance1) + "[staticInt=12345,staticString=staticString]",
+ ReflectionToStringBuilder.toString(instance1, null, false, true, SimpleReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[staticInt=12345,staticString=staticString]",
+ ReflectionToStringBuilder.toString(instance1, null, true, true, SimpleReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[staticInt=12345,staticString=staticString]",
+ this.toStringWithStatics(instance1, null, SimpleReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[staticInt=12345,staticString=staticString]",
+ this.toStringWithStatics(instance1, null, SimpleReflectionStaticFieldsFixture.class));
+ }
+
+ /**
+ * Tests ReflectionToStringBuilder.toString() for statics.
+ */
+ @Test
+ public void testReflectionStatics() {
+ final ReflectionStaticFieldsFixture instance1 = new ReflectionStaticFieldsFixture();
+ assertEquals(
+ this.toBaseString(instance1) + "[instanceInt=67890,instanceString=instanceString,staticInt=12345,staticString=staticString]",
+ ReflectionToStringBuilder.toString(instance1, null, false, true, ReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[instanceInt=67890,instanceString=instanceString,staticInt=12345,staticString=staticString,staticTransientInt=54321,staticTransientString=staticTransientString,transientInt=98765,transientString=transientString]",
+ ReflectionToStringBuilder.toString(instance1, null, true, true, ReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[instanceInt=67890,instanceString=instanceString,staticInt=12345,staticString=staticString]",
+ this.toStringWithStatics(instance1, null, ReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[instanceInt=67890,instanceString=instanceString,staticInt=12345,staticString=staticString]",
+ this.toStringWithStatics(instance1, null, ReflectionStaticFieldsFixture.class));
+ }
+
+ /**
+ * Tests ReflectionToStringBuilder.toString() for statics.
+ */
+ @Test
+ public void testInheritedReflectionStatics() {
+ final InheritedReflectionStaticFieldsFixture instance1 = new InheritedReflectionStaticFieldsFixture();
+ assertEquals(
+ this.toBaseString(instance1) + "[staticInt2=67890,staticString2=staticString2]",
+ ReflectionToStringBuilder.toString(instance1, null, false, true, InheritedReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[staticInt2=67890,staticString2=staticString2,staticInt=12345,staticString=staticString]",
+ ReflectionToStringBuilder.toString(instance1, null, false, true, SimpleReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[staticInt2=67890,staticString2=staticString2,staticInt=12345,staticString=staticString]",
+ this.toStringWithStatics(instance1, null, SimpleReflectionStaticFieldsFixture.class));
+ assertEquals(
+ this.toBaseString(instance1) + "[staticInt2=67890,staticString2=staticString2,staticInt=12345,staticString=staticString]",
+ this.toStringWithStatics(instance1, null, SimpleReflectionStaticFieldsFixture.class));
+ }
+
+ /**
+ * <p>This method uses reflection to build a suitable
+ * {@code toString} value which includes static fields.</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 fields are not output.</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
+ * {@code ToStringStyle} is used.</p>
+ *
+ * @param <T> the type of the output object
+ * @param object the Object to be output
+ * @param style the style of the {@code toString} to create,
+ * may be {@code 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}
+ */
+ public <T> String toStringWithStatics(final T object, final ToStringStyle style, final Class<? super T> reflectUpToClass) {
+ return ReflectionToStringBuilder.toString(object, style, false, true, reflectUpToClass);
+ }
+
+ /**
+ * Tests ReflectionToStringBuilder setUpToClass().
+ */
+ @Test
+ public void test_setUpToClass_valid() {
+ final Integer val = Integer.valueOf(5);
+ final ReflectionToStringBuilder test = new ReflectionToStringBuilder(val);
+ test.setUpToClass(Number.class);
+ test.toString();
+ }
+
+ /**
+ * Tests ReflectionToStringBuilder setUpToClass().
+ */
+ @Test
+ public void test_setUpToClass_invalid() {
+ final Integer val = Integer.valueOf(5);
+ final ReflectionToStringBuilder test = new ReflectionToStringBuilder(val);
+ assertThrows(IllegalArgumentException.class, () -> test.setUpToClass(String.class));
+ test.toString();
+ }
+
+ /**
+ * Tests ReflectionToStringBuilder.toString() for statics.
+ */
+ class ReflectionStaticFieldsFixture {
+ static final String staticString = "staticString";
+ static final int staticInt = 12345;
+ static final transient String staticTransientString = "staticTransientString";
+ static final transient int staticTransientInt = 54321;
+ String instanceString = "instanceString";
+ int instanceInt = 67890;
+ transient String transientString = "transientString";
+ transient int transientInt = 98765;
+ }
+
+ /**
+ * Test fixture for ReflectionToStringBuilder.toString() for statics.
+ */
+ class SimpleReflectionStaticFieldsFixture {
+ static final String staticString = "staticString";
+ static final int staticInt = 12345;
+ }
+
+ /**
+ * Test fixture for ReflectionToStringBuilder.toString() for statics.
+ */
+ @SuppressWarnings("unused")
+ class InheritedReflectionStaticFieldsFixture extends SimpleReflectionStaticFieldsFixture {
+ static final String staticString2 = "staticString2";
+ static final int staticInt2 = 67890;
+ }
+
+ @Test
+ public void testReflectionNull() {
+ assertThrows(NullPointerException.class, () -> ReflectionToStringBuilder.toString(null));
+ }
+
+ /**
+ * Points out failure to print anything from appendToString methods using MULTI_LINE_STYLE.
+ * See issue LANG-372.
+ */
+ class MultiLineTestObject {
+ Integer i = Integer.valueOf(31337);
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).append("testInt", i).toString();
+ }
+ }
+
+ @Test
+ public void testAppendToStringUsingMultiLineStyle() {
+ final MultiLineTestObject obj = new MultiLineTestObject();
+ final ToStringBuilder testBuilder = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+ .appendToString(obj.toString());
+ assertEquals(-1, testBuilder.toString().indexOf("testInt=31337"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ToStringStyleConcurrencyTest.java b/src/test/java/org/apache/commons/lang3/builder/ToStringStyleConcurrencyTest.java
new file mode 100644
index 000000000..0e6ff7a63
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ToStringStyleConcurrencyTest.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.builder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.concurrent.UncheckedFuture;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests concurrent access for the default {@link ToStringStyle}.
+ * <p>
+ * The {@link ToStringStyle} class includes a registry to avoid infinite loops for objects with circular references. We
+ * want to make sure that we do not get concurrency exceptions accessing this registry.
+ * </p>
+ * <p>
+ * This test passes but only tests one aspect of the issue.
+ * </p>
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/LANG-762">[LANG-762] Handle or document ReflectionToStringBuilder
+ * and ToStringBuilder for collections that are not thread safe</a>
+ * @since 3.1
+ */
+public class ToStringStyleConcurrencyTest extends AbstractLangTest {
+
+ static class CollectionHolder<T extends Collection<?>> {
+ T collection;
+
+ CollectionHolder(final T collection) {
+ this.collection = collection;
+ }
+ }
+
+ private static final List<Integer> LIST;
+ private static final int LIST_SIZE = 100000;
+ private static final int REPEAT = 100;
+
+ static {
+ LIST = new ArrayList<>(LIST_SIZE);
+ for (int i = 0; i < LIST_SIZE; i++) {
+ LIST.add(Integer.valueOf(i));
+ }
+ }
+
+ @Test
+ public void testLinkedList() throws InterruptedException {
+ this.testConcurrency(new CollectionHolder<>(new LinkedList<>()));
+ }
+
+ @Test
+ public void testArrayList() throws InterruptedException {
+ this.testConcurrency(new CollectionHolder<>(new ArrayList<>()));
+ }
+
+ @Test
+ public void testCopyOnWriteArrayList() throws InterruptedException {
+ this.testConcurrency(new CollectionHolder<>(new CopyOnWriteArrayList<>()));
+ }
+
+ private void testConcurrency(final CollectionHolder<List<Integer>> holder) throws InterruptedException {
+ final List<Integer> list = holder.collection;
+ // make a big array that takes a long time to toString()
+ list.addAll(LIST);
+ // Create a thread pool with two threads to cause the most contention on the underlying resource.
+ final ExecutorService threadPool = Executors.newFixedThreadPool(2);
+ try {
+ // Consumes toStrings
+ final Callable<Integer> consumer = () -> {
+ for (int i = 0; i < REPEAT; i++) {
+ // Calls ToStringStyle
+ new ToStringBuilder(holder).append(holder.collection);
+ }
+ return Integer.valueOf(REPEAT);
+ };
+ final Collection<Callable<Integer>> tasks = new ArrayList<>();
+ tasks.add(consumer);
+ tasks.add(consumer);
+ final List<Future<Integer>> futures = threadPool.invokeAll(tasks);
+ UncheckedFuture.on(futures).forEach(UncheckedFuture::get);
+ } finally {
+ threadPool.shutdown();
+ threadPool.awaitTermination(1, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/ToStringStyleTest.java
new file mode 100644
index 000000000..02399b6db
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/builder/ToStringStyleTest.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.builder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test case for ToStringStyle.
+ */
+public class ToStringStyleTest extends AbstractLangTest {
+
+ private static class ToStringStyleImpl extends ToStringStyle {
+ private static final long serialVersionUID = 1L;
+
+ }
+
+ @Test
+ public void testSetArrayStart() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setArrayStart(null);
+ assertEquals("", style.getArrayStart());
+ }
+
+ @Test
+ public void testSetArrayEnd() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setArrayEnd(null);
+ assertEquals("", style.getArrayEnd());
+ }
+
+ @Test
+ public void testSetArraySeparator() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setArraySeparator(null);
+ assertEquals("", style.getArraySeparator());
+ }
+
+ @Test
+ public void testSetContentStart() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setContentStart(null);
+ assertEquals("", style.getContentStart());
+ }
+
+ @Test
+ public void testSetContentEnd() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setContentEnd(null);
+ assertEquals("", style.getContentEnd());
+ }
+
+ @Test
+ public void testSetFieldNameValueSeparator() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setFieldNameValueSeparator(null);
+ assertEquals("", style.getFieldNameValueSeparator());
+ }
+
+ @Test
+ public void testSetFieldSeparator() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setFieldSeparator(null);
+ assertEquals("", style.getFieldSeparator());
+ }
+
+ @Test
+ public void testSetNullText() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setNullText(null);
+ assertEquals("", style.getNullText());
+ }
+
+ @Test
+ public void testSetSizeStartText() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setSizeStartText(null);
+ assertEquals("", style.getSizeStartText());
+ }
+
+ @Test
+ public void testSetSizeEndText() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setSizeEndText(null);
+ assertEquals("", style.getSizeEndText());
+ }
+
+ @Test
+ public void testSetSummaryObjectStartText() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setSummaryObjectStartText(null);
+ assertEquals("", style.getSummaryObjectStartText());
+ }
+
+ @Test
+ public void testSetSummaryObjectEndText() {
+ final ToStringStyle style = new ToStringStyleImpl();
+ style.setSummaryObjectEndText(null);
+ assertEquals("", style.getSummaryObjectEndText());
+ }
+
+ /**
+ * An object used to test {@link ToStringStyle}.
+ *
+ */
+ static class Person {
+ /**
+ * Test String field.
+ */
+ String name;
+
+ /**
+ * Test integer field.
+ */
+ int age;
+
+ /**
+ * Test boolean field.
+ */
+ boolean smoker;
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/compare/ComparableUtilsTest.java b/src/test/java/org/apache/commons/lang3/compare/ComparableUtilsTest.java
new file mode 100644
index 000000000..902987447
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/compare/ComparableUtilsTest.java
@@ -0,0 +1,474 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.apache.commons.lang3.compare.ComparableUtils.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+public class ComparableUtilsTest extends AbstractLangTest {
+
+ @Nested
+ class A_is_1 {
+
+ @DisplayName("B is 0 (B < A)")
+ @Nested
+ class B_is_0 {
+
+ @DisplayName("C is 0 ([B=C] < A)")
+ @Nested
+ class C_is_0 {
+
+ BigDecimal c = BigDecimal.ZERO;
+
+ @Test
+ void between_returns_false() {
+ assertFalse(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_false() {
+ assertFalse(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_false() {
+ assertFalse(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_false() {
+ assertFalse(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+
+ }
+
+ @DisplayName("C is 1 (B < A = C)")
+ @Nested
+ class C_is_1 {
+
+ BigDecimal c = BigDecimal.ONE;
+
+ @Test
+ void between_returns_true() {
+ assertTrue(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_false() {
+ assertFalse(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_true() {
+ assertTrue(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_false() {
+ assertFalse(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+ }
+
+ @DisplayName("C is 10 (B < A < C)")
+ @Nested
+ class C_is_10 {
+
+ BigDecimal c = BigDecimal.TEN;
+
+ @Test
+ void between_returns_true() {
+ assertTrue(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_true() {
+ assertTrue(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_true() {
+ assertTrue(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_true() {
+ assertTrue(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+ }
+
+ BigDecimal b = BigDecimal.ZERO;
+
+ @Test
+ void equalTo_returns_false() {
+ assertFalse(is(a).equalTo(b));
+ }
+
+ @Test
+ void greaterThan_returns_true() {
+ assertTrue(is(a).greaterThan(b));
+ }
+
+ @Test
+ void greaterThanOrEqualTo_returns_true() {
+ assertTrue(is(a).greaterThanOrEqualTo(b));
+ }
+
+ @Test
+ void lessThan_returns_false() {
+ assertFalse(is(a).lessThan(b));
+ }
+
+ @Test
+ void lessThanOrEqualTo_returns_false() {
+ assertFalse(is(a).lessThanOrEqualTo(b));
+ }
+
+ @Test
+ void static_gt_returns_true() {
+ assertTrue(ComparableUtils.gt(b).test(a));
+ }
+
+ @Test
+ void static_ge_returns_true() {
+ assertTrue(ComparableUtils.ge(b).test(a));
+ }
+
+ @Test
+ void static_lt_returns_false() {
+ assertFalse(ComparableUtils.lt(b).test(a));
+ }
+
+ @Test
+ void static_le_returns_false() {
+ assertFalse(ComparableUtils.le(b).test(a));
+ }
+ }
+
+ @DisplayName("B is 1 (B = A)")
+ @Nested
+ class B_is_1 {
+
+ @DisplayName("C is 0 (B = A > C)")
+ @Nested
+ class C_is_0 {
+
+ BigDecimal c = BigDecimal.ZERO;
+
+ @Test
+ void between_returns_true() {
+ assertTrue(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_false() {
+ assertFalse(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_true() {
+ assertTrue(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_false() {
+ assertFalse(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+ }
+
+ @DisplayName("C is 1 (B = A = C)")
+ @Nested
+ class C_is_1 {
+
+ BigDecimal c = BigDecimal.ONE;
+
+ @Test
+ void between_returns_true() {
+ assertTrue(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_false() {
+ assertFalse(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_true() {
+ assertTrue(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_false() {
+ assertFalse(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+ }
+
+ @DisplayName("C is 10 (B = A < C)")
+ @Nested
+ class C_is_10 {
+
+ BigDecimal c = BigDecimal.TEN;
+
+ @Test
+ void between_returns_true() {
+ assertTrue(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_false() {
+ assertFalse(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_true() {
+ assertTrue(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_false() {
+ assertFalse(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+ }
+
+ BigDecimal b = BigDecimal.ONE;
+
+ @Test
+ void equalTo_returns_true() {
+ assertTrue(is(a).equalTo(b));
+ }
+
+ @Test
+ void greaterThan_returns_false() {
+ assertFalse(is(a).greaterThan(b));
+ }
+
+ @Test
+ void greaterThanOrEqualTo_returns_true() {
+ assertTrue(is(a).greaterThanOrEqualTo(b));
+ }
+
+ @Test
+ void lessThan_returns_false() {
+ assertFalse(is(a).lessThan(b));
+ }
+
+ @Test
+ void lessThanOrEqualTo_returns_true() {
+ assertTrue(is(a).lessThanOrEqualTo(b));
+ }
+
+ @Test
+ void static_gt_returns_false() {
+ assertFalse(ComparableUtils.gt(b).test(a));
+ }
+
+ @Test
+ void static_ge_returns_true() {
+ assertTrue(ComparableUtils.ge(b).test(a));
+ }
+
+ @Test
+ void static_lt_returns_false() {
+ assertFalse(ComparableUtils.lt(b).test(a));
+ }
+
+ @Test
+ void static_le_returns_true() {
+ assertTrue(ComparableUtils.le(b).test(a));
+ }
+ }
+
+ @DisplayName("B is 10 (B > A)")
+ @Nested
+ class B_is_10 {
+
+ @DisplayName("C is 0 (B > A > C)")
+ @Nested
+ class C_is_0 {
+
+ BigDecimal c = BigDecimal.ZERO;
+
+ @Test
+ void between_returns_true() {
+ assertTrue(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_true() {
+ assertTrue(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_true() {
+ assertTrue(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_true() {
+ assertTrue(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+ }
+
+ @DisplayName("C is 1 (B > A = C)")
+ @Nested
+ class C_is_1 {
+
+ BigDecimal c = BigDecimal.ONE;
+
+ @Test
+ void between_returns_true() {
+ assertTrue(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_false() {
+ assertFalse(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_true() {
+ assertTrue(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_false() {
+ assertFalse(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+ }
+
+ @DisplayName("C is 10 ([B,C] > A)")
+ @Nested
+ class C_is_10 {
+
+ BigDecimal c = BigDecimal.TEN;
+
+ @Test
+ void between_returns_false() {
+ assertFalse(is(a).between(b, c));
+ }
+
+ @Test
+ void betweenExclusive_returns_false() {
+ assertFalse(is(a).betweenExclusive(b, c));
+ }
+
+ @Test
+ void static_between_returns_false() {
+ assertFalse(ComparableUtils.between(b, c).test(a));
+ }
+
+ @Test
+ void static_betweenExclusive_returns_false() {
+ assertFalse(ComparableUtils.betweenExclusive(b, c).test(a));
+ }
+ }
+
+ BigDecimal b = BigDecimal.TEN;
+
+ @Test
+ void equalTo_returns_false() {
+ assertFalse(is(a).equalTo(b));
+ }
+
+ @Test
+ void greaterThan_returns_false() {
+ assertFalse(is(a).greaterThan(b));
+ }
+
+ @Test
+ void greaterThanOrEqualTo_returns_false() {
+ assertFalse(is(a).greaterThanOrEqualTo(b));
+ }
+
+ @Test
+ void lessThan_returns_true() {
+ assertTrue(is(a).lessThan(b));
+ }
+
+ @Test
+ void lessThanOrEqualTo_returns_true() {
+ assertTrue(is(a).lessThanOrEqualTo(b));
+ }
+
+ @Test
+ void static_gt_returns_false() {
+ assertFalse(ComparableUtils.gt(b).test(a));
+ }
+
+ @Test
+ void static_ge_returns_false() {
+ assertFalse(ComparableUtils.ge(b).test(a));
+ }
+
+ @Test
+ void static_lt_returns_true() {
+ assertTrue(ComparableUtils.lt(b).test(a));
+ }
+
+ @Test
+ void static_le_returns_true() {
+ assertTrue(ComparableUtils.le(b).test(a));
+ }
+ }
+
+ BigDecimal a = BigDecimal.ONE;
+ }
+
+ @Test
+ public void testMax() {
+ assertEquals(Instant.MAX, ComparableUtils.max(Instant.MAX, Instant.MAX));
+ assertEquals(Instant.MIN, ComparableUtils.max(Instant.MIN, Instant.MIN));
+ assertEquals(Instant.MAX, ComparableUtils.max(Instant.MIN, Instant.MAX));
+ assertEquals(Instant.MAX, ComparableUtils.max(Instant.MAX, Instant.MIN));
+ //
+ assertEquals(Integer.MIN_VALUE, ComparableUtils.max(Integer.valueOf(Integer.MIN_VALUE), Integer.valueOf(Integer.MIN_VALUE)));
+ assertEquals(Integer.MAX_VALUE, ComparableUtils.max(Integer.valueOf(Integer.MAX_VALUE), Integer.valueOf(Integer.MAX_VALUE)));
+ assertEquals(Integer.MAX_VALUE, ComparableUtils.max(Integer.valueOf(Integer.MIN_VALUE), Integer.valueOf(Integer.MAX_VALUE)));
+ assertEquals(Integer.MAX_VALUE, ComparableUtils.max(Integer.valueOf(Integer.MAX_VALUE), Integer.valueOf(Integer.MIN_VALUE)));
+ //
+ assertEquals(Instant.MAX, ComparableUtils.max(null, Instant.MAX));
+ assertEquals(Instant.MAX, ComparableUtils.max(Instant.MAX, null));
+ }
+
+ @Test
+ public void testMin() {
+ assertEquals(Instant.MAX, ComparableUtils.min(Instant.MAX, Instant.MAX));
+ assertEquals(Instant.MIN, ComparableUtils.min(Instant.MIN, Instant.MIN));
+ assertEquals(Instant.MIN, ComparableUtils.min(Instant.MIN, Instant.MAX));
+ assertEquals(Instant.MIN, ComparableUtils.min(Instant.MAX, Instant.MIN));
+ //
+ assertEquals(Integer.MIN_VALUE, ComparableUtils.min(Integer.valueOf(Integer.MIN_VALUE), Integer.valueOf(Integer.MIN_VALUE)));
+ assertEquals(Integer.MAX_VALUE, ComparableUtils.min(Integer.valueOf(Integer.MAX_VALUE), Integer.valueOf(Integer.MAX_VALUE)));
+ assertEquals(Integer.MIN_VALUE, ComparableUtils.min(Integer.valueOf(Integer.MIN_VALUE), Integer.valueOf(Integer.MAX_VALUE)));
+ assertEquals(Integer.MIN_VALUE, ComparableUtils.min(Integer.valueOf(Integer.MAX_VALUE), Integer.valueOf(Integer.MIN_VALUE)));
+ //
+ assertEquals(Instant.MAX, ComparableUtils.min(null, Instant.MAX));
+ assertEquals(Instant.MAX, ComparableUtils.min(Instant.MAX, null));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/compare/ObjectToStringComparatorTest.java b/src/test/java/org/apache/commons/lang3/compare/ObjectToStringComparatorTest.java
new file mode 100644
index 000000000..984b6a5c3
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/compare/ObjectToStringComparatorTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.compare;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link ObjectToStringComparator}.
+ */
+public class ObjectToStringComparatorTest extends AbstractLangTest {
+
+ private static class Thing {
+
+ final String string;
+
+ Thing(final String string) {
+ this.string = string;
+ }
+
+ @Override
+ public String toString() {
+ return string;
+ }
+ }
+
+ @Test
+ public void testNull() {
+ final List<Thing> things = Arrays.asList(null, new Thing("y"), null);
+ things.sort(ObjectToStringComparator.INSTANCE);
+ assertEquals("y", things.get(0).string);
+ assertNull(things.get(1));
+ assertNull(things.get(2));
+ }
+
+ @Test
+ public void testNullToString() {
+ final List<Thing> things = Arrays.asList(new Thing(null), new Thing("y"), new Thing(null));
+ things.sort(ObjectToStringComparator.INSTANCE);
+ assertEquals("y", things.get(0).string);
+ assertNull(things.get(1).string);
+ assertNull(things.get(2).string);
+ }
+
+ @Test
+ public void testSortCollection() {
+ final List<Thing> things = Arrays.asList(new Thing("z"), new Thing("y"), new Thing("x"));
+ things.sort(ObjectToStringComparator.INSTANCE);
+ assertEquals("x", things.get(0).string);
+ assertEquals("y", things.get(1).string);
+ assertEquals("z", things.get(2).string);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java
new file mode 100644
index 000000000..d0b1db398
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.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.concurrent;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * <p>
+ * An abstract base class for tests of concrete {@code ConcurrentInitializer}
+ * implementations.
+ * </p>
+ * <p>
+ * This class provides some basic tests for initializer implementations. Derived
+ * class have to create a {@link ConcurrentInitializer} object on which the
+ * tests are executed.
+ * </p>
+ */
+public abstract class AbstractConcurrentInitializerTest extends AbstractLangTest {
+ /**
+ * Tests a simple invocation of the get() method.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException because the object under test may throw it.
+ */
+ @Test
+ public void testGet() throws ConcurrentException {
+ assertNotNull(createInitializer().get(), "No managed object");
+ }
+
+ /**
+ * Tests whether sequential get() invocations always return the same
+ * instance.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException because the object under test may throw it.
+ */
+ @Test
+ public void testGetMultipleTimes() throws ConcurrentException {
+ final ConcurrentInitializer<Object> initializer = createInitializer();
+ final Object obj = initializer.get();
+ for (int i = 0; i < 10; i++) {
+ assertEquals(obj, initializer.get(), "Got different object at " + i);
+ }
+ }
+
+ /**
+ * Tests whether get() can be invoked from multiple threads concurrently.
+ * Always the same object should be returned.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException because the object under test may throw it.
+ * @throws InterruptedException because the threading API my throw it.
+ */
+ @Test
+ public void testGetConcurrent() throws ConcurrentException,
+ InterruptedException {
+ final ConcurrentInitializer<Object> initializer = createInitializer();
+ final int threadCount = 20;
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ class GetThread extends Thread {
+ Object object;
+
+ @Override
+ public void run() {
+ try {
+ // wait until all threads are ready for maximum parallelism
+ startLatch.await();
+ // access the initializer
+ object = initializer.get();
+ } catch (final InterruptedException iex) {
+ // ignore
+ } catch (final ConcurrentException cex) {
+ object = cex;
+ }
+ }
+ }
+
+ final GetThread[] threads = new GetThread[threadCount];
+ for (int i = 0; i < threadCount; i++) {
+ threads[i] = new GetThread();
+ threads[i].start();
+ }
+
+ // fire all threads and wait until they are ready
+ startLatch.countDown();
+ for (final Thread t : threads) {
+ t.join();
+ }
+
+ // check results
+ final Object managedObject = initializer.get();
+ for (final GetThread t : threads) {
+ assertEquals(managedObject, t.object, "Wrong object");
+ }
+ }
+
+ /**
+ * Creates the {@link ConcurrentInitializer} object to be tested. This
+ * method is called whenever the test fixture needs to be obtained.
+ *
+ * @return the initializer object to be tested
+ */
+ protected abstract ConcurrentInitializer<Object> createInitializer();
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java
new file mode 100644
index 000000000..9b05a1ae3
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.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.concurrent;
+
+/**
+ * Test class for {@code AtomicInitializer}.
+ */
+public class AtomicInitializerTest extends AbstractConcurrentInitializerTest {
+ /**
+ * Returns the initializer to be tested.
+ *
+ * @return the {@code AtomicInitializer}
+ */
+ @Override
+ protected ConcurrentInitializer<Object> createInitializer() {
+ return new AtomicInitializer<Object>() {
+ @Override
+ protected Object initialize() {
+ return new Object();
+ }
+ };
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java
new file mode 100644
index 000000000..3352f99dd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@code AtomicSafeInitializer} which also serves as a simple example.
+ */
+public class AtomicSafeInitializerTest extends AbstractConcurrentInitializerTest {
+
+ /** The instance to be tested. */
+ private AtomicSafeInitializerTestImpl initializer;
+
+ @BeforeEach
+ public void setUp() {
+ initializer = new AtomicSafeInitializerTestImpl();
+ }
+
+ /**
+ * Returns the initializer to be tested.
+ *
+ * @return the {@code AtomicSafeInitializer} under test
+ */
+ @Override
+ protected ConcurrentInitializer<Object> createInitializer() {
+ return initializer;
+ }
+
+ /**
+ * Tests that initialize() is called only once.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException because {@link #testGetConcurrent()} may throw it
+ * @throws InterruptedException because {@link #testGetConcurrent()} may throw it
+ */
+ @Test
+ public void testNumberOfInitializeInvocations() throws ConcurrentException, InterruptedException {
+ testGetConcurrent();
+ assertEquals(1, initializer.initCounter.get(), "Wrong number of invocations");
+ }
+
+ /**
+ * A concrete test implementation of {@code AtomicSafeInitializer} which also serves as a simple example.
+ * <p>
+ * This implementation also counts the number of invocations of the initialize() method.
+ * </p>
+ */
+ private static class AtomicSafeInitializerTestImpl extends AtomicSafeInitializer<Object> {
+ /** A counter for initialize() invocations. */
+ final AtomicInteger initCounter = new AtomicInteger();
+
+ @Override
+ protected Object initialize() {
+ initCounter.incrementAndGet();
+ return new Object();
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/BackgroundInitializerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/BackgroundInitializerTest.java
new file mode 100644
index 000000000..d1a80da81
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/BackgroundInitializerTest.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ThreadUtils;
+import org.junit.jupiter.api.Test;
+
+public class BackgroundInitializerTest extends AbstractLangTest {
+ /**
+ * Helper method for checking whether the initialize() method was correctly
+ * called. start() must already have been invoked.
+ *
+ * @param init the initializer to test
+ */
+ private void checkInitialize(final BackgroundInitializerTestImpl init) throws ConcurrentException {
+ final Integer result = init.get();
+ assertEquals(1, result.intValue(), "Wrong result");
+ assertEquals(1, init.initializeCalls, "Wrong number of invocations");
+ assertNotNull(init.getFuture(), "No future");
+ }
+
+ /**
+ * Tests whether initialize() is invoked.
+ */
+ @Test
+ public void testInitialize() throws ConcurrentException {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ init.start();
+ checkInitialize(init);
+ }
+
+ /**
+ * Tries to obtain the executor before start(). It should not have been
+ * initialized yet.
+ */
+ @Test
+ public void testGetActiveExecutorBeforeStart() {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ assertNull(init.getActiveExecutor(), "Got an executor");
+ }
+
+ /**
+ * Tests whether an external executor is correctly detected.
+ */
+ @Test
+ public void testGetActiveExecutorExternal() throws InterruptedException, ConcurrentException {
+ final ExecutorService exec = Executors.newSingleThreadExecutor();
+ try {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl(
+ exec);
+ init.start();
+ assertSame(exec, init.getActiveExecutor(), "Wrong executor");
+ checkInitialize(init);
+ } finally {
+ exec.shutdown();
+ exec.awaitTermination(1, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Tests getActiveExecutor() for a temporary executor.
+ */
+ @Test
+ public void testGetActiveExecutorTemp() throws ConcurrentException {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ init.start();
+ assertNotNull(init.getActiveExecutor(), "No active executor");
+ checkInitialize(init);
+ }
+
+ /**
+ * Tests the execution of the background task if a temporary executor has to
+ * be created.
+ */
+ @Test
+ public void testInitializeTempExecutor() throws ConcurrentException {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ assertTrue(init.start(), "Wrong result of start()");
+ checkInitialize(init);
+ assertTrue(init.getActiveExecutor().isShutdown(), "Executor not shutdown");
+ }
+
+ /**
+ * Tests whether an external executor can be set using the
+ * setExternalExecutor() method.
+ */
+ @Test
+ public void testSetExternalExecutor() throws ConcurrentException {
+ final ExecutorService exec = Executors.newCachedThreadPool();
+ try {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ init.setExternalExecutor(exec);
+ assertEquals(exec, init.getExternalExecutor(), "Wrong executor service");
+ assertTrue(init.start(), "Wrong result of start()");
+ assertSame(exec, init.getActiveExecutor(), "Wrong active executor");
+ checkInitialize(init);
+ assertFalse(exec.isShutdown(), "Executor was shutdown");
+ } finally {
+ exec.shutdown();
+ }
+ }
+
+ /**
+ * Tests that setting an executor after start() causes an exception.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException because the test implementation may throw it
+ */
+ @Test
+ public void testSetExternalExecutorAfterStart() throws ConcurrentException, InterruptedException {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ init.start();
+ final ExecutorService exec = Executors.newSingleThreadExecutor();
+ try {
+ assertThrows(IllegalStateException.class, () -> init.setExternalExecutor(exec));
+ init.get();
+ } finally {
+ exec.shutdown();
+ exec.awaitTermination(1, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Tests invoking start() multiple times. Only the first invocation should
+ * have an effect.
+ */
+ @Test
+ public void testStartMultipleTimes() throws ConcurrentException {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ assertTrue(init.start(), "Wrong result for start()");
+ for (int i = 0; i < 10; i++) {
+ assertFalse(init.start(), "Could start again");
+ }
+ checkInitialize(init);
+ }
+
+ /**
+ * Tests calling get() before start(). This should cause an exception.
+ */
+ @Test
+ public void testGetBeforeStart() {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ assertThrows(IllegalStateException.class, init::get);
+ }
+
+ /**
+ * Tests the get() method if background processing causes a runtime
+ * exception.
+ */
+ @Test
+ public void testGetRuntimeException() {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ final RuntimeException rex = new RuntimeException();
+ init.ex = rex;
+ init.start();
+ final Exception ex = assertThrows(Exception.class, init::get);
+ assertEquals(rex, ex, "Runtime exception not thrown");
+ }
+
+ /**
+ * Tests the get() method if background processing causes a checked
+ * exception.
+ */
+ @Test
+ public void testGetCheckedException() {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ final Exception ex = new Exception();
+ init.ex = ex;
+ init.start();
+ final ConcurrentException cex = assertThrows(ConcurrentException.class, init::get);
+ assertEquals(ex, cex.getCause(), "Exception not thrown");
+ }
+
+ /**
+ * Tests the get() method if waiting for the initialization is interrupted.
+ *
+ * @throws InterruptedException because we're making use of Java's concurrent API
+ */
+ @Test
+ public void testGetInterruptedException() throws InterruptedException {
+ final ExecutorService exec = Executors.newSingleThreadExecutor();
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl(
+ exec);
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ init.shouldSleep = true;
+ init.start();
+ final AtomicReference<InterruptedException> iex = new AtomicReference<>();
+ final Thread getThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ init.get();
+ } catch (final ConcurrentException cex) {
+ if (cex.getCause() instanceof InterruptedException) {
+ iex.set((InterruptedException) cex.getCause());
+ }
+ } finally {
+ assertTrue(isInterrupted(), "Thread not interrupted");
+ latch1.countDown();
+ }
+ }
+ };
+ getThread.start();
+ getThread.interrupt();
+ latch1.await();
+ exec.shutdownNow();
+ exec.awaitTermination(1, TimeUnit.SECONDS);
+ assertNotNull(iex.get(), "No interrupted exception");
+ }
+
+ /**
+ * Tests isStarted() before start() was called.
+ */
+ @Test
+ public void testIsStartedFalse() {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ assertFalse(init.isStarted(), "Already started");
+ }
+
+ /**
+ * Tests isStarted() after start().
+ */
+ @Test
+ public void testIsStartedTrue() {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ init.start();
+ assertTrue(init.isStarted(), "Not started");
+ }
+
+ /**
+ * Tests isStarted() after the background task has finished.
+ */
+ @Test
+ public void testIsStartedAfterGet() throws ConcurrentException {
+ final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
+ init.start();
+ checkInitialize(init);
+ assertTrue(init.isStarted(), "Not started");
+ }
+
+ /**
+ * A concrete implementation of BackgroundInitializer. It also overloads
+ * some methods that simplify testing.
+ */
+ private static class BackgroundInitializerTestImpl extends
+ BackgroundInitializer<Integer> {
+ /** An exception to be thrown by initialize(). */
+ Exception ex;
+
+ /** A flag whether the background task should sleep a while. */
+ boolean shouldSleep;
+
+ /** The number of invocations of initialize(). */
+ volatile int initializeCalls;
+
+ BackgroundInitializerTestImpl() {
+ }
+
+ BackgroundInitializerTestImpl(final ExecutorService exec) {
+ super(exec);
+ }
+
+ /**
+ * Records this invocation. Optionally throws an exception or sleeps a
+ * while.
+ *
+ * @throws Exception in case of an error
+ */
+ @Override
+ protected Integer initialize() throws Exception {
+ if (ex != null) {
+ throw ex;
+ }
+ if (shouldSleep) {
+ ThreadUtils.sleep(Duration.ofMinutes(1));
+ }
+ return Integer.valueOf(++initializeCalls);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/BasicThreadFactoryTest.java b/src/test/java/org/apache/commons/lang3/concurrent/BasicThreadFactoryTest.java
new file mode 100644
index 000000000..db32e39b6
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/BasicThreadFactoryTest.java
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.concurrent.ThreadFactory;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.easymock.EasyMock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@code BasicThreadFactory}.
+ */
+public class BasicThreadFactoryTest extends AbstractLangTest {
+ /** Constant for the test naming pattern. */
+ private static final String PATTERN = "testThread-%d";
+
+ /** The builder for creating a thread factory. */
+ private BasicThreadFactory.Builder builder;
+
+ @BeforeEach
+ public void setUp() {
+ builder = new BasicThreadFactory.Builder();
+ }
+
+ /**
+ * Tests the default options of a thread factory.
+ *
+ * @param factory the factory to be checked
+ */
+ private void checkFactoryDefaults(final BasicThreadFactory factory) {
+ assertNull(factory.getNamingPattern(), "Got a naming pattern");
+ assertNull(factory.getUncaughtExceptionHandler(), "Got an exception handler");
+ assertNull(factory.getPriority(), "Got a priority");
+ assertNull(factory.getDaemonFlag(), "Got a daemon flag");
+ assertNotNull(factory.getWrappedFactory(), "No wrapped factory");
+ }
+
+ /**
+ * Tests the default values used by the builder.
+ */
+ @Test
+ public void testBuildDefaults() {
+ final BasicThreadFactory factory = builder.build();
+ checkFactoryDefaults(factory);
+ }
+
+ /**
+ * Tries to set a null naming pattern.
+ */
+ @Test
+ public void testBuildNamingPatternNull() {
+ assertThrows(NullPointerException.class, () -> builder.namingPattern(null));
+ }
+
+ /**
+ * Tries to set a null wrapped factory.
+ */
+ @Test
+ public void testBuildWrappedFactoryNull() {
+ assertThrows(NullPointerException.class, () -> builder.wrappedFactory(null));
+ }
+
+ /**
+ * Tries to set a null exception handler.
+ */
+ @Test
+ public void testBuildUncaughtExceptionHandlerNull() {
+ assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
+ }
+
+ /**
+ * Tests the reset() method of the builder.
+ */
+ @Test
+ public void testBuilderReset() {
+ final ThreadFactory wrappedFactory = EasyMock.createMock(ThreadFactory.class);
+ final Thread.UncaughtExceptionHandler exHandler = EasyMock
+ .createMock(Thread.UncaughtExceptionHandler.class);
+ EasyMock.replay(wrappedFactory, exHandler);
+ builder.namingPattern(PATTERN).daemon(true).priority(
+ Thread.MAX_PRIORITY).uncaughtExceptionHandler(exHandler)
+ .wrappedFactory(wrappedFactory);
+ builder.reset();
+ final BasicThreadFactory factory = builder.build();
+ checkFactoryDefaults(factory);
+ assertNotSame(wrappedFactory, factory.getWrappedFactory(), "Wrapped factory not reset");
+ EasyMock.verify(wrappedFactory, exHandler);
+ }
+
+ /**
+ * Tests whether reset() is automatically called after build().
+ */
+ @Test
+ public void testBuilderResetAfterBuild() {
+ builder.wrappedFactory(EasyMock.createNiceMock(ThreadFactory.class))
+ .namingPattern(PATTERN).daemon(true).build();
+ checkFactoryDefaults(builder.build());
+ }
+
+ /**
+ * Tests whether the naming pattern is applied to new threads.
+ */
+ @Test
+ public void testNewThreadNamingPattern() {
+ final ThreadFactory wrapped = EasyMock.createMock(ThreadFactory.class);
+ final Runnable r = EasyMock.createMock(Runnable.class);
+ final int count = 12;
+ for (int i = 0; i < count; i++) {
+ EasyMock.expect(wrapped.newThread(r)).andReturn(new Thread());
+ }
+ EasyMock.replay(wrapped, r);
+ final BasicThreadFactory factory = builder.wrappedFactory(wrapped)
+ .namingPattern(PATTERN).build();
+ for (int i = 0; i < count; i++) {
+ final Thread t = factory.newThread(r);
+ assertEquals(String.format(PATTERN, Long.valueOf(i + 1)), t.getName(), "Wrong thread name");
+ assertEquals(i + 1, factory.getThreadCount(), "Wrong thread count");
+ }
+ EasyMock.verify(wrapped, r);
+ }
+
+ /**
+ * Tests whether the thread name is not modified if no naming pattern is
+ * set.
+ */
+ @Test
+ public void testNewThreadNoNamingPattern() {
+ final ThreadFactory wrapped = EasyMock.createMock(ThreadFactory.class);
+ final Runnable r = EasyMock.createMock(Runnable.class);
+ final String name = "unchangedThreadName";
+ final Thread t = new Thread(name);
+ EasyMock.expect(wrapped.newThread(r)).andReturn(t);
+ EasyMock.replay(wrapped, r);
+ final BasicThreadFactory factory = builder.wrappedFactory(wrapped).build();
+ assertSame(t, factory.newThread(r), "Wrong thread");
+ assertEquals(name, t.getName(), "Name was changed");
+ EasyMock.verify(wrapped, r);
+ }
+
+ /**
+ * Helper method for testing whether the daemon flag is taken into account.
+ *
+ * @param flag the value of the flag
+ */
+ private void checkDaemonFlag(final boolean flag) {
+ final ThreadFactory wrapped = EasyMock.createMock(ThreadFactory.class);
+ final Runnable r = EasyMock.createMock(Runnable.class);
+ final Thread t = new Thread();
+ EasyMock.expect(wrapped.newThread(r)).andReturn(t);
+ EasyMock.replay(wrapped, r);
+ final BasicThreadFactory factory = builder.wrappedFactory(wrapped).daemon(
+ flag).build();
+ assertSame(t, factory.newThread(r), "Wrong thread");
+ assertEquals(flag, t.isDaemon(), "Wrong daemon flag");
+ EasyMock.verify(wrapped, r);
+ }
+
+ /**
+ * Tests whether daemon threads can be created.
+ */
+ @Test
+ public void testNewThreadDaemonTrue() {
+ checkDaemonFlag(true);
+ }
+
+ /**
+ * Tests whether the daemon status of new threads can be turned off.
+ */
+ @Test
+ public void testNewThreadDaemonFalse() {
+ checkDaemonFlag(false);
+ }
+
+ /**
+ * Tests whether the daemon flag is not touched on newly created threads if
+ * it is not specified.
+ */
+ @Test
+ public void testNewThreadNoDaemonFlag() {
+ final ThreadFactory wrapped = EasyMock.createMock(ThreadFactory.class);
+ final Runnable r1 = EasyMock.createMock(Runnable.class);
+ final Runnable r2 = EasyMock.createMock(Runnable.class);
+ final Thread t1 = new Thread();
+ final Thread t2 = new Thread();
+ t1.setDaemon(true);
+ EasyMock.expect(wrapped.newThread(r1)).andReturn(t1);
+ EasyMock.expect(wrapped.newThread(r2)).andReturn(t2);
+ EasyMock.replay(wrapped, r1, r2);
+ final BasicThreadFactory factory = builder.wrappedFactory(wrapped).build();
+ assertSame(t1, factory.newThread(r1), "Wrong thread 1");
+ assertTrue(t1.isDaemon(), "No daemon thread");
+ assertSame(t2, factory.newThread(r2), "Wrong thread 2");
+ assertFalse(t2.isDaemon(), "A daemon thread");
+ EasyMock.verify(wrapped, r1, r2);
+ }
+
+ /**
+ * Tests whether the priority is set on newly created threads.
+ */
+ @Test
+ public void testNewThreadPriority() {
+ final ThreadFactory wrapped = EasyMock.createMock(ThreadFactory.class);
+ final Runnable r = EasyMock.createMock(Runnable.class);
+ final Thread t = new Thread();
+ EasyMock.expect(wrapped.newThread(r)).andReturn(t);
+ EasyMock.replay(wrapped, r);
+ final int priority = Thread.NORM_PRIORITY + 1;
+ final BasicThreadFactory factory = builder.wrappedFactory(wrapped).priority(
+ priority).build();
+ assertSame(t, factory.newThread(r), "Wrong thread");
+ assertEquals(priority, t.getPriority(), "Wrong priority");
+ EasyMock.verify(wrapped, r);
+ }
+
+ /**
+ * Tests whether the original priority is not changed if no priority is
+ * specified.
+ */
+ @Test
+ public void testNewThreadNoPriority() {
+ final ThreadFactory wrapped = EasyMock.createMock(ThreadFactory.class);
+ final Runnable r = EasyMock.createMock(Runnable.class);
+ final int orgPriority = Thread.NORM_PRIORITY + 1;
+ final Thread t = new Thread();
+ t.setPriority(orgPriority);
+ EasyMock.expect(wrapped.newThread(r)).andReturn(t);
+ EasyMock.replay(wrapped, r);
+ final BasicThreadFactory factory = builder.wrappedFactory(wrapped).build();
+ assertSame(t, factory.newThread(r), "Wrong thread");
+ assertEquals(orgPriority, t.getPriority(), "Wrong priority");
+ EasyMock.verify(wrapped, r);
+ }
+
+ /**
+ * Tests whether the exception handler is set if one is provided.
+ */
+ @Test
+ public void testNewThreadExHandler() {
+ final ThreadFactory wrapped = EasyMock.createMock(ThreadFactory.class);
+ final Runnable r = EasyMock.createMock(Runnable.class);
+ final Thread.UncaughtExceptionHandler handler = EasyMock
+ .createMock(Thread.UncaughtExceptionHandler.class);
+ final Thread t = new Thread();
+ EasyMock.expect(wrapped.newThread(r)).andReturn(t);
+ EasyMock.replay(wrapped, r, handler);
+ final BasicThreadFactory factory = builder.wrappedFactory(wrapped)
+ .uncaughtExceptionHandler(handler).build();
+ assertSame(t, factory.newThread(r), "Wrong thread");
+ assertEquals(handler, t.getUncaughtExceptionHandler(), "Wrong exception handler");
+ EasyMock.verify(wrapped, r, handler);
+ }
+
+ /**
+ * Tests whether the original exception handler is not touched if none is
+ * specified.
+ */
+ @Test
+ public void testNewThreadNoExHandler() {
+ final ThreadFactory wrapped = EasyMock.createMock(ThreadFactory.class);
+ final Runnable r = EasyMock.createMock(Runnable.class);
+ final Thread.UncaughtExceptionHandler handler = EasyMock
+ .createMock(Thread.UncaughtExceptionHandler.class);
+ final Thread t = new Thread();
+ t.setUncaughtExceptionHandler(handler);
+ EasyMock.expect(wrapped.newThread(r)).andReturn(t);
+ EasyMock.replay(wrapped, r, handler);
+ final BasicThreadFactory factory = builder.wrappedFactory(wrapped).build();
+ assertSame(t, factory.newThread(r), "Wrong thread");
+ assertEquals(handler, t.getUncaughtExceptionHandler(), "Wrong exception handler");
+ EasyMock.verify(wrapped, r, handler);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializerTest.java
new file mode 100644
index 000000000..2a2e724ef
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializerTest.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.concurrent;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@code CallableBackgroundInitializer}
+ */
+public class CallableBackgroundInitializerTest extends AbstractLangTest {
+ /** Constant for the result of the call() invocation. */
+ private static final Integer RESULT = Integer.valueOf(42);
+
+ /**
+ * Tries to create an instance without a Callable. This should cause an
+ * exception.
+ */
+ @Test()
+ public void testInitNullCallable() {
+ assertThrows(NullPointerException.class, () -> new CallableBackgroundInitializer<>(null));
+ }
+
+ /**
+ * Tests whether the executor service is correctly passed to the super
+ * class.
+ */
+ @Test
+ public void testInitExecutor() throws InterruptedException {
+ final ExecutorService exec = Executors.newSingleThreadExecutor();
+ final CallableBackgroundInitializer<Integer> init = new CallableBackgroundInitializer<>(
+ new TestCallable(), exec);
+ assertEquals(exec, init.getExternalExecutor(), "Executor not set");
+ exec.shutdown();
+ exec.awaitTermination(1, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Tries to pass a null Callable to the constructor that takes an executor.
+ * This should cause an exception.
+ */
+ @Test
+ public void testInitExecutorNullCallable() throws InterruptedException {
+ final ExecutorService exec = Executors.newSingleThreadExecutor();
+ try {
+ assertThrows(NullPointerException.class, () -> new CallableBackgroundInitializer<Integer>(null, exec));
+ } finally {
+ exec.shutdown();
+ exec.awaitTermination(1, TimeUnit.SECONDS);
+ }
+
+ }
+
+ /**
+ * Tests the implementation of initialize().
+ *
+ * @throws Exception so we don't have to catch it
+ */
+ @Test
+ public void testInitialize() throws Exception {
+ final TestCallable call = new TestCallable();
+ final CallableBackgroundInitializer<Integer> init = new CallableBackgroundInitializer<>(
+ call);
+ assertEquals(RESULT, init.initialize(), "Wrong result");
+ assertEquals(1, call.callCount, "Wrong number of invocations");
+ }
+
+ /**
+ * A test Callable implementation for checking the initializer's
+ * implementation of the initialize() method.
+ */
+ private static class TestCallable implements Callable<Integer> {
+ /** A counter for the number of call() invocations. */
+ int callCount;
+
+ /**
+ * Records this invocation and returns the test result.
+ */
+ @Override
+ public Integer call() {
+ callCount++;
+ return RESULT;
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/CircuitBreakingExceptionTest.java b/src/test/java/org/apache/commons/lang3/concurrent/CircuitBreakingExceptionTest.java
new file mode 100644
index 000000000..bf19b49c4
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/CircuitBreakingExceptionTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.exception.AbstractExceptionTest;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * JUnit tests for {@link CircuitBreakingException}.
+ */
+public class CircuitBreakingExceptionTest extends AbstractExceptionTest {
+
+ @Test
+ public void testThrowingInformativeException() {
+ assertThrows(CircuitBreakingException.class, () -> {
+ throw new CircuitBreakingException(EXCEPTION_MESSAGE, generateCause());
+ });
+ }
+
+ @Test
+ public void testThrowingExceptionWithMessage() {
+ assertThrows(CircuitBreakingException.class, () -> {
+ throw new CircuitBreakingException(EXCEPTION_MESSAGE);
+ });
+ }
+
+ @Test
+ public void testThrowingExceptionWithCause() {
+ assertThrows(CircuitBreakingException.class, () -> {
+ throw new CircuitBreakingException(generateCause());
+ });
+ }
+
+ @Test
+ public void testThrowingEmptyException() {
+ assertThrows(CircuitBreakingException.class, () -> {
+ throw new CircuitBreakingException();
+ });
+ }
+
+ @Test
+ public void testWithCauseAndMessage() {
+ final Exception exception = new CircuitBreakingException(EXCEPTION_MESSAGE, generateCause());
+ assertNotNull(exception);
+ assertEquals(EXCEPTION_MESSAGE, exception.getMessage(), WRONG_EXCEPTION_MESSAGE);
+
+ final Throwable cause = exception.getCause();
+ assertNotNull(cause);
+ assertEquals(CAUSE_MESSAGE, cause.getMessage(), WRONG_CAUSE_MESSAGE);
+ }
+
+ @Test
+ public void testWithoutCause() {
+ final Exception exception = new CircuitBreakingException(EXCEPTION_MESSAGE);
+ assertNotNull(exception);
+ assertEquals(EXCEPTION_MESSAGE, exception.getMessage(), WRONG_EXCEPTION_MESSAGE);
+
+ final Throwable cause = exception.getCause();
+ assertNull(cause);
+ }
+
+ @Test
+ public void testWithoutMessage() {
+ final Exception exception = new CircuitBreakingException(generateCause());
+ assertNotNull(exception);
+ assertNotNull(exception.getMessage());
+
+ final Throwable cause = exception.getCause();
+ assertNotNull(cause);
+ assertEquals(CAUSE_MESSAGE, cause.getMessage(), WRONG_CAUSE_MESSAGE);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java b/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java
new file mode 100644
index 000000000..831c33349
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java
@@ -0,0 +1,496 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.concurrent.ConcurrentHashMap;
+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.AbstractLangTest;
+import org.easymock.EasyMock;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@link ConcurrentUtils}.
+ */
+public class ConcurrentUtilsTest extends AbstractLangTest {
+ /**
+ * Tests creating a ConcurrentException with a runtime exception as cause.
+ */
+ @Test
+ public void testConcurrentExceptionCauseUnchecked() {
+ assertThrows(IllegalArgumentException.class, () -> new ConcurrentException(new RuntimeException()));
+ }
+
+ /**
+ * Tests creating a ConcurrentException with an error as cause.
+ */
+ @Test
+ public void testConcurrentExceptionCauseError() {
+ assertThrows(IllegalArgumentException.class, () -> new ConcurrentException("An error", new Error()));
+ }
+
+ /**
+ * Tests creating a ConcurrentException with null as cause.
+ */
+ @Test
+ public void testConcurrentExceptionCauseNull() {
+ assertThrows(IllegalArgumentException.class, () -> new ConcurrentException(null));
+ }
+
+ /**
+ * Tries to create a ConcurrentRuntimeException with a runtime as cause.
+ */
+ @Test
+ public void testConcurrentRuntimeExceptionCauseUnchecked() {
+ assertThrows(IllegalArgumentException.class, () -> new ConcurrentRuntimeException(new RuntimeException()));
+ }
+
+ /**
+ * Tries to create a ConcurrentRuntimeException with an error as cause.
+ */
+ @Test
+ public void testConcurrentRuntimeExceptionCauseError() {
+ assertThrows(IllegalArgumentException.class, () -> new ConcurrentRuntimeException("An error", new Error()));
+ }
+
+ /**
+ * Tries to create a ConcurrentRuntimeException with null as cause.
+ */
+ @Test
+ public void testConcurrentRuntimeExceptionCauseNull() {
+ assertThrows(IllegalArgumentException.class, () -> new ConcurrentRuntimeException(null));
+ }
+
+ /**
+ * Tests extractCause() for a null exception.
+ */
+ @Test
+ public void testExtractCauseNull() {
+ assertNull(ConcurrentUtils.extractCause(null), "Non null result");
+ }
+
+ /**
+ * Tests extractCause() if the cause of the passed in exception is null.
+ */
+ @Test
+ public void testExtractCauseNullCause() {
+ assertNull(ConcurrentUtils.extractCause(new ExecutionException("Test", null)), "Non null result");
+ }
+
+ /**
+ * Tests extractCause() if the cause is an error.
+ */
+ @Test
+ public void testExtractCauseError() {
+ final Error err = new AssertionError("Test");
+ final AssertionError e = assertThrows(AssertionError.class, () -> ConcurrentUtils.extractCause(new ExecutionException(err)));
+ assertEquals(err, e, "Wrong error");
+ }
+
+ /**
+ * Tests extractCause() if the cause is an unchecked exception.
+ */
+ @Test
+ public void testExtractCauseUncheckedException() {
+ final RuntimeException rex = new RuntimeException("Test");
+ assertThrows(RuntimeException.class, () -> ConcurrentUtils.extractCause(new ExecutionException(rex)));
+ }
+
+ /**
+ * Tests extractCause() if the cause is a checked exception.
+ */
+ @Test
+ public void testExtractCauseChecked() {
+ final Exception ex = new Exception("Test");
+ final ConcurrentException cex = ConcurrentUtils.extractCause(new ExecutionException(ex));
+ assertSame(ex, cex.getCause(), "Wrong cause");
+ }
+
+ /**
+ * Tests extractCauseUnchecked() for a null exception.
+ */
+ @Test
+ public void testExtractCauseUncheckedNull() {
+ assertNull(ConcurrentUtils.extractCauseUnchecked(null), "Non null result");
+ }
+
+ /**
+ * Tests extractCauseUnchecked() if the cause of the passed in exception is null.
+ */
+ @Test
+ public void testExtractCauseUncheckedNullCause() {
+ assertNull(ConcurrentUtils.extractCauseUnchecked(new ExecutionException("Test", null)), "Non null result");
+ }
+
+ /**
+ * Tests extractCauseUnchecked() if the cause is an error.
+ */
+ @Test
+ public void testExtractCauseUncheckedError() {
+ final Error err = new AssertionError("Test");
+ final Error e = assertThrows(Error.class, () -> ConcurrentUtils.extractCauseUnchecked(new ExecutionException(err)));
+ assertEquals(err, e, "Wrong error");
+ }
+
+ /**
+ * Tests extractCauseUnchecked() if the cause is an unchecked exception.
+ */
+ @Test
+ public void testExtractCauseUncheckedUncheckedException() {
+ final RuntimeException rex = new RuntimeException("Test");
+ final RuntimeException r = assertThrows(RuntimeException.class, () -> ConcurrentUtils.extractCauseUnchecked(new ExecutionException(rex)));
+ assertEquals(rex, r, "Wrong exception");
+ }
+
+ /**
+ * Tests extractCauseUnchecked() if the cause is a checked exception.
+ */
+ @Test
+ public void testExtractCauseUncheckedChecked() {
+ final Exception ex = new Exception("Test");
+ final ConcurrentRuntimeException cex = ConcurrentUtils.extractCauseUnchecked(new ExecutionException(ex));
+ assertSame(ex, cex.getCause(), "Wrong cause");
+ }
+
+ /**
+ * Tests handleCause() if the cause is an error.
+ */
+ @Test
+ public void testHandleCauseError() {
+ final Error err = new AssertionError("Test");
+ final Error e = assertThrows(Error.class, () -> ConcurrentUtils.handleCause(new ExecutionException(err)));
+ assertEquals(err, e, "Wrong error");
+ }
+
+ /**
+ * Tests handleCause() if the cause is an unchecked exception.
+ */
+ @Test
+ public void testHandleCauseUncheckedException() {
+ final RuntimeException rex = new RuntimeException("Test");
+ final RuntimeException r = assertThrows(RuntimeException.class, () -> ConcurrentUtils.handleCause(new ExecutionException(rex)));
+ assertEquals(rex, r, "Wrong exception");
+ }
+
+ /**
+ * Tests handleCause() if the cause is a checked exception.
+ */
+ @Test
+ public void testHandleCauseChecked() {
+ final Exception ex = new Exception("Test");
+ final ConcurrentException cex = assertThrows(ConcurrentException.class, () -> ConcurrentUtils.handleCause(new ExecutionException(ex)));
+ assertEquals(ex, cex.getCause(), "Wrong cause");
+ }
+
+ /**
+ * Tests handleCause() for a null parameter or a null cause. In this case the method should do nothing. We can only test
+ * that no exception is thrown.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testHandleCauseNull() throws ConcurrentException {
+ ConcurrentUtils.handleCause(null);
+ ConcurrentUtils.handleCause(new ExecutionException("Test", null));
+ }
+
+ /**
+ * Tests handleCauseUnchecked() if the cause is an error.
+ */
+ @Test
+ public void testHandleCauseUncheckedError() {
+ final Error err = new AssertionError("Test");
+ final Error e = assertThrows(Error.class, () -> ConcurrentUtils.handleCauseUnchecked(new ExecutionException(err)));
+ assertEquals(err, e, "Wrong error");
+ }
+
+ /**
+ * Tests handleCauseUnchecked() if the cause is an unchecked exception.
+ */
+ @Test
+ public void testHandleCauseUncheckedUncheckedException() {
+ final RuntimeException rex = new RuntimeException("Test");
+ final RuntimeException r = assertThrows(RuntimeException.class, () -> ConcurrentUtils.handleCauseUnchecked(new ExecutionException(rex)));
+ assertEquals(rex, r, "Wrong exception");
+ }
+
+ /**
+ * Tests handleCauseUnchecked() if the cause is a checked exception.
+ */
+ @Test
+ public void testHandleCauseUncheckedChecked() {
+ final Exception ex = new Exception("Test");
+ final ConcurrentRuntimeException crex = assertThrows(ConcurrentRuntimeException.class,
+ () -> ConcurrentUtils.handleCauseUnchecked(new ExecutionException(ex)));
+ assertEquals(ex, crex.getCause(), "Wrong cause");
+ }
+
+ /**
+ * Tests handleCauseUnchecked() for a null parameter or a null cause. In this case the method should do nothing. We can
+ * only test that no exception is thrown.
+ */
+ @Test
+ public void testHandleCauseUncheckedNull() {
+ ConcurrentUtils.handleCauseUnchecked(null);
+ ConcurrentUtils.handleCauseUnchecked(new ExecutionException("Test", null));
+ }
+
+ /**
+ * Tests initialize() for a null argument.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeNull() throws ConcurrentException {
+ assertNull(ConcurrentUtils.initialize(null), "Got a result");
+ }
+
+ /**
+ * Tests a successful initialize() operation.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitialize() throws ConcurrentException {
+ final ConcurrentInitializer<Object> init = EasyMock.createMock(ConcurrentInitializer.class);
+ final Object result = new Object();
+ EasyMock.expect(init.get()).andReturn(result);
+ EasyMock.replay(init);
+ assertSame(result, ConcurrentUtils.initialize(init), "Wrong result object");
+ EasyMock.verify(init);
+ }
+
+ /**
+ * Tests initializeUnchecked() for a null argument.
+ */
+ @Test
+ public void testInitializeUncheckedNull() {
+ assertNull(ConcurrentUtils.initializeUnchecked(null), "Got a result");
+ }
+
+ /**
+ * Tests creating ConcurrentRuntimeException with no arguments.
+ */
+ @Test
+ public void testUninitializedConcurrentRuntimeException() {
+ assertNotNull(new ConcurrentRuntimeException(), "Error creating empty ConcurrentRuntimeException");
+ }
+
+ /**
+ * Tests a successful initializeUnchecked() operation.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeUnchecked() throws ConcurrentException {
+ final ConcurrentInitializer<Object> init = EasyMock.createMock(ConcurrentInitializer.class);
+ final Object result = new Object();
+ EasyMock.expect(init.get()).andReturn(result);
+ EasyMock.replay(init);
+ assertSame(result, ConcurrentUtils.initializeUnchecked(init), "Wrong result object");
+ EasyMock.verify(init);
+ }
+
+ /**
+ * Tests whether exceptions are correctly handled by initializeUnchecked().
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeUncheckedEx() throws ConcurrentException {
+ final ConcurrentInitializer<Object> init = EasyMock.createMock(ConcurrentInitializer.class);
+ final Exception cause = new Exception();
+ EasyMock.expect(init.get()).andThrow(new ConcurrentException(cause));
+ EasyMock.replay(init);
+ final ConcurrentRuntimeException crex = assertThrows(ConcurrentRuntimeException.class, () -> ConcurrentUtils.initializeUnchecked(init));
+ assertSame(cause, crex.getCause(), "Wrong cause");
+ EasyMock.verify(init);
+ }
+
+ /**
+ * Tests constant future.
+ *
+ * @throws Exception so we don't have to catch it
+ */
+ @Test
+ public void testConstantFuture_Integer() throws Exception {
+ final Integer value = Integer.valueOf(5);
+ final Future<Integer> test = ConcurrentUtils.constantFuture(value);
+ assertTrue(test.isDone());
+ assertSame(value, test.get());
+ assertSame(value, test.get(1000, TimeUnit.SECONDS));
+ assertSame(value, test.get(1000, null));
+ assertFalse(test.isCancelled());
+ assertFalse(test.cancel(true));
+ assertFalse(test.cancel(false));
+ }
+
+ /**
+ * Tests constant future.
+ *
+ * @throws Exception so we don't have to catch it
+ */
+ @Test
+ public void testConstantFuture_null() throws Exception {
+ final Integer value = null;
+ final Future<Integer> test = ConcurrentUtils.constantFuture(value);
+ assertTrue(test.isDone());
+ assertSame(value, test.get());
+ assertSame(value, test.get(1000, TimeUnit.SECONDS));
+ assertSame(value, test.get(1000, null));
+ assertFalse(test.isCancelled());
+ assertFalse(test.cancel(true));
+ assertFalse(test.cancel(false));
+ }
+
+ /**
+ * Tests putIfAbsent() if the map contains the key in question.
+ */
+ @Test
+ public void testPutIfAbsentKeyPresent() {
+ final String key = "testKey";
+ final Integer value = 42;
+ final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
+ map.put(key, value);
+ assertEquals(value, ConcurrentUtils.putIfAbsent(map, key, 0), "Wrong result");
+ assertEquals(value, map.get(key), "Wrong value in map");
+ }
+
+ /**
+ * Tests putIfAbsent() if the map does not contain the key in question.
+ */
+ @Test
+ public void testPutIfAbsentKeyNotPresent() {
+ final String key = "testKey";
+ final Integer value = 42;
+ final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
+ assertEquals(value, ConcurrentUtils.putIfAbsent(map, key, value), "Wrong result");
+ assertEquals(value, map.get(key), "Wrong value in map");
+ }
+
+ /**
+ * Tests putIfAbsent() if a null map is passed in.
+ */
+ @Test
+ public void testPutIfAbsentNullMap() {
+ assertNull(ConcurrentUtils.putIfAbsent(null, "test", 100), "Wrong result");
+ }
+
+ /**
+ * Tests createIfAbsent() if the key is found in the map.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testCreateIfAbsentKeyPresent() throws ConcurrentException {
+ final ConcurrentInitializer<Integer> init = EasyMock.createMock(ConcurrentInitializer.class);
+ EasyMock.replay(init);
+ final String key = "testKey";
+ final Integer value = 42;
+ final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
+ map.put(key, value);
+ assertEquals(value, ConcurrentUtils.createIfAbsent(map, key, init), "Wrong result");
+ assertEquals(value, map.get(key), "Wrong value in map");
+ EasyMock.verify(init);
+ }
+
+ /**
+ * Tests createIfAbsent() if the map does not contain the key in question.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testCreateIfAbsentKeyNotPresent() throws ConcurrentException {
+ final ConcurrentInitializer<Integer> init = EasyMock.createMock(ConcurrentInitializer.class);
+ final String key = "testKey";
+ final Integer value = 42;
+ EasyMock.expect(init.get()).andReturn(value);
+ EasyMock.replay(init);
+ final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
+ assertEquals(value, ConcurrentUtils.createIfAbsent(map, key, init), "Wrong result");
+ assertEquals(value, map.get(key), "Wrong value in map");
+ EasyMock.verify(init);
+ }
+
+ /**
+ * Tests createIfAbsent() if a null map is passed in.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testCreateIfAbsentNullMap() throws ConcurrentException {
+ final ConcurrentInitializer<Integer> init = EasyMock.createMock(ConcurrentInitializer.class);
+ EasyMock.replay(init);
+ assertNull(ConcurrentUtils.createIfAbsent(null, "test", init), "Wrong result");
+ EasyMock.verify(init);
+ }
+
+ /**
+ * Tests createIfAbsent() if a null initializer is passed in.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testCreateIfAbsentNullInit() throws ConcurrentException {
+ final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
+ final String key = "testKey";
+ final Integer value = 42;
+ map.put(key, value);
+ assertNull(ConcurrentUtils.createIfAbsent(map, key, null), "Wrong result");
+ assertEquals(value, map.get(key), "Map was changed");
+ }
+
+ /**
+ * Tests createIfAbsentUnchecked() if no exception is thrown.
+ */
+ @Test
+ public void testCreateIfAbsentUncheckedSuccess() {
+ final String key = "testKey";
+ final Integer value = 42;
+ final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
+ assertEquals(value, ConcurrentUtils.createIfAbsentUnchecked(map, key, new ConstantInitializer<>(value)), "Wrong result");
+ assertEquals(value, map.get(key), "Wrong value in map");
+ }
+
+ /**
+ * Tests createIfAbsentUnchecked() if an exception is thrown.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testCreateIfAbsentUncheckedException() throws ConcurrentException {
+ final ConcurrentInitializer<Integer> init = EasyMock.createMock(ConcurrentInitializer.class);
+ final Exception ex = new Exception();
+ EasyMock.expect(init.get()).andThrow(new ConcurrentException(ex));
+ EasyMock.replay(init);
+ final ConcurrentRuntimeException crex = assertThrows(ConcurrentRuntimeException.class,
+ () -> ConcurrentUtils.createIfAbsentUnchecked(new ConcurrentHashMap<>(), "test", init));
+ assertEquals(ex, crex.getCause(), "Wrong cause");
+ EasyMock.verify(init);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/ConstantInitializerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/ConstantInitializerTest.java
new file mode 100644
index 000000000..c296f056f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/ConstantInitializerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@code ConstantInitializer}.
+ */
+public class ConstantInitializerTest extends AbstractLangTest {
+ /** Constant for the object managed by the initializer. */
+ private static final Integer VALUE = 42;
+
+ /** The initializer to be tested. */
+ private ConstantInitializer<Integer> init;
+
+ @BeforeEach
+ public void setUp() {
+ init = new ConstantInitializer<>(VALUE);
+ }
+
+ /**
+ * Helper method for testing equals() and hashCode().
+ *
+ * @param obj the object to compare with the test instance
+ * @param expected the expected result
+ */
+ private void checkEquals(final Object obj, final boolean expected) {
+ assertEquals(expected, init.equals(obj), "Wrong result of equals");
+ if (obj != null) {
+ assertEquals(expected, obj.equals(init), "Not symmetric");
+ if (expected) {
+ assertEquals(init.hashCode(), obj.hashCode(), "Different hash codes");
+ }
+ }
+ }
+
+ /**
+ * Tests whether the correct object is returned.
+ */
+ @Test
+ public void testGetObject() {
+ assertEquals(VALUE, init.getObject(), "Wrong object");
+ }
+
+ /**
+ * Tests whether get() returns the correct object.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testGet() throws ConcurrentException {
+ assertEquals(VALUE, init.get(), "Wrong object");
+ }
+
+ /**
+ * Tests equals() if the expected result is true.
+ */
+ @Test
+ public void testEqualsTrue() {
+ checkEquals(init, true);
+ ConstantInitializer<Integer> init2 = new ConstantInitializer<>(
+ Integer.valueOf(VALUE.intValue()));
+ checkEquals(init2, true);
+ init = new ConstantInitializer<>(null);
+ init2 = new ConstantInitializer<>(null);
+ checkEquals(init2, true);
+ }
+
+ /**
+ * Tests equals() if the expected result is false.
+ */
+ @Test
+ public void testEqualsFalse() {
+ ConstantInitializer<Integer> init2 = new ConstantInitializer<>(
+ null);
+ checkEquals(init2, false);
+ init2 = new ConstantInitializer<>(VALUE + 1);
+ checkEquals(init2, false);
+ }
+
+ /**
+ * Tests equals() with objects of other classes.
+ */
+ @Test
+ public void testEqualsWithOtherObjects() {
+ checkEquals(null, false);
+ checkEquals(this, false);
+ checkEquals(new ConstantInitializer<>("Test"), false);
+ }
+
+ /**
+ * Tests the string representation.
+ */
+ @Test
+ public void testToString() {
+ final String s = init.toString();
+ final Pattern pattern = Pattern
+ .compile("ConstantInitializer@-?\\d+ \\[ object = " + VALUE
+ + " \\]");
+ assertTrue(pattern.matcher(s).matches(), "Wrong string: " + s);
+ }
+
+ /**
+ * Tests the string representation if the managed object is null.
+ */
+ @Test
+ public void testToStringNull() {
+ final String s = new ConstantInitializer<>(null).toString();
+ assertTrue(s.indexOf("object = null") > 0, "Object not found: " + s);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreakerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreakerTest.java
new file mode 100644
index 000000000..ef9bf2561
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreakerTest.java
@@ -0,0 +1,414 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@code EventCountCircuitBreaker}.
+ */
+public class EventCountCircuitBreakerTest extends AbstractLangTest {
+ /** Constant for the opening threshold. */
+ private static final int OPENING_THRESHOLD = 10;
+
+ /** Constant for the closing threshold. */
+ private static final int CLOSING_THRESHOLD = 5;
+
+ /** Constant for the factor for converting nanoseconds. */
+ private static final long NANO_FACTOR = 1000L * 1000L * 1000L;
+
+ /**
+ * Tests that time units are correctly taken into account by constructors.
+ */
+ @Test
+ public void testIntervalCalculation() {
+ final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 2, TimeUnit.MILLISECONDS);
+ assertEquals(NANO_FACTOR, breaker.getOpeningInterval(), "Wrong opening interval");
+ assertEquals(2 * NANO_FACTOR / 1000, breaker.getClosingInterval(), "Wrong closing interval");
+ }
+
+ /**
+ * Tests that the closing interval is the same as the opening interval if it is not
+ * specified.
+ */
+ @Test
+ public void testDefaultClosingInterval() {
+ final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD);
+ assertEquals(NANO_FACTOR, breaker.getClosingInterval(), "Wrong closing interval");
+ }
+
+ /**
+ * Tests that the closing threshold is the same as the opening threshold if not
+ * specified otherwise.
+ */
+ @Test
+ public void testDefaultClosingThreshold() {
+ final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS);
+ assertEquals(NANO_FACTOR, breaker.getClosingInterval(), "Wrong closing interval");
+ assertEquals(OPENING_THRESHOLD, breaker.getClosingThreshold(), "Wrong closing threshold");
+ }
+
+ /**
+ * Tests that a circuit breaker is closed after its creation.
+ */
+ @Test
+ public void testInitiallyClosed() {
+ final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS);
+ assertFalse(breaker.isOpen(), "Open");
+ assertTrue(breaker.isClosed(), "Not closed");
+ }
+
+ /**
+ * Tests whether the current time is correctly determined.
+ */
+ @Test
+ public void testNow() {
+ final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS);
+ final long nowNanos = breaker.nanoTime();
+ final long deltaNanos = Math.abs(System.nanoTime() - nowNanos);
+ assertTrue(deltaNanos < 100_000, String.format("Delta %,d ns to current time too large", deltaNanos));
+ }
+
+ /**
+ * Tests that the circuit breaker stays closed if the number of received events stays
+ * below the threshold.
+ */
+ @Test
+ public void testNotOpeningUnderThreshold() {
+ long startTime = 1000;
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ for (int i = 0; i < OPENING_THRESHOLD - 1; i++) {
+ assertTrue(breaker.at(startTime).incrementAndCheckState(), "In open state");
+ startTime++;
+ }
+ assertTrue(breaker.isClosed(), "Not closed");
+ }
+
+ /**
+ * Tests that the circuit breaker stays closed if there are a number of received
+ * events, but not in a single check interval.
+ */
+ @Test
+ public void testNotOpeningCheckIntervalExceeded() {
+ long startTime = 0L;
+ final long timeIncrement = 3 * NANO_FACTOR / (2 * OPENING_THRESHOLD);
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ for (int i = 0; i < 5 * OPENING_THRESHOLD; i++) {
+ assertTrue(breaker.at(startTime).incrementAndCheckState(), "In open state");
+ startTime += timeIncrement;
+ }
+ assertTrue(breaker.isClosed(), "Not closed");
+ }
+
+ /**
+ * Tests that the circuit breaker opens if all conditions are met.
+ */
+ @Test
+ public void testOpeningWhenThresholdReached() {
+ long startTime = 0;
+ final long timeIncrement = NANO_FACTOR / OPENING_THRESHOLD - 1;
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ boolean open = false;
+ for (int i = 0; i < OPENING_THRESHOLD + 1; i++) {
+ open = !breaker.at(startTime).incrementAndCheckState();
+ startTime += timeIncrement;
+ }
+ assertTrue(open, "Not open");
+ assertFalse(breaker.isClosed(), "Closed");
+ }
+
+ /**
+ * Tests that the circuit breaker opens if all conditions are met when using
+ * {@link EventCountCircuitBreaker#incrementAndCheckState(Integer increment)}.
+ */
+ @Test
+ public void testOpeningWhenThresholdReachedThroughBatch() {
+ final long timeIncrement = NANO_FACTOR / OPENING_THRESHOLD - 1;
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ final long startTime = timeIncrement * (OPENING_THRESHOLD + 1);
+ final boolean open = !breaker.at(startTime).incrementAndCheckState(OPENING_THRESHOLD + 1);
+ assertTrue(open, "Not open");
+ assertFalse(breaker.isClosed(), "Closed");
+ }
+
+ /**
+ * Tests that an open circuit breaker does not close itself when the number of events
+ * received is over the threshold.
+ */
+ @Test
+ public void testNotClosingOverThreshold() {
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD,
+ 10, TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ long startTime = 0;
+ breaker.open();
+ for (int i = 0; i <= CLOSING_THRESHOLD; i++) {
+ assertFalse(breaker.at(startTime).incrementAndCheckState(), "Not open");
+ startTime += 1000;
+ }
+ assertFalse(breaker.at(startTime + NANO_FACTOR).incrementAndCheckState(), "Closed in new interval");
+ assertTrue(breaker.isOpen(), "Not open at end");
+ }
+
+ /**
+ * Tests that the circuit breaker closes automatically if the number of events
+ * received goes under the closing threshold.
+ */
+ @Test
+ public void testClosingWhenThresholdReached() {
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD,
+ 10, TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ breaker.open();
+ breaker.at(1000).incrementAndCheckState();
+ assertFalse(breaker.at(2000).checkState(), "Already closed");
+ assertFalse(breaker.at(NANO_FACTOR).checkState(), "Closed at interval end");
+ assertTrue(breaker.at(NANO_FACTOR + 1).checkState(), "Not closed after interval end");
+ assertTrue(breaker.isClosed(), "Not closed at end");
+ }
+
+ /**
+ * Tests whether an explicit open operation fully initializes the internal check data
+ * object. Otherwise, the circuit breaker may close itself directly afterwards.
+ */
+ @Test
+ public void testOpenStartsNewCheckInterval() {
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 2,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ breaker.at(NANO_FACTOR - 1000).open();
+ assertTrue(breaker.isOpen(), "Not open");
+ assertFalse(breaker.at(NANO_FACTOR + 100).checkState(), "Already closed");
+ }
+
+ /**
+ * Tests whether a new check interval is started if the circuit breaker has a
+ * transition to open state.
+ */
+ @Test
+ public void testAutomaticOpenStartsNewCheckInterval() {
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 2,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ long time = 10 * NANO_FACTOR;
+ for (int i = 0; i <= OPENING_THRESHOLD; i++) {
+ breaker.at(time++).incrementAndCheckState();
+ }
+ assertTrue(breaker.isOpen(), "Not open");
+ time += NANO_FACTOR - 1000;
+ assertFalse(breaker.at(time).incrementAndCheckState(), "Already closed");
+ time += 1001;
+ assertTrue(breaker.at(time).checkState(), "Not closed in time interval");
+ }
+
+ /**
+ * Tests whether the circuit breaker can be closed explicitly.
+ */
+ @Test
+ public void testClose() {
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 2,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ long time = 0;
+ for (int i = 0; i <= OPENING_THRESHOLD; i++, time += 1000) {
+ breaker.at(time).incrementAndCheckState();
+ }
+ assertTrue(breaker.isOpen(), "Not open");
+ breaker.close();
+ assertTrue(breaker.isClosed(), "Not closed");
+ assertTrue(breaker.at(time + 1000).incrementAndCheckState(), "Open again");
+ }
+
+ /**
+ * Tests whether events are generated when the state is changed.
+ */
+ @Test
+ public void testChangeEvents() {
+ final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS);
+ final ChangeListener listener = new ChangeListener(breaker);
+ breaker.addChangeListener(listener);
+ breaker.open();
+ breaker.close();
+ listener.verify(Boolean.TRUE, Boolean.FALSE);
+ }
+
+ /**
+ * Tests whether a change listener can be removed.
+ */
+ @Test
+ public void testRemoveChangeListener() {
+ final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS);
+ final ChangeListener listener = new ChangeListener(breaker);
+ breaker.addChangeListener(listener);
+ breaker.open();
+ breaker.removeChangeListener(listener);
+ breaker.close();
+ listener.verify(Boolean.TRUE);
+ }
+
+ /**
+ * Tests that a state transition triggered by multiple threads is handled correctly.
+ * Only the first transition should cause an event to be sent.
+ */
+ @Test
+ public void testStateTransitionGuarded() throws InterruptedException {
+ final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
+ TimeUnit.SECONDS);
+ final ChangeListener listener = new ChangeListener(breaker);
+ breaker.addChangeListener(listener);
+
+ final int threadCount = 128;
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Thread[] threads = new Thread[threadCount];
+ for (int i = 0; i < threadCount; i++) {
+ threads[i] = new Thread() {
+ @Override
+ public void run() {
+ try {
+ latch.await();
+ } catch (final InterruptedException iex) {
+ // ignore
+ }
+ breaker.open();
+ }
+ };
+ threads[i].start();
+ }
+ latch.countDown();
+ for (final Thread thread : threads) {
+ thread.join();
+ }
+ listener.verify(Boolean.TRUE);
+ }
+
+ /**
+ * Tests that automatic state transitions generate change events as well.
+ */
+ @Test
+ public void testChangeEventsGeneratedByAutomaticTransitions() {
+ final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 2,
+ TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
+ final ChangeListener listener = new ChangeListener(breaker);
+ breaker.addChangeListener(listener);
+ long time = 0;
+ for (int i = 0; i <= OPENING_THRESHOLD; i++, time += 1000) {
+ breaker.at(time).incrementAndCheckState();
+ }
+ breaker.at(NANO_FACTOR + 1).checkState();
+ breaker.at(3 * NANO_FACTOR).checkState();
+ listener.verify(Boolean.TRUE, Boolean.FALSE);
+ }
+
+ /**
+ * A test implementation of {@code EventCountCircuitBreaker} which supports mocking the timer.
+ * This is useful for the creation of deterministic tests for switching the circuit
+ * breaker's state.
+ */
+ private static class EventCountCircuitBreakerTestImpl extends EventCountCircuitBreaker {
+ /** The current time in nanoseconds. */
+ private long currentTime;
+
+ EventCountCircuitBreakerTestImpl(final int openingThreshold, final long openingInterval,
+ final TimeUnit openingUnit, final int closingThreshold, final long closingInterval,
+ final TimeUnit closingUnit) {
+ super(openingThreshold, openingInterval, openingUnit, closingThreshold,
+ closingInterval, closingUnit);
+ }
+
+ /**
+ * Sets the current time to be used by this test object for the next operation.
+ *
+ * @param time the time to set
+ * @return a reference to this object
+ */
+ public EventCountCircuitBreakerTestImpl at(final long time) {
+ currentTime = time;
+ return this;
+ }
+
+ /**
+ * {@inheritDoc} This implementation returns the value passed to the {@code at()}
+ * method.
+ */
+ @Override
+ long nanoTime() {
+ return currentTime;
+ }
+ }
+
+ /**
+ * A test change listener for checking whether correct change events are generated.
+ */
+ private static class ChangeListener implements PropertyChangeListener {
+ /** The expected event source. */
+ private final Object expectedSource;
+
+ /** A list with the updated values extracted from received change events. */
+ private final List<Boolean> changedValues;
+
+ /**
+ * Creates a new instance of {@code ChangeListener} and sets the expected event
+ * source.
+ *
+ * @param source the expected event source
+ */
+ ChangeListener(final Object source) {
+ expectedSource = source;
+ changedValues = new ArrayList<>();
+ }
+
+ @Override
+ public void propertyChange(final PropertyChangeEvent evt) {
+ assertEquals(expectedSource, evt.getSource(), "Wrong event source");
+ assertEquals("open", evt.getPropertyName(), "Wrong property name");
+ final Boolean newValue = (Boolean) evt.getNewValue();
+ final Boolean oldValue = (Boolean) evt.getOldValue();
+ assertNotEquals(newValue, oldValue, "Old and new value are equal");
+ changedValues.add(newValue);
+ }
+
+ /**
+ * Verifies that change events for the expected values have been received.
+ *
+ * @param values the expected values
+ */
+ public void verify(final Boolean... values) {
+ assertArrayEquals(values, changedValues.toArray(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY));
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/FutureTasksTest.java b/src/test/java/org/apache/commons/lang3/concurrent/FutureTasksTest.java
new file mode 100644
index 000000000..08b83a103
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/FutureTasksTest.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.concurrent;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link FutureTask}.
+ */
+public class FutureTasksTest extends AbstractLangTest {
+
+ @Test
+ public void testRun() throws InterruptedException, ExecutionException {
+ final String data = "Hello";
+ final FutureTask<String> f = FutureTasks.run(() -> data);
+ assertTrue(f.isDone());
+ assertEquals(data, f.get());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.java
new file mode 100644
index 000000000..16eb12451
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.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.concurrent;
+
+import org.junit.jupiter.api.BeforeEach;
+
+/**
+ * Test class for {@code LazyInitializer}.
+ */
+public class LazyInitializerTest extends AbstractConcurrentInitializerTest {
+ /** The initializer to be tested. */
+ private LazyInitializerTestImpl initializer;
+
+ @BeforeEach
+ public void setUp() {
+ initializer = new LazyInitializerTestImpl();
+ }
+
+ /**
+ * Returns the initializer to be tested. This implementation returns the
+ * {@code LazyInitializer} created in the {@code setUp()} method.
+ *
+ * @return the initializer to be tested
+ */
+ @Override
+ protected ConcurrentInitializer<Object> createInitializer() {
+ return initializer;
+ }
+
+ /**
+ * A test implementation of LazyInitializer. This class creates a plain
+ * Object. As Object does not provide a specific equals() method, it is easy
+ * to check whether multiple instances were created.
+ */
+ private static class LazyInitializerTestImpl extends
+ LazyInitializer<Object> {
+ @Override
+ protected Object initialize() {
+ return new Object();
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/MemoizerComputableTest.java b/src/test/java/org/apache/commons/lang3/concurrent/MemoizerComputableTest.java
new file mode 100644
index 000000000..30ed9f5a9
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/MemoizerComputableTest.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.concurrent;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.easymock.EasyMock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class MemoizerComputableTest extends AbstractLangTest {
+
+ private Computable<Integer, Integer> computable;
+
+ @BeforeEach
+ public void setUpComputableMock() {
+ computable = EasyMock.mock(Computable.class);
+ }
+
+ @Test
+ public void testDefaultBehaviourNotToRecalculateExecutionExceptions() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(computable);
+ final InterruptedException interruptedException = new InterruptedException();
+ expect(computable.compute(input)).andThrow(interruptedException);
+ replay(computable);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertThrows(IllegalStateException.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testDoesNotRecalculateWhenSetToFalse() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(computable, false);
+ final InterruptedException interruptedException = new InterruptedException();
+ expect(computable.compute(input)).andThrow(interruptedException);
+ replay(computable);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertThrows(IllegalStateException.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testDoesRecalculateWhenSetToTrue() throws Exception {
+ final Integer input = 1;
+ final Integer answer = 3;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(computable, true);
+ final InterruptedException interruptedException = new InterruptedException();
+ expect(computable.compute(input)).andThrow(interruptedException).andReturn(answer);
+ replay(computable);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertEquals(answer, memoizer.compute(input));
+ }
+
+ @Test
+ public void testOnlyCallComputableOnceIfDoesNotThrowException() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(computable);
+ expect(computable.compute(input)).andReturn(input);
+ replay(computable);
+
+ assertEquals(input, memoizer.compute(input), "Should call computable first time");
+ assertEquals(input, memoizer.compute(input), "Should not call the computable the second time");
+ }
+
+ @Test
+ public void testWhenComputableThrowsError() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(computable);
+ final Error error = new Error();
+ expect(computable.compute(input)).andThrow(error);
+ replay(computable);
+
+ assertThrows(Error.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testWhenComputableThrowsRuntimeException() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(computable);
+ final RuntimeException runtimeException = new RuntimeException("Some runtime exception");
+ expect(computable.compute(input)).andThrow(runtimeException);
+ replay(computable);
+
+ assertThrows(RuntimeException.class, () -> memoizer.compute(input));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/MemoizerFunctionTest.java b/src/test/java/org/apache/commons/lang3/concurrent/MemoizerFunctionTest.java
new file mode 100644
index 000000000..4b2ff52dc
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/MemoizerFunctionTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.function.Function;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.easymock.EasyMock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class MemoizerFunctionTest extends AbstractLangTest {
+
+ private Function<Integer, Integer> function;
+
+ @BeforeEach
+ public void setUpComputableMock() {
+ function = EasyMock.mock(Function.class);
+ }
+
+ @Test
+ public void testDefaultBehaviourNotToRecalculateExecutionExceptions() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(function);
+ final IllegalArgumentException interruptedException = new IllegalArgumentException();
+ expect(function.apply(input)).andThrow(interruptedException);
+ replay(function);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertThrows(IllegalArgumentException.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testDoesNotRecalculateWhenSetToFalse() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(function, false);
+ final IllegalArgumentException interruptedException = new IllegalArgumentException();
+ expect(function.apply(input)).andThrow(interruptedException);
+ replay(function);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertThrows(IllegalArgumentException.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testDoesRecalculateWhenSetToTrue() throws Exception {
+ final Integer input = 1;
+ final Integer answer = 3;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(function, true);
+ final IllegalArgumentException interruptedException = new IllegalArgumentException();
+ expect(function.apply(input)).andThrow(interruptedException).andReturn(answer);
+ replay(function);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertEquals(answer, memoizer.compute(input));
+ }
+
+ @Test
+ public void testOnlyCallComputableOnceIfDoesNotThrowException() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(function);
+ expect(function.apply(input)).andReturn(input);
+ replay(function);
+
+ assertEquals(input, memoizer.compute(input), "Should call computable first time");
+ assertEquals(input, memoizer.compute(input), "Should not call the computable the second time");
+ }
+
+ @Test
+ public void testWhenComputableThrowsError() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(function);
+ final Error error = new Error();
+ expect(function.apply(input)).andThrow(error);
+ replay(function);
+
+ assertThrows(Error.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testWhenComputableThrowsRuntimeException() throws Exception {
+ final Integer input = 1;
+ final Memoizer<Integer, Integer> memoizer = new Memoizer<>(function);
+ final RuntimeException runtimeException = new RuntimeException("Some runtime exception");
+ expect(function.apply(input)).andThrow(runtimeException);
+ replay(function);
+
+ assertThrows(RuntimeException.class, () -> memoizer.compute(input));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializerTest.java
new file mode 100644
index 000000000..e5d46a4e7
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializerTest.java
@@ -0,0 +1,399 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@link MultiBackgroundInitializer}.
+ */
+public class MultiBackgroundInitializerTest extends AbstractLangTest {
+ /** Constant for the names of the child initializers. */
+ private static final String CHILD_INIT = "childInitializer";
+
+ /** The initializer to be tested. */
+ private MultiBackgroundInitializer initializer;
+
+ @BeforeEach
+ public void setUp() {
+ initializer = new MultiBackgroundInitializer();
+ }
+
+ /**
+ * Tests whether a child initializer has been executed. Optionally the
+ * expected executor service can be checked, too.
+ *
+ * @param child the child initializer
+ * @param expExec the expected executor service (null if the executor should
+ * not be checked)
+ * @throws ConcurrentException if an error occurs
+ */
+ private void checkChild(final BackgroundInitializer<?> child,
+ final ExecutorService expExec) throws ConcurrentException {
+ final ChildBackgroundInitializer cinit = (ChildBackgroundInitializer) child;
+ final Integer result = cinit.get();
+ assertEquals(1, result.intValue(), "Wrong result");
+ assertEquals(1, cinit.initializeCalls, "Wrong number of executions");
+ if (expExec != null) {
+ assertEquals(expExec, cinit.currentExecutor, "Wrong executor service");
+ }
+ }
+
+ /**
+ * Tests addInitializer() if a null name is passed in. This should cause an
+ * exception.
+ */
+ @Test
+ public void testAddInitializerNullName() {
+ assertThrows(NullPointerException.class, () -> initializer.addInitializer(null, new ChildBackgroundInitializer()));
+ }
+
+ /**
+ * Tests addInitializer() if a null initializer is passed in. This should
+ * cause an exception.
+ */
+ @Test
+ public void testAddInitializerNullInit() {
+ assertThrows(NullPointerException.class, () -> initializer.addInitializer(CHILD_INIT, null));
+ }
+
+ /**
+ * Tests the background processing if there are no child initializers.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeNoChildren() throws ConcurrentException {
+ assertTrue(initializer.start(), "Wrong result of start()");
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
+ .get();
+ assertTrue(res.initializerNames().isEmpty(), "Got child initializers");
+ assertTrue(initializer.getActiveExecutor().isShutdown(), "Executor not shutdown");
+ }
+
+ /**
+ * Helper method for testing the initialize() method. This method can
+ * operate with both an external and a temporary executor service.
+ *
+ * @return the result object produced by the initializer
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ private MultiBackgroundInitializer.MultiBackgroundInitializerResults checkInitialize()
+ throws ConcurrentException {
+ final int count = 5;
+ for (int i = 0; i < count; i++) {
+ initializer.addInitializer(CHILD_INIT + i,
+ new ChildBackgroundInitializer());
+ }
+ initializer.start();
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
+ .get();
+ assertEquals(count, res.initializerNames().size(), "Wrong number of child initializers");
+ for (int i = 0; i < count; i++) {
+ final String key = CHILD_INIT + i;
+ assertTrue(res.initializerNames().contains(key), "Name not found: " + key);
+ assertEquals(Integer.valueOf(1), res.getResultObject(key), "Wrong result object");
+ assertFalse(res.isException(key), "Exception flag");
+ assertNull(res.getException(key), "Got an exception");
+ checkChild(res.getInitializer(key), initializer.getActiveExecutor());
+ }
+ return res;
+ }
+
+ /**
+ * Tests background processing if a temporary executor is used.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeTempExec() throws ConcurrentException {
+ checkInitialize();
+ assertTrue(initializer.getActiveExecutor().isShutdown(), "Executor not shutdown");
+ }
+
+ /**
+ * Tests background processing if an external executor service is provided.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeExternalExec() throws ConcurrentException, InterruptedException {
+ final ExecutorService exec = Executors.newCachedThreadPool();
+ try {
+ initializer = new MultiBackgroundInitializer(exec);
+ checkInitialize();
+ assertEquals(exec, initializer.getActiveExecutor(), "Wrong executor");
+ assertFalse(exec.isShutdown(), "Executor was shutdown");
+ } finally {
+ exec.shutdown();
+ exec.awaitTermination(1, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Tests the behavior of initialize() if a child initializer has a specific
+ * executor service. Then this service should not be overridden.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeChildWithExecutor() throws ConcurrentException, InterruptedException {
+ final String initExec = "childInitializerWithExecutor";
+ final ExecutorService exec = Executors.newSingleThreadExecutor();
+ try {
+ final ChildBackgroundInitializer c1 = new ChildBackgroundInitializer();
+ final ChildBackgroundInitializer c2 = new ChildBackgroundInitializer();
+ c2.setExternalExecutor(exec);
+ initializer.addInitializer(CHILD_INIT, c1);
+ initializer.addInitializer(initExec, c2);
+ initializer.start();
+ initializer.get();
+ checkChild(c1, initializer.getActiveExecutor());
+ checkChild(c2, exec);
+ } finally {
+ exec.shutdown();
+ exec.awaitTermination(1, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Tries to add another child initializer after the start() method has been
+ * called. This should not be allowed.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testAddInitializerAfterStart() throws ConcurrentException {
+ initializer.start();
+ assertThrows(
+ IllegalStateException.class,
+ () -> initializer.addInitializer(CHILD_INIT, new ChildBackgroundInitializer()),
+ "Could add initializer after start()!");
+ initializer.get();
+ }
+
+ /**
+ * Tries to query an unknown child initializer from the results object. This
+ * should cause an exception.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testResultGetInitializerUnknown() throws ConcurrentException {
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = checkInitialize();
+ assertThrows(NoSuchElementException.class, () -> res.getInitializer("unknown"));
+ }
+
+ /**
+ * Tries to query the results of an unknown child initializer from the
+ * results object. This should cause an exception.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testResultGetResultObjectUnknown() throws ConcurrentException {
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = checkInitialize();
+ assertThrows(NoSuchElementException.class, () -> res.getResultObject("unknown"));
+ }
+
+ /**
+ * Tries to query the exception of an unknown child initializer from the
+ * results object. This should cause an exception.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testResultGetExceptionUnknown() throws ConcurrentException {
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = checkInitialize();
+ assertThrows(NoSuchElementException.class, () -> res.getException("unknown"));
+ }
+
+ /**
+ * Tries to query the exception flag of an unknown child initializer from
+ * the results object. This should cause an exception.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testResultIsExceptionUnknown() throws ConcurrentException {
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = checkInitialize();
+ assertThrows(NoSuchElementException.class, () -> res.isException("unknown"));
+ }
+
+ /**
+ * Tests that the set with the names of the initializers cannot be modified.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testResultInitializerNamesModify() throws ConcurrentException {
+ checkInitialize();
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
+ .get();
+ final Iterator<String> it = res.initializerNames().iterator();
+ it.next();
+ assertThrows(UnsupportedOperationException.class, it::remove);
+ }
+
+ /**
+ * Tests the behavior of the initializer if one of the child initializers
+ * throws a runtime exception.
+ */
+ @Test
+ public void testInitializeRuntimeEx() {
+ final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
+ child.ex = new RuntimeException();
+ initializer.addInitializer(CHILD_INIT, child);
+ initializer.start();
+ final Exception ex = assertThrows(Exception.class, initializer::get);
+ assertEquals(child.ex, ex, "Wrong exception");
+ }
+
+ /**
+ * Tests the behavior of the initializer if one of the child initializers
+ * throws a checked exception.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeEx() throws ConcurrentException {
+ final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
+ child.ex = new Exception();
+ initializer.addInitializer(CHILD_INIT, child);
+ initializer.start();
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
+ .get();
+ assertTrue(res.isException(CHILD_INIT), "No exception flag");
+ assertNull(res.getResultObject(CHILD_INIT), "Got a results object");
+ final ConcurrentException cex = res.getException(CHILD_INIT);
+ assertEquals(child.ex, cex.getCause(), "Wrong cause");
+ }
+
+ /**
+ * Tests the isSuccessful() method of the result object if no child
+ * initializer has thrown an exception.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeResultsIsSuccessfulTrue()
+ throws ConcurrentException {
+ final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
+ initializer.addInitializer(CHILD_INIT, child);
+ initializer.start();
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
+ .get();
+ assertTrue(res.isSuccessful(), "Wrong success flag");
+ }
+
+ /**
+ * Tests the isSuccessful() method of the result object if at least one
+ * child initializer has thrown an exception.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeResultsIsSuccessfulFalse()
+ throws ConcurrentException {
+ final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
+ child.ex = new Exception();
+ initializer.addInitializer(CHILD_INIT, child);
+ initializer.start();
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
+ .get();
+ assertFalse(res.isSuccessful(), "Wrong success flag");
+ }
+
+ /**
+ * Tests whether MultiBackgroundInitializers can be combined in a nested
+ * way.
+ *
+ * @throws org.apache.commons.lang3.concurrent.ConcurrentException so we don't have to catch it
+ */
+ @Test
+ public void testInitializeNested() throws ConcurrentException {
+ final String nameMulti = "multiChildInitializer";
+ initializer
+ .addInitializer(CHILD_INIT, new ChildBackgroundInitializer());
+ final MultiBackgroundInitializer mi2 = new MultiBackgroundInitializer();
+ final int count = 3;
+ for (int i = 0; i < count; i++) {
+ mi2
+ .addInitializer(CHILD_INIT + i,
+ new ChildBackgroundInitializer());
+ }
+ initializer.addInitializer(nameMulti, mi2);
+ initializer.start();
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
+ .get();
+ final ExecutorService exec = initializer.getActiveExecutor();
+ checkChild(res.getInitializer(CHILD_INIT), exec);
+ final MultiBackgroundInitializer.MultiBackgroundInitializerResults res2 = (MultiBackgroundInitializer.MultiBackgroundInitializerResults) res
+ .getResultObject(nameMulti);
+ assertEquals(count, res2.initializerNames().size(), "Wrong number of initializers");
+ for (int i = 0; i < count; i++) {
+ checkChild(res2.getInitializer(CHILD_INIT + i), exec);
+ }
+ assertTrue(exec.isShutdown(), "Executor not shutdown");
+ }
+
+ /**
+ * A concrete implementation of {@code BackgroundInitializer} used for
+ * defining background tasks for {@code MultiBackgroundInitializer}.
+ */
+ private static class ChildBackgroundInitializer extends
+ BackgroundInitializer<Integer> {
+ /** Stores the current executor service. */
+ volatile ExecutorService currentExecutor;
+
+ /** A counter for the invocations of initialize(). */
+ volatile int initializeCalls;
+
+ /** An exception to be thrown by initialize(). */
+ Exception ex;
+
+ /**
+ * Records this invocation. Optionally throws an exception.
+ */
+ @Override
+ protected Integer initialize() throws Exception {
+ currentExecutor = getActiveExecutor();
+ initializeCalls++;
+
+ if (ex != null) {
+ throw ex;
+ }
+
+ return Integer.valueOf(initializeCalls);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreakerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreakerTest.java
new file mode 100644
index 000000000..c6a97fe44
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreakerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@code ThresholdCircuitBreaker}.
+ */
+public class ThresholdCircuitBreakerTest extends AbstractLangTest {
+
+ /**
+ * Threshold used in tests.
+ */
+ private static final long threshold = 10L;
+
+ private static final long zeroThreshold = 0L;
+
+ /**
+ * Tests that the threshold is working as expected when incremented and no exception is thrown.
+ */
+ @Test
+ public void testThreshold() {
+ final ThresholdCircuitBreaker circuit = new ThresholdCircuitBreaker(threshold);
+ circuit.incrementAndCheckState(9L);
+ assertFalse(circuit.incrementAndCheckState(1L), "Circuit opened before reaching the threshold");
+ }
+
+ /**
+ * Tests that exceeding the threshold raises an exception.
+ */
+ @Test
+ public void testThresholdCircuitBreakingException() {
+ final ThresholdCircuitBreaker circuit = new ThresholdCircuitBreaker(threshold);
+ circuit.incrementAndCheckState(9L);
+ assertTrue(circuit.incrementAndCheckState(2L), "The circuit was supposed to be open after increment above the threshold");
+ }
+
+ /**
+ * Test that when threshold is zero, the circuit breaker is always open.
+ */
+ @Test
+ public void testThresholdEqualsZero() {
+ final ThresholdCircuitBreaker circuit = new ThresholdCircuitBreaker(zeroThreshold);
+ assertTrue(circuit.incrementAndCheckState(0L), "When the threshold is zero, the circuit is supposed to be always open");
+ }
+
+ /**
+ * Tests that closing a {@code ThresholdCircuitBreaker} resets the internal counter.
+ */
+ @Test
+ public void testClosingThresholdCircuitBreaker() {
+ final ThresholdCircuitBreaker circuit = new ThresholdCircuitBreaker(threshold);
+ circuit.incrementAndCheckState(9L);
+ circuit.close();
+ // now the internal counter is back at zero, not 9 anymore. So it is safe to increment 9 again
+ assertFalse(circuit.incrementAndCheckState(9L), "Internal counter was not reset back to zero");
+ }
+
+ /**
+ * Tests that we can get the threshold value correctly.
+ */
+ @Test
+ public void testGettingThreshold() {
+ final ThresholdCircuitBreaker circuit = new ThresholdCircuitBreaker(threshold);
+ assertEquals(Long.valueOf(threshold), Long.valueOf(circuit.getThreshold()), "Wrong value of threshold");
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java b/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
new file mode 100644
index 000000000..5f158660c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
@@ -0,0 +1,553 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+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.AbstractLangTest;
+import org.apache.commons.lang3.ThreadUtils;
+import org.easymock.EasyMock;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for TimedSemaphore.
+ */
+public class TimedSemaphoreTest extends AbstractLangTest {
+ /** Constant for the time period. */
+ private static final long PERIOD_MILLIS = 500;
+
+ private static final Duration DURATION = Duration.ofMillis(PERIOD_MILLIS);
+
+ /** Constant for the time unit. */
+ private static final TimeUnit UNIT = TimeUnit.MILLISECONDS;
+
+ /** Constant for the default limit. */
+ private static final int LIMIT = 10;
+
+ /**
+ * Tests creating a new instance.
+ */
+ @Test
+ public void testInit() {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ EasyMock.replay(service);
+ final TimedSemaphore semaphore = new TimedSemaphore(service, PERIOD_MILLIS, UNIT,
+ LIMIT);
+ EasyMock.verify(service);
+ assertEquals(service, semaphore.getExecutorService(), "Wrong service");
+ assertEquals(PERIOD_MILLIS, semaphore.getPeriod(), "Wrong period");
+ assertEquals(UNIT, semaphore.getUnit(), "Wrong unit");
+ assertEquals(0, semaphore.getLastAcquiresPerPeriod(), "Statistic available");
+ assertEquals(0.0, semaphore.getAverageCallsPerPeriod(), .05, "Average available");
+ assertFalse(semaphore.isShutdown(), "Already shutdown");
+ assertEquals(LIMIT, semaphore.getLimit(), "Wrong limit");
+ }
+
+ /**
+ * Tries to create an instance with a negative period. This should cause an
+ * exception.
+ */
+ @Test
+ public void testInitInvalidPeriod() {
+ assertThrows(IllegalArgumentException.class, () -> new TimedSemaphore(0L, UNIT, LIMIT));
+ }
+
+ /**
+ * Tests whether a default executor service is created if no service is
+ * provided.
+ */
+ @Test
+ public void testInitDefaultService() {
+ final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS, UNIT, LIMIT);
+ final ScheduledThreadPoolExecutor exec = (ScheduledThreadPoolExecutor) semaphore
+ .getExecutorService();
+ assertFalse(exec.getContinueExistingPeriodicTasksAfterShutdownPolicy(), "Wrong periodic task policy");
+ assertFalse(exec.getExecuteExistingDelayedTasksAfterShutdownPolicy(), "Wrong delayed task policy");
+ assertFalse(exec.isShutdown(), "Already shutdown");
+ semaphore.shutdown();
+ }
+
+ /**
+ * Tests starting the timer.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testStartTimer() throws InterruptedException {
+ final TimedSemaphoreTestImpl semaphore = new TimedSemaphoreTestImpl(PERIOD_MILLIS,
+ UNIT, LIMIT);
+ final ScheduledFuture<?> future = semaphore.startTimer();
+ assertNotNull(future, "No future returned");
+ ThreadUtils.sleepQuietly(DURATION);
+ final int trials = 10;
+ int count = 0;
+ do {
+ Thread.sleep(PERIOD_MILLIS);
+ assertFalse(count++ > trials, "endOfPeriod() not called!");
+ } while (semaphore.getPeriodEnds() <= 0);
+ semaphore.shutdown();
+ }
+
+ /**
+ * Tests the shutdown() method if the executor belongs to the semaphore. In
+ * this case it has to be shut down.
+ */
+ @Test
+ public void testShutdownOwnExecutor() {
+ final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS, UNIT, LIMIT);
+ semaphore.shutdown();
+ assertTrue(semaphore.isShutdown(), "Not shutdown");
+ assertTrue(semaphore.getExecutorService().isShutdown(), "Executor not shutdown");
+ }
+
+ /**
+ * Tests the shutdown() method for a shared executor service before a task
+ * was started. This should do pretty much nothing.
+ */
+ @Test
+ public void testShutdownSharedExecutorNoTask() {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ EasyMock.replay(service);
+ final TimedSemaphore semaphore = new TimedSemaphore(service, PERIOD_MILLIS, UNIT,
+ LIMIT);
+ semaphore.shutdown();
+ assertTrue(semaphore.isShutdown(), "Not shutdown");
+ EasyMock.verify(service);
+ }
+
+ /**
+ * Prepares an executor service mock to expect the start of the timer.
+ *
+ * @param service the mock
+ * @param future the future
+ */
+ private void prepareStartTimer(final ScheduledExecutorService service,
+ final ScheduledFuture<?> future) {
+ service.scheduleAtFixedRate((Runnable) EasyMock.anyObject(), EasyMock
+ .eq(PERIOD_MILLIS), EasyMock.eq(PERIOD_MILLIS), EasyMock.eq(UNIT));
+ EasyMock.expectLastCall().andReturn(future);
+ }
+
+ /**
+ * Tests the shutdown() method for a shared executor after the task was
+ * started. In this case the task must be canceled.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testShutdownSharedExecutorTask() throws InterruptedException {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ final ScheduledFuture<?> future = EasyMock.createMock(ScheduledFuture.class);
+ prepareStartTimer(service, future);
+ EasyMock.expect(Boolean.valueOf(future.cancel(false))).andReturn(Boolean.TRUE);
+ EasyMock.replay(service, future);
+ final TimedSemaphoreTestImpl semaphore = new TimedSemaphoreTestImpl(service,
+ PERIOD_MILLIS, UNIT, LIMIT);
+ semaphore.acquire();
+ semaphore.shutdown();
+ assertTrue(semaphore.isShutdown(), "Not shutdown");
+ EasyMock.verify(service, future);
+ }
+
+ /**
+ * Tests multiple invocations of the shutdown() method.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testShutdownMultipleTimes() throws InterruptedException {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ final ScheduledFuture<?> future = EasyMock.createMock(ScheduledFuture.class);
+ prepareStartTimer(service, future);
+ EasyMock.expect(Boolean.valueOf(future.cancel(false))).andReturn(Boolean.TRUE);
+ EasyMock.replay(service, future);
+ final TimedSemaphoreTestImpl semaphore = new TimedSemaphoreTestImpl(service,
+ PERIOD_MILLIS, UNIT, LIMIT);
+ semaphore.acquire();
+ for (int i = 0; i < 10; i++) {
+ semaphore.shutdown();
+ }
+ EasyMock.verify(service, future);
+ }
+
+ /**
+ * Tests the acquire() method if a limit is set.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testAcquireLimit() throws InterruptedException {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ final ScheduledFuture<?> future = EasyMock.createMock(ScheduledFuture.class);
+ prepareStartTimer(service, future);
+ EasyMock.replay(service, future);
+ final int count = 10;
+ final CountDownLatch latch = new CountDownLatch(count - 1);
+ final TimedSemaphore semaphore = new TimedSemaphore(service, PERIOD_MILLIS, UNIT, 1);
+ final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count,
+ count - 1);
+ semaphore.setLimit(count - 1);
+
+ // start a thread that calls the semaphore count times
+ t.start();
+ latch.await();
+ // now the semaphore's limit should be reached and the thread blocked
+ assertEquals(count - 1, semaphore.getAcquireCount(), "Wrong semaphore count");
+
+ // this wakes up the thread, it should call the semaphore once more
+ semaphore.endOfPeriod();
+ t.join();
+ assertEquals(1, semaphore.getAcquireCount(), "Wrong semaphore count (2)");
+ assertEquals(count - 1, semaphore.getLastAcquiresPerPeriod(), "Wrong acquire() count");
+ EasyMock.verify(service, future);
+ }
+
+ /**
+ * Tests the acquire() method if more threads are involved than the limit.
+ * This method starts a number of threads that all invoke the semaphore. The
+ * semaphore's limit is set to 1, so in each period only a single thread can
+ * acquire the semaphore.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testAcquireMultipleThreads() throws InterruptedException {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ final ScheduledFuture<?> future = EasyMock.createMock(ScheduledFuture.class);
+ prepareStartTimer(service, future);
+ EasyMock.replay(service, future);
+ final TimedSemaphoreTestImpl semaphore = new TimedSemaphoreTestImpl(service,
+ PERIOD_MILLIS, UNIT, 1);
+ semaphore.latch = new CountDownLatch(1);
+ final int count = 10;
+ final SemaphoreThread[] threads = new SemaphoreThread[count];
+ for (int i = 0; i < count; i++) {
+ threads[i] = new SemaphoreThread(semaphore, null, 1, 0);
+ threads[i].start();
+ }
+ for (int i = 0; i < count; i++) {
+ semaphore.latch.await();
+ assertEquals(1, semaphore.getAcquireCount(), "Wrong count");
+ semaphore.latch = new CountDownLatch(1);
+ semaphore.endOfPeriod();
+ assertEquals(1, semaphore.getLastAcquiresPerPeriod(), "Wrong acquire count");
+ }
+ for (int i = 0; i < count; i++) {
+ threads[i].join();
+ }
+ EasyMock.verify(service, future);
+ }
+
+ /**
+ * Tests the acquire() method if no limit is set. A test thread is started
+ * that calls the semaphore a large number of times. Even if the semaphore's
+ * period does not end, the thread should never block.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testAcquireNoLimit() throws InterruptedException {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ final ScheduledFuture<?> future = EasyMock.createMock(ScheduledFuture.class);
+ prepareStartTimer(service, future);
+ EasyMock.replay(service, future);
+ final TimedSemaphoreTestImpl semaphore = new TimedSemaphoreTestImpl(service,
+ PERIOD_MILLIS, UNIT, TimedSemaphore.NO_LIMIT);
+ final int count = 1000;
+ final CountDownLatch latch = new CountDownLatch(count);
+ final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count, count);
+ t.start();
+ latch.await();
+ EasyMock.verify(service, future);
+ }
+
+ /**
+ * Tries to call acquire() after shutdown(). This should cause an exception.
+ */
+ @Test
+ public void testPassAfterShutdown() {
+ final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS, UNIT, LIMIT);
+ semaphore.shutdown();
+ assertThrows(IllegalStateException.class, semaphore::acquire);
+ }
+
+ /**
+ * Tests a bigger number of invocations that span multiple periods. The
+ * period is set to a very short time. A background thread calls the
+ * semaphore a large number of times. While it runs at last one end of a
+ * period should be reached.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testAcquireMultiplePeriods() throws InterruptedException {
+ final int count = 1000;
+ final TimedSemaphoreTestImpl semaphore = new TimedSemaphoreTestImpl(
+ PERIOD_MILLIS / 10, TimeUnit.MILLISECONDS, 1);
+ semaphore.setLimit(count / 4);
+ final CountDownLatch latch = new CountDownLatch(count);
+ final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count, count);
+ t.start();
+ latch.await();
+ semaphore.shutdown();
+ assertTrue(semaphore.getPeriodEnds() > 0, "End of period not reached");
+ }
+
+ /**
+ * Tests the methods for statistics.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testGetAverageCallsPerPeriod() throws InterruptedException {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ final ScheduledFuture<?> future = EasyMock.createMock(ScheduledFuture.class);
+ prepareStartTimer(service, future);
+ EasyMock.replay(service, future);
+ final TimedSemaphore semaphore = new TimedSemaphore(service, PERIOD_MILLIS, UNIT,
+ LIMIT);
+ semaphore.acquire();
+ semaphore.endOfPeriod();
+ assertEquals(1.0, semaphore.getAverageCallsPerPeriod(), .005, "Wrong average (1)");
+ semaphore.acquire();
+ semaphore.acquire();
+ semaphore.endOfPeriod();
+ assertEquals(1.5, semaphore.getAverageCallsPerPeriod(), .005, "Wrong average (2)");
+ EasyMock.verify(service, future);
+ }
+
+ /**
+ * Tests whether the available non-blocking calls can be queried.
+ *
+ * @throws InterruptedException so we don't have to catch it
+ */
+ @Test
+ public void testGetAvailablePermits() throws InterruptedException {
+ final ScheduledExecutorService service = EasyMock
+ .createMock(ScheduledExecutorService.class);
+ final ScheduledFuture<?> future = EasyMock.createMock(ScheduledFuture.class);
+ prepareStartTimer(service, future);
+ EasyMock.replay(service, future);
+ final TimedSemaphore semaphore = new TimedSemaphore(service, PERIOD_MILLIS, UNIT,
+ LIMIT);
+ for (int i = 0; i < LIMIT; i++) {
+ assertEquals(LIMIT - i, semaphore.getAvailablePermits(), "Wrong available count at " + i);
+ semaphore.acquire();
+ }
+ semaphore.endOfPeriod();
+ assertEquals(LIMIT, semaphore.getAvailablePermits(), "Wrong available count in new period");
+ EasyMock.verify(service, future);
+ }
+
+ /**
+ * Tests the tryAcquire() method. It is checked whether the semaphore can be acquired
+ * by a bunch of threads the expected number of times and not more.
+ */
+ @Test
+ public void testTryAcquire() throws InterruptedException {
+ final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS, TimeUnit.SECONDS,
+ LIMIT);
+ final TryAcquireThread[] threads = new TryAcquireThread[3 * LIMIT];
+ final CountDownLatch latch = new CountDownLatch(1);
+ for (int i = 0; i < threads.length; i++) {
+ threads[i] = new TryAcquireThread(semaphore, latch);
+ threads[i].start();
+ }
+
+ latch.countDown();
+ int permits = 0;
+ for (final TryAcquireThread t : threads) {
+ t.join();
+ if (t.acquired) {
+ permits++;
+ }
+ }
+ assertEquals(LIMIT, permits, "Wrong number of permits granted");
+ }
+
+ /**
+ * Tries to call tryAcquire() after shutdown(). This should cause an exception.
+ */
+ @Test
+ public void testTryAcquireAfterShutdown() {
+ final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS, UNIT, LIMIT);
+ semaphore.shutdown();
+ assertThrows(IllegalStateException.class, semaphore::tryAcquire);
+ }
+
+ /**
+ * A specialized implementation of {@code TimedSemaphore} that is easier to
+ * test.
+ */
+ private static class TimedSemaphoreTestImpl extends TimedSemaphore {
+ /** A mock scheduled future. */
+ ScheduledFuture<?> schedFuture;
+
+ /** A latch for synchronizing with the main thread. */
+ volatile CountDownLatch latch;
+
+ /** Counter for the endOfPeriod() invocations. */
+ private int periodEnds;
+
+ TimedSemaphoreTestImpl(final long timePeriod, final TimeUnit timeUnit,
+ final int limit) {
+ super(timePeriod, timeUnit, limit);
+ }
+
+ TimedSemaphoreTestImpl(final ScheduledExecutorService service,
+ final long timePeriod, final TimeUnit timeUnit, final int limit) {
+ super(service, timePeriod, timeUnit, limit);
+ }
+
+ /**
+ * Returns the number of invocations of the endOfPeriod() method.
+ *
+ * @return the endOfPeriod() invocations
+ */
+ int getPeriodEnds() {
+ synchronized (this) {
+ return periodEnds;
+ }
+ }
+
+ /**
+ * Invokes the latch if one is set.
+ *
+ * @throws InterruptedException because it is declared that way in TimedSemaphore
+ */
+ @Override
+ public synchronized void acquire() throws InterruptedException {
+ super.acquire();
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ /**
+ * Counts the number of invocations.
+ */
+ @Override
+ protected synchronized void endOfPeriod() {
+ super.endOfPeriod();
+ periodEnds++;
+ }
+
+ /**
+ * Either returns the mock future or calls the super method.
+ */
+ @Override
+ protected ScheduledFuture<?> startTimer() {
+ return schedFuture != null ? schedFuture : super.startTimer();
+ }
+ }
+
+ /**
+ * A test thread class that will be used by tests for triggering the
+ * semaphore. The thread calls the semaphore a configurable number of times.
+ * When this is done, it can notify the main thread.
+ */
+ private static class SemaphoreThread extends Thread {
+ /** The semaphore. */
+ private final TimedSemaphore semaphore;
+
+ /** A latch for communication with the main thread. */
+ private final CountDownLatch latch;
+
+ /** The number of acquire() calls. */
+ private final int count;
+
+ /** The number of invocations of the latch. */
+ private final int latchCount;
+
+ SemaphoreThread(final TimedSemaphore b, final CountDownLatch l, final int c, final int lc) {
+ semaphore = b;
+ latch = l;
+ count = c;
+ latchCount = lc;
+ }
+
+ /**
+ * Calls acquire() on the semaphore for the specified number of times.
+ * Optionally the latch will also be triggered to synchronize with the
+ * main test thread.
+ */
+ @Override
+ public void run() {
+ try {
+ for (int i = 0; i < count; i++) {
+ semaphore.acquire();
+
+ if (i < latchCount) {
+ latch.countDown();
+ }
+ }
+ } catch (final InterruptedException iex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * A test thread class which invokes {@code tryAcquire()} on the test semaphore and
+ * records the return value.
+ */
+ private static class TryAcquireThread extends Thread {
+ /** The semaphore. */
+ private final TimedSemaphore semaphore;
+
+ /** A latch for communication with the main thread. */
+ private final CountDownLatch latch;
+
+ /** Flag whether a permit could be acquired. */
+ private boolean acquired;
+
+ TryAcquireThread(final TimedSemaphore s, final CountDownLatch l) {
+ semaphore = s;
+ latch = l;
+ }
+
+ @Override
+ public void run() {
+ try {
+ if (latch.await(10, TimeUnit.SECONDS)) {
+ acquired = semaphore.tryAcquire();
+ }
+ } catch (final InterruptedException iex) {
+ // ignore
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/UncheckedExecutionExceptionTest.java b/src/test/java/org/apache/commons/lang3/concurrent/UncheckedExecutionExceptionTest.java
new file mode 100644
index 000000000..b9115fda9
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/UncheckedExecutionExceptionTest.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.concurrent;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UncheckedExecutionException}.
+ */
+public class UncheckedExecutionExceptionTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructWithCause() {
+ final Exception e = new Exception();
+ assertSame(e, new UncheckedExecutionException(e).getCause());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/UncheckedFutureTest.java b/src/test/java/org/apache/commons/lang3/concurrent/UncheckedFutureTest.java
new file mode 100644
index 000000000..d583680be
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/UncheckedFutureTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Arrays;
+import java.util.List;
+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 org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.exception.UncheckedInterruptedException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UncheckedFuture}.
+ */
+public class UncheckedFutureTest extends AbstractLangTest {
+
+ private static class TestFuture<V> extends AbstractFutureProxy<V> {
+
+ private final Exception exception;
+
+ TestFuture(final Exception throwable) {
+ super(ConcurrentUtils.constantFuture(null));
+ this.exception = throwable;
+ }
+
+ TestFuture(final V value) {
+ super(ConcurrentUtils.constantFuture(value));
+ this.exception = null;
+ }
+
+ @SuppressWarnings("unchecked") // Programming error if call site blows up at runtime.
+ private <T extends Exception> void checkException() throws T {
+ if (exception != null) {
+ throw (T) exception;
+ }
+ }
+
+ @Override
+ public V get() throws InterruptedException, ExecutionException {
+ checkException();
+ return super.get();
+ }
+
+ @Override
+ public V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ checkException();
+ return super.get(timeout, unit);
+ }
+
+ }
+
+ @Test
+ public void testGetExecutionException() {
+ final ExecutionException e = new ExecutionException(new Exception());
+ assertThrows(UncheckedExecutionException.class, () -> UncheckedFuture.on(new TestFuture<>(e)).get());
+ }
+
+ @Test
+ public void testGetInterruptedException() {
+ final InterruptedException e = new InterruptedException();
+ assertThrows(UncheckedInterruptedException.class, () -> UncheckedFuture.on(new TestFuture<>(e)).get());
+ }
+
+ @Test
+ public void testGetLongExecutionException() {
+ final ExecutionException e = new ExecutionException(new Exception());
+ assertThrows(UncheckedExecutionException.class, () -> UncheckedFuture.on(new TestFuture<>(e)).get(1, TimeUnit.MICROSECONDS));
+ }
+
+ @Test
+ public void testGetLongInterruptedException() {
+ final InterruptedException e = new InterruptedException();
+ assertThrows(UncheckedInterruptedException.class, () -> UncheckedFuture.on(new TestFuture<>(e)).get(1, TimeUnit.MICROSECONDS));
+ }
+
+ @Test
+ public void testGetLongTimeoutException() {
+ final TimeoutException e = new TimeoutException();
+ assertThrows(UncheckedTimeoutException.class, () -> UncheckedFuture.on(new TestFuture<>(e)).get(1, TimeUnit.MICROSECONDS));
+ }
+
+ @Test
+ public void testMap() {
+ final List<String> expected = Arrays.asList("Y", "Z");
+ final List<Future<String>> input = Arrays.asList(new TestFuture<>("Y"), new TestFuture<>("Z"));
+ assertEquals(expected, UncheckedFuture.map(input).map(UncheckedFuture::get).collect(Collectors.toList()));
+ }
+
+ @Test
+ public void testOnCollection() {
+ final List<String> expected = Arrays.asList("Y", "Z");
+ final List<Future<String>> input = Arrays.asList(new TestFuture<>("Y"), new TestFuture<>("Z"));
+ assertEquals(expected, UncheckedFuture.on(input).stream().map(UncheckedFuture::get).collect(Collectors.toList()));
+ }
+
+ @Test
+ public void testOnFuture() {
+ assertEquals("Z", UncheckedFuture.on(new TestFuture<>("Z")).get());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/UncheckedTimeoutExceptionTest.java b/src/test/java/org/apache/commons/lang3/concurrent/UncheckedTimeoutExceptionTest.java
new file mode 100644
index 000000000..5b0e98954
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/UncheckedTimeoutExceptionTest.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.concurrent;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UncheckedTimeoutException}.
+ */
+public class UncheckedTimeoutExceptionTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructWithCause() {
+ final Exception e = new Exception();
+ assertSame(e, new UncheckedTimeoutException(e).getCause());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java b/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java
new file mode 100644
index 000000000..d8d1a453d
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.Duration;
+import java.util.function.LongConsumer;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ThreadUtils;
+import org.apache.commons.lang3.concurrent.locks.LockingVisitors.LockVisitor;
+import org.apache.commons.lang3.concurrent.locks.LockingVisitors.StampedLockVisitor;
+import org.apache.commons.lang3.function.FailableConsumer;
+import org.junit.jupiter.api.Test;
+
+public class LockingVisitorsTest extends AbstractLangTest {
+
+ private static final Duration SHORT_DELAY = Duration.ofMillis(100);
+ private static final Duration DELAY = Duration.ofMillis(1500);
+ private static final int NUMBER_OF_THREADS = 10;
+ private static final Duration TOTAL_DELAY = DELAY.multipliedBy(NUMBER_OF_THREADS);
+
+ protected boolean containsTrue(final boolean[] booleanArray) {
+ synchronized (booleanArray) {
+ return ArrayUtils.contains(booleanArray, true);
+ }
+ }
+
+ private void runTest(final Duration delay, final boolean exclusiveLock, final LongConsumer runTimeCheck,
+ final boolean[] booleanValues, final LockVisitor<boolean[], ?> visitor) throws InterruptedException {
+ final boolean[] runningValues = new boolean[10];
+
+ final long startTimeMillis = System.currentTimeMillis();
+ for (int i = 0; i < booleanValues.length; i++) {
+ final int index = i;
+ final FailableConsumer<boolean[], ?> consumer = b -> {
+ b[index] = false;
+ ThreadUtils.sleep(delay);
+ b[index] = true;
+ set(runningValues, index, false);
+ };
+ final Thread t = new Thread(() -> {
+ if (exclusiveLock) {
+ visitor.acceptWriteLocked(consumer);
+ } else {
+ visitor.acceptReadLocked(consumer);
+ }
+ });
+ set(runningValues, i, true);
+ t.start();
+ }
+ while (containsTrue(runningValues)) {
+ ThreadUtils.sleep(SHORT_DELAY);
+ }
+ final long endTimeMillis = System.currentTimeMillis();
+ for (final boolean booleanValue : booleanValues) {
+ assertTrue(booleanValue);
+ }
+ // WRONG assumption
+ // runTimeCheck.accept(endTimeMillis - startTimeMillis);
+ }
+
+ protected void set(final boolean[] booleanArray, final int offset, final boolean value) {
+ synchronized (booleanArray) {
+ booleanArray[offset] = value;
+ }
+ }
+
+ @Test
+ public void testReentrantReadWriteLockExclusive() throws Exception {
+
+ /*
+ * If our threads are running concurrently, then we expect to be no faster than running one after the other.
+ */
+ final boolean[] booleanValues = new boolean[10];
+ runTest(DELAY, true, millis -> assertTrue(millis >= TOTAL_DELAY.toMillis()), booleanValues,
+ LockingVisitors.reentrantReadWriteLockVisitor(booleanValues));
+ }
+
+ @Test
+ public void testReentrantReadWriteLockNotExclusive() throws Exception {
+
+ /*
+ * If our threads are running concurrently, then we expect to be faster than running one after the other.
+ */
+ final boolean[] booleanValues = new boolean[10];
+ runTest(DELAY, false, millis -> assertTrue(millis < TOTAL_DELAY.toMillis()), booleanValues,
+ LockingVisitors.reentrantReadWriteLockVisitor(booleanValues));
+ }
+
+ @Test
+ public void testResultValidation() {
+ final Object hidden = new Object();
+ final StampedLockVisitor<Object> lock = LockingVisitors.stampedLockVisitor(hidden);
+ final Object o1 = lock.applyReadLocked(h -> new Object());
+ assertNotNull(o1);
+ assertNotSame(hidden, o1);
+ final Object o2 = lock.applyWriteLocked(h -> new Object());
+ assertNotNull(o2);
+ assertNotSame(hidden, o2);
+ }
+
+ @Test
+ public void testStampedLockExclusive() throws Exception {
+
+ /*
+ * If our threads are running concurrently, then we expect to be no faster than running one after the other.
+ */
+ final boolean[] booleanValues = new boolean[10];
+ runTest(DELAY, true, millis -> assertTrue(millis >= TOTAL_DELAY.toMillis()), booleanValues,
+ LockingVisitors.stampedLockVisitor(booleanValues));
+ }
+
+ @Test
+ public void testStampedLockNotExclusive() throws Exception {
+
+ /*
+ * If our threads are running concurrently, then we expect to be faster than running one after the other.
+ */
+ final boolean[] booleanValues = new boolean[10];
+ runTest(DELAY, false, millis -> assertTrue(millis < TOTAL_DELAY.toMillis()), booleanValues,
+ LockingVisitors.stampedLockVisitor(booleanValues));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java b/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java
new file mode 100644
index 000000000..1b730dc1e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.easymock.EasyMock;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @since 3.0
+ */
+public class EventListenerSupportTest extends AbstractLangTest {
+
+ @Test
+ public void testAddListenerNoDuplicates() {
+ final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
+
+ final VetoableChangeListener[] listeners = listenerSupport.getListeners();
+ assertEquals(0, listeners.length);
+ assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
+ final VetoableChangeListener[] empty = listeners;
+ //for fun, show that the same empty instance is used
+ assertSame(empty, listenerSupport.getListeners());
+
+ final VetoableChangeListener listener1 = EasyMock.createNiceMock(VetoableChangeListener.class);
+ listenerSupport.addListener(listener1);
+ assertEquals(1, listenerSupport.getListeners().length);
+ listenerSupport.addListener(listener1, false);
+ assertEquals(1, listenerSupport.getListeners().length);
+ listenerSupport.removeListener(listener1);
+ assertSame(empty, listenerSupport.getListeners());
+ }
+
+ @Test
+ public void testAddNullListener() {
+ final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
+ assertThrows(NullPointerException.class, () -> listenerSupport.addListener(null));
+ }
+
+ @Test
+ public void testRemoveNullListener() {
+ final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
+ assertThrows(NullPointerException.class, () -> listenerSupport.removeListener(null));
+ }
+
+ @Test
+ public void testEventDispatchOrder() throws PropertyVetoException {
+ final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
+ final List<VetoableChangeListener> calledListeners = new ArrayList<>();
+
+ final VetoableChangeListener listener1 = createListener(calledListeners);
+ final VetoableChangeListener listener2 = createListener(calledListeners);
+ listenerSupport.addListener(listener1);
+ listenerSupport.addListener(listener2);
+ listenerSupport.fire().vetoableChange(new PropertyChangeEvent(new Date(), "Day", 4, 5));
+ assertEquals(calledListeners.size(), 2);
+ assertSame(calledListeners.get(0), listener1);
+ assertSame(calledListeners.get(1), listener2);
+ }
+
+ @Test
+ public void testCreateWithNonInterfaceParameter() {
+ assertThrows(IllegalArgumentException.class, () -> EventListenerSupport.create(String.class));
+ }
+
+ @Test
+ public void testCreateWithNullParameter() {
+ assertThrows(NullPointerException.class, () -> EventListenerSupport.create(null));
+ }
+
+ @Test
+ public void testRemoveListenerDuringEvent() throws PropertyVetoException {
+ final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
+ for (int i = 0; i < 10; ++i) {
+ addDeregisterListener(listenerSupport);
+ }
+ assertEquals(listenerSupport.getListenerCount(), 10);
+ listenerSupport.fire().vetoableChange(new PropertyChangeEvent(new Date(), "Day", 4, 5));
+ assertEquals(listenerSupport.getListenerCount(), 0);
+ }
+
+ @Test
+ public void testGetListeners() {
+ final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
+
+ final VetoableChangeListener[] listeners = listenerSupport.getListeners();
+ assertEquals(0, listeners.length);
+ assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
+ final VetoableChangeListener[] empty = listeners;
+ //for fun, show that the same empty instance is used
+ assertSame(empty, listenerSupport.getListeners());
+
+ final VetoableChangeListener listener1 = EasyMock.createNiceMock(VetoableChangeListener.class);
+ listenerSupport.addListener(listener1);
+ assertEquals(1, listenerSupport.getListeners().length);
+ final VetoableChangeListener listener2 = EasyMock.createNiceMock(VetoableChangeListener.class);
+ listenerSupport.addListener(listener2);
+ assertEquals(2, listenerSupport.getListeners().length);
+ listenerSupport.removeListener(listener1);
+ assertEquals(1, listenerSupport.getListeners().length);
+ listenerSupport.removeListener(listener2);
+ assertSame(empty, listenerSupport.getListeners());
+ }
+
+ @Test
+ public void testSerialization() throws IOException, ClassNotFoundException, PropertyVetoException {
+ final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
+ listenerSupport.addListener(Function.identity()::apply);
+ listenerSupport.addListener(EasyMock.createNiceMock(VetoableChangeListener.class));
+
+ //serialize:
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {
+ objectOutputStream.writeObject(listenerSupport);
+ }
+
+ //deserialize:
+ @SuppressWarnings("unchecked")
+ final
+ EventListenerSupport<VetoableChangeListener> deserializedListenerSupport = (EventListenerSupport<VetoableChangeListener>) new ObjectInputStream(
+ new ByteArrayInputStream(outputStream.toByteArray())).readObject();
+
+ //make sure we get a listener array back, of the correct component type, and that it contains only the serializable mock
+ final VetoableChangeListener[] listeners = deserializedListenerSupport.getListeners();
+ assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
+ assertEquals(1, listeners.length);
+
+ //now verify that the mock still receives events; we can infer that the proxy was correctly reconstituted
+ final VetoableChangeListener listener = listeners[0];
+ final PropertyChangeEvent evt = new PropertyChangeEvent(new Date(), "Day", 7, 9);
+ listener.vetoableChange(evt);
+ EasyMock.replay(listener);
+ deserializedListenerSupport.fire().vetoableChange(evt);
+ EasyMock.verify(listener);
+
+ //remove listener and verify we get an empty array of listeners
+ deserializedListenerSupport.removeListener(listener);
+ assertEquals(0, deserializedListenerSupport.getListeners().length);
+ }
+
+ @Test
+ public void testSubclassInvocationHandling() throws PropertyVetoException {
+
+ final EventListenerSupport<VetoableChangeListener> eventListenerSupport = new EventListenerSupport<VetoableChangeListener>(
+ VetoableChangeListener.class) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected java.lang.reflect.InvocationHandler createInvocationHandler() {
+ return new ProxyInvocationHandler() {
+ @Override
+ public Object invoke(final Object proxy, final Method method, final Object[] args)
+ throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ return "vetoableChange".equals(method.getName()) && "Hour".equals(((PropertyChangeEvent) args[0]).getPropertyName()) ? null
+ : super.invoke(proxy, method, args);
+ }
+ };
+ }
+ };
+
+ final VetoableChangeListener listener = EasyMock.createNiceMock(VetoableChangeListener.class);
+ eventListenerSupport.addListener(listener);
+ final Object source = new Date();
+ final PropertyChangeEvent ignore = new PropertyChangeEvent(source, "Hour", 5, 6);
+ final PropertyChangeEvent respond = new PropertyChangeEvent(source, "Day", 6, 7);
+ listener.vetoableChange(respond);
+ EasyMock.replay(listener);
+ eventListenerSupport.fire().vetoableChange(ignore);
+ eventListenerSupport.fire().vetoableChange(respond);
+ EasyMock.verify(listener);
+ }
+
+ private void addDeregisterListener(final EventListenerSupport<VetoableChangeListener> listenerSupport) {
+ listenerSupport.addListener(new VetoableChangeListener() {
+ @Override
+ public void vetoableChange(final PropertyChangeEvent e) {
+ listenerSupport.removeListener(this);
+ }
+ });
+ }
+
+ private VetoableChangeListener createListener(final List<VetoableChangeListener> calledListeners) {
+ return new VetoableChangeListener() {
+ @Override
+ public void vetoableChange(final PropertyChangeEvent e) {
+ calledListeners.add(this);
+ }
+ };
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/event/EventUtilsTest.java b/src/test/java/org/apache/commons/lang3/event/EventUtilsTest.java
new file mode 100644
index 000000000..04c110034
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/event/EventUtilsTest.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.VetoableChangeListener;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.Date;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.naming.event.ObjectChangeListener;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @since 3.0
+ */
+public class EventUtilsTest extends AbstractLangTest {
+ @Test
+ public void testConstructor() {
+ assertNotNull(new EventUtils());
+ final Constructor<?>[] cons = EventUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(EventUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(EventUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testAddEventListener() {
+ final PropertyChangeSource src = new PropertyChangeSource();
+ final EventCountingInvocationHandler handler = new EventCountingInvocationHandler();
+ final PropertyChangeListener listener = handler.createListener(PropertyChangeListener.class);
+ assertEquals(0, handler.getEventCount("propertyChange"));
+ EventUtils.addEventListener(src, PropertyChangeListener.class, listener);
+ assertEquals(0, handler.getEventCount("propertyChange"));
+ src.setProperty("newValue");
+ assertEquals(1, handler.getEventCount("propertyChange"));
+ }
+
+ @Test
+ public void testAddEventListenerWithNoAddMethod() {
+ final PropertyChangeSource src = new PropertyChangeSource();
+ final EventCountingInvocationHandler handler = new EventCountingInvocationHandler();
+ final ObjectChangeListener listener = handler.createListener(ObjectChangeListener.class);
+ final IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class, () -> EventUtils.addEventListener(src, ObjectChangeListener.class, listener));
+ assertEquals("Class " + src.getClass().getName() + " does not have a public add" + ObjectChangeListener.class.getSimpleName() + " method which takes a parameter of type " + ObjectChangeListener.class.getName() + ".",
+ e.getMessage());
+ }
+
+ @Test
+ public void testAddEventListenerThrowsException() {
+ final ExceptionEventSource src = new ExceptionEventSource();
+ assertThrows(RuntimeException.class, () ->
+ EventUtils.addEventListener(src, PropertyChangeListener.class, e -> {
+ // Do nothing!
+ })
+ );
+ }
+
+ @Test
+ public void testAddEventListenerWithPrivateAddMethod() {
+ final PropertyChangeSource src = new PropertyChangeSource();
+ final EventCountingInvocationHandler handler = new EventCountingInvocationHandler();
+ final VetoableChangeListener listener = handler.createListener(VetoableChangeListener.class);
+ final IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class, () -> EventUtils.addEventListener(src, VetoableChangeListener.class, listener));
+ assertEquals("Class " + src.getClass().getName() + " does not have a public add" + VetoableChangeListener.class.getSimpleName() + " method which takes a parameter of type " + VetoableChangeListener.class.getName() + ".",
+ e.getMessage());
+ }
+
+ @Test
+ public void testBindEventsToMethod() {
+ final PropertyChangeSource src = new PropertyChangeSource();
+ final EventCounter counter = new EventCounter();
+ EventUtils.bindEventsToMethod(counter, "eventOccurred", src, PropertyChangeListener.class);
+ assertEquals(0, counter.getCount());
+ src.setProperty("newValue");
+ assertEquals(1, counter.getCount());
+ }
+
+
+ @Test
+ public void testBindEventsToMethodWithEvent() {
+ final PropertyChangeSource src = new PropertyChangeSource();
+ final EventCounterWithEvent counter = new EventCounterWithEvent();
+ EventUtils.bindEventsToMethod(counter, "eventOccurred", src, PropertyChangeListener.class);
+ assertEquals(0, counter.getCount());
+ src.setProperty("newValue");
+ assertEquals(1, counter.getCount());
+ }
+
+
+ @Test
+ public void testBindFilteredEventsToMethod() {
+ final MultipleEventSource src = new MultipleEventSource();
+ final EventCounter counter = new EventCounter();
+ EventUtils.bindEventsToMethod(counter, "eventOccurred", src, MultipleEventListener.class, "event1");
+ assertEquals(0, counter.getCount());
+ src.listeners.fire().event1(new PropertyChangeEvent(new Date(), "Day", Integer.valueOf(0), Integer.valueOf(1)));
+ assertEquals(1, counter.getCount());
+ src.listeners.fire().event2(new PropertyChangeEvent(new Date(), "Day", Integer.valueOf(1), Integer.valueOf(2)));
+ assertEquals(1, counter.getCount());
+ }
+
+ public interface MultipleEventListener {
+ void event1(PropertyChangeEvent e);
+
+ void event2(PropertyChangeEvent e);
+ }
+
+ public static class EventCounter {
+ private int count;
+
+ public void eventOccurred() {
+ count++;
+ }
+
+ public int getCount() {
+ return count;
+ }
+ }
+
+ public static class EventCounterWithEvent {
+ private int count;
+
+ public void eventOccurred(final PropertyChangeEvent e) {
+ count++;
+ }
+
+ public int getCount() {
+ return count;
+ }
+ }
+
+
+ private static class EventCountingInvocationHandler implements InvocationHandler {
+ private final Map<String, Integer> eventCounts = new TreeMap<>();
+
+ public <L> L createListener(final Class<L> listenerType) {
+ return listenerType.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
+ new Class[]{listenerType},
+ this));
+ }
+
+ public int getEventCount(final String eventName) {
+ final Integer count = eventCounts.get(eventName);
+ return count == null ? 0 : count.intValue();
+ }
+
+ @Override
+ public Object invoke(final Object proxy, final Method method, final Object[] args) {
+ final Integer count = eventCounts.get(method.getName());
+ if (count == null) {
+ eventCounts.put(method.getName(), Integer.valueOf(1));
+ } else {
+ eventCounts.put(method.getName(), Integer.valueOf(count.intValue() + 1));
+ }
+ return null;
+ }
+ }
+
+ public static class MultipleEventSource {
+ private final EventListenerSupport<MultipleEventListener> listeners = EventListenerSupport.create(MultipleEventListener.class);
+
+ public void addMultipleEventListener(final MultipleEventListener listener) {
+ listeners.addListener(listener);
+ }
+ }
+
+ public static class ExceptionEventSource {
+ public void addPropertyChangeListener(final PropertyChangeListener listener) {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class PropertyChangeSource {
+ private final EventListenerSupport<PropertyChangeListener> listeners = EventListenerSupport.create(PropertyChangeListener.class);
+
+ private String property;
+
+ public void setProperty(final String property) {
+ final String oldValue = this.property;
+ this.property = property;
+ listeners.fire().propertyChange(new PropertyChangeEvent(this, "property", oldValue, property));
+ }
+
+ protected void addVetoableChangeListener(final VetoableChangeListener listener) {
+ // Do nothing!
+ }
+
+ public void addPropertyChangeListener(final PropertyChangeListener listener) {
+ listeners.addListener(listener);
+ }
+
+ public void removePropertyChangeListener(final PropertyChangeListener listener) {
+ listeners.removeListener(listener);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/AbstractExceptionContextTest.java b/src/test/java/org/apache/commons/lang3/exception/AbstractExceptionContextTest.java
new file mode 100644
index 000000000..3fbe50da5
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/AbstractExceptionContextTest.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.exception;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Abstract test of an ExceptionContext implementation.
+ */
+public abstract class AbstractExceptionContextTest<T extends ExceptionContext & Serializable> extends AbstractLangTest {
+
+ protected static final String TEST_MESSAGE_2 = "This is monotonous";
+ protected static final String TEST_MESSAGE = "Test Message";
+ protected T exceptionContext;
+
+ protected static class ObjectWithFaultyToString {
+ @Override
+ public String toString() {
+ throw new RuntimeException("Crap");
+ }
+ }
+
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ exceptionContext
+ .addContextValue("test1", null)
+ .addContextValue("test2", "some value")
+ .addContextValue("test Date", new Date())
+ .addContextValue("test Nbr", Integer.valueOf(5))
+ .addContextValue("test Poorly written obj", new ObjectWithFaultyToString());
+ }
+
+ @Test
+ public void testAddContextValue() {
+ final String message = exceptionContext.getFormattedExceptionMessage(TEST_MESSAGE);
+ assertTrue(message.contains(TEST_MESSAGE));
+ assertTrue(message.contains("test1"));
+ assertTrue(message.contains("test2"));
+ assertTrue(message.contains("test Date"));
+ assertTrue(message.contains("test Nbr"));
+ assertTrue(message.contains("some value"));
+ assertTrue(message.contains("5"));
+
+ assertNull(exceptionContext.getFirstContextValue("test1"));
+ assertEquals("some value", exceptionContext.getFirstContextValue("test2"));
+
+ assertEquals(5, exceptionContext.getContextLabels().size());
+ assertTrue(exceptionContext.getContextLabels().contains("test1"));
+ assertTrue(exceptionContext.getContextLabels().contains("test2"));
+ assertTrue(exceptionContext.getContextLabels().contains("test Date"));
+ assertTrue(exceptionContext.getContextLabels().contains("test Nbr"));
+
+ exceptionContext.addContextValue("test2", "different value");
+ assertEquals(5, exceptionContext.getContextLabels().size());
+ assertTrue(exceptionContext.getContextLabels().contains("test2"));
+
+ final String contextMessage = exceptionContext.getFormattedExceptionMessage(null);
+ assertFalse(contextMessage.contains(TEST_MESSAGE));
+ }
+
+ @Test
+ public void testSetContextValue() {
+ exceptionContext.addContextValue("test2", "different value");
+ exceptionContext.setContextValue("test3", "3");
+
+ final String message = exceptionContext.getFormattedExceptionMessage(TEST_MESSAGE);
+ assertTrue(message.contains(TEST_MESSAGE));
+ assertTrue(message.contains("test Poorly written obj"));
+ assertTrue(message.contains("Crap"));
+
+ assertNull(exceptionContext.getFirstContextValue("crap"));
+ assertTrue(exceptionContext.getFirstContextValue("test Poorly written obj") instanceof ObjectWithFaultyToString);
+
+ assertEquals(7, exceptionContext.getContextEntries().size());
+ assertEquals(6, exceptionContext.getContextLabels().size());
+
+ assertTrue(exceptionContext.getContextLabels().contains("test Poorly written obj"));
+ assertFalse(exceptionContext.getContextLabels().contains("crap"));
+
+ exceptionContext.setContextValue("test Poorly written obj", "replacement");
+
+ assertEquals(7, exceptionContext.getContextEntries().size());
+ assertEquals(6, exceptionContext.getContextLabels().size());
+
+ exceptionContext.setContextValue("test2", "another");
+
+ assertEquals(6, exceptionContext.getContextEntries().size());
+ assertEquals(6, exceptionContext.getContextLabels().size());
+
+ final String contextMessage = exceptionContext.getFormattedExceptionMessage(null);
+ assertFalse(contextMessage.contains(TEST_MESSAGE));
+ }
+
+ @Test
+ public void testGetFirstContextValue() {
+ exceptionContext.addContextValue("test2", "different value");
+
+ assertNull(exceptionContext.getFirstContextValue("test1"));
+ assertEquals("some value", exceptionContext.getFirstContextValue("test2"));
+ assertNull(exceptionContext.getFirstContextValue("crap"));
+
+ exceptionContext.setContextValue("test2", "another");
+
+ assertEquals("another", exceptionContext.getFirstContextValue("test2"));
+ }
+
+ @Test
+ public void testGetContextValues() {
+ exceptionContext.addContextValue("test2", "different value");
+
+ assertEquals(exceptionContext.getContextValues("test1"), Collections.singletonList(null));
+ assertEquals(exceptionContext.getContextValues("test2"), Arrays.asList("some value", "different value"));
+
+ exceptionContext.setContextValue("test2", "another");
+
+ assertEquals("another", exceptionContext.getFirstContextValue("test2"));
+ }
+
+ @Test
+ public void testGetContextLabels() {
+ assertEquals(5, exceptionContext.getContextEntries().size());
+
+ exceptionContext.addContextValue("test2", "different value");
+
+ final Set<String> labels = exceptionContext.getContextLabels();
+ assertEquals(6, exceptionContext.getContextEntries().size());
+ assertEquals(5, labels.size());
+ assertTrue(labels.contains("test1"));
+ assertTrue(labels.contains("test2"));
+ assertTrue(labels.contains("test Date"));
+ assertTrue(labels.contains("test Nbr"));
+ }
+
+ @Test
+ public void testGetContextEntries() {
+ assertEquals(5, exceptionContext.getContextEntries().size());
+
+ exceptionContext.addContextValue("test2", "different value");
+
+ final List<Pair<String, Object>> entries = exceptionContext.getContextEntries();
+ assertEquals(6, entries.size());
+ assertEquals("test1", entries.get(0).getKey());
+ assertEquals("test2", entries.get(1).getKey());
+ assertEquals("test Date", entries.get(2).getKey());
+ assertEquals("test Nbr", entries.get(3).getKey());
+ assertEquals("test Poorly written obj", entries.get(4).getKey());
+ assertEquals("test2", entries.get(5).getKey());
+ }
+
+ @Test
+ public void testJavaSerialization() {
+ exceptionContext.setContextValue("test Poorly written obj", "serializable replacement");
+
+ final T clone = SerializationUtils.deserialize(SerializationUtils.serialize(exceptionContext));
+
+ assertEquals(exceptionContext.getFormattedExceptionMessage(null), clone.getFormattedExceptionMessage(null));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/AbstractExceptionTest.java b/src/test/java/org/apache/commons/lang3/exception/AbstractExceptionTest.java
new file mode 100644
index 000000000..3be21f2fc
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/AbstractExceptionTest.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.exception;
+
+import org.apache.commons.lang3.AbstractLangTest;
+
+/**
+ * Base class for testing {@link Exception} descendants
+ */
+public abstract class AbstractExceptionTest extends AbstractLangTest {
+
+ protected static final String CAUSE_MESSAGE = "Cause message";
+ protected static final String EXCEPTION_MESSAGE = "Exception message";
+
+ protected static final String WRONG_EXCEPTION_MESSAGE = "Wrong exception message";
+ protected static final String WRONG_CAUSE_MESSAGE = "Wrong cause message";
+
+ protected Exception generateCause() {
+ return new Exception(CAUSE_MESSAGE);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/CloneFailedExceptionTest.java b/src/test/java/org/apache/commons/lang3/exception/CloneFailedExceptionTest.java
new file mode 100644
index 000000000..5123fe174
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/CloneFailedExceptionTest.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.exception;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests for {@link CloneFailedExceptionTest}.
+ */
+public class CloneFailedExceptionTest extends AbstractExceptionTest {
+
+ @Test
+ public void testThrowingInformativeException() {
+ assertThrows(CloneFailedException.class, () -> {
+ throw new CloneFailedException(EXCEPTION_MESSAGE, generateCause());
+ });
+ }
+
+ @Test
+ public void testThrowingExceptionWithMessage() {
+ assertThrows(CloneFailedException.class, () -> {
+ throw new CloneFailedException(EXCEPTION_MESSAGE);
+ });
+ }
+
+ @Test
+ public void testThrowingExceptionWithCause() {
+ assertThrows(CloneFailedException.class, () -> {
+ throw new CloneFailedException(generateCause());
+ });
+ }
+
+ @Test
+ public void testWithCauseAndMessage() {
+ final Exception exception = new CloneFailedException(EXCEPTION_MESSAGE, generateCause());
+ assertNotNull(exception);
+ assertEquals(EXCEPTION_MESSAGE, exception.getMessage(), WRONG_EXCEPTION_MESSAGE);
+
+ final Throwable cause = exception.getCause();
+ assertNotNull(cause);
+ assertEquals(CAUSE_MESSAGE, cause.getMessage(), WRONG_CAUSE_MESSAGE);
+ }
+
+ @Test
+ public void testWithoutCause() {
+ final Exception exception = new CloneFailedException(EXCEPTION_MESSAGE);
+ assertNotNull(exception);
+ assertEquals(EXCEPTION_MESSAGE, exception.getMessage(), WRONG_EXCEPTION_MESSAGE);
+
+ final Throwable cause = exception.getCause();
+ assertNull(cause);
+ }
+
+ @Test
+ public void testWithoutMessage() {
+ final Exception exception = new CloneFailedException(generateCause());
+ assertNotNull(exception);
+ assertNotNull(exception.getMessage());
+
+ final Throwable cause = exception.getCause();
+ assertNotNull(cause);
+ assertEquals(CAUSE_MESSAGE, cause.getMessage(), WRONG_CAUSE_MESSAGE);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/ContextedExceptionTest.java b/src/test/java/org/apache/commons/lang3/exception/ContextedExceptionTest.java
new file mode 100644
index 000000000..131ec937c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/ContextedExceptionTest.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.exception;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Date;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests for ContextedException.
+ */
+public class ContextedExceptionTest extends AbstractExceptionContextTest<ContextedException> {
+
+ @BeforeEach
+ @Override
+ public void setUp() throws Exception {
+ exceptionContext = new ContextedException(new Exception(TEST_MESSAGE));
+ super.setUp();
+ }
+
+ @Test
+ public void testContextedException() {
+ exceptionContext = new ContextedException();
+ final String message = exceptionContext.getMessage();
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains("ContextedException"));
+ assertTrue(StringUtils.isEmpty(message));
+ }
+
+ @Test
+ public void testNullException() {
+ assertEquals("", ExceptionUtils.getStackTrace(null), "Empty response.");
+ }
+
+ @Test
+ public void testContextedExceptionString() {
+ exceptionContext = new ContextedException(TEST_MESSAGE);
+ assertEquals(TEST_MESSAGE, exceptionContext.getMessage());
+
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains(TEST_MESSAGE));
+ }
+
+ @Test
+ public void testContextedExceptionThrowable() {
+ exceptionContext = new ContextedException(new Exception(TEST_MESSAGE));
+ final String message = exceptionContext.getMessage();
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains("ContextedException"));
+ assertTrue(trace.contains(TEST_MESSAGE));
+ assertTrue(message.contains(TEST_MESSAGE));
+ }
+
+ @Test
+ public void testContextedExceptionStringThrowable() {
+ exceptionContext = new ContextedException(TEST_MESSAGE_2, new Exception(TEST_MESSAGE));
+ final String message = exceptionContext.getMessage();
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains("ContextedException"));
+ assertTrue(trace.contains(TEST_MESSAGE));
+ assertTrue(trace.contains(TEST_MESSAGE_2));
+ assertTrue(message.contains(TEST_MESSAGE_2));
+ }
+
+ @Test
+ public void testContextedExceptionStringThrowableContext() {
+ exceptionContext = new ContextedException(TEST_MESSAGE_2, new Exception(TEST_MESSAGE), new DefaultExceptionContext());
+ final String message = exceptionContext.getMessage();
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains("ContextedException"));
+ assertTrue(trace.contains(TEST_MESSAGE));
+ assertTrue(trace.contains(TEST_MESSAGE_2));
+ assertTrue(message.contains(TEST_MESSAGE_2));
+ }
+
+ @Test
+ public void testNullExceptionPassing() {
+ exceptionContext = new ContextedException(TEST_MESSAGE_2, new Exception(TEST_MESSAGE), null)
+ .addContextValue("test1", null)
+ .addContextValue("test2", "some value")
+ .addContextValue("test Date", new Date())
+ .addContextValue("test Nbr", Integer.valueOf(5))
+ .addContextValue("test Poorly written obj", new ObjectWithFaultyToString());
+
+ final String message = exceptionContext.getMessage();
+ assertNotNull(message);
+ }
+
+ @Test
+ public void testRawMessage() {
+ assertEquals(Exception.class.getName() + ": " + TEST_MESSAGE, exceptionContext.getRawMessage());
+ exceptionContext = new ContextedException(TEST_MESSAGE_2, new Exception(TEST_MESSAGE), new DefaultExceptionContext());
+ assertEquals(TEST_MESSAGE_2, exceptionContext.getRawMessage());
+ exceptionContext = new ContextedException(null, new Exception(TEST_MESSAGE), new DefaultExceptionContext());
+ assertNull(exceptionContext.getRawMessage());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/ContextedRuntimeExceptionTest.java b/src/test/java/org/apache/commons/lang3/exception/ContextedRuntimeExceptionTest.java
new file mode 100644
index 000000000..76f718c36
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/ContextedRuntimeExceptionTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Date;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests for ContextedRuntimeException.
+ */
+public class ContextedRuntimeExceptionTest extends AbstractExceptionContextTest<ContextedRuntimeException> {
+
+ @BeforeEach
+ @Override
+ public void setUp() throws Exception {
+ exceptionContext = new ContextedRuntimeException(new Exception(TEST_MESSAGE));
+ super.setUp();
+ }
+
+ @Test
+ public void testContextedException() {
+ exceptionContext = new ContextedRuntimeException();
+ final String message = exceptionContext.getMessage();
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains("ContextedException"));
+ assertTrue(StringUtils.isEmpty(message));
+ }
+
+ @Test
+ public void testContextedExceptionString() {
+ exceptionContext = new ContextedRuntimeException(TEST_MESSAGE);
+ assertEquals(TEST_MESSAGE, exceptionContext.getMessage());
+
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains(TEST_MESSAGE));
+ }
+
+ @Test
+ public void testContextedExceptionThrowable() {
+ exceptionContext = new ContextedRuntimeException(new Exception(TEST_MESSAGE));
+ final String message = exceptionContext.getMessage();
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains("ContextedException"));
+ assertTrue(trace.contains(TEST_MESSAGE));
+ assertTrue(message.contains(TEST_MESSAGE));
+ }
+
+ @Test
+ public void testContextedExceptionStringThrowable() {
+ exceptionContext = new ContextedRuntimeException(TEST_MESSAGE_2, new Exception(TEST_MESSAGE));
+ final String message = exceptionContext.getMessage();
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains("ContextedException"));
+ assertTrue(trace.contains(TEST_MESSAGE));
+ assertTrue(trace.contains(TEST_MESSAGE_2));
+ assertTrue(message.contains(TEST_MESSAGE_2));
+ }
+
+ @Test
+ public void testContextedExceptionStringThrowableContext() {
+ // Use an anonymous subclass to make sure users can provide custom implementations
+ exceptionContext = new ContextedRuntimeException(TEST_MESSAGE_2, new Exception(TEST_MESSAGE),
+ new DefaultExceptionContext() {
+ private static final long serialVersionUID = 1L;
+ });
+ final String message = exceptionContext.getMessage();
+ final String trace = ExceptionUtils.getStackTrace(exceptionContext);
+ assertTrue(trace.contains("ContextedException"));
+ assertTrue(trace.contains(TEST_MESSAGE));
+ assertTrue(trace.contains(TEST_MESSAGE_2));
+ assertTrue(message.contains(TEST_MESSAGE_2));
+ }
+
+ @Test
+ public void testNullExceptionPassing() {
+ exceptionContext = new ContextedRuntimeException(TEST_MESSAGE_2, new Exception(TEST_MESSAGE), null)
+ .addContextValue("test1", null)
+ .addContextValue("test2", "some value")
+ .addContextValue("test Date", new Date())
+ .addContextValue("test Nbr", Integer.valueOf(5))
+ .addContextValue("test Poorly written obj", new ObjectWithFaultyToString());
+
+ final String message = exceptionContext.getMessage();
+ assertNotNull(message);
+ }
+
+ @Test
+ public void testRawMessage() {
+ assertEquals(Exception.class.getName() + ": " + TEST_MESSAGE, exceptionContext.getRawMessage());
+ exceptionContext = new ContextedRuntimeException(TEST_MESSAGE_2, new Exception(TEST_MESSAGE), new DefaultExceptionContext());
+ assertEquals(TEST_MESSAGE_2, exceptionContext.getRawMessage());
+ exceptionContext = new ContextedRuntimeException(null, new Exception(TEST_MESSAGE), new DefaultExceptionContext());
+ assertNull(exceptionContext.getRawMessage());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/CustomCheckedException.java b/src/test/java/org/apache/commons/lang3/exception/CustomCheckedException.java
new file mode 100644
index 000000000..fe8b1e67e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/CustomCheckedException.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;
+
+public class CustomCheckedException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public CustomCheckedException() {
+ }
+
+ public CustomCheckedException(final String message) {
+ super(message);
+ }
+
+ public CustomCheckedException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public CustomCheckedException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/CustomUncheckedException.java b/src/test/java/org/apache/commons/lang3/exception/CustomUncheckedException.java
new file mode 100644
index 000000000..f6029f907
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/CustomUncheckedException.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;
+
+public class CustomUncheckedException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public CustomUncheckedException() {
+ }
+
+ public CustomUncheckedException(final String message) {
+ super(message);
+ }
+
+ public CustomUncheckedException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public CustomUncheckedException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/DefaultExceptionContextTest.java b/src/test/java/org/apache/commons/lang3/exception/DefaultExceptionContextTest.java
new file mode 100644
index 000000000..00aa50ba8
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/DefaultExceptionContextTest.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.exception;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests for DefaultExceptionContext.
+ */
+public class DefaultExceptionContextTest extends AbstractExceptionContextTest<DefaultExceptionContext> {
+
+ @Override
+ @BeforeEach
+ public void setUp() throws Exception {
+ exceptionContext = new DefaultExceptionContext();
+ super.setUp();
+ }
+
+ @Test
+ public void testFormattedExceptionMessageNull() {
+ exceptionContext = new DefaultExceptionContext();
+ exceptionContext.getFormattedExceptionMessage(null);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
new file mode 100644
index 000000000..b12d66950
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
@@ -0,0 +1,853 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.test.NotVisibleExceptionFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link org.apache.commons.lang3.exception.ExceptionUtils}.
+ *
+ * @since 1.0
+ */
+public class ExceptionUtilsTest extends AbstractLangTest {
+
+ /**
+ * Provides a method with a well known chained/nested exception
+ * name which matches the full signature (e.g. has a return value
+ * of {@code Throwable}).
+ */
+ private static class ExceptionWithCause extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private Throwable cause;
+
+ ExceptionWithCause(final String str, final Throwable cause) {
+ super(str);
+ setCause(cause);
+ }
+
+ ExceptionWithCause(final Throwable cause) {
+ setCause(cause);
+ }
+
+ @Override
+ public synchronized Throwable getCause() {
+ return cause;
+ }
+
+ public void setCause(final Throwable cause) {
+ this.cause = cause;
+ }
+ }
+
+ /**
+ * Provides a method with a well known chained/nested exception
+ * name which does not match the full signature (e.g. lacks a
+ * return value of {@code Throwable}).
+ */
+ private static class ExceptionWithoutCause extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ @SuppressWarnings("unused")
+ public void getTargetException() {
+ // noop
+ }
+ }
+
+ // Temporary classes to allow the nested exception code to be removed
+ // prior to a rewrite of this test class.
+ private static class NestableException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ @SuppressWarnings("unused")
+ NestableException() {
+ }
+
+ NestableException(final Throwable t) {
+ super(t);
+ }
+ }
+
+ public static class TestThrowable extends Throwable {
+ private static final long serialVersionUID = 1L;
+ }
+
+ private static int redeclareCheckedException() {
+ return throwsCheckedException();
+ }
+
+ private static int throwsCheckedException() {
+ try {
+ throw new IOException();
+ } catch (final Exception e) {
+ return ExceptionUtils.<Integer>rethrow(e);
+ }
+ }
+
+ private NestableException nested;
+
+
+ private Throwable withCause;
+
+ private Throwable withoutCause;
+
+ private Throwable jdkNoCause;
+
+ private ExceptionWithCause cyclicCause;
+
+ private Throwable notVisibleException;
+
+ private Throwable createExceptionWithCause() {
+ try {
+ try {
+ throw new ExceptionWithCause(createExceptionWithoutCause());
+ } catch (final Throwable t) {
+ throw new ExceptionWithCause(t);
+ }
+ } catch (final Throwable t) {
+ return t;
+ }
+ }
+
+ private Throwable createExceptionWithoutCause() {
+ try {
+ throw new ExceptionWithoutCause();
+ } catch (final Throwable t) {
+ return t;
+ }
+ }
+
+ @BeforeEach
+ public void setUp() {
+ withoutCause = createExceptionWithoutCause();
+ nested = new NestableException(withoutCause);
+ withCause = new ExceptionWithCause(nested);
+ jdkNoCause = new NullPointerException();
+ final ExceptionWithCause a = new ExceptionWithCause(null);
+ final ExceptionWithCause b = new ExceptionWithCause(a);
+ a.setCause(b);
+ cyclicCause = new ExceptionWithCause(a);
+ notVisibleException = NotVisibleExceptionFactory.createException(withoutCause);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ withoutCause = null;
+ nested = null;
+ withCause = null;
+ jdkNoCause = null;
+ cyclicCause = null;
+ notVisibleException = null;
+ }
+
+ @Test
+ public void test_getMessage_Throwable() {
+ Throwable th = null;
+ assertEquals("", ExceptionUtils.getMessage(th));
+
+ th = new IllegalArgumentException("Base");
+ assertEquals("IllegalArgumentException: Base", ExceptionUtils.getMessage(th));
+
+ th = new ExceptionWithCause("Wrapper", th);
+ assertEquals("ExceptionUtilsTest.ExceptionWithCause: Wrapper", ExceptionUtils.getMessage(th));
+ }
+
+ @Test
+ public void test_getRootCauseMessage_Throwable() {
+ Throwable th = null;
+ assertEquals("", ExceptionUtils.getRootCauseMessage(th));
+
+ th = new IllegalArgumentException("Base");
+ assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th));
+
+ th = new ExceptionWithCause("Wrapper", th);
+ assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th));
+ }
+
+ @Test
+ public void testCatchTechniques() {
+ IOException ioe = assertThrows(IOException.class, ExceptionUtilsTest::throwsCheckedException);
+ assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
+
+ ioe = assertThrows(IOException.class, ExceptionUtilsTest::redeclareCheckedException);
+ assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new ExceptionUtils());
+ final Constructor<?>[] cons = ExceptionUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(ExceptionUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(ExceptionUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testForEach_jdkNoCause() {
+ final List<Throwable> throwables = new ArrayList<>();
+ ExceptionUtils.forEach(jdkNoCause, throwables::add);
+ assertEquals(1, throwables.size());
+ assertSame(jdkNoCause, throwables.get(0));
+ }
+
+ @Test
+ public void testForEach_nested() {
+ final List<Throwable> throwables = new ArrayList<>();
+ ExceptionUtils.forEach(nested, throwables::add);
+ assertEquals(2, throwables.size());
+ assertSame(nested, throwables.get(0));
+ assertSame(withoutCause, throwables.get(1));
+ }
+
+ @Test
+ public void testForEach_null() {
+ final List<Throwable> throwables = new ArrayList<>();
+ ExceptionUtils.forEach(null, throwables::add);
+ assertEquals(0, throwables.size());
+ }
+
+ @Test
+ public void testForEach_recursiveCause() {
+ final List<Throwable> throwables = new ArrayList<>();
+ ExceptionUtils.forEach(cyclicCause, throwables::add);
+ assertEquals(3, throwables.size());
+ assertSame(cyclicCause, throwables.get(0));
+ assertSame(cyclicCause.getCause(), throwables.get(1));
+ assertSame(cyclicCause.getCause().getCause(), throwables.get(2));
+ }
+
+ @Test
+ public void testForEach_withCause() {
+ final List<Throwable> throwables = new ArrayList<>();
+ ExceptionUtils.forEach(withCause, throwables::add);
+ assertEquals(3, throwables.size());
+ assertSame(withCause, throwables.get(0));
+ assertSame(nested, throwables.get(1));
+ assertSame(withoutCause, throwables.get(2));
+ }
+
+ @Test
+ public void testForEach_withoutCause() {
+ final List<Throwable> throwables = new ArrayList<>();
+ ExceptionUtils.forEach(withoutCause, throwables::add);
+ assertEquals(1, throwables.size());
+ assertSame(withoutCause, throwables.get(0));
+ }
+
+ @SuppressWarnings("deprecation") // Specifically tests the deprecated methods
+ @Test
+ public void testGetCause_Throwable() {
+ assertSame(null, ExceptionUtils.getCause(null));
+ assertSame(null, ExceptionUtils.getCause(withoutCause));
+ assertSame(withoutCause, ExceptionUtils.getCause(nested));
+ assertSame(nested, ExceptionUtils.getCause(withCause));
+ assertSame(null, ExceptionUtils.getCause(jdkNoCause));
+ assertSame(cyclicCause.getCause(), ExceptionUtils.getCause(cyclicCause));
+ assertSame(cyclicCause.getCause().getCause(), ExceptionUtils.getCause(cyclicCause.getCause()));
+ assertSame(cyclicCause.getCause(), ExceptionUtils.getCause(cyclicCause.getCause().getCause()));
+ assertSame(withoutCause, ExceptionUtils.getCause(notVisibleException));
+ }
+
+ @SuppressWarnings("deprecation") // Specifically tests the deprecated methods
+ @Test
+ public void testGetCause_ThrowableArray() {
+ assertSame(null, ExceptionUtils.getCause(null, null));
+ assertSame(null, ExceptionUtils.getCause(null, new String[0]));
+
+ // not known type, so match on supplied method names
+ assertSame(nested, ExceptionUtils.getCause(withCause, null)); // default names
+ assertSame(null, ExceptionUtils.getCause(withCause, new String[0]));
+ assertSame(null, ExceptionUtils.getCause(withCause, new String[]{null}));
+ assertSame(nested, ExceptionUtils.getCause(withCause, new String[]{"getCause"}));
+
+ // not known type, so match on supplied method names
+ assertSame(null, ExceptionUtils.getCause(withoutCause, null));
+ assertSame(null, ExceptionUtils.getCause(withoutCause, new String[0]));
+ assertSame(null, ExceptionUtils.getCause(withoutCause, new String[]{null}));
+ assertSame(null, ExceptionUtils.getCause(withoutCause, new String[]{"getCause"}));
+ assertSame(null, ExceptionUtils.getCause(withoutCause, new String[]{"getTargetException"}));
+ }
+
+ @Test
+ public void testGetRootCause_Throwable() {
+ assertSame(null, ExceptionUtils.getRootCause(null));
+ assertSame(withoutCause, ExceptionUtils.getRootCause(withoutCause));
+ assertSame(withoutCause, ExceptionUtils.getRootCause(nested));
+ assertSame(withoutCause, ExceptionUtils.getRootCause(withCause));
+ assertSame(jdkNoCause, ExceptionUtils.getRootCause(jdkNoCause));
+ assertSame(cyclicCause.getCause().getCause(), ExceptionUtils.getRootCause(cyclicCause));
+ }
+
+ @Test
+ public void testGetRootCauseStackTrace_Throwable() {
+ assertEquals(0, ExceptionUtils.getRootCauseStackTrace(null).length);
+
+ final Throwable cause = createExceptionWithCause();
+ String[] stackTrace = ExceptionUtils.getRootCauseStackTrace(cause);
+ boolean match = false;
+ for (final String element : stackTrace) {
+ if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
+ match = true;
+ break;
+ }
+ }
+ assertTrue(match);
+
+ stackTrace = ExceptionUtils.getRootCauseStackTrace(withoutCause);
+ match = false;
+ for (final String element : stackTrace) {
+ if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
+ match = true;
+ break;
+ }
+ }
+ assertFalse(match);
+ }
+
+ @Test
+ public void testGetRootCauseStackTraceList_Throwable() {
+ assertEquals(0, ExceptionUtils.getRootCauseStackTraceList(null).size());
+
+ final Throwable cause = createExceptionWithCause();
+ List<String> stackTrace = ExceptionUtils.getRootCauseStackTraceList(cause);
+ boolean match = false;
+ for (final String element : stackTrace) {
+ if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
+ match = true;
+ break;
+ }
+ }
+ assertTrue(match);
+
+ stackTrace = ExceptionUtils.getRootCauseStackTraceList(withoutCause);
+ match = false;
+ for (final String element : stackTrace) {
+ if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
+ match = true;
+ break;
+ }
+ }
+ assertFalse(match);
+ }
+
+ @Test
+ @DisplayName("getStackFrames returns empty string array when the argument is null")
+ public void testgetStackFramesHappyPath() {
+ final String[] actual = ExceptionUtils.getStackFrames(new Throwable() {
+ private static final long serialVersionUID = 1L;
+
+ // provide static stack trace to make test stable
+ @Override
+ public void printStackTrace(final PrintWriter s) {
+ s.write("org.apache.commons.lang3.exception.ExceptionUtilsTest$1\n" +
+ "\tat org.apache.commons.lang3.exception.ExceptionUtilsTest.testgetStackFramesGappyPath(ExceptionUtilsTest.java:706)\n" +
+ "\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" +
+ "\tat com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)\n" +
+ "\tat com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)\n");
+ }
+ });
+
+ assertArrayEquals(new String[]{
+ "org.apache.commons.lang3.exception.ExceptionUtilsTest$1",
+ "\tat org.apache.commons.lang3.exception.ExceptionUtilsTest.testgetStackFramesGappyPath(ExceptionUtilsTest.java:706)",
+ "\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
+ "\tat com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)",
+ "\tat com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)"
+ }, actual);
+ }
+
+ @Test
+ @DisplayName("getStackFrames returns the string array of the stack frames when there is a real exception")
+ public void testgetStackFramesNullArg() {
+ final String[] actual = ExceptionUtils.getStackFrames((Throwable) null);
+ assertEquals(0, actual.length);
+ }
+
+ @Test
+ public void testGetThrowableCount_Throwable() {
+ assertEquals(0, ExceptionUtils.getThrowableCount(null));
+ assertEquals(1, ExceptionUtils.getThrowableCount(withoutCause));
+ assertEquals(2, ExceptionUtils.getThrowableCount(nested));
+ assertEquals(3, ExceptionUtils.getThrowableCount(withCause));
+ assertEquals(1, ExceptionUtils.getThrowableCount(jdkNoCause));
+ assertEquals(3, ExceptionUtils.getThrowableCount(cyclicCause));
+ }
+
+ @Test
+ public void testGetThrowableList_Throwable_jdkNoCause() {
+ final List<?> throwables = ExceptionUtils.getThrowableList(jdkNoCause);
+ assertEquals(1, throwables.size());
+ assertSame(jdkNoCause, throwables.get(0));
+ }
+
+ @Test
+ public void testGetThrowableList_Throwable_nested() {
+ final List<?> throwables = ExceptionUtils.getThrowableList(nested);
+ assertEquals(2, throwables.size());
+ assertSame(nested, throwables.get(0));
+ assertSame(withoutCause, throwables.get(1));
+ }
+
+ @Test
+ public void testGetThrowableList_Throwable_null() {
+ final List<?> throwables = ExceptionUtils.getThrowableList(null);
+ assertEquals(0, throwables.size());
+ }
+
+ @Test
+ public void testGetThrowableList_Throwable_recursiveCause() {
+ final List<?> throwables = ExceptionUtils.getThrowableList(cyclicCause);
+ assertEquals(3, throwables.size());
+ assertSame(cyclicCause, throwables.get(0));
+ assertSame(cyclicCause.getCause(), throwables.get(1));
+ assertSame(cyclicCause.getCause().getCause(), throwables.get(2));
+ }
+
+ @Test
+ public void testGetThrowableList_Throwable_withCause() {
+ final List<?> throwables = ExceptionUtils.getThrowableList(withCause);
+ assertEquals(3, throwables.size());
+ assertSame(withCause, throwables.get(0));
+ assertSame(nested, throwables.get(1));
+ assertSame(withoutCause, throwables.get(2));
+ }
+
+ @Test
+ public void testGetThrowableList_Throwable_withoutCause() {
+ final List<?> throwables = ExceptionUtils.getThrowableList(withoutCause);
+ assertEquals(1, throwables.size());
+ assertSame(withoutCause, throwables.get(0));
+ }
+
+ @Test
+ public void testGetThrowables_Throwable_jdkNoCause() {
+ final Throwable[] throwables = ExceptionUtils.getThrowables(jdkNoCause);
+ assertEquals(1, throwables.length);
+ assertSame(jdkNoCause, throwables[0]);
+ }
+
+ @Test
+ public void testGetThrowables_Throwable_nested() {
+ final Throwable[] throwables = ExceptionUtils.getThrowables(nested);
+ assertEquals(2, throwables.length);
+ assertSame(nested, throwables[0]);
+ assertSame(withoutCause, throwables[1]);
+ }
+
+ @Test
+ public void testGetThrowables_Throwable_null() {
+ assertEquals(0, ExceptionUtils.getThrowables(null).length);
+ }
+
+ @Test
+ public void testGetThrowables_Throwable_recursiveCause() {
+ final Throwable[] throwables = ExceptionUtils.getThrowables(cyclicCause);
+ assertEquals(3, throwables.length);
+ assertSame(cyclicCause, throwables[0]);
+ assertSame(cyclicCause.getCause(), throwables[1]);
+ assertSame(cyclicCause.getCause().getCause(), throwables[2]);
+ }
+
+ @Test
+ public void testGetThrowables_Throwable_withCause() {
+ final Throwable[] throwables = ExceptionUtils.getThrowables(withCause);
+ assertEquals(3, throwables.length);
+ assertSame(withCause, throwables[0]);
+ assertSame(nested, throwables[1]);
+ assertSame(withoutCause, throwables[2]);
+ }
+
+ @Test
+ public void testGetThrowables_Throwable_withoutCause() {
+ final Throwable[] throwables = ExceptionUtils.getThrowables(withoutCause);
+ assertEquals(1, throwables.length);
+ assertSame(withoutCause, throwables[0]);
+ }
+
+ @Test
+ public void testIndexOf_ThrowableClass() {
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(null, null));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(null, NestableException.class));
+
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, null));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, ExceptionWithCause.class));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, NestableException.class));
+ assertEquals(0, ExceptionUtils.indexOfThrowable(withoutCause, ExceptionWithoutCause.class));
+
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(nested, null));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(nested, ExceptionWithCause.class));
+ assertEquals(0, ExceptionUtils.indexOfThrowable(nested, NestableException.class));
+ assertEquals(1, ExceptionUtils.indexOfThrowable(nested, ExceptionWithoutCause.class));
+
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, null));
+ assertEquals(0, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class));
+ assertEquals(1, ExceptionUtils.indexOfThrowable(withCause, NestableException.class));
+ assertEquals(2, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithoutCause.class));
+
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Exception.class));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Throwable.class));
+ }
+
+ @Test
+ public void testIndexOf_ThrowableClassInt() {
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(null, null, 0));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(null, NestableException.class, 0));
+
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, null));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, ExceptionWithCause.class, 0));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, NestableException.class, 0));
+ assertEquals(0, ExceptionUtils.indexOfThrowable(withoutCause, ExceptionWithoutCause.class, 0));
+
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(nested, null, 0));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(nested, ExceptionWithCause.class, 0));
+ assertEquals(0, ExceptionUtils.indexOfThrowable(nested, NestableException.class, 0));
+ assertEquals(1, ExceptionUtils.indexOfThrowable(nested, ExceptionWithoutCause.class, 0));
+
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, null));
+ assertEquals(0, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, 0));
+ assertEquals(1, ExceptionUtils.indexOfThrowable(withCause, NestableException.class, 0));
+ assertEquals(2, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithoutCause.class, 0));
+
+ assertEquals(0, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, -1));
+ assertEquals(0, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, 0));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, 1));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, 9));
+
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Exception.class, 0));
+ assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Throwable.class, 0));
+ }
+
+ @Test
+ public void testIndexOfType_ThrowableClass() {
+ assertEquals(-1, ExceptionUtils.indexOfType(null, null));
+ assertEquals(-1, ExceptionUtils.indexOfType(null, NestableException.class));
+
+ assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, null));
+ assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, ExceptionWithCause.class));
+ assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, NestableException.class));
+ assertEquals(0, ExceptionUtils.indexOfType(withoutCause, ExceptionWithoutCause.class));
+
+ assertEquals(-1, ExceptionUtils.indexOfType(nested, null));
+ assertEquals(-1, ExceptionUtils.indexOfType(nested, ExceptionWithCause.class));
+ assertEquals(0, ExceptionUtils.indexOfType(nested, NestableException.class));
+ assertEquals(1, ExceptionUtils.indexOfType(nested, ExceptionWithoutCause.class));
+
+ assertEquals(-1, ExceptionUtils.indexOfType(withCause, null));
+ assertEquals(0, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class));
+ assertEquals(1, ExceptionUtils.indexOfType(withCause, NestableException.class));
+ assertEquals(2, ExceptionUtils.indexOfType(withCause, ExceptionWithoutCause.class));
+
+ assertEquals(0, ExceptionUtils.indexOfType(withCause, Exception.class));
+ assertEquals(0, ExceptionUtils.indexOfType(withCause, Throwable.class));
+ }
+
+ @Test
+ public void testIndexOfType_ThrowableClassInt() {
+ assertEquals(-1, ExceptionUtils.indexOfType(null, null, 0));
+ assertEquals(-1, ExceptionUtils.indexOfType(null, NestableException.class, 0));
+
+ assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, null));
+ assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, ExceptionWithCause.class, 0));
+ assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, NestableException.class, 0));
+ assertEquals(0, ExceptionUtils.indexOfType(withoutCause, ExceptionWithoutCause.class, 0));
+
+ assertEquals(-1, ExceptionUtils.indexOfType(nested, null, 0));
+ assertEquals(-1, ExceptionUtils.indexOfType(nested, ExceptionWithCause.class, 0));
+ assertEquals(0, ExceptionUtils.indexOfType(nested, NestableException.class, 0));
+ assertEquals(1, ExceptionUtils.indexOfType(nested, ExceptionWithoutCause.class, 0));
+
+ assertEquals(-1, ExceptionUtils.indexOfType(withCause, null));
+ assertEquals(0, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, 0));
+ assertEquals(1, ExceptionUtils.indexOfType(withCause, NestableException.class, 0));
+ assertEquals(2, ExceptionUtils.indexOfType(withCause, ExceptionWithoutCause.class, 0));
+
+ assertEquals(0, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, -1));
+ assertEquals(0, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, 0));
+ assertEquals(-1, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, 1));
+ assertEquals(-1, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, 9));
+
+ assertEquals(0, ExceptionUtils.indexOfType(withCause, Exception.class, 0));
+ assertEquals(0, ExceptionUtils.indexOfType(withCause, Throwable.class, 0));
+ }
+
+ @Test
+ public void testPrintRootCauseStackTrace_Throwable() {
+ ExceptionUtils.printRootCauseStackTrace(null);
+ // could pipe system.err to a known stream, but not much point as
+ // internally this method calls stream method anyway
+ }
+
+ @Test
+ public void testPrintRootCauseStackTrace_ThrowableStream() {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
+ ExceptionUtils.printRootCauseStackTrace(null, (PrintStream) null);
+ ExceptionUtils.printRootCauseStackTrace(null, new PrintStream(out));
+ assertEquals(0, out.toString().length());
+
+ out = new ByteArrayOutputStream(1024);
+ assertThrows(
+ NullPointerException.class,
+ () -> ExceptionUtils.printRootCauseStackTrace(withCause, (PrintStream) null));
+
+ out = new ByteArrayOutputStream(1024);
+ final Throwable cause = createExceptionWithCause();
+ ExceptionUtils.printRootCauseStackTrace(cause, new PrintStream(out));
+ String stackTrace = out.toString();
+ assertTrue(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
+
+ out = new ByteArrayOutputStream(1024);
+ ExceptionUtils.printRootCauseStackTrace(withoutCause, new PrintStream(out));
+ stackTrace = out.toString();
+ assertFalse(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
+ }
+
+ @Test
+ public void testPrintRootCauseStackTrace_ThrowableWriter() {
+ StringWriter writer = new StringWriter(1024);
+ ExceptionUtils.printRootCauseStackTrace(null, (PrintWriter) null);
+ ExceptionUtils.printRootCauseStackTrace(null, new PrintWriter(writer));
+ assertEquals(0, writer.getBuffer().length());
+
+ writer = new StringWriter(1024);
+ assertThrows(
+ NullPointerException.class,
+ () -> ExceptionUtils.printRootCauseStackTrace(withCause, (PrintWriter) null));
+
+ writer = new StringWriter(1024);
+ final Throwable cause = createExceptionWithCause();
+ ExceptionUtils.printRootCauseStackTrace(cause, new PrintWriter(writer));
+ String stackTrace = writer.toString();
+ assertTrue(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
+
+ writer = new StringWriter(1024);
+ ExceptionUtils.printRootCauseStackTrace(withoutCause, new PrintWriter(writer));
+ stackTrace = writer.toString();
+ assertFalse(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
+ }
+
+ @Test
+ public void testRemoveCommonFrames_ListList() {
+ assertThrows(NullPointerException.class, () -> ExceptionUtils.removeCommonFrames(null, null));
+ }
+
+ @Test
+ public void testStream_jdkNoCause() {
+ assertEquals(1, ExceptionUtils.stream(jdkNoCause).count());
+ assertSame(jdkNoCause, ExceptionUtils.stream(jdkNoCause).toArray()[0]);
+ }
+
+ @Test
+ public void testStream_nested() {
+ assertEquals(2, ExceptionUtils.stream(nested).count());
+ final Object[] array = ExceptionUtils.stream(nested).toArray();
+ assertSame(nested, array[0]);
+ assertSame(withoutCause, array[1]);
+ }
+
+ @Test
+ public void testStream_null() {
+ assertEquals(0, ExceptionUtils.stream(null).count());
+ }
+
+ @Test
+ public void testStream_recursiveCause() {
+ final List<?> throwables = ExceptionUtils.stream(cyclicCause).collect(Collectors.toList());
+ assertEquals(3, throwables.size());
+ assertSame(cyclicCause, throwables.get(0));
+ assertSame(cyclicCause.getCause(), throwables.get(1));
+ assertSame(cyclicCause.getCause().getCause(), throwables.get(2));
+ }
+
+ @Test
+ public void testStream_withCause() {
+ final List<?> throwables = ExceptionUtils.stream(withCause).collect(Collectors.toList());
+ assertEquals(3, throwables.size());
+ assertSame(withCause, throwables.get(0));
+ assertSame(nested, throwables.get(1));
+ assertSame(withoutCause, throwables.get(2));
+ }
+
+ @Test
+ public void testStream_withoutCause() {
+ final List<?> throwables = ExceptionUtils.stream(withoutCause).collect(Collectors.toList());
+ assertEquals(1, throwables.size());
+ assertSame(withoutCause, throwables.get(0));
+ }
+
+ @Test
+ public void testThrow() {
+ final Exception expected = new InterruptedException();
+ final Exception actual = assertThrows(Exception.class, () -> ExceptionUtils.rethrow(expected));
+ assertSame(expected, actual);
+ }
+
+ @Test
+ public void testThrowableOf_ThrowableClass() {
+ assertNull(ExceptionUtils.throwableOfThrowable(null, null));
+ assertNull(ExceptionUtils.throwableOfThrowable(null, NestableException.class));
+
+ assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, null));
+ assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithCause.class));
+ assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, NestableException.class));
+ assertEquals(withoutCause, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithoutCause.class));
+
+ assertNull(ExceptionUtils.throwableOfThrowable(nested, null));
+ assertNull(ExceptionUtils.throwableOfThrowable(nested, ExceptionWithCause.class));
+ assertEquals(nested, ExceptionUtils.throwableOfThrowable(nested, NestableException.class));
+ assertEquals(nested.getCause(), ExceptionUtils.throwableOfThrowable(nested, ExceptionWithoutCause.class));
+
+ assertNull(ExceptionUtils.throwableOfThrowable(withCause, null));
+ assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class));
+ assertEquals(withCause.getCause(), ExceptionUtils.throwableOfThrowable(withCause, NestableException.class));
+ assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithoutCause.class));
+
+ assertNull(ExceptionUtils.throwableOfThrowable(withCause, Exception.class));
+ assertNull(ExceptionUtils.throwableOfThrowable(withCause, Throwable.class));
+ }
+
+ @Test
+ public void testThrowableOf_ThrowableClassInt() {
+ assertNull(ExceptionUtils.throwableOfThrowable(null, null, 0));
+ assertNull(ExceptionUtils.throwableOfThrowable(null, NestableException.class, 0));
+
+ assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, null));
+ assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithCause.class, 0));
+ assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, NestableException.class, 0));
+ assertEquals(withoutCause, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithoutCause.class, 0));
+
+ assertNull(ExceptionUtils.throwableOfThrowable(nested, null, 0));
+ assertNull(ExceptionUtils.throwableOfThrowable(nested, ExceptionWithCause.class, 0));
+ assertEquals(nested, ExceptionUtils.throwableOfThrowable(nested, NestableException.class, 0));
+ assertEquals(nested.getCause(), ExceptionUtils.throwableOfThrowable(nested, ExceptionWithoutCause.class, 0));
+
+ assertNull(ExceptionUtils.throwableOfThrowable(withCause, null));
+ assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 0));
+ assertEquals(withCause.getCause(), ExceptionUtils.throwableOfThrowable(withCause, NestableException.class, 0));
+ assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithoutCause.class, 0));
+
+ assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, -1));
+ assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 0));
+ assertNull(ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 1));
+ assertNull(ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 9));
+
+ assertNull(ExceptionUtils.throwableOfThrowable(withCause, Exception.class, 0));
+ assertNull(ExceptionUtils.throwableOfThrowable(withCause, Throwable.class, 0));
+ }
+
+ @Test
+ public void testThrowableOfType_ThrowableClass() {
+ assertNull(ExceptionUtils.throwableOfType(null, null));
+ assertNull(ExceptionUtils.throwableOfType(null, NestableException.class));
+
+ assertNull(ExceptionUtils.throwableOfType(withoutCause, null));
+ assertNull(ExceptionUtils.throwableOfType(withoutCause, ExceptionWithCause.class));
+ assertNull(ExceptionUtils.throwableOfType(withoutCause, NestableException.class));
+ assertEquals(withoutCause, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithoutCause.class));
+
+ assertNull(ExceptionUtils.throwableOfType(nested, null));
+ assertNull(ExceptionUtils.throwableOfType(nested, ExceptionWithCause.class));
+ assertEquals(nested, ExceptionUtils.throwableOfType(nested, NestableException.class));
+ assertEquals(nested.getCause(), ExceptionUtils.throwableOfType(nested, ExceptionWithoutCause.class));
+
+ assertNull(ExceptionUtils.throwableOfType(withCause, null));
+ assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class));
+ assertEquals(withCause.getCause(), ExceptionUtils.throwableOfType(withCause, NestableException.class));
+ assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfType(withCause, ExceptionWithoutCause.class));
+
+ assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Exception.class));
+ assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Throwable.class));
+ }
+
+ @Test
+ public void testThrowableOfType_ThrowableClassInt() {
+ assertNull(ExceptionUtils.throwableOfType(null, null, 0));
+ assertNull(ExceptionUtils.throwableOfType(null, NestableException.class, 0));
+
+ assertNull(ExceptionUtils.throwableOfType(withoutCause, null));
+ assertNull(ExceptionUtils.throwableOfType(withoutCause, ExceptionWithCause.class, 0));
+ assertNull(ExceptionUtils.throwableOfType(withoutCause, NestableException.class, 0));
+ assertEquals(withoutCause, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithoutCause.class, 0));
+
+ assertNull(ExceptionUtils.throwableOfType(nested, null, 0));
+ assertNull(ExceptionUtils.throwableOfType(nested, ExceptionWithCause.class, 0));
+ assertEquals(nested, ExceptionUtils.throwableOfType(nested, NestableException.class, 0));
+ assertEquals(nested.getCause(), ExceptionUtils.throwableOfType(nested, ExceptionWithoutCause.class, 0));
+
+ assertNull(ExceptionUtils.throwableOfType(withCause, null));
+ assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 0));
+ assertEquals(withCause.getCause(), ExceptionUtils.throwableOfType(withCause, NestableException.class, 0));
+ assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfType(withCause, ExceptionWithoutCause.class, 0));
+
+ assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, -1));
+ assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 0));
+ assertNull(ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 1));
+ assertNull(ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 9));
+
+ assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Exception.class, 0));
+ assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Throwable.class, 0));
+ }
+
+ @Test
+ public void testWrapAndUnwrapCheckedException() {
+ final Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new IOException()));
+ assertTrue(ExceptionUtils.hasCause(t, IOException.class));
+ }
+
+ @Test
+ public void testWrapAndUnwrapError() {
+ final Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new OutOfMemoryError()));
+ assertTrue(ExceptionUtils.hasCause(t, Error.class));
+ }
+
+ @Test
+ public void testWrapAndUnwrapRuntimeException() {
+ final Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new IllegalArgumentException()));
+ assertTrue(ExceptionUtils.hasCause(t, RuntimeException.class));
+ }
+
+ @Test
+ public void testWrapAndUnwrapThrowable() {
+ final Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new TestThrowable()));
+ assertTrue(ExceptionUtils.hasCause(t, TestThrowable.class));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/UncheckedExceptionTest.java b/src/test/java/org/apache/commons/lang3/exception/UncheckedExceptionTest.java
new file mode 100644
index 000000000..72c38cdd0
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/UncheckedExceptionTest.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.exception;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UncheckedException}.
+ */
+public class UncheckedExceptionTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructWithCause() {
+ final Exception e = new Exception();
+ assertSame(e, new UncheckedException(e).getCause());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/UncheckedIllegalAccessExceptionTest.java b/src/test/java/org/apache/commons/lang3/exception/UncheckedIllegalAccessExceptionTest.java
new file mode 100644
index 000000000..bef674a97
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/UncheckedIllegalAccessExceptionTest.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.exception;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UncheckedIllegalAccessException}.
+ */
+public class UncheckedIllegalAccessExceptionTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructWithCause() {
+ final Exception e = new Exception();
+ assertSame(e, new UncheckedIllegalAccessException(e).getCause());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/UncheckedInterruptedExceptionTest.java b/src/test/java/org/apache/commons/lang3/exception/UncheckedInterruptedExceptionTest.java
new file mode 100644
index 000000000..422996268
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/UncheckedInterruptedExceptionTest.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.exception;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UncheckedInterruptedException}.
+ */
+public class UncheckedInterruptedExceptionTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructWithCause() {
+ final Exception e = new Exception();
+ assertSame(e, new UncheckedInterruptedException(e).getCause());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/exception/UncheckedReflectiveOperationExceptionTest.java b/src/test/java/org/apache/commons/lang3/exception/UncheckedReflectiveOperationExceptionTest.java
new file mode 100644
index 000000000..a9012f222
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/exception/UncheckedReflectiveOperationExceptionTest.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.exception;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UncheckedReflectiveOperationException}.
+ */
+public class UncheckedReflectiveOperationExceptionTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructWithCause() {
+ final Exception e = new Exception();
+ assertSame(e, new UncheckedReflectiveOperationException(e).getCause());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/AnnotationTestFixture.java b/src/test/java/org/apache/commons/lang3/function/AnnotationTestFixture.java
new file mode 100644
index 000000000..a179a1f84
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/AnnotationTestFixture.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.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@interface AnnotationTestFixture {
+ // empty
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/BooleanConsumerTest.java b/src/test/java/org/apache/commons/lang3/function/BooleanConsumerTest.java
new file mode 100644
index 000000000..2a2101fac
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/BooleanConsumerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link BooleanConsumer}.
+ */
+public class BooleanConsumerTest extends AbstractLangTest {
+
+ private BooleanConsumer accept(final BooleanConsumer consumer, final boolean expected) {
+ consumer.accept(expected);
+ return consumer;
+ }
+
+ @Test
+ public void testAccept() {
+ final AtomicBoolean aBool = new AtomicBoolean();
+ accept(aBool::lazySet, true);
+ assertTrue(aBool.get());
+ accept(aBool::lazySet, false);
+ assertFalse(aBool.get());
+ }
+
+ @Test
+ public void testAndThen() throws Throwable {
+ final BooleanConsumer nop = BooleanConsumer.nop();
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> nop.andThen(null));
+
+ final AtomicBoolean aBool1 = new AtomicBoolean();
+ final AtomicBoolean aBool2 = new AtomicBoolean();
+
+ final BooleanConsumer bc = aBool1::lazySet;
+ final BooleanConsumer composite = bc.andThen(aBool2::lazySet);
+
+ composite.accept(true);
+ assertTrue(aBool1.get());
+ assertTrue(aBool2.get());
+
+ composite.accept(false);
+ assertFalse(aBool1.get());
+ assertFalse(aBool2.get());
+
+ // Check order
+ final BooleanConsumer bad = value -> {
+ throw new IllegalStateException();
+ };
+ final BooleanConsumer badComposite = bad.andThen(aBool2::lazySet);
+
+ Assertions.assertThrows(IllegalStateException.class, () -> badComposite.accept(true));
+ assertFalse(aBool2.get(), "Second consumer should not be invoked");
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/ConsumersTest.java b/src/test/java/org/apache/commons/lang3/function/ConsumersTest.java
new file mode 100644
index 000000000..35c7dda7b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/ConsumersTest.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.stream.Stream;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link Consumers}.
+ */
+public class ConsumersTest extends AbstractLangTest {
+
+ /**
+ * Tests {@link Consumers#nop()}.
+ */
+ @Test
+ public void testNop() {
+ Stream.of("").forEach(Consumers.nop());
+ //
+ final Consumer<?> c1 = Consumers.nop();
+ c1.accept(null);
+ final Consumer<Object> c2 = Consumers.nop();
+ c2.accept(null);
+ final Consumer<String> c3 = Consumers.nop();
+ c3.accept(null);
+ //
+ Consumers.nop().accept(null);
+ Consumers.nop().accept("");
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/FailableFunctionsTest.java b/src/test/java/org/apache/commons/lang3/function/FailableFunctionsTest.java
new file mode 100644
index 000000000..37cd3a161
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/FailableFunctionsTest.java
@@ -0,0 +1,2671 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.reflect.UndeclaredThrowableException;
+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 org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests "failable" interfaces defined in this package.
+ */
+public class FailableFunctionsTest extends AbstractLangTest {
+
+ public static class CloseableObject {
+ private boolean closed;
+
+ public void close() {
+ closed = true;
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public void reset() {
+ closed = false;
+ }
+
+ public void run(final Throwable pTh) throws Throwable {
+ if (pTh != null) {
+ throw pTh;
+ }
+ }
+ }
+
+ public static class FailureOnOddInvocations {
+ private static int invocations;
+
+ static boolean failingBool() throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+
+ static boolean testDouble(final double value) throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+
+ static boolean testInt(final int value) throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+
+ static boolean testLong(final long value) throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+
+ private static void throwOnOdd() throws SomeException {
+ final int i = ++invocations;
+ if (i % 2 == 1) {
+ throw new SomeException("Odd Invocation: " + i);
+ }
+ }
+
+ FailureOnOddInvocations() throws SomeException {
+ throwOnOdd();
+ }
+
+ boolean getAsBoolean() throws SomeException {
+ throwOnOdd();
+ return true;
+ }
+ }
+
+ public static class SomeException extends Exception {
+
+ private static final long serialVersionUID = -4965704778119283411L;
+
+ private Throwable t;
+
+ SomeException(final String message) {
+ super(message);
+ }
+
+ public void setThrowable(final Throwable throwable) {
+ t = throwable;
+ }
+
+ public void test() throws Throwable {
+ if (t != null) {
+ throw t;
+ }
+ }
+ }
+
+ public static class Testable<T, P> {
+ private T acceptedObject;
+ private P acceptedPrimitiveObject1;
+ private P acceptedPrimitiveObject2;
+ private Throwable throwable;
+
+ Testable(final Throwable throwable) {
+ this.throwable = throwable;
+ }
+
+ public T getAcceptedObject() {
+ return acceptedObject;
+ }
+
+ public P getAcceptedPrimitiveObject1() {
+ return acceptedPrimitiveObject1;
+ }
+
+ public P getAcceptedPrimitiveObject2() {
+ return acceptedPrimitiveObject2;
+ }
+
+ public void setThrowable(final Throwable throwable) {
+ this.throwable = throwable;
+ }
+
+ public void test() throws Throwable {
+ test(throwable);
+ }
+
+ public Object test(final Object input1, final Object input2) throws Throwable {
+ test(throwable);
+ return acceptedObject;
+ }
+
+ public void test(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ }
+
+ public boolean testAsBooleanPrimitive() throws Throwable {
+ return testAsBooleanPrimitive(throwable);
+ }
+
+ public boolean testAsBooleanPrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return false;
+ }
+
+ public double testAsDoublePrimitive() throws Throwable {
+ return testAsDoublePrimitive(throwable);
+ }
+
+ public double testAsDoublePrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public Integer testAsInteger() throws Throwable {
+ return testAsInteger(throwable);
+ }
+
+ public Integer testAsInteger(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public int testAsIntPrimitive() throws Throwable {
+ return testAsIntPrimitive(throwable);
+ }
+
+ public int testAsIntPrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public long testAsLongPrimitive() throws Throwable {
+ return testAsLongPrimitive(throwable);
+ }
+
+ public long testAsLongPrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public short testAsShortPrimitive() throws Throwable {
+ return testAsShortPrimitive(throwable);
+ }
+
+ public short testAsShortPrimitive(final Throwable throwable) throws Throwable {
+ if (throwable != null) {
+ throw throwable;
+ }
+ return 0;
+ }
+
+ public void testDouble(final double i) throws Throwable {
+ test(throwable);
+ acceptedPrimitiveObject1 = (P) (Double) i;
+ }
+
+ public double testDoubleDouble(final double i, final double j) throws Throwable {
+ test(throwable);
+ acceptedPrimitiveObject1 = (P) (Double) i;
+ acceptedPrimitiveObject2 = (P) (Double) j;
+ return 3d;
+ }
+
+ public void testInt(final int i) throws Throwable {
+ test(throwable);
+ acceptedPrimitiveObject1 = (P) (Integer) i;
+ }
+
+ public void testLong(final long i) throws Throwable {
+ test(throwable);
+ acceptedPrimitiveObject1 = (P) (Long) i;
+ }
+
+ public void testObjDouble(final T object, final double i) throws Throwable {
+ test(throwable);
+ acceptedObject = object;
+ acceptedPrimitiveObject1 = (P) (Double) i;
+ }
+
+ public void testObjInt(final T object, final int i) throws Throwable {
+ test(throwable);
+ acceptedObject = object;
+ acceptedPrimitiveObject1 = (P) (Integer) i;
+ }
+
+ public void testObjLong(final T object, final long i) throws Throwable {
+ test(throwable);
+ acceptedObject = object;
+ acceptedPrimitiveObject1 = (P) (Long) i;
+ }
+ }
+
+ private static final OutOfMemoryError ERROR = new OutOfMemoryError();
+
+ private static final IllegalStateException ILLEGAL_STATE_EXCEPTION = new IllegalStateException();
+
+ @Test
+ public void testAcceptBiConsumer() {
+ final Testable<?, ?> testable = new Testable<>(null);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.accept(Testable::test, testable, ILLEGAL_STATE_EXCEPTION));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.accept(Testable::test, testable, ERROR));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.accept(Testable::test, testable, ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ Failable.accept(Testable::test, testable, (Throwable) null);
+ }
+
+ @Test
+ public void testAcceptConsumer() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Failable.accept(Testable::test, testable));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.accept(Testable::test, testable));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.accept(Testable::test, testable));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ Failable.accept(Testable::test, testable);
+ }
+
+ @Test
+ public void testAcceptDoubleConsumer() {
+ final Testable<?, Double> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Failable.accept(testable::testDouble, 1d));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.accept(testable::testDouble, 1d));
+ assertSame(ERROR, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.accept(testable::testDouble, 1d));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Failable.accept(testable::testDouble, 1d);
+ assertEquals(1, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptIntConsumer() {
+ final Testable<?, Integer> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Failable.accept(testable::testInt, 1));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.accept(testable::testInt, 1));
+ assertSame(ERROR, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.accept(testable::testInt, 1));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Failable.accept(testable::testInt, 1);
+ assertEquals(1, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptLongConsumer() {
+ final Testable<?, Long> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Failable.accept(testable::testLong, 1L));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.accept(testable::testLong, 1L));
+ assertSame(ERROR, e);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.accept(testable::testLong, 1L));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Failable.accept(testable::testLong, 1L);
+ assertEquals(1, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptObjDoubleConsumer() {
+ final Testable<String, Double> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.accept(testable::testObjDouble, "X", 1d));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.accept(testable::testObjDouble, "X", 1d));
+ assertSame(ERROR, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.accept(testable::testObjDouble, "X", 1d));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Failable.accept(testable::testObjDouble, "X", 1d);
+ assertEquals("X", testable.getAcceptedObject());
+ assertEquals(1d, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptObjIntConsumer() {
+ final Testable<String, Integer> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Failable.accept(testable::testObjInt, "X", 1));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.accept(testable::testObjInt, "X", 1));
+ assertSame(ERROR, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.accept(testable::testObjInt, "X", 1));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Failable.accept(testable::testObjInt, "X", 1);
+ assertEquals("X", testable.getAcceptedObject());
+ assertEquals(1, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testAcceptObjLongConsumer() {
+ final Testable<String, Long> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Failable.accept(testable::testObjLong, "X", 1L));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.accept(testable::testObjLong, "X", 1L));
+ assertSame(ERROR, e);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.accept(testable::testObjLong, "X", 1L));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+ assertNull(testable.getAcceptedObject());
+ assertNull(testable.getAcceptedPrimitiveObject1());
+
+ testable.setThrowable(null);
+ Failable.accept(testable::testObjLong, "X", 1L);
+ assertEquals("X", testable.getAcceptedObject());
+ assertEquals(1L, testable.getAcceptedPrimitiveObject1());
+ }
+
+ @Test
+ public void testApplyBiFunction() {
+ final Testable<?, ?> testable = new Testable<>(null);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.apply(Testable::testAsInteger, testable, ILLEGAL_STATE_EXCEPTION));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.apply(Testable::testAsInteger, testable, ERROR));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ e = assertThrows(UncheckedIOException.class, () -> Failable.apply(Testable::testAsInteger, testable, ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ final Integer i = Failable.apply(Testable::testAsInteger, testable, (Throwable) null);
+ assertNotNull(i);
+ assertEquals(0, i.intValue());
+ }
+
+ @Test
+ public void testApplyDoubleBinaryOperator() {
+ final Testable<?, Double> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ final Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.applyAsDouble(testable::testDoubleDouble, 1d, 2d));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ final Testable<?, Double> testable2 = new Testable<>(null);
+ final double i = Failable.applyAsDouble(testable2::testDoubleDouble, 1d, 2d);
+ assertEquals(3d, i);
+ }
+
+ @Test
+ public void testApplyFunction() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.apply(Testable::testAsInteger, testable));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.apply(Testable::testAsInteger, testable));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.apply(Testable::testAsInteger, testable));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ final Integer i = Failable.apply(Testable::testAsInteger, testable);
+ assertNotNull(i);
+ assertEquals(0, i.intValue());
+ }
+
+ @Test
+ public void testAsCallable() {
+ FailureOnOddInvocations.invocations = 0;
+ final FailableCallable<FailureOnOddInvocations, SomeException> failableCallable = FailureOnOddInvocations::new;
+ final Callable<FailureOnOddInvocations> callable = Failable.asCallable(failableCallable);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class, callable::call);
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final FailureOnOddInvocations instance;
+ try {
+ instance = callable.call();
+ } catch (final Exception ex) {
+ throw Failable.rethrow(ex);
+ }
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testAsConsumer() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ final Consumer<Testable<?, ?>> consumer = Failable.asConsumer(Testable::test);
+ Throwable e = assertThrows(IllegalStateException.class, () -> consumer.accept(testable));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> consumer.accept(testable));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> consumer.accept(testable));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ Failable.accept(Testable::test, testable);
+ }
+
+ @Test
+ public void testAsRunnable() {
+ FailureOnOddInvocations.invocations = 0;
+ final Runnable runnable = Failable.asRunnable(FailureOnOddInvocations::new);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class, runnable::run);
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+
+ // Even invocations, should not throw an exception
+ runnable.run();
+ }
+
+ @Test
+ public void testAsSupplier() {
+ FailureOnOddInvocations.invocations = 0;
+ final FailableSupplier<FailureOnOddInvocations, Throwable> failableSupplier = FailureOnOddInvocations::new;
+ final Supplier<FailureOnOddInvocations> supplier = Failable.asSupplier(failableSupplier);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class, supplier::get);
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ assertNotNull(supplier.get());
+ }
+
+ @Test
+ public void testBiConsumer() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableBiConsumer<Testable<?, ?>, Throwable, Throwable> failableBiConsumer = (t, th) -> {
+ t.setThrowable(th);
+ t.test();
+ };
+ final BiConsumer<Testable<?, ?>, Throwable> consumer = Failable.asBiConsumer(failableBiConsumer);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> consumer.accept(testable, ILLEGAL_STATE_EXCEPTION));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ e = assertThrows(OutOfMemoryError.class, () -> consumer.accept(testable, ERROR));
+ assertSame(ERROR, e);
+
+ e = assertThrows(OutOfMemoryError.class, () -> failableBiConsumer.accept(testable, ERROR));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> consumer.accept(testable, ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ consumer.accept(testable, null);
+ }
+
+ @Test
+ public void testBiConsumerAndThen() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableBiConsumer<Testable<?, ?>, Throwable, Throwable> failing = (t, th) -> {
+ t.setThrowable(th);
+ t.test();
+ };
+ final FailableBiConsumer<Testable<?, ?>, Throwable, Throwable> nop = FailableBiConsumer.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.andThen(failing).accept(testable, ERROR));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.andThen(nop).accept(testable, ERROR));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.andThen(null));
+ }
+
+ @Test
+ public void testBiFunction() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ final FailableBiFunction<Testable<?, ?>, Throwable, Integer, Throwable> failableBiFunction = (t, th) -> {
+ t.setThrowable(th);
+ return t.testAsInteger();
+ };
+ final BiFunction<Testable<?, ?>, Throwable, Integer> biFunction = Failable.asBiFunction(failableBiFunction);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> biFunction.apply(testable, ILLEGAL_STATE_EXCEPTION));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> biFunction.apply(testable, ERROR));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> biFunction.apply(testable, ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ assertEquals(0, biFunction.apply(testable, null).intValue());
+ }
+
+ @Test
+ public void testBiFunctionAndThen() throws IOException {
+ // Unchecked usage pattern in JRE
+ final BiFunction<Object, Integer, Integer> nopBiFunction = (t, u) -> null;
+ final Function<Object, Integer> nopFunction = t -> null;
+ nopBiFunction.andThen(nopFunction);
+ // Checked usage pattern
+ final FailableBiFunction<Object, Integer, Integer, IOException> failingBiFunctionTest = (t, u) -> {
+ throw new IOException();
+ };
+ final FailableFunction<Object, Integer, IOException> failingFunction = t -> {
+ throw new IOException();
+ };
+ final FailableBiFunction<Object, Integer, Integer, IOException> nopFailableBiFunction = FailableBiFunction
+ .nop();
+ final FailableFunction<Object, Integer, IOException> nopFailableFunction = FailableFunction.nop();
+ //
+ assertThrows(IOException.class, () -> failingBiFunctionTest.andThen(failingFunction).apply(null, null));
+ assertThrows(IOException.class, () -> failingBiFunctionTest.andThen(nopFailableFunction).apply(null, null));
+ //
+ assertThrows(IOException.class, () -> nopFailableBiFunction.andThen(failingFunction).apply(null, null));
+ nopFailableBiFunction.andThen(nopFailableFunction).apply(null, null);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failingBiFunctionTest.andThen(null));
+ }
+
+ @Test
+ @DisplayName("Test that asPredicate(FailableBiPredicate) is converted to -> BiPredicate ")
+ public void testBiPredicate() {
+ FailureOnOddInvocations.invocations = 0;
+ final FailableBiPredicate<Object, Object, Throwable> failableBiPredicate = (t1, t2) -> FailureOnOddInvocations
+ .failingBool();
+ final BiPredicate<?, ?> predicate = Failable.asBiPredicate(failableBiPredicate);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> predicate.test(null, null));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ assertTrue(predicate.test(null, null));
+ }
+
+ @Test
+ public void testBiPredicateAnd() throws Throwable {
+ assertTrue(FailableBiPredicate.TRUE.and(FailableBiPredicate.TRUE).test(null, null));
+ assertFalse(FailableBiPredicate.TRUE.and(FailableBiPredicate.FALSE).test(null, null));
+ assertFalse(FailableBiPredicate.FALSE.and(FailableBiPredicate.TRUE).test(null, null));
+ assertFalse(FailableBiPredicate.FALSE.and(FailableBiPredicate.FALSE).test(null, null));
+ // null tests
+ assertThrows(NullPointerException.class,
+ () -> assertFalse(FailableBiPredicate.falsePredicate().and(null).test(null, null)));
+ assertThrows(NullPointerException.class,
+ () -> assertTrue(FailableBiPredicate.truePredicate().and(null).test(null, null)));
+ }
+
+ @Test
+ public void testBiPredicateNegate() throws Throwable {
+ assertFalse(FailableBiPredicate.TRUE.negate().test(null, null));
+ assertFalse(FailableBiPredicate.truePredicate().negate().test(null, null));
+ assertTrue(FailableBiPredicate.FALSE.negate().test(null, null));
+ assertTrue(FailableBiPredicate.falsePredicate().negate().test(null, null));
+ }
+
+ @Test
+ public void testBiPredicateOr() throws Throwable {
+ assertTrue(FailableBiPredicate.TRUE.or(FailableBiPredicate.TRUE).test(null, null));
+ assertTrue(FailableBiPredicate.TRUE.or(FailableBiPredicate.FALSE).test(null, null));
+ assertTrue(FailableBiPredicate.FALSE.or(FailableBiPredicate.TRUE).test(null, null));
+ assertFalse(FailableBiPredicate.FALSE.or(FailableBiPredicate.FALSE).test(null, null));
+ // null tests
+ assertThrows(NullPointerException.class,
+ () -> assertFalse(FailableBiPredicate.falsePredicate().or(null).test(null, null)));
+ assertThrows(NullPointerException.class,
+ () -> assertTrue(FailableBiPredicate.truePredicate().or(null).test(null, null)));
+ }
+
+ @Test
+ public void testCallable() {
+ FailureOnOddInvocations.invocations = 0;
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> Failable.run(FailureOnOddInvocations::new));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final FailureOnOddInvocations instance = Failable.call(FailureOnOddInvocations::new);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testConsumerAndThen() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableConsumer<Throwable, Throwable> failableConsumer = th -> {
+ testable.setThrowable(th);
+ testable.test();
+ };
+ final FailableConsumer<Throwable, Throwable> nop = FailableConsumer.nop();
+ final Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.andThen(failableConsumer).accept(ERROR));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failableConsumer.andThen(null));
+ }
+
+ @Test
+ public void testDoubleConsumerAndThen() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableDoubleConsumer<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ };
+ final FailableDoubleConsumer<Throwable> nop = FailableDoubleConsumer.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.andThen(failing).accept(0d));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.andThen(nop).accept(0d));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.andThen(null));
+ }
+
+ @Test
+ public void testDoublePredicate() throws Throwable {
+ FailureOnOddInvocations.invocations = 0;
+ final FailableDoublePredicate<Throwable> failablePredicate = FailureOnOddInvocations::testDouble;
+ assertThrows(SomeException.class, () -> failablePredicate.test(1d));
+ failablePredicate.test(1d);
+ }
+
+ @Test
+ public void testDoublePredicateAnd() throws Throwable {
+ assertTrue(FailableDoublePredicate.TRUE.and(FailableDoublePredicate.TRUE).test(0));
+ assertFalse(FailableDoublePredicate.TRUE.and(FailableDoublePredicate.FALSE).test(0));
+ assertFalse(FailableDoublePredicate.FALSE.and(FailableDoublePredicate.TRUE).test(0));
+ assertFalse(FailableDoublePredicate.FALSE.and(FailableDoublePredicate.FALSE).test(0));
+ // null tests
+ assertThrows(NullPointerException.class,
+ () -> assertFalse(FailableDoublePredicate.falsePredicate().and(null).test(0)));
+ assertThrows(NullPointerException.class,
+ () -> assertTrue(FailableDoublePredicate.truePredicate().and(null).test(0)));
+ }
+
+ @Test
+ public void testDoublePredicateNegate() throws Throwable {
+ assertFalse(FailableDoublePredicate.TRUE.negate().test(0d));
+ assertFalse(FailableDoublePredicate.truePredicate().negate().test(0d));
+ assertTrue(FailableDoublePredicate.FALSE.negate().test(0d));
+ assertTrue(FailableDoublePredicate.falsePredicate().negate().test(0d));
+ }
+
+ @Test
+ public void testDoublePredicateOr() throws Throwable {
+ assertTrue(FailableDoublePredicate.TRUE.or(FailableDoublePredicate.TRUE).test(0));
+ assertTrue(FailableDoublePredicate.TRUE.or(FailableDoublePredicate.FALSE).test(0));
+ assertTrue(FailableDoublePredicate.FALSE.or(FailableDoublePredicate.TRUE).test(0));
+ assertFalse(FailableDoublePredicate.FALSE.or(FailableDoublePredicate.FALSE).test(0));
+ // null tests
+ assertThrows(NullPointerException.class,
+ () -> assertFalse(FailableDoublePredicate.falsePredicate().or(null).test(0)));
+ assertThrows(NullPointerException.class,
+ () -> assertTrue(FailableDoublePredicate.truePredicate().or(null).test(0)));
+ }
+
+ @Test
+ public void testDoubleUnaryOperatorAndThen() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableDoubleUnaryOperator<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ return 0d;
+ };
+ final FailableDoubleUnaryOperator<Throwable> nop = FailableDoubleUnaryOperator.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.andThen(failing).applyAsDouble(0d));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.andThen(nop).applyAsDouble(0d));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.andThen(null));
+ }
+
+ @Test
+ public void testDoubleUnaryOperatorCompose() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableDoubleUnaryOperator<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ return 0d;
+ };
+ final FailableDoubleUnaryOperator<Throwable> nop = FailableDoubleUnaryOperator.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.compose(failing).applyAsDouble(0d));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.compose(nop).applyAsDouble(0d));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.compose(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.compose(null));
+ }
+
+ @Test
+ public void testDoubleUnaryOperatorIdentity() throws Throwable {
+ final FailableDoubleUnaryOperator<Throwable> nop = FailableDoubleUnaryOperator.identity();
+ // Does not throw
+ nop.compose(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> nop.compose(null));
+ }
+
+ @Test
+ public void testFunction() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ final FailableFunction<Throwable, Integer, Throwable> failableFunction = th -> {
+ testable.setThrowable(th);
+ return testable.testAsInteger();
+ };
+ final Function<Throwable, Integer> function = Failable.asFunction(failableFunction);
+ Throwable e = assertThrows(IllegalStateException.class, () -> function.apply(ILLEGAL_STATE_EXCEPTION));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> function.apply(ERROR));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> function.apply(ioe));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ assertEquals(0, function.apply(null).intValue());
+ }
+
+ @Test
+ public void testFunctionAndThen() throws IOException {
+ // Unchecked usage pattern in JRE
+ final Function<Object, Integer> nopFunction = t -> null;
+ nopFunction.andThen(nopFunction);
+ // Checked usage pattern
+ final FailableFunction<Object, Integer, IOException> failingFunction = t -> {
+ throw new IOException();
+ };
+ final FailableFunction<Object, Integer, IOException> nopFailableFunction = FailableFunction.nop();
+ //
+ assertThrows(IOException.class, () -> failingFunction.andThen(failingFunction).apply(null));
+ assertThrows(IOException.class, () -> failingFunction.andThen(nopFailableFunction).apply(null));
+ //
+ assertThrows(IOException.class, () -> nopFailableFunction.andThen(failingFunction).apply(null));
+ nopFailableFunction.andThen(nopFailableFunction).apply(null);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failingFunction.andThen(null));
+ }
+
+ @Test
+ public void testFunctionCompose() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableFunction<Object, Integer, Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ return 0;
+ };
+ final FailableFunction<Object, Integer, Throwable> nop = FailableFunction.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.compose(failing).apply(0));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.compose(nop).apply(0));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.compose(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.compose(null));
+ }
+
+ @Test
+ public void testFunctionIdentity() throws Throwable {
+ final FailableFunction<Integer, Integer, Throwable> nop = FailableFunction.identity();
+ // Does not throw
+ nop.compose(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> nop.compose(null));
+ }
+
+ @Test
+ public void testGetAsBooleanSupplier() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.getAsBoolean(testable::testAsBooleanPrimitive));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.getAsBoolean(testable::testAsBooleanPrimitive));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.getAsBoolean(testable::testAsBooleanPrimitive));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ assertFalse(Failable.getAsBoolean(testable::testAsBooleanPrimitive));
+ }
+
+ @Test
+ public void testGetAsDoubleSupplier() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.getAsDouble(testable::testAsDoublePrimitive));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.getAsDouble(testable::testAsDoublePrimitive));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.getAsDouble(testable::testAsDoublePrimitive));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ assertEquals(0, Failable.getAsDouble(testable::testAsDoublePrimitive));
+ }
+
+ @Test
+ public void testGetAsIntSupplier() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Failable.getAsInt(testable::testAsIntPrimitive));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.getAsInt(testable::testAsIntPrimitive));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.getAsInt(testable::testAsIntPrimitive));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ final int i = Failable.getAsInt(testable::testAsInteger);
+ assertEquals(0, i);
+ }
+
+ @Test
+ public void testGetAsLongSupplier() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.getAsLong(testable::testAsLongPrimitive));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.getAsLong(testable::testAsLongPrimitive));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.getAsLong(testable::testAsLongPrimitive));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ final long i = Failable.getAsLong(testable::testAsLongPrimitive);
+ assertEquals(0, i);
+ }
+
+ @Test
+ public void testGetAsShortSupplier() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.getAsShort(testable::testAsShortPrimitive));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.getAsShort(testable::testAsShortPrimitive));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.getAsShort(testable::testAsShortPrimitive));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ final short i = Failable.getAsShort(testable::testAsShortPrimitive);
+ assertEquals(0, i);
+ }
+
+ @Test
+ public void testGetFromSupplier() {
+ FailureOnOddInvocations.invocations = 0;
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> Failable.run(FailureOnOddInvocations::new));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final FailureOnOddInvocations instance = Failable.call(FailureOnOddInvocations::new);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testGetSupplier() {
+ final Testable<?, ?> testable = new Testable<>(ILLEGAL_STATE_EXCEPTION);
+ Throwable e = assertThrows(IllegalStateException.class, () -> Failable.get(testable::testAsInteger));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ testable.setThrowable(ERROR);
+ e = assertThrows(OutOfMemoryError.class, () -> Failable.get(testable::testAsInteger));
+ assertSame(ERROR, e);
+
+ final IOException ioe = new IOException("Unknown I/O error");
+ testable.setThrowable(ioe);
+ e = assertThrows(UncheckedIOException.class, () -> Failable.get(testable::testAsInteger));
+ final Throwable t = e.getCause();
+ assertNotNull(t);
+ assertSame(ioe, t);
+
+ testable.setThrowable(null);
+ final Integer i = Failable.apply(Testable::testAsInteger, testable);
+ assertNotNull(i);
+ assertEquals(0, i.intValue());
+ }
+
+ @Test
+ public void testIntConsumerAndThen() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableIntConsumer<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ };
+ final FailableIntConsumer<Throwable> nop = FailableIntConsumer.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.andThen(failing).accept(0));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.andThen(nop).accept(0));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.andThen(null));
+ }
+
+ @Test
+ public void testIntPredicate() throws Throwable {
+ FailureOnOddInvocations.invocations = 0;
+ final FailableIntPredicate<Throwable> failablePredicate = FailureOnOddInvocations::testInt;
+ assertThrows(SomeException.class, () -> failablePredicate.test(1));
+ failablePredicate.test(1);
+ }
+
+ @Test
+ public void testIntPredicateAnd() throws Throwable {
+ assertTrue(FailableIntPredicate.TRUE.and(FailableIntPredicate.TRUE).test(0));
+ assertFalse(FailableIntPredicate.TRUE.and(FailableIntPredicate.FALSE).test(0));
+ assertFalse(FailableIntPredicate.FALSE.and(FailableIntPredicate.TRUE).test(0));
+ assertFalse(FailableIntPredicate.FALSE.and(FailableIntPredicate.FALSE).test(0));
+ // null tests
+ assertThrows(NullPointerException.class,
+ () -> assertFalse(FailableIntPredicate.falsePredicate().and(null).test(0)));
+ assertThrows(NullPointerException.class,
+ () -> assertTrue(FailableIntPredicate.truePredicate().and(null).test(0)));
+ }
+
+ @Test
+ public void testIntPredicateNegate() throws Throwable {
+ assertFalse(FailableIntPredicate.TRUE.negate().test(0));
+ assertFalse(FailableIntPredicate.truePredicate().negate().test(0));
+ assertTrue(FailableIntPredicate.FALSE.negate().test(0));
+ assertTrue(FailableIntPredicate.falsePredicate().negate().test(0));
+ }
+
+ @Test
+ public void testIntPredicateOr() throws Throwable {
+ assertTrue(FailableIntPredicate.TRUE.or(FailableIntPredicate.TRUE).test(0));
+ assertTrue(FailableIntPredicate.TRUE.or(FailableIntPredicate.FALSE).test(0));
+ assertTrue(FailableIntPredicate.FALSE.or(FailableIntPredicate.TRUE).test(0));
+ assertFalse(FailableIntPredicate.FALSE.or(FailableIntPredicate.FALSE).test(0));
+ // null tests
+ assertThrows(NullPointerException.class,
+ () -> assertFalse(FailableIntPredicate.falsePredicate().or(null).test(0)));
+ assertThrows(NullPointerException.class,
+ () -> assertTrue(FailableIntPredicate.truePredicate().or(null).test(0)));
+ }
+
+ @Test
+ public void testIntUnaryOperatorAndThen() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableIntUnaryOperator<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ return 0;
+ };
+ final FailableIntUnaryOperator<Throwable> nop = FailableIntUnaryOperator.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.andThen(failing).applyAsInt(0));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.andThen(nop).applyAsInt(0));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.andThen(null));
+ }
+
+ @Test
+ public void testIntUnaryOperatorCompose() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableIntUnaryOperator<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ return 0;
+ };
+ final FailableIntUnaryOperator<Throwable> nop = FailableIntUnaryOperator.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.compose(failing).applyAsInt(0));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.compose(nop).applyAsInt(0));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.compose(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.compose(null));
+ }
+
+ @Test
+ public void testIntUnaryOperatorIdentity() throws Throwable {
+ final FailableIntUnaryOperator<Throwable> nop = FailableIntUnaryOperator.identity();
+ // Does not throw
+ nop.compose(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> nop.compose(null));
+ }
+
+ @Test
+ public void testLongConsumerAndThen() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableLongConsumer<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ };
+ final FailableLongConsumer<Throwable> nop = FailableLongConsumer.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.andThen(failing).accept(0L));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.andThen(nop).accept(0L));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.andThen(null));
+ }
+
+ @Test
+ public void testLongPredicate() throws Throwable {
+ FailureOnOddInvocations.invocations = 0;
+ final FailableLongPredicate<Throwable> failablePredicate = FailureOnOddInvocations::testLong;
+ assertThrows(SomeException.class, () -> failablePredicate.test(1L));
+ failablePredicate.test(1L);
+ }
+
+ @Test
+ public void testLongPredicateAnd() throws Throwable {
+ assertTrue(FailableLongPredicate.TRUE.and(FailableLongPredicate.TRUE).test(0));
+ assertFalse(FailableLongPredicate.TRUE.and(FailableLongPredicate.FALSE).test(0));
+ assertFalse(FailableLongPredicate.FALSE.and(FailableLongPredicate.TRUE).test(0));
+ assertFalse(FailableLongPredicate.FALSE.and(FailableLongPredicate.FALSE).test(0));
+ // null tests
+ assertThrows(NullPointerException.class, () -> assertFalse(FailableLongPredicate.falsePredicate().and(null).test(0)));
+ assertThrows(NullPointerException.class, () -> assertTrue(FailableLongPredicate.truePredicate().and(null).test(0)));
+ }
+
+ @Test
+ public void testLongPredicateNegate() throws Throwable {
+ assertFalse(FailableLongPredicate.TRUE.negate().test(0L));
+ assertFalse(FailableLongPredicate.truePredicate().negate().test(0L));
+ assertTrue(FailableLongPredicate.FALSE.negate().test(0L));
+ assertTrue(FailableLongPredicate.falsePredicate().negate().test(0L));
+ }
+
+ @Test
+ public void testLongPredicateOr() throws Throwable {
+ assertTrue(FailableLongPredicate.TRUE.or(FailableLongPredicate.TRUE).test(0));
+ assertTrue(FailableLongPredicate.TRUE.or(FailableLongPredicate.FALSE).test(0));
+ assertTrue(FailableLongPredicate.FALSE.or(FailableLongPredicate.TRUE).test(0));
+ assertFalse(FailableLongPredicate.FALSE.or(FailableLongPredicate.FALSE).test(0));
+ // null tests
+ assertThrows(NullPointerException.class, () -> assertFalse(FailableLongPredicate.falsePredicate().or(null).test(0)));
+ assertThrows(NullPointerException.class, () -> assertTrue(FailableLongPredicate.truePredicate().or(null).test(0)));
+ }
+
+ @Test
+ public void testLongUnaryOperatorAndThen() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableLongUnaryOperator<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ return 0L;
+ };
+ final FailableLongUnaryOperator<Throwable> nop = FailableLongUnaryOperator.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.andThen(failing).applyAsLong(0L));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.andThen(nop).applyAsLong(0L));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.andThen(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.andThen(null));
+ }
+
+ @Test
+ public void testLongUnaryOperatorCompose() throws Throwable {
+ final Testable<?, ?> testable = new Testable<>(null);
+ final FailableLongUnaryOperator<Throwable> failing = t -> {
+ testable.setThrowable(ERROR);
+ testable.test();
+ return 0L;
+ };
+ final FailableLongUnaryOperator<Throwable> nop = FailableLongUnaryOperator.nop();
+ Throwable e = assertThrows(OutOfMemoryError.class, () -> nop.compose(failing).applyAsLong(0L));
+ assertSame(ERROR, e);
+ e = assertThrows(OutOfMemoryError.class, () -> failing.compose(nop).applyAsLong(0L));
+ assertSame(ERROR, e);
+ // Does not throw
+ nop.compose(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> failing.compose(null));
+ }
+
+ @Test
+ public void testLongUnaryOperatorIdentity() throws Throwable {
+ final FailableLongUnaryOperator<Throwable> nop = FailableLongUnaryOperator.identity();
+ // Does not throw
+ nop.compose(nop);
+ // Documented in Javadoc edge-case.
+ assertThrows(NullPointerException.class, () -> nop.compose(null));
+ }
+
+ @Test
+ @DisplayName("Test that asPredicate(FailablePredicate) is converted to -> Predicate ")
+ public void testPredicate() {
+ FailureOnOddInvocations.invocations = 0;
+ final FailablePredicate<Object, Throwable> failablePredicate = t -> FailureOnOddInvocations.failingBool();
+ final Predicate<?> predicate = Failable.asPredicate(failablePredicate);
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> predicate.test(null));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+ final boolean instance = predicate.test(null);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void testPredicateAnd() throws Throwable {
+ assertTrue(FailablePredicate.TRUE.and(FailablePredicate.TRUE).test(null));
+ assertFalse(FailablePredicate.TRUE.and(FailablePredicate.FALSE).test(null));
+ assertFalse(FailablePredicate.FALSE.and(FailablePredicate.TRUE).test(null));
+ assertFalse(FailablePredicate.FALSE.and(FailablePredicate.FALSE).test(null));
+ // null tests
+ assertThrows(NullPointerException.class, () -> assertFalse(FailablePredicate.FALSE.and(null).test(null)));
+ assertThrows(NullPointerException.class, () -> assertTrue(FailablePredicate.TRUE.and(null).test(null)));
+ }
+
+ @Test
+ public void testPredicateOr() throws Throwable {
+ assertTrue(FailablePredicate.TRUE.or(FailablePredicate.TRUE).test(null));
+ assertTrue(FailablePredicate.TRUE.or(FailablePredicate.FALSE).test(null));
+ assertTrue(FailablePredicate.FALSE.or(FailablePredicate.TRUE).test(null));
+ assertFalse(FailablePredicate.FALSE.or(FailablePredicate.FALSE).test(null));
+ // null tests
+ assertThrows(NullPointerException.class, () -> assertFalse(FailablePredicate.FALSE.or(null).test(null)));
+ assertThrows(NullPointerException.class, () -> assertTrue(FailablePredicate.TRUE.or(null).test(null)));
+ }
+
+ @Test
+ public void testPredicateNegate() throws Throwable {
+ assertFalse(FailablePredicate.TRUE.negate().test(null));
+ assertFalse(FailablePredicate.truePredicate().negate().test(null));
+ assertTrue(FailablePredicate.FALSE.negate().test(null));
+ assertTrue(FailablePredicate.falsePredicate().negate().test(null));
+ }
+
+ @Test
+ public void testRunnable() {
+ FailureOnOddInvocations.invocations = 0;
+ final UndeclaredThrowableException e = assertThrows(UndeclaredThrowableException.class,
+ () -> Failable.run(FailureOnOddInvocations::new));
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof SomeException);
+ assertEquals("Odd Invocation: 1", cause.getMessage());
+
+ // Even invocations, should not throw an exception
+ Failable.run(FailureOnOddInvocations::new);
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableBiConsumer_Object_Throwable() {
+ new FailableBiConsumer<Object, Object, Throwable>() {
+
+ @Override
+ public void accept(final Object object1, final Object object2) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableBiConsumer_String_IOException() {
+ new FailableBiConsumer<String, String, IOException>() {
+
+ @Override
+ public void accept(final String object1, final String object2) throws IOException {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableBiFunction_Object_Throwable() {
+ new FailableBiFunction<Object, Object, Object, Throwable>() {
+
+ @Override
+ public Object apply(final Object input1, final Object input2) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableBiFunction_String_IOException() {
+ new FailableBiFunction<String, String, String, IOException>() {
+
+ @Override
+ public String apply(final String input1, final String input2) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableBiPredicate_Object_Throwable() {
+ new FailableBiPredicate<Object, Object, Throwable>() {
+
+ @Override
+ public boolean test(final Object object1, final Object object2) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableBiPredicate_String_IOException() {
+ new FailableBiPredicate<String, String, IOException>() {
+
+ @Override
+ public boolean test(final String object1, final String object2) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableBooleanSupplier_IOException() {
+ new FailableBooleanSupplier<IOException>() {
+
+ @Override
+ public boolean getAsBoolean() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableBooleanSupplier_Throwable() {
+ new FailableBooleanSupplier<Throwable>() {
+
+ @Override
+ public boolean getAsBoolean() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableCallable_Object_Throwable() {
+ new FailableCallable<Object, Throwable>() {
+
+ @Override
+ public Object call() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableCallable_String_IOException() {
+ new FailableCallable<String, IOException>() {
+
+ @Override
+ public String call() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableConsumer_Object_Throwable() {
+ new FailableConsumer<Object, Throwable>() {
+
+ @Override
+ public void accept(final Object object) throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableConsumer_String_IOException() {
+ new FailableConsumer<String, IOException>() {
+
+ @Override
+ public void accept(final String object) throws IOException {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableDoubleBinaryOperator_IOException() {
+ new FailableDoubleBinaryOperator<IOException>() {
+
+ @Override
+ public double applyAsDouble(final double left, final double right) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableDoubleBinaryOperator_Throwable() {
+ new FailableDoubleBinaryOperator<Throwable>() {
+
+ @Override
+ public double applyAsDouble(final double left, final double right) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableDoubleConsumer_IOException() {
+ new FailableDoubleConsumer<IOException>() {
+
+ @Override
+ public void accept(final double value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableDoubleConsumer_Throwable() {
+ new FailableDoubleConsumer<Throwable>() {
+
+ @Override
+ public void accept(final double value) throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableDoubleFunction_IOException() {
+ new FailableDoubleFunction<String, IOException>() {
+
+ @Override
+ public String apply(final double input) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableDoubleFunction_Throwable() {
+ new FailableDoubleFunction<Object, Throwable>() {
+
+ @Override
+ public Object apply(final double input) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableDoubleSupplier_IOException() {
+ new FailableDoubleSupplier<IOException>() {
+
+ @Override
+ public double getAsDouble() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableDoubleSupplier_Throwable() {
+ new FailableDoubleSupplier<Throwable>() {
+
+ @Override
+ public double getAsDouble() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableDoubleToIntFunction_IOException() {
+ new FailableDoubleToIntFunction<IOException>() {
+
+ @Override
+ public int applyAsInt(final double value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableDoubleToIntFunction_Throwable() {
+ new FailableDoubleToIntFunction<Throwable>() {
+
+ @Override
+ public int applyAsInt(final double value) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableDoubleToLongFunction_IOException() {
+ new FailableDoubleToLongFunction<IOException>() {
+
+ @Override
+ public int applyAsLong(final double value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableDoubleToLongFunction_Throwable() {
+ new FailableDoubleToLongFunction<Throwable>() {
+
+ @Override
+ public int applyAsLong(final double value) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableFunction_Object_Throwable() {
+ new FailableFunction<Object, Object, Throwable>() {
+
+ @Override
+ public Object apply(final Object input) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableFunction_String_IOException() {
+ new FailableFunction<String, String, IOException>() {
+
+ @Override
+ public String apply(final String input) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableIntBinaryOperator_IOException() {
+ new FailableIntBinaryOperator<IOException>() {
+
+ @Override
+ public int applyAsInt(final int left, final int right) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableIntBinaryOperator_Throwable() {
+ new FailableIntBinaryOperator<Throwable>() {
+
+ @Override
+ public int applyAsInt(final int left, final int right) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableIntConsumer_IOException() {
+ new FailableIntConsumer<IOException>() {
+
+ @Override
+ public void accept(final int value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableIntConsumer_Throwable() {
+ new FailableIntConsumer<Throwable>() {
+
+ @Override
+ public void accept(final int value) throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableIntFunction_Object_Throwable() {
+ new FailableIntFunction<Object, Throwable>() {
+
+ @Override
+ public Object apply(final int input) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableIntFunction_String_IOException() {
+ new FailableIntFunction<String, IOException>() {
+
+ @Override
+ public String apply(final int input) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableIntSupplier_IOException() {
+ new FailableIntSupplier<IOException>() {
+
+ @Override
+ public int getAsInt() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableIntSupplier_Throwable() {
+ new FailableIntSupplier<Throwable>() {
+
+ @Override
+ public int getAsInt() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableIntToDoubleFunction_IOException() {
+ new FailableIntToDoubleFunction<IOException>() {
+
+ @Override
+ public double applyAsDouble(final int value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableIntToDoubleFunction_Throwable() {
+ new FailableIntToDoubleFunction<Throwable>() {
+
+ @Override
+ public double applyAsDouble(final int value) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableIntToLongFunction_IOException() {
+ new FailableIntToLongFunction<IOException>() {
+
+ @Override
+ public long applyAsLong(final int value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableIntToLongFunction_Throwable() {
+ new FailableIntToLongFunction<Throwable>() {
+
+ @Override
+ public long applyAsLong(final int value) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableLongBinaryOperator_IOException() {
+ new FailableLongBinaryOperator<IOException>() {
+
+ @Override
+ public long applyAsLong(final long left, final long right) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableLongBinaryOperator_Throwable() {
+ new FailableLongBinaryOperator<Throwable>() {
+
+ @Override
+ public long applyAsLong(final long left, final long right) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableLongConsumer_IOException() {
+ new FailableLongConsumer<IOException>() {
+
+ @Override
+ public void accept(final long object) throws IOException {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableLongConsumer_Throwable() {
+ new FailableLongConsumer<Throwable>() {
+
+ @Override
+ public void accept(final long object) throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableLongFunction_IOException() {
+ new FailableLongFunction<String, IOException>() {
+
+ @Override
+ public String apply(final long input) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableLongFunction_Throwable() {
+ new FailableLongFunction<Object, Throwable>() {
+
+ @Override
+ public Object apply(final long input) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableLongSupplier_IOException() {
+ new FailableLongSupplier<IOException>() {
+
+ @Override
+ public long getAsLong() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableLongSupplier_Throwable() {
+ new FailableLongSupplier<Throwable>() {
+
+ @Override
+ public long getAsLong() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableLongToDoubleFunction_IOException() {
+ new FailableLongToDoubleFunction<IOException>() {
+
+ @Override
+ public double applyAsDouble(final long value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableLongToDoubleFunction_Throwable() {
+ new FailableLongToDoubleFunction<Throwable>() {
+
+ @Override
+ public double applyAsDouble(final long value) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableLongToIntFunction_IOException() {
+ new FailableLongToIntFunction<IOException>() {
+
+ @Override
+ public int applyAsInt(final long value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableLongToIntFunction_Throwable() {
+ new FailableLongToIntFunction<Throwable>() {
+
+ @Override
+ public int applyAsInt(final long value) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableObjDoubleConsumer_Object_Throwable() {
+ new FailableObjDoubleConsumer<Object, Throwable>() {
+
+ @Override
+ public void accept(final Object object, final double value) throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableObjDoubleConsumer_String_IOException() {
+ new FailableObjDoubleConsumer<String, IOException>() {
+
+ @Override
+ public void accept(final String object, final double value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableObjIntConsumer_Object_Throwable() {
+ new FailableObjIntConsumer<Object, Throwable>() {
+
+ @Override
+ public void accept(final Object object, final int value) throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableObjIntConsumer_String_IOException() {
+ new FailableObjIntConsumer<String, IOException>() {
+
+ @Override
+ public void accept(final String object, final int value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableObjLongConsumer_Object_Throwable() {
+ new FailableObjLongConsumer<Object, Throwable>() {
+
+ @Override
+ public void accept(final Object object, final long value) throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableObjLongConsumer_String_IOException() {
+ new FailableObjLongConsumer<String, IOException>() {
+
+ @Override
+ public void accept(final String object, final long value) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailablePredicate_Object_Throwable() {
+ new FailablePredicate<Object, Throwable>() {
+
+ @Override
+ public boolean test(final Object object) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailablePredicate_String_IOException() {
+ new FailablePredicate<String, IOException>() {
+
+ @Override
+ public boolean test(final String object) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableRunnable_IOException() {
+ new FailableRunnable<IOException>() {
+
+ @Override
+ public void run() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableRunnable_Throwable() {
+ new FailableRunnable<Throwable>() {
+
+ @Override
+ public void run() throws Throwable {
+ throw new IOException("test");
+
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableShortSupplier_IOException() {
+ new FailableShortSupplier<IOException>() {
+
+ @Override
+ public short getAsShort() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableShortSupplier_Throwable() {
+ new FailableShortSupplier<Throwable>() {
+
+ @Override
+ public short getAsShort() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableSupplier_Object_Throwable() {
+ new FailableSupplier<Object, Throwable>() {
+
+ @Override
+ public Object get() throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableSupplier_String_IOException() {
+ new FailableSupplier<String, IOException>() {
+
+ @Override
+ public String get() throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableToDoubleBiFunction_Object_Throwable() {
+ new FailableToDoubleBiFunction<Object, Object, Throwable>() {
+
+ @Override
+ public double applyAsDouble(final Object t, final Object u) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableToDoubleBiFunction_String_IOException() {
+ new FailableToDoubleBiFunction<String, String, IOException>() {
+
+ @Override
+ public double applyAsDouble(final String t, final String u) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableToDoubleFunction_Object_Throwable() {
+ new FailableToDoubleFunction<Object, Throwable>() {
+
+ @Override
+ public double applyAsDouble(final Object t) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableToDoubleFunction_String_IOException() {
+ new FailableToDoubleFunction<String, IOException>() {
+
+ @Override
+ public double applyAsDouble(final String t) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableToIntBiFunction_Object_Throwable() {
+ new FailableToIntBiFunction<Object, Object, Throwable>() {
+
+ @Override
+ public int applyAsInt(final Object t, final Object u) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableToIntBiFunction_String_IOException() {
+ new FailableToIntBiFunction<String, String, IOException>() {
+
+ @Override
+ public int applyAsInt(final String t, final String u) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableToIntFunction_Object_Throwable() {
+ new FailableToIntFunction<Object, Throwable>() {
+
+ @Override
+ public int applyAsInt(final Object t) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableToIntFunction_String_IOException() {
+ new FailableToIntFunction<String, IOException>() {
+
+ @Override
+ public int applyAsInt(final String t) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableToLongBiFunction_Object_Throwable() {
+ new FailableToLongBiFunction<Object, Object, Throwable>() {
+
+ @Override
+ public long applyAsLong(final Object t, final Object u) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableToLongBiFunction_String_IOException() {
+ new FailableToLongBiFunction<String, String, IOException>() {
+
+ @Override
+ public long applyAsLong(final String t, final String u) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using the top level generic types
+ * Object and Throwable.
+ */
+ @Test
+ public void testThrows_FailableToLongFunction_Object_Throwable() {
+ new FailableToLongFunction<Object, Throwable>() {
+
+ @Override
+ public long applyAsLong(final Object t) throws Throwable {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ /**
+ * Tests that our failable interface is properly defined to throw any exception using String and IOExceptions as
+ * generic test types.
+ */
+ @Test
+ public void testThrows_FailableToLongFunction_String_IOException() {
+ new FailableToLongFunction<String, IOException>() {
+
+ @Override
+ public long applyAsLong(final String t) throws IOException {
+ throw new IOException("test");
+ }
+ };
+ }
+
+ @Test
+ public void testTryWithResources() {
+ final CloseableObject closeable = new CloseableObject();
+ final FailableConsumer<Throwable, ? extends Throwable> consumer = closeable::run;
+ Throwable e = assertThrows(IllegalStateException.class,
+ () -> Failable.tryWithResources(() -> consumer.accept(ILLEGAL_STATE_EXCEPTION), closeable::close));
+ assertSame(ILLEGAL_STATE_EXCEPTION, e);
+
+ assertTrue(closeable.isClosed());
+ closeable.reset();
+ e = assertThrows(OutOfMemoryError.class,
+ () -> Failable.tryWithResources(() -> consumer.accept(ERROR), closeable::close));
+ assertSame(ERROR, e);
+
+ assertTrue(closeable.isClosed());
+ closeable.reset();
+ final IOException ioe = new IOException("Unknown I/O error");
+ final UncheckedIOException uioe = assertThrows(UncheckedIOException.class,
+ () -> Failable.tryWithResources(() -> consumer.accept(ioe), closeable::close));
+ final IOException cause = uioe.getCause();
+ assertSame(ioe, cause);
+
+ assertTrue(closeable.isClosed());
+ closeable.reset();
+ Failable.tryWithResources(() -> consumer.accept(null), closeable::close);
+ assertTrue(closeable.isClosed());
+ }
+
+ @Test
+ public void testFailableDoubleToIntFunctionNop() throws Throwable {
+ assertEquals(0, FailableDoubleToIntFunction.nop().applyAsInt(Double.MAX_VALUE), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableDoubleToLongFunctionNop() throws Throwable {
+ assertEquals(0, FailableDoubleToLongFunction.nop().applyAsLong(Double.MAX_VALUE), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableIntToDoubleFunctionNop() throws Throwable {
+ assertEquals(0, FailableIntToDoubleFunction.nop().applyAsDouble(Integer.MAX_VALUE), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableIntToLongFunctionNop() throws Throwable {
+ assertEquals(0, FailableIntToLongFunction.nop().applyAsLong(Integer.MAX_VALUE), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableLongToDoubleFunctionNop() throws Throwable {
+ assertEquals(0, FailableLongToDoubleFunction.nop().applyAsDouble(Long.MAX_VALUE), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableLongToIntFunctionNop() throws Throwable {
+ assertEquals(0, FailableLongToIntFunction.nop().applyAsInt(Long.MAX_VALUE), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableToIntFunctionNop() throws Throwable {
+ assertEquals(0, FailableToIntFunction.nop().applyAsInt("Foo"), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableToIntBiFunctionNop() throws Throwable {
+ assertEquals(0, FailableToIntBiFunction.nop().applyAsInt("Foo", "Bar"), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableToLongFunctionNop() throws Throwable {
+ assertEquals(0, FailableToLongFunction.nop().applyAsLong("Foo"), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableToLongBiFunctionNop() throws Throwable {
+ assertEquals(0, FailableToLongBiFunction.nop().applyAsLong("Foo", "Bar"), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableToDoubleFunctionNop() throws Throwable {
+ assertEquals(0, FailableToDoubleFunction.nop().applyAsDouble("Foo"), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableToDoubleBiFunctionNop() throws Throwable {
+ assertEquals(0, FailableToDoubleBiFunction.nop().applyAsDouble("Foo", "Bar"), "Expect NOP to return 0");
+ }
+
+ @Test
+ public void testFailableBiFunctionNop() throws Throwable {
+ assertNull(FailableBiFunction.nop().apply("Foo", "Bar"), "Expect NOP to return null");
+ }
+
+ @Test
+ public void testFailableDoubleFunctionNop() throws Throwable {
+ assertNull(FailableDoubleFunction.nop().apply(Double.MAX_VALUE), "Expect NOP to return null");
+ }
+
+ @Test
+ public void testFailableIntFunctionNop() throws Throwable {
+ assertNull(FailableIntFunction.nop().apply(Integer.MAX_VALUE), "Expect NOP to return null");
+ }
+
+ @Test
+ public void testFailableLongFunctionNop() throws Throwable {
+ assertNull(FailableLongFunction.nop().apply(Long.MAX_VALUE), "Expect NOP to return null");
+ }
+
+ @Test
+ public void testFailableConsumerNop() throws Throwable {
+ // Expect nothing thrown
+ FailableConsumer.nop().accept("Foo");
+ }
+
+ @Test
+ public void testFailableObjDoubleConsumerNop() throws Throwable {
+ // Expect nothing thrown
+ FailableObjDoubleConsumer.nop().accept("Foo", Double.MAX_VALUE);
+ }
+
+ @Test
+ public void testFailableObjIntConsumerNop() throws Throwable {
+ // Expect nothing thrown
+ FailableObjIntConsumer.nop().accept("Foo", Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void testFailableObjLongConsumerNop() throws Throwable {
+ // Expect nothing thrown
+ FailableObjLongConsumer.nop().accept("Foo", Long.MAX_VALUE);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/IntToCharFunctionTest.java b/src/test/java/org/apache/commons/lang3/function/IntToCharFunctionTest.java
new file mode 100644
index 000000000..adbfdca09
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/IntToCharFunctionTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IntToCharFunction}.
+ */
+public class IntToCharFunctionTest extends AbstractLangTest {
+
+ @Test
+ public void test() {
+ final IntToCharFunction func = i -> (char) i;
+ assertEquals('A', func.applyAsChar(65));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodFixtures.java b/src/test/java/org/apache/commons/lang3/function/MethodFixtures.java
new file mode 100644
index 000000000..527154dc0
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodFixtures.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.lang.reflect.Method;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.exception.CustomCheckedException;
+import org.apache.commons.lang3.exception.CustomUncheckedException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+class MethodFixtures extends AbstractLangTest {
+
+ static MethodFixtures INSTANCE = new MethodFixtures();
+
+ static Method getDeclaredMethod(final String name, final Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException {
+ return MethodFixtures.class.getDeclaredMethod(name, parameterTypes);
+ }
+
+ static Method getMethodForGetString() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getString");
+ }
+
+ static Method getMethodForGetString1Arg() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getString1Arg", String.class);
+ }
+
+ static Method getMethodForGetString1ArgChecked() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getString1ArgChecked", String.class);
+ }
+
+ static Method getMethodForGetString1ArgThrowsChecked() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getString1ArgThrowsChecked", String.class);
+ }
+
+ static Method getMethodForGetString1ArgThrowsUnchecked() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getString1ArgThrowsUnchecked", String.class);
+ }
+
+ static Method getMethodForGetString2Arg() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getString2Args", String.class, String.class);
+ }
+
+ static Method getMethodForGetStringChecked() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getStringChecked");
+ }
+
+ static Method getMethodForGetStringsVarArg() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getStringArrayVarStringArgs", String[].class);
+ }
+
+ static Method getMethodForGetStringThrowsChecked() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getStringThrowsChecked");
+ }
+
+ static Method getMethodForGetStringThrowsUnchecked() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("getStringThrowsUnchecked");
+ }
+
+ static Method getMethodForGetStringVarStringArgs() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("geStringtVarStringArgs", String[].class);
+ }
+
+ static Method getMethodForSetString1Arg() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("setValue1", String.class);
+ }
+
+ static Method getMethodForSetString1ArgThrows() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("setValue1Throws", String.class);
+ }
+
+ static Method getMethodForSetString1ArgThrowsChecked() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("setValue1ThrowsChecked", String.class);
+ }
+
+ static Method getMethodForSetString1ArgThrowsUnchecked() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("setValue1ThrowsUnchecked", String.class);
+ }
+
+ static Method getMethodForSetString2Args() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("setValue1And2", String.class, String.class);
+ }
+
+ static Method getMethodForSetStringsVarArg() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("setValueArray", String[].class);
+ }
+
+ static Method getMethodForStaticGetString() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("staticGetString");
+ }
+
+ static Method getMethodForVoidMethod() throws NoSuchMethodException, SecurityException {
+ return getDeclaredMethod("voidMethod");
+ }
+
+ public static String staticGetString() {
+ return "Static.ABC";
+ }
+
+ private String value1;
+
+ private String value2;
+
+ private String[] valueArray;
+
+ @BeforeEach
+ @AfterEach
+ public void clear() {
+ value1 = null;
+ value1 = null;
+ valueArray = null;
+ }
+
+ public String geStringtVarStringArgs(final String... strings) {
+ return "XYZ";
+ }
+
+ @AnnotationTestFixture
+ public String getString() {
+ return "ABC";
+ }
+
+ public String getString1Arg(final String value) {
+ return value;
+ }
+
+ @SuppressWarnings("unused") // IOException is declared but never thrown.
+ public String getString1ArgChecked(final String value) throws IOException {
+ return value;
+ }
+
+ public String getString1ArgThrowsChecked(final String value) throws CustomCheckedException {
+ throw new CustomCheckedException("getString1ArgThrowsChecked");
+ }
+
+ public String getString1ArgThrowsUnchecked(final String value) {
+ throw new CustomUncheckedException("getString1ArgThrowsUnchecked");
+ }
+
+ @AnnotationTestFixture
+ public String getString2() {
+ return "EFG";
+ }
+
+ public String getString2Args(final String value1, final String value2) {
+ return value1 + value2;
+ }
+
+ public String[] getStringArrayVarStringArgs(final String... strings) {
+ return strings;
+ }
+
+ public String getStringChecked() throws Exception {
+ return "ABC";
+ }
+
+ public String getStringThrowsChecked() throws CustomCheckedException {
+ throw new CustomCheckedException("getStringThrowsChecked");
+ }
+
+ public String getStringThrowsUnchecked() {
+ throw new CustomUncheckedException("getStringThrowsUnchecked");
+ }
+
+ String getValue1() {
+ return value1;
+ }
+
+ String getValue2() {
+ return value2;
+ }
+
+ String[] getValueArray() {
+ return valueArray;
+ }
+
+ void setValue1(final String value1) throws Exception {
+ this.value1 = value1;
+ }
+
+ void setValue1And2(final String value1, final String value2) throws Exception {
+ this.value1 = value1;
+ this.value2 = value2;
+ }
+
+ void setValue1ThrowsChecked(final String value1) throws CustomCheckedException {
+ throw new CustomCheckedException("setValue1ThrowsChecked");
+ }
+
+ void setValue1ThrowsUnchecked(final String value1) {
+ throw new CustomUncheckedException("setValue1ThrowsUnchecked");
+ }
+
+ void setValue2(final String value2) {
+ this.value2 = value2;
+ }
+
+ void setValueArray(final String... values) throws Exception {
+ this.valueArray = values;
+ }
+
+ public void voidMethod() {
+ // noop
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodInvokersBiConsumerTest.java b/src/test/java/org/apache/commons/lang3/function/MethodInvokersBiConsumerTest.java
new file mode 100644
index 000000000..9d02c2432
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodInvokersBiConsumerTest.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.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Method;
+import java.util.function.BiConsumer;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link MethodInvokers#asBiConsumer(Method)}.
+ */
+public class MethodInvokersBiConsumerTest extends MethodFixtures {
+
+ @Test
+ public void testApply1Arg() throws NoSuchMethodException, SecurityException {
+ final BiConsumer<Object, Object> biConsumer = MethodInvokers.asBiConsumer(getMethodForSetString1Arg());
+ biConsumer.accept(INSTANCE, "A");
+ assertEquals("A", INSTANCE.getValue1());
+ biConsumer.accept(INSTANCE, "B");
+ assertEquals("B", INSTANCE.getValue1());
+ }
+
+ @Test
+ public void testConstructorForNull() throws SecurityException {
+ assertThrows(NullPointerException.class, () -> MethodInvokers.asBiConsumer(null));
+ }
+
+ @Test
+ public void testToString() throws SecurityException, ReflectiveOperationException {
+ // Should not blow up and must return _something_
+ final BiConsumer<Object, Object> biConsumer = MethodInvokers.asBiConsumer(getMethodForSetString1Arg());
+ assertFalse(biConsumer.toString().isEmpty());
+ assertFalse(biConsumer.toString().isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodInvokersBiFunctionTest.java b/src/test/java/org/apache/commons/lang3/function/MethodInvokersBiFunctionTest.java
new file mode 100644
index 000000000..90c24ce2f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodInvokersBiFunctionTest.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.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Method;
+import java.util.function.BiFunction;
+
+import org.apache.commons.lang3.exception.CustomUncheckedException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link MethodInvokers#asBiFunction(Method)}.
+ */
+public class MethodInvokersBiFunctionTest extends MethodFixtures {
+
+ @Test
+ public void testApply1Arg() throws NoSuchMethodException, SecurityException {
+ final BiFunction<MethodFixtures, String, String> func = MethodInvokers.asBiFunction(getMethodForGetString1Arg());
+ assertEquals(INSTANCE.getString1Arg("A"), func.apply(INSTANCE, "A"));
+ }
+
+ @Test
+ public void testApply1ArgThrowsUnchecked() throws NoSuchMethodException, SecurityException {
+ final BiFunction<MethodFixtures, String, String> func = MethodInvokers.asBiFunction(getMethodForGetString1ArgThrowsUnchecked());
+ assertThrows(CustomUncheckedException.class, () -> func.apply(INSTANCE, "A"));
+ }
+
+ @Test
+ public void testConstructorForNull() throws SecurityException {
+ assertThrows(NullPointerException.class, () -> MethodInvokers.asBiFunction(null));
+ }
+
+ @Test
+ public void testFullExample() throws SecurityException, ReflectiveOperationException {
+ final Method method = String.class.getMethod("charAt", int.class);
+ final BiFunction<String, Integer, Character> function = MethodInvokers.asBiFunction(method);
+ assertEquals('C', function.apply("ABC", 2));
+ }
+
+ @Test
+ public void testToString() throws SecurityException, ReflectiveOperationException {
+ // Should not blow up and must return _something_
+ assertFalse(MethodInvokers.asBiFunction(getMethodForGetString1Arg()).toString().isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableBiConsumerTest.java b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableBiConsumerTest.java
new file mode 100644
index 000000000..4d369d993
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableBiConsumerTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Method;
+
+import org.apache.commons.lang3.exception.CustomCheckedException;
+import org.apache.commons.lang3.exception.CustomUncheckedException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link MethodInvokers#asFailableBiConsumer(Method)}.
+ */
+public class MethodInvokersFailableBiConsumerTest extends MethodFixtures {
+
+ @Test
+ public void testApply1Arg() throws Throwable {
+ MethodInvokers.asFailableBiConsumer(getMethodForSetString1Arg()).accept(INSTANCE, "A");
+ assertEquals("A", INSTANCE.getValue1());
+ }
+
+ @Test
+ public void testApply1ArgThrowsChecked() throws Exception {
+ assertThrows(CustomCheckedException.class, () -> MethodInvokers.asFailableBiConsumer(getMethodForSetString1ArgThrowsChecked()).accept(INSTANCE, "A"));
+ }
+
+ @Test
+ public void testApply1ArgThrowsUnchecked() throws Exception {
+ assertThrows(CustomUncheckedException.class, () -> MethodInvokers.asFailableBiConsumer(getMethodForSetString1ArgThrowsUnchecked()).accept(INSTANCE, "A"));
+ }
+
+ @Test
+ public void testConstructorForNull() throws Exception {
+ assertThrows(NullPointerException.class, () -> MethodInvokers.asFailableBiConsumer(null));
+ }
+
+ @Test
+ public void testToString() throws SecurityException, ReflectiveOperationException {
+ // Should not blow up and must return _something_
+ assertFalse(MethodInvokers.asFailableBiConsumer(getMethodForSetString1Arg()).toString().isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableBiFunctionTest.java b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableBiFunctionTest.java
new file mode 100644
index 000000000..4b768cb5a
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableBiFunctionTest.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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Method;
+
+import org.apache.commons.lang3.exception.CustomCheckedException;
+import org.apache.commons.lang3.exception.CustomUncheckedException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link MethodInvokers#asFailableBiFunction(Method)}.
+ */
+public class MethodInvokersFailableBiFunctionTest extends MethodFixtures {
+
+ @Test
+ public void testApply1Arg() throws Throwable {
+ // Use a local variable typed to the interface to make sure we compile.
+ final FailableBiFunction<MethodFixtures, String, String[], Throwable> func = MethodInvokers.asFailableBiFunction(getMethodForGetString1ArgChecked());
+ assertEquals(INSTANCE.getString1ArgChecked("A"), func.apply(INSTANCE, "A"));
+ }
+
+ @Test
+ public void testApply1ArgThrowsChecked() throws NoSuchMethodException, SecurityException {
+ // Use a local variable typed to the interface to make sure we compile.
+ final FailableBiFunction<MethodFixtures, String, String[], Throwable> func = MethodInvokers
+ .asFailableBiFunction(getMethodForGetString1ArgThrowsChecked());
+ assertThrows(CustomCheckedException.class, () -> func.apply(INSTANCE, "A"));
+ }
+
+ @Test
+ public void testApply1ArgThrowsUnchecked() throws NoSuchMethodException, SecurityException {
+ // Use a local variable typed to the interface to make sure we compile.
+ final FailableBiFunction<MethodFixtures, String, String[], Throwable> func = MethodInvokers
+ .asFailableBiFunction(getMethodForGetString1ArgThrowsUnchecked());
+ assertThrows(CustomUncheckedException.class, () -> func.apply(INSTANCE, "A"));
+ }
+
+ @Test
+ public void testConstructorForNull() throws SecurityException {
+ assertThrows(NullPointerException.class, () -> MethodInvokers.asFailableBiFunction(null));
+ }
+
+ @Test
+ public void testToString() throws SecurityException, Throwable {
+ // Should not blow up and must return _something_
+ assertFalse(MethodInvokers.asFailableBiFunction(getMethodForGetString1ArgChecked()).toString().isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableFunctionTest.java b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableFunctionTest.java
new file mode 100644
index 000000000..8e3ddf2ab
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableFunctionTest.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.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.exception.UncheckedException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link MethodInvokers#asFailableFunction(Method)}.
+ */
+public class MethodInvokersFailableFunctionTest extends MethodFixtures {
+
+ @Test
+ public void testApply0Arg() throws Throwable {
+ assertEquals(INSTANCE.getString(), MethodInvokers.asFailableFunction(getMethodForGetString()).apply(INSTANCE));
+ }
+
+ @Test
+ public void testConstructorForNull() throws SecurityException {
+ assertThrows(NullPointerException.class, () -> MethodInvokers.asFailableFunction((Method) null));
+ }
+
+ @Test
+ public void testBuildVarArg() throws SecurityException, NoSuchMethodException {
+ MethodInvokers.asFailableFunction(getMethodForGetStringVarStringArgs());
+ }
+
+ @Test
+ public void testFindAndInvoke() throws SecurityException {
+ // Finding
+ final List<FailableFunction<Object, Object, Throwable>> invokers = Stream.of(MethodFixtures.class.getDeclaredMethods())
+ .filter(m -> m.isAnnotationPresent(AnnotationTestFixture.class)).map(MethodInvokers::asFailableFunction).collect(Collectors.toList());
+ assertEquals(2, invokers.size());
+ // ...
+ // Invoking
+ final Set<Object> set = invokers.stream().map(i -> {
+ try {
+ return i.apply(MethodFixtures.INSTANCE);
+ } catch (final Throwable e) {
+ throw new UncheckedException(e);
+ }
+ }).collect(Collectors.toSet());
+ assertEquals(new HashSet<>(Arrays.asList(INSTANCE.getString(), INSTANCE.getString2())), set);
+ }
+
+ @Test
+ public void testThrowsChecked() throws Exception {
+ assertThrows(Exception.class, () -> MethodInvokers.asFailableFunction(getMethodForGetStringThrowsChecked()).apply(INSTANCE));
+ }
+
+ @Test
+ public void testToString() throws SecurityException, ReflectiveOperationException {
+ // Should not blow up and must return _something_
+ assertFalse(MethodInvokers.asFailableFunction(getMethodForGetString()).toString().isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableSupplierTest.java b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableSupplierTest.java
new file mode 100644
index 000000000..62ccc602e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFailableSupplierTest.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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.lang.reflect.Method;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link MethodInvokers#asFailableSupplier(Method)}.
+ */
+public class MethodInvokersFailableSupplierTest extends MethodFixtures {
+
+ @Test
+ public void testSupplierStatic() throws Throwable {
+ assertEquals(staticGetString(), MethodInvokers.asFailableSupplier(getMethodForStaticGetString()).get());
+ assertEquals(staticGetString(), MethodInvokers.asFailableSupplier(getMethodForStaticGetString()).get());
+ }
+
+ @Test
+ public void testSupplierToString() throws SecurityException, ReflectiveOperationException {
+ // Should not blow up and must return _something_
+ assertFalse(MethodInvokers.asFailableSupplier(getMethodForStaticGetString()).toString().isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodInvokersFunctionTest.java b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFunctionTest.java
new file mode 100644
index 000000000..eb556f93c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodInvokersFunctionTest.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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.exception.CustomUncheckedException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link MethodInvokers#asFunction(Method)}.
+ */
+public class MethodInvokersFunctionTest extends MethodFixtures {
+
+ @Test
+ public void testApply0Arg() throws NoSuchMethodException, SecurityException {
+ final Function<MethodFixtures, String> func = MethodInvokers.asFunction(getMethodForGetString());
+ assertEquals(INSTANCE.getString(), func.apply(INSTANCE));
+ }
+
+ @Test
+ public void testApply0ArgThrowsUnchecked() throws NoSuchMethodException, SecurityException {
+ final Function<MethodFixtures, String> func = MethodInvokers.asFunction(getMethodForGetStringThrowsUnchecked());
+ assertThrows(CustomUncheckedException.class, () -> func.apply(INSTANCE));
+ }
+
+ @Test
+ public void testBuildVarArg() throws SecurityException, NoSuchMethodException {
+ MethodInvokers.asFunction(getMethodForGetStringVarStringArgs());
+ }
+
+ @Test
+ public void testConstructorForNull() throws SecurityException {
+ assertThrows(NullPointerException.class, () -> MethodInvokers.asFunction(null));
+ }
+
+ @Test
+ public void testFindAndInvoke() throws SecurityException {
+ // Finding
+ final List<Function<Object, Object>> invokers = Stream.of(MethodFixtures.class.getDeclaredMethods())
+ .filter(m -> m.isAnnotationPresent(AnnotationTestFixture.class)).map(MethodInvokers::asFunction).collect(Collectors.toList());
+ assertEquals(2, invokers.size());
+ // ...
+ // Invoking
+ final Set<Object> set1 = invokers.stream().map(i -> i.apply(MethodFixtures.INSTANCE)).collect(Collectors.toSet());
+ assertEquals(new HashSet<>(Arrays.asList(INSTANCE.getString(), INSTANCE.getString2())), set1);
+ final Set<Object> set2 = Stream.of(INSTANCE).map(invokers.get(0)).collect(Collectors.toSet());
+ final Set<Object> set3 = Stream.of(INSTANCE).map(invokers.get(1)).collect(Collectors.toSet());
+ set2.addAll(set3);
+ assertEquals(new HashSet<>(Arrays.asList(INSTANCE.getString(), INSTANCE.getString2())), set2);
+ }
+
+ @Test
+ public void testFullExample() throws SecurityException, ReflectiveOperationException {
+ final Method method = String.class.getMethod("length");
+ final Function<String, Integer> function = MethodInvokers.asFunction(method);
+ assertEquals(3, function.apply("ABC"));
+ }
+
+ @Test
+ public void testMapComputeIfAbsent() throws NoSuchMethodException, SecurityException {
+ final Map<MethodFixtures, String> map = new HashMap<>();
+ map.computeIfAbsent(INSTANCE, MethodInvokers.asFunction(getMethodForGetString()));
+ assertEquals(INSTANCE.getString(), map.get(INSTANCE));
+ }
+
+ @Test
+ public void testToString() throws SecurityException, ReflectiveOperationException {
+ // Should not blow up and must return _something_
+ assertFalse(MethodInvokers.asFunction(getMethodForGetString()).toString().isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/MethodInvokersSupplierTest.java b/src/test/java/org/apache/commons/lang3/function/MethodInvokersSupplierTest.java
new file mode 100644
index 000000000..99ba0673e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/MethodInvokersSupplierTest.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.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link MethodInvokers#asSupplier(Method)}.
+ */
+public class MethodInvokersSupplierTest extends MethodFixtures {
+
+ @Test
+ public void testSupplierStaticGetMethod() throws NoSuchMethodException, SecurityException {
+ final Supplier<String> supplier = MethodInvokers.asSupplier(getMethodForStaticGetString());
+ assertEquals(staticGetString(), supplier.get());
+ assertEquals(staticGetString(), supplier.get());
+ }
+
+ @Test
+ public void testSupplierStaticGetMethodToString() throws SecurityException, ReflectiveOperationException {
+ // Should not blow up and must return _something_
+ final Supplier<Object> supplier = MethodInvokers.asSupplier(getMethodForStaticGetString());
+ assertFalse(supplier.toString().isEmpty());
+ assertFalse(supplier.toString().isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/Objects.java b/src/test/java/org/apache/commons/lang3/function/Objects.java
new file mode 100755
index 000000000..f07a0edbd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/Objects.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * This class provides some replacements for the corresponding methods in
+ * {@link java.util.Objects}. The replacements have the advantage, that they are properly
+ * annotated with {@link Nullable}, and/or {@link Nonnull}, so they let the
+ * compiler know what their respective results are.
+ *
+ * The various {@code requireNonNull} methods are particularly handy, when
+ * dealing with external code, that a) doesn't support the {@link Nonnull}
+ * annotation, or if you know for other reasons, that an object is non-null.
+ * Take for example, a {@link java.util.Map map}, that you have filled with
+ * non-null values. So, in your opinion, the following should be perfectly
+ * valid code:
+ * <pre>
+ * final Map&lt;String,Object&gt; map = getMapOfNonNullValues();
+ * final @Nonnull Object o = map.get("SomeKey");
+ * </pre>
+ * However, if your Java compiler *does* null analysis, it will reject this
+ * example as invalid, because {@link java.util.Map#get(Object)} might return
+ * a null value. As a workaround, you can use this:
+ * <pre>
+ * import static org.apache.commons.lang3.function.Objects.requireNonNull;
+ *
+ * final Map&lt;String,Object&gt; map = getMapOfNonNullValues();
+ * final @Nonnull Object o = requireNonNull(map.get("SomeKey"));
+ * </pre>
+ *
+ * This class is somewhat redundant with regards to {@link ObjectUtils}.
+ * For example, {@link #requireNonNull(Object, Object)} is almost equivalent
+ * with {@link ObjectUtils#defaultIfNull(Object, Object)}. However, it isn't
+ * quite the same, because the latter can, in fact, return null. The former
+ * can't, and the Java compiler confirms this.(An alternative to redundancy
+ * would have been to change the {@code ObjectUtils} class. However, that
+ * would mean loosing upwards compatibility, and we don't do that.)
+ *
+ * @since 3.12.0
+ */
+public class Objects {
+
+ /**
+ * Checks, whether the given object is non-null. If so, returns the non-null
+ * object as a result value. Otherwise, a NullPointerException is thrown.
+ * @param <T> The type of parameter {@code value}, also the result type.
+ * @param value The value, which is being checked.
+ * @return The given input value, if it was found to be non-null.
+ * @throws NullPointerException The input value was null.
+ * @see java.util.Objects#requireNonNull(Object)
+ */
+ public static <T> @Nonnull T requireNonNull(@Nullable final T value) throws NullPointerException {
+ return requireNonNull(value, "The value must not be null.");
+ }
+
+ /**
+ * Checks, whether the given object is non-null. If so, returns the non-null
+ * object as a result value. Otherwise, invokes the given {@link Supplier},
+ * and returns the suppliers result value.
+ * @param <T> The type of parameter {@code value}, also the result type of
+ * the default value supplier, and of the method itself.
+ * @param <E> The type of exception, that the {@code default value supplier},
+ * may throw.
+ * @param value The value, which is being checked.
+ * @param defaultValueSupplier The supplier, which returns the default value. This default
+ * value <em>must</em> be non-null. The supplier will only be invoked, if
+ * necessary. (If the {@code value} parameter is null, that is.)
+ * @return The given input value, if it was found to be non-null. Otherwise,
+ * the value, that has been returned by the default value supplier.
+ * @see #requireNonNull(Object)
+ * @see #requireNonNull(Object, String)
+ * @see #requireNonNull(Object, Supplier)
+ * @throws NullPointerException The default value supplier is null, or the default
+ * value supplier has returned null.
+ */
+ public static <T, E extends Throwable> @Nonnull T requireNonNull(@Nullable final T value, @Nonnull final FailableSupplier<T, E> defaultValueSupplier) throws NullPointerException {
+ if (value == null) {
+ final FailableSupplier<T, ?> supplier = requireNonNull(defaultValueSupplier, "The supplier must not be null");
+ final T defaultValue;
+ try {
+ defaultValue = supplier.get();
+ } catch (final Throwable t) {
+ throw Failable.rethrow(t);
+ }
+ return requireNonNull(defaultValue, "The supplier must not return null.");
+ }
+ return value;
+ }
+
+ /**
+ * Checks, whether the given object is non-null. If so, returns the non-null
+ * object as a result value. Otherwise, a NullPointerException is thrown.
+ * @param <T> The type of parameter {@code value}, also the result type.
+ * @param value The value, which is being checked.
+ * @param msg A string, which is being used as the exceptions message, if the
+ * check fails.
+ * @return The given input value, if it was found to be non-null.
+ * @throws NullPointerException The input value was null.
+ * @see java.util.Objects#requireNonNull(Object, String)
+ * @see #requireNonNull(Object, Supplier)
+ */
+ public static <T> @Nonnull T requireNonNull(@Nullable final T value, @Nonnull final String msg) throws NullPointerException {
+ if (value == null) {
+ throw new NullPointerException(msg);
+ }
+ return value;
+ }
+
+ /**
+ * Checks, whether the given object is non-null. If so, returns the non-null
+ * object as a result value. Otherwise, a NullPointerException is thrown.
+ * @param <T> The type of parameter {@code value}, also the result type.
+ * @param value The value, which is being checked.
+ * @param msgSupplier A supplier, which creates the exception message, if the check fails.
+ * This supplier will only be invoked, if necessary.
+ * @return The given input value, if it was found to be non-null.
+ * @throws NullPointerException The input value was null.
+ * @see java.util.Objects#requireNonNull(Object, String)
+ * @see #requireNonNull(Object, String)
+ */
+ public static <T> @Nonnull T requireNonNull(@Nullable final T value, @Nonnull final Supplier<String> msgSupplier) throws NullPointerException {
+ if (value == null) {
+ throw new NullPointerException(msgSupplier.get());
+ }
+ return value;
+ }
+
+ /**
+ * Checks, whether the given object is non-null. If so, returns the non-null
+ * object as a result value. Otherwise, a NullPointerException is thrown.
+ * @param <T> The type of parameter {@code value}, also the result type.
+ * @param value The value, which is being checked.
+ * @param defaultValue The default value, which is being returned, if the
+ * check fails, and the {@code value} is null.
+ * @throws NullPointerException The input value, and the default value are null.
+ * @return The given input value, if it was found to be non-null.
+ * @see java.util.Objects#requireNonNull(Object)
+ */
+ public static <T> @Nonnull T requireNonNull(@Nullable final T value, @Nonnull final T defaultValue) throws NullPointerException {
+ return value == null ? requireNonNull(defaultValue) : value;
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/ObjectsTest.java b/src/test/java/org/apache/commons/lang3/function/ObjectsTest.java
new file mode 100755
index 000000000..36788830e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/ObjectsTest.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.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+public class ObjectsTest extends AbstractLangTest {
+
+ public static class TestableFailableSupplier<O, E extends Exception> implements FailableSupplier<O, E> {
+ private final FailableSupplier<O, E> supplier;
+ private boolean invoked;
+
+ TestableFailableSupplier(final FailableSupplier<O, E> pSupplier) {
+ this.supplier = pSupplier;
+ }
+
+ @Override
+ public O get() throws E {
+ invoked = true;
+ return supplier.get();
+ }
+
+ public boolean isInvoked() {
+ return invoked;
+ }
+ }
+
+ public static class TestableSupplier<O> implements Supplier<O> {
+ private final Supplier<O> supplier;
+ private boolean invoked;
+
+ TestableSupplier(final Supplier<O> pSupplier) {
+ this.supplier = pSupplier;
+ }
+
+ @Override
+ public O get() {
+ invoked = true;
+ return supplier.get();
+ }
+
+ public boolean isInvoked() {
+ return invoked;
+ }
+ }
+
+ @Test
+ void testRequireNonNullObject() {
+ assertSame("foo", Objects.requireNonNull("foo"));
+ try {
+ Objects.requireNonNull(null);
+ fail("Expected Exception");
+ } catch (final NullPointerException e) {
+ assertEquals("The value must not be null.", e.getMessage());
+ }
+ }
+
+ @Test
+ void testRequireNonNullObjectFailableSupplierString() {
+ final TestableFailableSupplier<String, ?> supplier = new TestableFailableSupplier<>(() -> null);
+ assertSame("foo", Objects.requireNonNull("foo", supplier));
+ assertFalse(supplier.isInvoked());
+ try {
+ Objects.requireNonNull(null, supplier);
+ fail("Expected Exception");
+ } catch (final NullPointerException e) {
+ assertEquals("The supplier must not return null.", e.getMessage());
+ assertTrue(supplier.isInvoked());
+ }
+ final TestableFailableSupplier<String, ?> supplier2 = new TestableFailableSupplier<>(() -> null);
+ try {
+ Objects.requireNonNull(null, supplier2);
+ fail("Expected Exception");
+ } catch (final NullPointerException e) {
+ assertEquals("The supplier must not return null.", e.getMessage());
+ assertTrue(supplier2.isInvoked());
+ }
+ final TestableFailableSupplier<String, ?> supplier3 = new TestableFailableSupplier<>(() -> "bar");
+ assertSame("bar", Objects.requireNonNull(null, supplier3));
+ final RuntimeException rte = new RuntimeException();
+ final TestableFailableSupplier<String, ?> supplier4 = new TestableFailableSupplier<>(() -> {
+ throw rte;
+ });
+ try {
+ Objects.requireNonNull(null, supplier4);
+ fail("Expected Exception");
+ } catch (final RuntimeException e) {
+ assertSame(rte, e);
+ assertTrue(supplier4.isInvoked());
+ }
+ }
+
+ @Test
+ void testRequireNonNullObjectString() {
+ assertSame("foo", Objects.requireNonNull("foo", "bar"));
+ try {
+ Objects.requireNonNull(null, "bar");
+ fail("Expected Exception");
+ } catch (final NullPointerException e) {
+ assertEquals("bar", e.getMessage());
+ }
+ }
+
+ @Test
+ void testRequireNonNullObjectSupplierString() {
+ final TestableSupplier<String> supplier = new TestableSupplier<>(() -> "bar");
+ assertSame("foo", Objects.requireNonNull("foo", supplier));
+ assertFalse(supplier.isInvoked());
+ try {
+ Objects.requireNonNull(null, supplier);
+ fail("Expected Exception");
+ } catch (final NullPointerException e) {
+ assertEquals("bar", e.getMessage());
+ assertTrue(supplier.isInvoked());
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/SuppliersTest.java b/src/test/java/org/apache/commons/lang3/function/SuppliersTest.java
new file mode 100644
index 000000000..1c912757c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/SuppliersTest.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.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link Suppliers}.
+ */
+public class SuppliersTest extends AbstractLangTest {
+
+ /**
+ * Tests {@link Suppliers#get(Supplier)}.
+ */
+ @Test
+ public void testGet() {
+ assertNull(Suppliers.get(null));
+ assertNull(Suppliers.get(() -> null));
+ assertEquals("foo", Suppliers.get(() -> "foo"));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/ToBooleanBiFunctionTest.java b/src/test/java/org/apache/commons/lang3/function/ToBooleanBiFunctionTest.java
new file mode 100644
index 000000000..13c79f318
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/ToBooleanBiFunctionTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link ToBooleanBiFunction}.
+ */
+public class ToBooleanBiFunctionTest extends AbstractLangTest {
+
+ @Test
+ public void test() {
+ final ToBooleanBiFunction<String, Integer> func = (t, u) -> Integer.valueOf(t).equals(u);
+ assertTrue(func.applyAsBoolean("1", 1));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/TriConsumerTest.java b/src/test/java/org/apache/commons/lang3/function/TriConsumerTest.java
new file mode 100644
index 000000000..5a959f562
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/TriConsumerTest.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.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link TriConsumer}.
+ */
+public class TriConsumerTest extends AbstractLangTest {
+
+ @Test
+ public void testAccept() throws Throwable {
+ final AtomicReference<Character> ref1 = new AtomicReference<>();
+ final AtomicReference<Short> ref2 = new AtomicReference<>();
+ final AtomicReference<String> ref3 = new AtomicReference<>();
+ final TriConsumer<AtomicReference<Character>, AtomicReference<Short>, AtomicReference<String>> tri = (t, u,
+ v) -> {
+ ref1.set(Character.valueOf('a'));
+ ref2.set(Short.valueOf((short) 1));
+ ref3.set("z");
+ };
+ tri.accept(ref1, ref2, ref3);
+ assertEquals(Character.valueOf('a'), ref1.get());
+ assertEquals(Short.valueOf((short) 1), ref2.get());
+ assertEquals("z", ref3.get());
+ }
+
+ @Test
+ public void testAndThen() throws Throwable {
+ final AtomicReference<Character> ref1 = new AtomicReference<>();
+ final AtomicReference<Short> ref2 = new AtomicReference<>();
+ final AtomicReference<String> ref3 = new AtomicReference<>();
+ final TriConsumer<AtomicReference<Character>, AtomicReference<Short>, AtomicReference<String>> tri = (t, u,
+ v) -> {
+ ref1.set(Character.valueOf('a'));
+ ref2.set(Short.valueOf((short) 1));
+ ref3.set("z");
+ };
+ final TriConsumer<AtomicReference<Character>, AtomicReference<Short>, AtomicReference<String>> triAfter = (t, u,
+ v) -> {
+ ref1.set(Character.valueOf('b'));
+ ref2.set(Short.valueOf((short) 2));
+ ref3.set("zz");
+ };
+ tri.andThen(triAfter).accept(ref1, ref2, ref3);
+ assertEquals(Character.valueOf('b'), ref1.get());
+ assertEquals(Short.valueOf((short) 2), ref2.get());
+ assertEquals("zz", ref3.get());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/function/TriFunctionTest.java b/src/test/java/org/apache/commons/lang3/function/TriFunctionTest.java
new file mode 100644
index 000000000..cdcf963b7
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/function/TriFunctionTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link TriFunction}.
+ */
+public class TriFunctionTest extends AbstractLangTest {
+
+ @Test
+ public void testAccept() throws Throwable {
+ final AtomicReference<Character> ref1 = new AtomicReference<>();
+ final AtomicReference<Short> ref2 = new AtomicReference<>();
+ final AtomicReference<String> ref3 = new AtomicReference<>();
+ final TriFunction<AtomicReference<Character>, AtomicReference<Short>, AtomicReference<String>, String> tri = (t, u, v) -> {
+ ref1.set(Character.valueOf('a'));
+ ref2.set(Short.valueOf((short) 1));
+ ref3.set("z");
+ return "ABC";
+ };
+ assertEquals("ABC", tri.apply(ref1, ref2, ref3));
+ assertEquals(Character.valueOf('a'), ref1.get());
+ assertEquals(Short.valueOf((short) 1), ref2.get());
+ assertEquals("z", ref3.get());
+ }
+
+ @Test
+ public void testAndThen() throws Throwable {
+ final AtomicReference<Character> ref1 = new AtomicReference<>();
+ final AtomicReference<Short> ref2 = new AtomicReference<>();
+ final AtomicReference<String> ref3 = new AtomicReference<>();
+ final TriFunction<AtomicReference<Character>, AtomicReference<Short>, AtomicReference<String>, String> tri = (t, u, v) -> {
+ ref1.set(Character.valueOf('a'));
+ ref2.set(Short.valueOf((short) 1));
+ ref3.set("z");
+ return "9";
+ };
+ final Function<String, BigInteger> after = t -> {
+ ref1.set(Character.valueOf('b'));
+ ref2.set(Short.valueOf((short) 2));
+ ref3.set("zz");
+ return BigInteger.valueOf(Long.parseLong(t)).add(BigInteger.ONE);
+ };
+ assertEquals(BigInteger.TEN, tri.andThen(after).apply(ref1, ref2, ref3));
+ assertEquals(Character.valueOf('b'), ref1.get());
+ assertEquals(Short.valueOf((short) 2), ref2.get());
+ assertEquals("zz", ref3.get());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/math/FractionTest.java b/src/test/java/org/apache/commons/lang3/math/FractionTest.java
new file mode 100644
index 000000000..269c14558
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/math/FractionTest.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.math;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test cases for the {@link Fraction} class
+ */
+public class FractionTest extends AbstractLangTest {
+
+ private static final int SKIP = 500; //53
+
+ @Test
+ public void testAbs() {
+ Fraction f;
+
+ f = Fraction.getFraction(50, 75);
+ f = f.abs();
+ assertEquals(50, f.getNumerator());
+ assertEquals(75, f.getDenominator());
+
+ f = Fraction.getFraction(-50, 75);
+ f = f.abs();
+ assertEquals(50, f.getNumerator());
+ assertEquals(75, f.getDenominator());
+
+ f = Fraction.getFraction(Integer.MAX_VALUE, 1);
+ f = f.abs();
+ assertEquals(Integer.MAX_VALUE, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getFraction(Integer.MAX_VALUE, -1);
+ f = f.abs();
+ assertEquals(Integer.MAX_VALUE, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).abs());
+ }
+
+ @Test
+ public void testAdd() {
+ Fraction f;
+ Fraction f1;
+ Fraction f2;
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(1, 5);
+ f = f1.add(f2);
+ assertEquals(4, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(2, 5);
+ f = f1.add(f2);
+ assertEquals(1, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(3, 5);
+ f = f1.add(f2);
+ assertEquals(6, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(-4, 5);
+ f = f1.add(f2);
+ assertEquals(-1, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f1 = Fraction.getFraction(Integer.MAX_VALUE - 1, 1);
+ f2 = Fraction.ONE;
+ f = f1.add(f2);
+ assertEquals(Integer.MAX_VALUE, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(1, 2);
+ f = f1.add(f2);
+ assertEquals(11, f.getNumerator());
+ assertEquals(10, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 8);
+ f2 = Fraction.getFraction(1, 6);
+ f = f1.add(f2);
+ assertEquals(13, f.getNumerator());
+ assertEquals(24, f.getDenominator());
+
+ f1 = Fraction.getFraction(0, 5);
+ f2 = Fraction.getFraction(1, 5);
+ f = f1.add(f2);
+ assertSame(f2, f);
+ f = f2.add(f1);
+ assertSame(f2, f);
+
+ f1 = Fraction.getFraction(-1, 13*13*2*2);
+ f2 = Fraction.getFraction(-2, 13*17*2);
+ final Fraction fr = f1.add(f2);
+ assertEquals(13*13*17*2*2, fr.getDenominator());
+ assertEquals(-17 - 2*13*2, fr.getNumerator());
+
+ assertThrows(NullPointerException.class, () -> fr.add(null));
+
+ // if this fraction is added naively, it will overflow.
+ // check that it doesn't.
+ f1 = Fraction.getFraction(1, 32768*3);
+ f2 = Fraction.getFraction(1, 59049);
+ f = f1.add(f2);
+ assertEquals(52451, f.getNumerator());
+ assertEquals(1934917632, f.getDenominator());
+
+ f1 = Fraction.getFraction(Integer.MIN_VALUE, 3);
+ f2 = Fraction.ONE_THIRD;
+ f = f1.add(f2);
+ assertEquals(Integer.MIN_VALUE+1, f.getNumerator());
+ assertEquals(3, f.getDenominator());
+
+ f1 = Fraction.getFraction(Integer.MAX_VALUE - 1, 1);
+ f2 = Fraction.ONE;
+ f = f1.add(f2);
+ assertEquals(Integer.MAX_VALUE, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ final Fraction overflower = f;
+ assertThrows(ArithmeticException.class, () -> overflower.add(Fraction.ONE)); // should overflow
+
+ // denominator should not be a multiple of 2 or 3 to trigger overflow
+ assertThrows(
+ ArithmeticException.class,
+ () -> Fraction.getFraction(Integer.MIN_VALUE, 5).add(Fraction.getFraction(-1, 5)));
+
+ final Fraction maxValue = Fraction.getFraction(-Integer.MAX_VALUE, 1);
+ assertThrows(ArithmeticException.class, () -> maxValue.add(maxValue));
+
+ final Fraction negativeMaxValue = Fraction.getFraction(-Integer.MAX_VALUE, 1);
+ assertThrows(ArithmeticException.class, () -> negativeMaxValue.add(negativeMaxValue));
+
+ final Fraction f3 = Fraction.getFraction(3, 327680);
+ final Fraction f4 = Fraction.getFraction(2, 59049);
+ assertThrows(ArithmeticException.class, () -> f3.add(f4)); // should overflow
+ }
+
+ @Test
+ public void testCompareTo() {
+ final Fraction f1;
+ Fraction f2;
+
+ f1 = Fraction.getFraction(3, 5);
+ assertEquals(0, f1.compareTo(f1));
+
+ final Fraction fr = f1;
+ assertThrows(NullPointerException.class, () -> fr.compareTo(null));
+
+ f2 = Fraction.getFraction(2, 5);
+ assertTrue(f1.compareTo(f2) > 0);
+ assertEquals(0, f2.compareTo(f2));
+
+ f2 = Fraction.getFraction(4, 5);
+ assertTrue(f1.compareTo(f2) < 0);
+ assertEquals(0, f2.compareTo(f2));
+
+ f2 = Fraction.getFraction(3, 5);
+ assertEquals(0, f1.compareTo(f2));
+ assertEquals(0, f2.compareTo(f2));
+
+ f2 = Fraction.getFraction(6, 10);
+ assertEquals(0, f1.compareTo(f2));
+ assertEquals(0, f2.compareTo(f2));
+
+ f2 = Fraction.getFraction(-1, 1, Integer.MAX_VALUE);
+ assertTrue(f1.compareTo(f2) > 0);
+ assertEquals(0, f2.compareTo(f2));
+
+ }
+
+ @Test
+ public void testConstants() {
+ assertEquals(0, Fraction.ZERO.getNumerator());
+ assertEquals(1, Fraction.ZERO.getDenominator());
+
+ assertEquals(1, Fraction.ONE.getNumerator());
+ assertEquals(1, Fraction.ONE.getDenominator());
+
+ assertEquals(1, Fraction.ONE_HALF.getNumerator());
+ assertEquals(2, Fraction.ONE_HALF.getDenominator());
+
+ assertEquals(1, Fraction.ONE_THIRD.getNumerator());
+ assertEquals(3, Fraction.ONE_THIRD.getDenominator());
+
+ assertEquals(2, Fraction.TWO_THIRDS.getNumerator());
+ assertEquals(3, Fraction.TWO_THIRDS.getDenominator());
+
+ assertEquals(1, Fraction.ONE_QUARTER.getNumerator());
+ assertEquals(4, Fraction.ONE_QUARTER.getDenominator());
+
+ assertEquals(2, Fraction.TWO_QUARTERS.getNumerator());
+ assertEquals(4, Fraction.TWO_QUARTERS.getDenominator());
+
+ assertEquals(3, Fraction.THREE_QUARTERS.getNumerator());
+ assertEquals(4, Fraction.THREE_QUARTERS.getDenominator());
+
+ assertEquals(1, Fraction.ONE_FIFTH.getNumerator());
+ assertEquals(5, Fraction.ONE_FIFTH.getDenominator());
+
+ assertEquals(2, Fraction.TWO_FIFTHS.getNumerator());
+ assertEquals(5, Fraction.TWO_FIFTHS.getDenominator());
+
+ assertEquals(3, Fraction.THREE_FIFTHS.getNumerator());
+ assertEquals(5, Fraction.THREE_FIFTHS.getDenominator());
+
+ assertEquals(4, Fraction.FOUR_FIFTHS.getNumerator());
+ assertEquals(5, Fraction.FOUR_FIFTHS.getDenominator());
+ }
+
+ @Test
+ public void testConversions() {
+ final Fraction f;
+
+ f = Fraction.getFraction(3, 7, 8);
+ assertEquals(3, f.intValue());
+ assertEquals(3L, f.longValue());
+ assertEquals(3.875f, f.floatValue(), 0.00001f);
+ assertEquals(3.875d, f.doubleValue(), 0.00001d);
+ }
+
+ @Test
+ public void testDivide() {
+ Fraction f;
+ Fraction f1;
+ Fraction f2;
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(2, 5);
+ f = f1.divideBy(f2);
+ assertEquals(3, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(3, 5).divideBy(Fraction.ZERO));
+
+ f1 = Fraction.getFraction(0, 5);
+ f2 = Fraction.getFraction(2, 7);
+ f = f1.divideBy(f2);
+ assertSame(Fraction.ZERO, f);
+
+ f1 = Fraction.getFraction(2, 7);
+ f2 = Fraction.ONE;
+ f = f1.divideBy(f2);
+ assertEquals(2, f.getNumerator());
+ assertEquals(7, f.getDenominator());
+
+ f1 = Fraction.getFraction(1, Integer.MAX_VALUE);
+ f = f1.divideBy(f1);
+ assertEquals(1, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f1 = Fraction.getFraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ f2 = Fraction.getFraction(1, Integer.MAX_VALUE);
+ final Fraction fr = f1.divideBy(f2);
+ assertEquals(Integer.MIN_VALUE, fr.getNumerator());
+ assertEquals(1, fr.getDenominator());
+
+ assertThrows(NullPointerException.class, () -> fr.divideBy(null));
+
+ final Fraction smallest = Fraction.getFraction(1, Integer.MAX_VALUE);
+ assertThrows(ArithmeticException.class, () -> smallest.divideBy(smallest.invert())); // Should overflow
+
+ final Fraction negative = Fraction.getFraction(1, -Integer.MAX_VALUE);
+ assertThrows(ArithmeticException.class, () -> negative.divideBy(negative.invert())); // Should overflow
+ }
+
+
+ @Test
+ public void testEquals() {
+ Fraction f1;
+ Fraction f2;
+
+ f1 = Fraction.getFraction(3, 5);
+ assertNotEquals(null, f1);
+ assertNotEquals(f1, new Object());
+ assertNotEquals(f1, Integer.valueOf(6));
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(2, 5);
+ assertNotEquals(f1, f2);
+ assertEquals(f1, f1);
+ assertEquals(f2, f2);
+
+ f2 = Fraction.getFraction(3, 5);
+ assertEquals(f1, f2);
+
+ f2 = Fraction.getFraction(6, 10);
+ assertNotEquals(f1, f2);
+ }
+
+ @Test
+ public void testFactory_double() {
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Double.NaN));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Double.POSITIVE_INFINITY));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Double.NEGATIVE_INFINITY));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction((double) Integer.MAX_VALUE + 1));
+
+ // zero
+ Fraction f = Fraction.getFraction(0.0d);
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ // one
+ f = Fraction.getFraction(1.0d);
+ assertEquals(1, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ // one half
+ f = Fraction.getFraction(0.5d);
+ assertEquals(1, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ // negative
+ f = Fraction.getFraction(-0.875d);
+ assertEquals(-7, f.getNumerator());
+ assertEquals(8, f.getDenominator());
+
+ // over 1
+ f = Fraction.getFraction(1.25d);
+ assertEquals(5, f.getNumerator());
+ assertEquals(4, f.getDenominator());
+
+ // two thirds
+ f = Fraction.getFraction(0.66666d);
+ assertEquals(2, f.getNumerator());
+ assertEquals(3, f.getDenominator());
+
+ // small
+ f = Fraction.getFraction(1.0d/10001d);
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ // normal
+ Fraction f2 = null;
+ for (int i = 1; i <= 100; i++) { // denominator
+ for (int j = 1; j <= i; j++) { // numerator
+ f = Fraction.getFraction((double) j / (double) i);
+
+ f2 = Fraction.getReducedFraction(j, i);
+ assertEquals(f2.getNumerator(), f.getNumerator());
+ assertEquals(f2.getDenominator(), f.getDenominator());
+ }
+ }
+ // save time by skipping some tests! (
+ for (int i = 1001; i <= 10000; i+=SKIP) { // denominator
+ for (int j = 1; j <= i; j++) { // numerator
+ f = Fraction.getFraction((double) j / (double) i);
+ f2 = Fraction.getReducedFraction(j, i);
+ assertEquals(f2.getNumerator(), f.getNumerator());
+ assertEquals(f2.getDenominator(), f.getDenominator());
+ }
+ }
+ }
+
+ @Test
+ public void testFactory_int_int() {
+ Fraction f;
+
+ // zero
+ f = Fraction.getFraction(0, 1);
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getFraction(0, 2);
+ assertEquals(0, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ // normal
+ f = Fraction.getFraction(1, 1);
+ assertEquals(1, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getFraction(2, 1);
+ assertEquals(2, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getFraction(23, 345);
+ assertEquals(23, f.getNumerator());
+ assertEquals(345, f.getDenominator());
+
+ // improper
+ f = Fraction.getFraction(22, 7);
+ assertEquals(22, f.getNumerator());
+ assertEquals(7, f.getDenominator());
+
+ // negatives
+ f = Fraction.getFraction(-6, 10);
+ assertEquals(-6, f.getNumerator());
+ assertEquals(10, f.getDenominator());
+
+ f = Fraction.getFraction(6, -10);
+ assertEquals(-6, f.getNumerator());
+ assertEquals(10, f.getDenominator());
+
+ f = Fraction.getFraction(-6, -10);
+ assertEquals(6, f.getNumerator());
+ assertEquals(10, f.getDenominator());
+
+ // zero denominator
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, 0));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(2, 0));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-3, 0));
+
+ // very large: can't represent as unsimplified fraction, although
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(4, Integer.MIN_VALUE));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, Integer.MIN_VALUE));
+ }
+
+ @Test
+ public void testFactory_int_int_int() {
+ Fraction f;
+
+ // zero
+ f = Fraction.getFraction(0, 0, 2);
+ assertEquals(0, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ f = Fraction.getFraction(2, 0, 2);
+ assertEquals(4, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ f = Fraction.getFraction(0, 1, 2);
+ assertEquals(1, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ // normal
+ f = Fraction.getFraction(1, 1, 2);
+ assertEquals(3, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ // negatives
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, -6, -10));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, -6, -10));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, -6, -10));
+
+ // negative whole
+ f = Fraction.getFraction(-1, 6, 10);
+ assertEquals(-16, f.getNumerator());
+ assertEquals(10, f.getDenominator());
+
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, -6, 10));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, 6, -10));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, -6, -10));
+
+ // zero denominator
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(0, 1, 0));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, 2, 0));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, -3, 0));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MAX_VALUE, 1, 2));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-Integer.MAX_VALUE, 1, 2));
+
+ // very large
+ f = Fraction.getFraction(-1, 0, Integer.MAX_VALUE);
+ assertEquals(-Integer.MAX_VALUE, f.getNumerator());
+ assertEquals(Integer.MAX_VALUE, f.getDenominator());
+
+ // negative denominators not allowed in this constructor.
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(0, 4, Integer.MIN_VALUE));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, 1, Integer.MAX_VALUE));
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, 2, Integer.MAX_VALUE));
+ }
+
+ @Test
+ public void testFactory_String() {
+ assertThrows(NullPointerException.class, () -> Fraction.getFraction(null));
+ }
+
+ @Test
+ public void testFactory_String_double() {
+ Fraction f;
+
+ f = Fraction.getFraction("0.0");
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getFraction("0.2");
+ assertEquals(1, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f = Fraction.getFraction("0.5");
+ assertEquals(1, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ f = Fraction.getFraction("0.66666");
+ assertEquals(2, f.getNumerator());
+ assertEquals(3, f.getDenominator());
+
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2.3R"));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2147483648")); // too big
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("."));
+ }
+
+ @Test
+ public void testFactory_String_improper() {
+ Fraction f;
+
+ f = Fraction.getFraction("0/1");
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getFraction("1/5");
+ assertEquals(1, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f = Fraction.getFraction("1/2");
+ assertEquals(1, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ f = Fraction.getFraction("2/3");
+ assertEquals(2, f.getNumerator());
+ assertEquals(3, f.getDenominator());
+
+ f = Fraction.getFraction("7/3");
+ assertEquals(7, f.getNumerator());
+ assertEquals(3, f.getDenominator());
+
+ f = Fraction.getFraction("2/4");
+ assertEquals(2, f.getNumerator());
+ assertEquals(4, f.getDenominator());
+
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2/d"));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2e/3"));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2/"));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("/"));
+ }
+
+ @Test
+ public void testFactory_String_proper() {
+ Fraction f;
+
+ f = Fraction.getFraction("0 0/1");
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getFraction("1 1/5");
+ assertEquals(6, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f = Fraction.getFraction("7 1/2");
+ assertEquals(15, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ f = Fraction.getFraction("1 2/4");
+ assertEquals(6, f.getNumerator());
+ assertEquals(4, f.getDenominator());
+
+ f = Fraction.getFraction("-7 1/2");
+ assertEquals(-15, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ f = Fraction.getFraction("-1 2/4");
+ assertEquals(-6, f.getNumerator());
+ assertEquals(4, f.getDenominator());
+
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2 3"));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("a 3"));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2 b/4"));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2 "));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction(" 3"));
+ assertThrows(NumberFormatException.class, () -> Fraction.getFraction(" "));
+ }
+
+ @Test
+ public void testGets() {
+ Fraction f;
+
+ f = Fraction.getFraction(3, 5, 6);
+ assertEquals(23, f.getNumerator());
+ assertEquals(3, f.getProperWhole());
+ assertEquals(5, f.getProperNumerator());
+ assertEquals(6, f.getDenominator());
+
+ f = Fraction.getFraction(-3, 5, 6);
+ assertEquals(-23, f.getNumerator());
+ assertEquals(-3, f.getProperWhole());
+ assertEquals(5, f.getProperNumerator());
+ assertEquals(6, f.getDenominator());
+
+ f = Fraction.getFraction(Integer.MIN_VALUE, 0, 1);
+ assertEquals(Integer.MIN_VALUE, f.getNumerator());
+ assertEquals(Integer.MIN_VALUE, f.getProperWhole());
+ assertEquals(0, f.getProperNumerator());
+ assertEquals(1, f.getDenominator());
+ }
+
+ @Test
+ public void testHashCode() {
+ final Fraction f1 = Fraction.getFraction(3, 5);
+ Fraction f2 = Fraction.getFraction(3, 5);
+
+ assertEquals(f1.hashCode(), f2.hashCode());
+
+ f2 = Fraction.getFraction(2, 5);
+ assertTrue(f1.hashCode() != f2.hashCode());
+
+ f2 = Fraction.getFraction(6, 10);
+ assertTrue(f1.hashCode() != f2.hashCode());
+ }
+
+ @Test
+ public void testInvert() {
+ Fraction f;
+
+ f = Fraction.getFraction(50, 75);
+ f = f.invert();
+ assertEquals(75, f.getNumerator());
+ assertEquals(50, f.getDenominator());
+
+ f = Fraction.getFraction(4, 3);
+ f = f.invert();
+ assertEquals(3, f.getNumerator());
+ assertEquals(4, f.getDenominator());
+
+ f = Fraction.getFraction(-15, 47);
+ f = f.invert();
+ assertEquals(-47, f.getNumerator());
+ assertEquals(15, f.getDenominator());
+
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(0, 3).invert());
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).invert());
+
+ f = Fraction.getFraction(Integer.MAX_VALUE, 1);
+ f = f.invert();
+ assertEquals(1, f.getNumerator());
+ assertEquals(Integer.MAX_VALUE, f.getDenominator());
+ }
+
+ @Test
+ public void testMultiply() {
+ Fraction f;
+ Fraction f1;
+ Fraction f2;
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(2, 5);
+ f = f1.multiplyBy(f2);
+ assertEquals(6, f.getNumerator());
+ assertEquals(25, f.getDenominator());
+
+ f1 = Fraction.getFraction(6, 10);
+ f2 = Fraction.getFraction(6, 10);
+ f = f1.multiplyBy(f2);
+ assertEquals(9, f.getNumerator());
+ assertEquals(25, f.getDenominator());
+ f = f.multiplyBy(f2);
+ assertEquals(27, f.getNumerator());
+ assertEquals(125, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(-2, 5);
+ f = f1.multiplyBy(f2);
+ assertEquals(-6, f.getNumerator());
+ assertEquals(25, f.getDenominator());
+
+ f1 = Fraction.getFraction(-3, 5);
+ f2 = Fraction.getFraction(-2, 5);
+ f = f1.multiplyBy(f2);
+ assertEquals(6, f.getNumerator());
+ assertEquals(25, f.getDenominator());
+
+
+ f1 = Fraction.getFraction(0, 5);
+ f2 = Fraction.getFraction(2, 7);
+ f = f1.multiplyBy(f2);
+ assertSame(Fraction.ZERO, f);
+
+ f1 = Fraction.getFraction(2, 7);
+ f2 = Fraction.ONE;
+ f = f1.multiplyBy(f2);
+ assertEquals(2, f.getNumerator());
+ assertEquals(7, f.getDenominator());
+
+ f1 = Fraction.getFraction(Integer.MAX_VALUE, 1);
+ f2 = Fraction.getFraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ f = f1.multiplyBy(f2);
+ assertEquals(Integer.MIN_VALUE, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ final Fraction fr = f;
+ assertThrows(NullPointerException.class, () -> fr.multiplyBy(null));
+
+ final Fraction fr1 = Fraction.getFraction(1, Integer.MAX_VALUE);
+ assertThrows(ArithmeticException.class, () -> fr1.multiplyBy(fr1));
+
+ final Fraction fr2 = Fraction.getFraction(1, -Integer.MAX_VALUE);
+ assertThrows(ArithmeticException.class, () -> fr2.multiplyBy(fr2));
+ }
+
+ @Test
+ public void testNegate() {
+ Fraction f;
+
+ f = Fraction.getFraction(50, 75);
+ f = f.negate();
+ assertEquals(-50, f.getNumerator());
+ assertEquals(75, f.getDenominator());
+
+ f = Fraction.getFraction(-50, 75);
+ f = f.negate();
+ assertEquals(50, f.getNumerator());
+ assertEquals(75, f.getDenominator());
+
+ // large values
+ f = Fraction.getFraction(Integer.MAX_VALUE-1, Integer.MAX_VALUE);
+ f = f.negate();
+ assertEquals(Integer.MIN_VALUE+2, f.getNumerator());
+ assertEquals(Integer.MAX_VALUE, f.getDenominator());
+
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).negate());
+ }
+
+ @Test
+ public void testPow() {
+ Fraction f;
+
+ f = Fraction.getFraction(3, 5);
+ assertEquals(Fraction.ONE, f.pow(0));
+
+ f = Fraction.getFraction(3, 5);
+ assertSame(f, f.pow(1));
+ assertEquals(f, f.pow(1));
+
+ f = Fraction.getFraction(3, 5);
+ f = f.pow(2);
+ assertEquals(9, f.getNumerator());
+ assertEquals(25, f.getDenominator());
+
+ f = Fraction.getFraction(3, 5);
+ f = f.pow(3);
+ assertEquals(27, f.getNumerator());
+ assertEquals(125, f.getDenominator());
+
+ f = Fraction.getFraction(3, 5);
+ f = f.pow(-1);
+ assertEquals(5, f.getNumerator());
+ assertEquals(3, f.getDenominator());
+
+ f = Fraction.getFraction(3, 5);
+ f = f.pow(-2);
+ assertEquals(25, f.getNumerator());
+ assertEquals(9, f.getDenominator());
+
+ // check unreduced fractions stay that way.
+ f = Fraction.getFraction(6, 10);
+ assertEquals(Fraction.ONE, f.pow(0));
+
+ f = Fraction.getFraction(6, 10);
+ assertEquals(f, f.pow(1));
+ assertNotEquals(f.pow(1), Fraction.getFraction(3, 5));
+
+ f = Fraction.getFraction(6, 10);
+ f = f.pow(2);
+ assertEquals(9, f.getNumerator());
+ assertEquals(25, f.getDenominator());
+
+ f = Fraction.getFraction(6, 10);
+ f = f.pow(3);
+ assertEquals(27, f.getNumerator());
+ assertEquals(125, f.getDenominator());
+
+ f = Fraction.getFraction(6, 10);
+ f = f.pow(-1);
+ assertEquals(10, f.getNumerator());
+ assertEquals(6, f.getDenominator());
+
+ f = Fraction.getFraction(6, 10);
+ f = f.pow(-2);
+ assertEquals(25, f.getNumerator());
+ assertEquals(9, f.getDenominator());
+
+ // zero to any positive power is still zero.
+ f = Fraction.getFraction(0, 1231);
+ f = f.pow(1);
+ assertEquals(0, f.compareTo(Fraction.ZERO));
+ assertEquals(0, f.getNumerator());
+ assertEquals(1231, f.getDenominator());
+ f = f.pow(2);
+ assertEquals(0, f.compareTo(Fraction.ZERO));
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ // zero to negative powers should throw an exception
+ final Fraction fr = f;
+ assertThrows(ArithmeticException.class, () -> fr.pow(-1));
+ assertThrows(ArithmeticException.class, () -> fr.pow(Integer.MIN_VALUE));
+
+ // one to any power is still one.
+ f = Fraction.getFraction(1, 1);
+ f = f.pow(0);
+ assertEquals(f, Fraction.ONE);
+ f = f.pow(1);
+ assertEquals(f, Fraction.ONE);
+ f = f.pow(-1);
+ assertEquals(f, Fraction.ONE);
+ f = f.pow(Integer.MAX_VALUE);
+ assertEquals(f, Fraction.ONE);
+ f = f.pow(Integer.MIN_VALUE);
+ assertEquals(f, Fraction.ONE);
+
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MAX_VALUE, 1).pow(2));
+
+ // Numerator growing too negative during the pow operation.
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).pow(3));
+
+ assertThrows(ArithmeticException.class, () -> Fraction.getFraction(65536, 1).pow(2));
+ }
+
+ @Test
+ public void testReduce() {
+ Fraction f;
+
+ f = Fraction.getFraction(50, 75);
+ Fraction result = f.reduce();
+ assertEquals(2, result.getNumerator());
+ assertEquals(3, result.getDenominator());
+
+ f = Fraction.getFraction(-2, -3);
+ result = f.reduce();
+ assertEquals(2, result.getNumerator());
+ assertEquals(3, result.getDenominator());
+
+ f = Fraction.getFraction(2, -3);
+ result = f.reduce();
+ assertEquals(-2, result.getNumerator());
+ assertEquals(3, result.getDenominator());
+
+ f = Fraction.getFraction(-2, 3);
+ result = f.reduce();
+ assertEquals(-2, result.getNumerator());
+ assertEquals(3, result.getDenominator());
+ assertSame(f, result);
+
+ f = Fraction.getFraction(2, 3);
+ result = f.reduce();
+ assertEquals(2, result.getNumerator());
+ assertEquals(3, result.getDenominator());
+ assertSame(f, result);
+
+ f = Fraction.getFraction(0, 1);
+ result = f.reduce();
+ assertEquals(0, result.getNumerator());
+ assertEquals(1, result.getDenominator());
+ assertSame(f, result);
+
+ f = Fraction.getFraction(0, 100);
+ result = f.reduce();
+ assertEquals(0, result.getNumerator());
+ assertEquals(1, result.getDenominator());
+ assertSame(result, Fraction.ZERO);
+
+ f = Fraction.getFraction(Integer.MIN_VALUE, 2);
+ result = f.reduce();
+ assertEquals(Integer.MIN_VALUE / 2, result.getNumerator());
+ assertEquals(1, result.getDenominator());
+ }
+
+ @Test
+ public void testReducedFactory_int_int() {
+ Fraction f;
+
+ // zero
+ f = Fraction.getReducedFraction(0, 1);
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ // normal
+ f = Fraction.getReducedFraction(1, 1);
+ assertEquals(1, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getReducedFraction(2, 1);
+ assertEquals(2, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ // improper
+ f = Fraction.getReducedFraction(22, 7);
+ assertEquals(22, f.getNumerator());
+ assertEquals(7, f.getDenominator());
+
+ // negatives
+ f = Fraction.getReducedFraction(-6, 10);
+ assertEquals(-3, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f = Fraction.getReducedFraction(6, -10);
+ assertEquals(-3, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f = Fraction.getReducedFraction(-6, -10);
+ assertEquals(3, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ // zero denominator
+ assertThrows(ArithmeticException.class, () -> Fraction.getReducedFraction(1, 0));
+ assertThrows(ArithmeticException.class, () -> Fraction.getReducedFraction(2, 0));
+ assertThrows(ArithmeticException.class, () -> Fraction.getReducedFraction(-3, 0));
+
+ // reduced
+ f = Fraction.getReducedFraction(0, 2);
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getReducedFraction(2, 2);
+ assertEquals(1, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f = Fraction.getReducedFraction(2, 4);
+ assertEquals(1, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ f = Fraction.getReducedFraction(15, 10);
+ assertEquals(3, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ f = Fraction.getReducedFraction(121, 22);
+ assertEquals(11, f.getNumerator());
+ assertEquals(2, f.getDenominator());
+
+ // Extreme values
+ // OK, can reduce before negating
+ f = Fraction.getReducedFraction(-2, Integer.MIN_VALUE);
+ assertEquals(1, f.getNumerator());
+ assertEquals(-(Integer.MIN_VALUE / 2), f.getDenominator());
+
+ // Can't reduce, negation will throw
+ assertThrows(ArithmeticException.class, () -> Fraction.getReducedFraction(-7, Integer.MIN_VALUE));
+
+ // LANG-662
+ f = Fraction.getReducedFraction(Integer.MIN_VALUE, 2);
+ assertEquals(Integer.MIN_VALUE / 2, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+ }
+
+ @Test
+ public void testSubtract() {
+ Fraction f;
+ Fraction f1;
+ Fraction f2;
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(1, 5);
+ f = f1.subtract(f2);
+ assertEquals(2, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f1 = Fraction.getFraction(7, 5);
+ f2 = Fraction.getFraction(2, 5);
+ f = f1.subtract(f2);
+ assertEquals(1, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(3, 5);
+ f = f1.subtract(f2);
+ assertEquals(0, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(-4, 5);
+ f = f1.subtract(f2);
+ assertEquals(7, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f1 = Fraction.getFraction(0, 5);
+ f2 = Fraction.getFraction(4, 5);
+ f = f1.subtract(f2);
+ assertEquals(-4, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f1 = Fraction.getFraction(0, 5);
+ f2 = Fraction.getFraction(-4, 5);
+ f = f1.subtract(f2);
+ assertEquals(4, f.getNumerator());
+ assertEquals(5, f.getDenominator());
+
+ f1 = Fraction.getFraction(3, 5);
+ f2 = Fraction.getFraction(1, 2);
+ f = f1.subtract(f2);
+ assertEquals(1, f.getNumerator());
+ assertEquals(10, f.getDenominator());
+
+ f1 = Fraction.getFraction(0, 5);
+ f2 = Fraction.getFraction(1, 5);
+ f = f2.subtract(f1);
+ assertSame(f2, f);
+
+ final Fraction fr = f;
+ assertThrows(NullPointerException.class, () -> fr.subtract(null));
+
+ // if this fraction is subtracted naively, it will overflow.
+ // check that it doesn't.
+ f1 = Fraction.getFraction(1, 32768*3);
+ f2 = Fraction.getFraction(1, 59049);
+ f = f1.subtract(f2);
+ assertEquals(-13085, f.getNumerator());
+ assertEquals(1934917632, f.getDenominator());
+
+ f1 = Fraction.getFraction(Integer.MIN_VALUE, 3);
+ f2 = Fraction.ONE_THIRD.negate();
+ f = f1.subtract(f2);
+ assertEquals(Integer.MIN_VALUE+1, f.getNumerator());
+ assertEquals(3, f.getDenominator());
+
+ f1 = Fraction.getFraction(Integer.MAX_VALUE, 1);
+ f2 = Fraction.ONE;
+ f = f1.subtract(f2);
+ assertEquals(Integer.MAX_VALUE-1, f.getNumerator());
+ assertEquals(1, f.getDenominator());
+
+ // Should overflow
+ assertThrows(
+ ArithmeticException.class,
+ () -> Fraction.getFraction(1, Integer.MAX_VALUE).subtract(Fraction.getFraction(1, Integer.MAX_VALUE - 1)));
+ f = f1.subtract(f2);
+
+ // denominator should not be a multiple of 2 or 3 to trigger overflow
+ assertThrows(
+ ArithmeticException.class,
+ () -> Fraction.getFraction(Integer.MIN_VALUE, 5).subtract(Fraction.getFraction(1, 5)));
+
+ assertThrows(
+ ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).subtract(Fraction.ONE));
+
+ assertThrows(
+ ArithmeticException.class,
+ () -> Fraction.getFraction(Integer.MAX_VALUE, 1).subtract(Fraction.ONE.negate()));
+
+ // Should overflow
+ assertThrows(
+ ArithmeticException.class,
+ () -> Fraction.getFraction(3, 327680).subtract(Fraction.getFraction(2, 59049)));
+ }
+
+ @Test
+ public void testToProperString() {
+ Fraction f;
+
+ f = Fraction.getFraction(3, 5);
+ final String str = f.toProperString();
+ assertEquals("3/5", str);
+ assertSame(str, f.toProperString());
+
+ f = Fraction.getFraction(7, 5);
+ assertEquals("1 2/5", f.toProperString());
+
+ f = Fraction.getFraction(14, 10);
+ assertEquals("1 4/10", f.toProperString());
+
+ f = Fraction.getFraction(4, 2);
+ assertEquals("2", f.toProperString());
+
+ f = Fraction.getFraction(0, 2);
+ assertEquals("0", f.toProperString());
+
+ f = Fraction.getFraction(2, 2);
+ assertEquals("1", f.toProperString());
+
+ f = Fraction.getFraction(-7, 5);
+ assertEquals("-1 2/5", f.toProperString());
+
+ f = Fraction.getFraction(Integer.MIN_VALUE, 0, 1);
+ assertEquals("-2147483648", f.toProperString());
+
+ f = Fraction.getFraction(-1, 1, Integer.MAX_VALUE);
+ assertEquals("-1 1/2147483647", f.toProperString());
+
+ assertEquals("-1", Fraction.getFraction(-1).toProperString());
+ }
+
+ @Test
+ public void testToString() {
+ Fraction f;
+
+ f = Fraction.getFraction(3, 5);
+ final String str = f.toString();
+ assertEquals("3/5", str);
+ assertSame(str, f.toString());
+
+ f = Fraction.getFraction(7, 5);
+ assertEquals("7/5", f.toString());
+
+ f = Fraction.getFraction(4, 2);
+ assertEquals("4/2", f.toString());
+
+ f = Fraction.getFraction(0, 2);
+ assertEquals("0/2", f.toString());
+
+ f = Fraction.getFraction(2, 2);
+ assertEquals("2/2", f.toString());
+
+ f = Fraction.getFraction(Integer.MIN_VALUE, 0, 1);
+ assertEquals("-2147483648/1", f.toString());
+
+ f = Fraction.getFraction(-1, 1, Integer.MAX_VALUE);
+ assertEquals("-2147483648/2147483647", f.toString());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/math/IEEE754rUtilsTest.java b/src/test/java/org/apache/commons/lang3/math/IEEE754rUtilsTest.java
new file mode 100644
index 000000000..3af1af0aa
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/math/IEEE754rUtilsTest.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.math;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.math.IEEE754rUtils}.
+ */
+public class IEEE754rUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructorExists() {
+ new IEEE754rUtils();
+ }
+
+ @Test
+ public void testEnforceExceptions() {
+ assertThrows(
+ NullPointerException.class,
+ () -> IEEE754rUtils.min( (float[]) null),
+ "IllegalArgumentException expected for null input");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ IEEE754rUtils::min,
+ "IllegalArgumentException expected for empty input");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> IEEE754rUtils.max( (float[]) null),
+ "IllegalArgumentException expected for null input");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ IEEE754rUtils::max,
+ "IllegalArgumentException expected for empty input");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> IEEE754rUtils.min( (double[]) null),
+ "IllegalArgumentException expected for null input");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ IEEE754rUtils::min,
+ "IllegalArgumentException expected for empty input");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> IEEE754rUtils.max( (double[]) null),
+ "IllegalArgumentException expected for null input");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ IEEE754rUtils::max,
+ "IllegalArgumentException expected for empty input");
+ }
+
+ @Test
+ public void testLang381() {
+ assertEquals(1.2, IEEE754rUtils.min(1.2, 2.5, Double.NaN), 0.01);
+ assertEquals(2.5, IEEE754rUtils.max(1.2, 2.5, Double.NaN), 0.01);
+ assertTrue(Double.isNaN(IEEE754rUtils.max(Double.NaN, Double.NaN, Double.NaN)));
+ assertEquals(1.2f, IEEE754rUtils.min(1.2f, 2.5f, Float.NaN), 0.01);
+ assertEquals(2.5f, IEEE754rUtils.max(1.2f, 2.5f, Float.NaN), 0.01);
+ assertTrue(Float.isNaN(IEEE754rUtils.max(Float.NaN, Float.NaN, Float.NaN)));
+
+ final double[] a = { 1.2, Double.NaN, 3.7, 27.0, 42.0, Double.NaN };
+ assertEquals(42.0, IEEE754rUtils.max(a), 0.01);
+ assertEquals(1.2, IEEE754rUtils.min(a), 0.01);
+
+ final double[] b = { Double.NaN, 1.2, Double.NaN, 3.7, 27.0, 42.0, Double.NaN };
+ assertEquals(42.0, IEEE754rUtils.max(b), 0.01);
+ assertEquals(1.2, IEEE754rUtils.min(b), 0.01);
+
+ final float[] aF = { 1.2f, Float.NaN, 3.7f, 27.0f, 42.0f, Float.NaN };
+ assertEquals(1.2f, IEEE754rUtils.min(aF), 0.01);
+ assertEquals(42.0f, IEEE754rUtils.max(aF), 0.01);
+
+ final float[] bF = { Float.NaN, 1.2f, Float.NaN, 3.7f, 27.0f, 42.0f, Float.NaN };
+ assertEquals(1.2f, IEEE754rUtils.min(bF), 0.01);
+ assertEquals(42.0f, IEEE754rUtils.max(bF), 0.01);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java b/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java
new file mode 100644
index 000000000..2a01fab16
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java
@@ -0,0 +1,1746 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.math.NumberUtils}.
+ */
+public class NumberUtilsTest extends AbstractLangTest {
+
+ private boolean checkCreateNumber(final String val) {
+ try {
+ final Object obj = NumberUtils.createNumber(val);
+ return obj != null;
+ } catch (final NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @Test
+ public void compareByte() {
+ assertTrue(NumberUtils.compare((byte) -3, (byte) 0) < 0);
+ assertEquals(0, NumberUtils.compare((byte) 113, (byte) 113));
+ assertTrue(NumberUtils.compare((byte) 123, (byte) 32) > 0);
+ }
+
+ @Test
+ public void compareInt() {
+ assertTrue(NumberUtils.compare(-3, 0) < 0);
+ assertEquals(0, NumberUtils.compare(113, 113));
+ assertTrue(NumberUtils.compare(213, 32) > 0);
+ }
+
+ private void compareIsCreatableWithCreateNumber(final String val, final boolean expected) {
+ final boolean isValid = NumberUtils.isCreatable(val);
+ final boolean canCreate = checkCreateNumber(val);
+ assertTrue(isValid == expected && canCreate == expected, "Expecting " + expected
+ + " for isCreatable/createNumber using \"" + val + "\" but got " + isValid + " and " + canCreate);
+ }
+
+ @SuppressWarnings("deprecation")
+ private void compareIsNumberWithCreateNumber(final String val, final boolean expected) {
+ final boolean isValid = NumberUtils.isNumber(val);
+ final boolean canCreate = checkCreateNumber(val);
+ assertTrue(isValid == expected && canCreate == expected, "Expecting " + expected
+ + " for isNumber/createNumber using \"" + val + "\" but got " + isValid + " and " + canCreate);
+ }
+
+ @Test
+ public void compareLong() {
+ assertTrue(NumberUtils.compare(-3L, 0L) < 0);
+ assertEquals(0, NumberUtils.compare(113L, 113L));
+ assertTrue(NumberUtils.compare(213L, 32L) > 0);
+ }
+
+ @Test
+ public void compareShort() {
+ assertTrue(NumberUtils.compare((short) -3, (short) 0) < 0);
+ assertEquals(0, NumberUtils.compare((short) 113, (short) 113));
+ assertTrue(NumberUtils.compare((short) 213, (short) 32) > 0);
+ }
+
+ /**
+ * Test for {@link NumberUtils#toDouble(BigDecimal)}
+ */
+ @Test
+ public void testBigIntegerToDoubleBigInteger() {
+ assertEquals(0.0d, NumberUtils.toDouble((BigDecimal) null), "toDouble(BigInteger) 1 failed");
+ assertEquals(8.5d, NumberUtils.toDouble(BigDecimal.valueOf(8.5d)), "toDouble(BigInteger) 2 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toDouble(BigDecimal, double)}
+ */
+ @Test
+ public void testBigIntegerToDoubleBigIntegerD() {
+ assertEquals(1.1d, NumberUtils.toDouble((BigDecimal) null, 1.1d), "toDouble(BigInteger) 1 failed");
+ assertEquals(8.5d, NumberUtils.toDouble(BigDecimal.valueOf(8.5d), 1.1d), "toDouble(BigInteger) 2 failed");
+ }
+
+ // Testing JDK against old Lang functionality
+ @Test
+ public void testCompareDouble() {
+ assertEquals(0, Double.compare(Double.NaN, Double.NaN));
+ assertEquals(Double.compare(Double.NaN, Double.POSITIVE_INFINITY), +1);
+ assertEquals(Double.compare(Double.NaN, Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(Double.NaN, 1.2d), +1);
+ assertEquals(Double.compare(Double.NaN, 0.0d), +1);
+ assertEquals(Double.compare(Double.NaN, -0.0d), +1);
+ assertEquals(Double.compare(Double.NaN, -1.2d), +1);
+ assertEquals(Double.compare(Double.NaN, -Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(Double.NaN, Double.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Double.compare(Double.POSITIVE_INFINITY, Double.NaN), -1);
+ assertEquals(0, Double.compare(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
+ assertEquals(Double.compare(Double.POSITIVE_INFINITY, Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(Double.POSITIVE_INFINITY, 1.2d), +1);
+ assertEquals(Double.compare(Double.POSITIVE_INFINITY, 0.0d), +1);
+ assertEquals(Double.compare(Double.POSITIVE_INFINITY, -0.0d), +1);
+ assertEquals(Double.compare(Double.POSITIVE_INFINITY, -1.2d), +1);
+ assertEquals(Double.compare(Double.POSITIVE_INFINITY, -Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Double.compare(Double.MAX_VALUE, Double.NaN), -1);
+ assertEquals(Double.compare(Double.MAX_VALUE, Double.POSITIVE_INFINITY), -1);
+ assertEquals(0, Double.compare(Double.MAX_VALUE, Double.MAX_VALUE));
+ assertEquals(Double.compare(Double.MAX_VALUE, 1.2d), +1);
+ assertEquals(Double.compare(Double.MAX_VALUE, 0.0d), +1);
+ assertEquals(Double.compare(Double.MAX_VALUE, -0.0d), +1);
+ assertEquals(Double.compare(Double.MAX_VALUE, -1.2d), +1);
+ assertEquals(Double.compare(Double.MAX_VALUE, -Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(Double.MAX_VALUE, Double.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Double.compare(1.2d, Double.NaN), -1);
+ assertEquals(Double.compare(1.2d, Double.POSITIVE_INFINITY), -1);
+ assertEquals(Double.compare(1.2d, Double.MAX_VALUE), -1);
+ assertEquals(0, Double.compare(1.2d, 1.2d));
+ assertEquals(Double.compare(1.2d, 0.0d), +1);
+ assertEquals(Double.compare(1.2d, -0.0d), +1);
+ assertEquals(Double.compare(1.2d, -1.2d), +1);
+ assertEquals(Double.compare(1.2d, -Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(1.2d, Double.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Double.compare(0.0d, Double.NaN), -1);
+ assertEquals(Double.compare(0.0d, Double.POSITIVE_INFINITY), -1);
+ assertEquals(Double.compare(0.0d, Double.MAX_VALUE), -1);
+ assertEquals(Double.compare(0.0d, 1.2d), -1);
+ assertEquals(0, Double.compare(0.0d, 0.0d));
+ assertEquals(Double.compare(0.0d, -0.0d), +1);
+ assertEquals(Double.compare(0.0d, -1.2d), +1);
+ assertEquals(Double.compare(0.0d, -Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(0.0d, Double.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Double.compare(-0.0d, Double.NaN), -1);
+ assertEquals(Double.compare(-0.0d, Double.POSITIVE_INFINITY), -1);
+ assertEquals(Double.compare(-0.0d, Double.MAX_VALUE), -1);
+ assertEquals(Double.compare(-0.0d, 1.2d), -1);
+ assertEquals(Double.compare(-0.0d, 0.0d), -1);
+ assertEquals(0, Double.compare(-0.0d, -0.0d));
+ assertEquals(Double.compare(-0.0d, -1.2d), +1);
+ assertEquals(Double.compare(-0.0d, -Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(-0.0d, Double.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Double.compare(-1.2d, Double.NaN), -1);
+ assertEquals(Double.compare(-1.2d, Double.POSITIVE_INFINITY), -1);
+ assertEquals(Double.compare(-1.2d, Double.MAX_VALUE), -1);
+ assertEquals(Double.compare(-1.2d, 1.2d), -1);
+ assertEquals(Double.compare(-1.2d, 0.0d), -1);
+ assertEquals(Double.compare(-1.2d, -0.0d), -1);
+ assertEquals(0, Double.compare(-1.2d, -1.2d));
+ assertEquals(Double.compare(-1.2d, -Double.MAX_VALUE), +1);
+ assertEquals(Double.compare(-1.2d, Double.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Double.compare(-Double.MAX_VALUE, Double.NaN), -1);
+ assertEquals(Double.compare(-Double.MAX_VALUE, Double.POSITIVE_INFINITY), -1);
+ assertEquals(Double.compare(-Double.MAX_VALUE, Double.MAX_VALUE), -1);
+ assertEquals(Double.compare(-Double.MAX_VALUE, 1.2d), -1);
+ assertEquals(Double.compare(-Double.MAX_VALUE, 0.0d), -1);
+ assertEquals(Double.compare(-Double.MAX_VALUE, -0.0d), -1);
+ assertEquals(Double.compare(-Double.MAX_VALUE, -1.2d), -1);
+ assertEquals(0, Double.compare(-Double.MAX_VALUE, -Double.MAX_VALUE));
+ assertEquals(Double.compare(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Double.compare(Double.NEGATIVE_INFINITY, Double.NaN), -1);
+ assertEquals(Double.compare(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), -1);
+ assertEquals(Double.compare(Double.NEGATIVE_INFINITY, Double.MAX_VALUE), -1);
+ assertEquals(Double.compare(Double.NEGATIVE_INFINITY, 1.2d), -1);
+ assertEquals(Double.compare(Double.NEGATIVE_INFINITY, 0.0d), -1);
+ assertEquals(Double.compare(Double.NEGATIVE_INFINITY, -0.0d), -1);
+ assertEquals(Double.compare(Double.NEGATIVE_INFINITY, -1.2d), -1);
+ assertEquals(Double.compare(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE), -1);
+ assertEquals(0, Double.compare(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
+ }
+
+ @Test
+ public void testCompareFloat() {
+ assertEquals(0, Float.compare(Float.NaN, Float.NaN));
+ assertEquals(Float.compare(Float.NaN, Float.POSITIVE_INFINITY), +1);
+ assertEquals(Float.compare(Float.NaN, Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(Float.NaN, 1.2f), +1);
+ assertEquals(Float.compare(Float.NaN, 0.0f), +1);
+ assertEquals(Float.compare(Float.NaN, -0.0f), +1);
+ assertEquals(Float.compare(Float.NaN, -1.2f), +1);
+ assertEquals(Float.compare(Float.NaN, -Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(Float.NaN, Float.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Float.compare(Float.POSITIVE_INFINITY, Float.NaN), -1);
+ assertEquals(0, Float.compare(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));
+ assertEquals(Float.compare(Float.POSITIVE_INFINITY, Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(Float.POSITIVE_INFINITY, 1.2f), +1);
+ assertEquals(Float.compare(Float.POSITIVE_INFINITY, 0.0f), +1);
+ assertEquals(Float.compare(Float.POSITIVE_INFINITY, -0.0f), +1);
+ assertEquals(Float.compare(Float.POSITIVE_INFINITY, -1.2f), +1);
+ assertEquals(Float.compare(Float.POSITIVE_INFINITY, -Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Float.compare(Float.MAX_VALUE, Float.NaN), -1);
+ assertEquals(Float.compare(Float.MAX_VALUE, Float.POSITIVE_INFINITY), -1);
+ assertEquals(0, Float.compare(Float.MAX_VALUE, Float.MAX_VALUE));
+ assertEquals(Float.compare(Float.MAX_VALUE, 1.2f), +1);
+ assertEquals(Float.compare(Float.MAX_VALUE, 0.0f), +1);
+ assertEquals(Float.compare(Float.MAX_VALUE, -0.0f), +1);
+ assertEquals(Float.compare(Float.MAX_VALUE, -1.2f), +1);
+ assertEquals(Float.compare(Float.MAX_VALUE, -Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(Float.MAX_VALUE, Float.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Float.compare(1.2f, Float.NaN), -1);
+ assertEquals(Float.compare(1.2f, Float.POSITIVE_INFINITY), -1);
+ assertEquals(Float.compare(1.2f, Float.MAX_VALUE), -1);
+ assertEquals(0, Float.compare(1.2f, 1.2f));
+ assertEquals(Float.compare(1.2f, 0.0f), +1);
+ assertEquals(Float.compare(1.2f, -0.0f), +1);
+ assertEquals(Float.compare(1.2f, -1.2f), +1);
+ assertEquals(Float.compare(1.2f, -Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(1.2f, Float.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Float.compare(0.0f, Float.NaN), -1);
+ assertEquals(Float.compare(0.0f, Float.POSITIVE_INFINITY), -1);
+ assertEquals(Float.compare(0.0f, Float.MAX_VALUE), -1);
+ assertEquals(Float.compare(0.0f, 1.2f), -1);
+ assertEquals(0, Float.compare(0.0f, 0.0f));
+ assertEquals(Float.compare(0.0f, -0.0f), +1);
+ assertEquals(Float.compare(0.0f, -1.2f), +1);
+ assertEquals(Float.compare(0.0f, -Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(0.0f, Float.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Float.compare(-0.0f, Float.NaN), -1);
+ assertEquals(Float.compare(-0.0f, Float.POSITIVE_INFINITY), -1);
+ assertEquals(Float.compare(-0.0f, Float.MAX_VALUE), -1);
+ assertEquals(Float.compare(-0.0f, 1.2f), -1);
+ assertEquals(Float.compare(-0.0f, 0.0f), -1);
+ assertEquals(0, Float.compare(-0.0f, -0.0f));
+ assertEquals(Float.compare(-0.0f, -1.2f), +1);
+ assertEquals(Float.compare(-0.0f, -Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(-0.0f, Float.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Float.compare(-1.2f, Float.NaN), -1);
+ assertEquals(Float.compare(-1.2f, Float.POSITIVE_INFINITY), -1);
+ assertEquals(Float.compare(-1.2f, Float.MAX_VALUE), -1);
+ assertEquals(Float.compare(-1.2f, 1.2f), -1);
+ assertEquals(Float.compare(-1.2f, 0.0f), -1);
+ assertEquals(Float.compare(-1.2f, -0.0f), -1);
+ assertEquals(0, Float.compare(-1.2f, -1.2f));
+ assertEquals(Float.compare(-1.2f, -Float.MAX_VALUE), +1);
+ assertEquals(Float.compare(-1.2f, Float.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Float.compare(-Float.MAX_VALUE, Float.NaN), -1);
+ assertEquals(Float.compare(-Float.MAX_VALUE, Float.POSITIVE_INFINITY), -1);
+ assertEquals(Float.compare(-Float.MAX_VALUE, Float.MAX_VALUE), -1);
+ assertEquals(Float.compare(-Float.MAX_VALUE, 1.2f), -1);
+ assertEquals(Float.compare(-Float.MAX_VALUE, 0.0f), -1);
+ assertEquals(Float.compare(-Float.MAX_VALUE, -0.0f), -1);
+ assertEquals(Float.compare(-Float.MAX_VALUE, -1.2f), -1);
+ assertEquals(0, Float.compare(-Float.MAX_VALUE, -Float.MAX_VALUE));
+ assertEquals(Float.compare(-Float.MAX_VALUE, Float.NEGATIVE_INFINITY), +1);
+
+ assertEquals(Float.compare(Float.NEGATIVE_INFINITY, Float.NaN), -1);
+ assertEquals(Float.compare(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY), -1);
+ assertEquals(Float.compare(Float.NEGATIVE_INFINITY, Float.MAX_VALUE), -1);
+ assertEquals(Float.compare(Float.NEGATIVE_INFINITY, 1.2f), -1);
+ assertEquals(Float.compare(Float.NEGATIVE_INFINITY, 0.0f), -1);
+ assertEquals(Float.compare(Float.NEGATIVE_INFINITY, -0.0f), -1);
+ assertEquals(Float.compare(Float.NEGATIVE_INFINITY, -1.2f), -1);
+ assertEquals(Float.compare(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE), -1);
+ assertEquals(0, Float.compare(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));
+ }
+
+ @SuppressWarnings("cast") // suppress instanceof warning check
+ @Test
+ public void testConstants() {
+ assertTrue(NumberUtils.LONG_ZERO instanceof Long);
+ assertTrue(NumberUtils.LONG_ONE instanceof Long);
+ assertTrue(NumberUtils.LONG_MINUS_ONE instanceof Long);
+ assertTrue(NumberUtils.INTEGER_ZERO instanceof Integer);
+ assertTrue(NumberUtils.INTEGER_ONE instanceof Integer);
+ assertTrue(NumberUtils.INTEGER_MINUS_ONE instanceof Integer);
+ assertTrue(NumberUtils.SHORT_ZERO instanceof Short);
+ assertTrue(NumberUtils.SHORT_ONE instanceof Short);
+ assertTrue(NumberUtils.SHORT_MINUS_ONE instanceof Short);
+ assertTrue(NumberUtils.BYTE_ZERO instanceof Byte);
+ assertTrue(NumberUtils.BYTE_ONE instanceof Byte);
+ assertTrue(NumberUtils.BYTE_MINUS_ONE instanceof Byte);
+ assertTrue(NumberUtils.DOUBLE_ZERO instanceof Double);
+ assertTrue(NumberUtils.DOUBLE_ONE instanceof Double);
+ assertTrue(NumberUtils.DOUBLE_MINUS_ONE instanceof Double);
+ assertTrue(NumberUtils.FLOAT_ZERO instanceof Float);
+ assertTrue(NumberUtils.FLOAT_ONE instanceof Float);
+ assertTrue(NumberUtils.FLOAT_MINUS_ONE instanceof Float);
+
+ assertEquals(0, NumberUtils.LONG_ZERO.longValue());
+ assertEquals(1, NumberUtils.LONG_ONE.longValue());
+ assertEquals(NumberUtils.LONG_MINUS_ONE.longValue(), -1);
+ assertEquals(0, NumberUtils.INTEGER_ZERO.intValue());
+ assertEquals(1, NumberUtils.INTEGER_ONE.intValue());
+ assertEquals(NumberUtils.INTEGER_MINUS_ONE.intValue(), -1);
+ assertEquals(0, NumberUtils.SHORT_ZERO.shortValue());
+ assertEquals(1, NumberUtils.SHORT_ONE.shortValue());
+ assertEquals(NumberUtils.SHORT_MINUS_ONE.shortValue(), -1);
+ assertEquals(0, NumberUtils.BYTE_ZERO.byteValue());
+ assertEquals(1, NumberUtils.BYTE_ONE.byteValue());
+ assertEquals(NumberUtils.BYTE_MINUS_ONE.byteValue(), -1);
+ assertEquals(0.0d, NumberUtils.DOUBLE_ZERO.doubleValue());
+ assertEquals(1.0d, NumberUtils.DOUBLE_ONE.doubleValue());
+ assertEquals(NumberUtils.DOUBLE_MINUS_ONE.doubleValue(), -1.0d);
+ assertEquals(0.0f, NumberUtils.FLOAT_ZERO.floatValue());
+ assertEquals(1.0f, NumberUtils.FLOAT_ONE.floatValue());
+ assertEquals(NumberUtils.FLOAT_MINUS_ONE.floatValue(), -1.0f);
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new NumberUtils());
+ final Constructor<?>[] cons = NumberUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(NumberUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(NumberUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testCreateBigDecimal() {
+ assertEquals(new BigDecimal("1234.5"), NumberUtils.createBigDecimal("1234.5"),
+ "createBigDecimal(String) failed");
+ assertNull(NumberUtils.createBigDecimal(null), "createBigDecimal(null) failed");
+ this.testCreateBigDecimalFailure("");
+ this.testCreateBigDecimalFailure(" ");
+ this.testCreateBigDecimalFailure("\b\t\n\f\r");
+ // Funky whitespaces
+ this.testCreateBigDecimalFailure("\u00A0\uFEFF\u000B\u000C\u001C\u001D\u001E\u001F");
+ // sign alone not valid
+ this.testCreateBigDecimalFailure("-");
+ // comment in NumberUtils suggests some implementations may incorrectly allow this
+ this.testCreateBigDecimalFailure("--");
+ this.testCreateBigDecimalFailure("--0");
+ // sign alone not valid
+ this.testCreateBigDecimalFailure("+");
+ // in case this was also allowed by some JVMs
+ this.testCreateBigDecimalFailure("++");
+ this.testCreateBigDecimalFailure("++0");
+ }
+
+ protected void testCreateBigDecimalFailure(final String str) {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createBigDecimal(str),
+ "createBigDecimal(\"" + str + "\") should have failed.");
+ }
+
+ @Test
+ public void testCreateBigInteger() {
+ assertEquals(new BigInteger("12345"), NumberUtils.createBigInteger("12345"), "createBigInteger(String) failed");
+ assertNull(NumberUtils.createBigInteger(null), "createBigInteger(null) failed");
+ this.testCreateBigIntegerFailure("");
+ this.testCreateBigIntegerFailure(" ");
+ this.testCreateBigIntegerFailure("\b\t\n\f\r");
+ // Funky whitespaces
+ this.testCreateBigIntegerFailure("\u00A0\uFEFF\u000B\u000C\u001C\u001D\u001E\u001F");
+ assertEquals(new BigInteger("255"), NumberUtils.createBigInteger("0xff"), "createBigInteger(String) failed");
+ assertEquals(new BigInteger("255"), NumberUtils.createBigInteger("0Xff"), "createBigInteger(String) failed");
+ assertEquals(new BigInteger("255"), NumberUtils.createBigInteger("#ff"), "createBigInteger(String) failed");
+ assertEquals(new BigInteger("-255"), NumberUtils.createBigInteger("-0xff"), "createBigInteger(String) failed");
+ assertEquals(new BigInteger("255"), NumberUtils.createBigInteger("0377"), "createBigInteger(String) failed");
+ assertEquals(new BigInteger("-255"), NumberUtils.createBigInteger("-0377"), "createBigInteger(String) failed");
+ assertEquals(new BigInteger("-255"), NumberUtils.createBigInteger("-0377"), "createBigInteger(String) failed");
+ assertEquals(new BigInteger("-0"), NumberUtils.createBigInteger("-0"), "createBigInteger(String) failed");
+ assertEquals(new BigInteger("0"), NumberUtils.createBigInteger("0"), "createBigInteger(String) failed");
+ testCreateBigIntegerFailure("#");
+ testCreateBigIntegerFailure("-#");
+ testCreateBigIntegerFailure("0x");
+ testCreateBigIntegerFailure("-0x");
+ // LANG-1645
+ assertEquals(new BigInteger("+FFFFFFFFFFFFFFFF", 16), NumberUtils.createBigInteger("+0xFFFFFFFFFFFFFFFF"));
+ assertEquals(new BigInteger("+FFFFFFFFFFFFFFFF", 16), NumberUtils.createBigInteger("+#FFFFFFFFFFFFFFFF"));
+ assertEquals(new BigInteger("+1234567", 8), NumberUtils.createBigInteger("+01234567"));
+ }
+
+ protected void testCreateBigIntegerFailure(final String str) {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createBigInteger(str),
+ "createBigInteger(\"" + str + "\") should have failed.");
+ }
+
+ @Test
+ public void testCreateDouble() {
+ assertEquals(Double.valueOf("1234.5"), NumberUtils.createDouble("1234.5"), "createDouble(String) failed");
+ assertNull(NumberUtils.createDouble(null), "createDouble(null) failed");
+ this.testCreateDoubleFailure("");
+ this.testCreateDoubleFailure(" ");
+ this.testCreateDoubleFailure("\b\t\n\f\r");
+ // Funky whitespaces
+ this.testCreateDoubleFailure("\u00A0\uFEFF\u000B\u000C\u001C\u001D\u001E\u001F");
+ }
+
+ protected void testCreateDoubleFailure(final String str) {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createDouble(str),
+ "createDouble(\"" + str + "\") should have failed.");
+ }
+
+ @Test
+ public void testCreateFloat() {
+ assertEquals(Float.valueOf("1234.5"), NumberUtils.createFloat("1234.5"), "createFloat(String) failed");
+ assertNull(NumberUtils.createFloat(null), "createFloat(null) failed");
+ this.testCreateFloatFailure("");
+ this.testCreateFloatFailure(" ");
+ this.testCreateFloatFailure("\b\t\n\f\r");
+ // Funky whitespaces
+ this.testCreateFloatFailure("\u00A0\uFEFF\u000B\u000C\u001C\u001D\u001E\u001F");
+ }
+
+ protected void testCreateFloatFailure(final String str) {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createFloat(str),
+ "createFloat(\"" + str + "\") should have failed.");
+ }
+
+ @Test
+ public void testCreateInteger() {
+ assertEquals(Integer.valueOf("12345"), NumberUtils.createInteger("12345"), "createInteger(String) failed");
+ assertNull(NumberUtils.createInteger(null), "createInteger(null) failed");
+ this.testCreateIntegerFailure("");
+ this.testCreateIntegerFailure(" ");
+ this.testCreateIntegerFailure("\b\t\n\f\r");
+ // Funky whitespaces
+ this.testCreateIntegerFailure("\u00A0\uFEFF\u000B\u000C\u001C\u001D\u001E\u001F");
+ // LANG-1645
+ assertEquals(Integer.decode("+0xF"), NumberUtils.createInteger("+0xF"));
+ }
+
+ protected void testCreateIntegerFailure(final String str) {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createInteger(str),
+ "createInteger(\"" + str + "\") should have failed.");
+ }
+
+ @Test
+ public void testCreateLong() {
+ assertEquals(Long.valueOf("12345"), NumberUtils.createLong("12345"), "createLong(String) failed");
+ assertNull(NumberUtils.createLong(null), "createLong(null) failed");
+ this.testCreateLongFailure("");
+ this.testCreateLongFailure(" ");
+ this.testCreateLongFailure("\b\t\n\f\r");
+ // Funky whitespaces
+ this.testCreateLongFailure("\u00A0\uFEFF\u000B\u000C\u001C\u001D\u001E\u001F");
+ // LANG-1645
+ assertEquals(Long.decode("+0xFFFFFFFF"), NumberUtils.createLong("+0xFFFFFFFF"));
+ }
+
+ protected void testCreateLongFailure(final String str) {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createLong(str),
+ "createLong(\"" + str + "\") should have failed.");
+ }
+
+ @Test
+ public void testCreateNumber() {
+ // a lot of things can go wrong
+ assertEquals(Float.valueOf("1234.5"), NumberUtils.createNumber("1234.5"), "createNumber(String) 1 failed");
+ assertEquals(Integer.valueOf("12345"), NumberUtils.createNumber("12345"), "createNumber(String) 2 failed");
+ assertEquals(Double.valueOf("1234.5"), NumberUtils.createNumber("1234.5D"), "createNumber(String) 3 failed");
+ assertEquals(Double.valueOf("1234.5"), NumberUtils.createNumber("1234.5d"), "createNumber(String) 3 failed");
+ assertEquals(Float.valueOf("1234.5"), NumberUtils.createNumber("1234.5F"), "createNumber(String) 4 failed");
+ assertEquals(Float.valueOf("1234.5"), NumberUtils.createNumber("1234.5f"), "createNumber(String) 4 failed");
+ assertEquals(Long.valueOf(Integer.MAX_VALUE + 1L), NumberUtils.createNumber("" + (Integer.MAX_VALUE + 1L)),
+ "createNumber(String) 5 failed");
+ assertEquals(Long.valueOf(12345), NumberUtils.createNumber("12345L"), "createNumber(String) 6 failed");
+ assertEquals(Long.valueOf(12345), NumberUtils.createNumber("12345l"), "createNumber(String) 6 failed");
+ assertEquals(Float.valueOf("-1234.5"), NumberUtils.createNumber("-1234.5"), "createNumber(String) 7 failed");
+ assertEquals(Integer.valueOf("-12345"), NumberUtils.createNumber("-12345"), "createNumber(String) 8 failed");
+ assertEquals(0xFADE, NumberUtils.createNumber("0xFADE").intValue(), "createNumber(String) 9a failed");
+ assertEquals(0xFADE, NumberUtils.createNumber("0Xfade").intValue(), "createNumber(String) 9b failed");
+ assertEquals(-0xFADE, NumberUtils.createNumber("-0xFADE").intValue(), "createNumber(String) 10a failed");
+ assertEquals(-0xFADE, NumberUtils.createNumber("-0Xfade").intValue(), "createNumber(String) 10b failed");
+ assertEquals(Double.valueOf("1.1E200"), NumberUtils.createNumber("1.1E200"), "createNumber(String) 11 failed");
+ assertEquals(Float.valueOf("1.1E20"), NumberUtils.createNumber("1.1E20"), "createNumber(String) 12 failed");
+ assertEquals(Double.valueOf("-1.1E200"), NumberUtils.createNumber("-1.1E200"),
+ "createNumber(String) 13 failed");
+ assertEquals(Double.valueOf("1.1E-200"), NumberUtils.createNumber("1.1E-200"),
+ "createNumber(String) 14 failed");
+ assertNull(NumberUtils.createNumber(null), "createNumber(null) failed");
+ assertEquals(new BigInteger("12345678901234567890"), NumberUtils.createNumber("12345678901234567890L"),
+ "createNumber(String) failed");
+
+ assertEquals(new BigDecimal("1.1E-700"), NumberUtils.createNumber("1.1E-700F"),
+ "createNumber(String) 15 failed");
+
+ assertEquals(Long.valueOf("10" + Integer.MAX_VALUE), NumberUtils.createNumber("10" + Integer.MAX_VALUE + "L"),
+ "createNumber(String) 16 failed");
+ assertEquals(Long.valueOf("10" + Integer.MAX_VALUE), NumberUtils.createNumber("10" + Integer.MAX_VALUE),
+ "createNumber(String) 17 failed");
+ assertEquals(new BigInteger("10" + Long.MAX_VALUE), NumberUtils.createNumber("10" + Long.MAX_VALUE),
+ "createNumber(String) 18 failed");
+
+ // LANG-521
+ assertEquals(Float.valueOf("2."), NumberUtils.createNumber("2."), "createNumber(String) LANG-521 failed");
+
+ // LANG-638
+ assertFalse(checkCreateNumber("1eE"), "createNumber(String) succeeded");
+
+ // LANG-693
+ assertEquals(Double.valueOf(Double.MAX_VALUE), NumberUtils.createNumber("" + Double.MAX_VALUE),
+ "createNumber(String) LANG-693 failed");
+
+ // LANG-822
+ // ensure that the underlying negative number would create a BigDecimal
+ final Number bigNum = NumberUtils.createNumber("-1.1E-700F");
+ assertNotNull(bigNum);
+ assertEquals(BigDecimal.class, bigNum.getClass());
+
+ // LANG-1018
+ assertEquals(Double.valueOf("-160952.54"), NumberUtils.createNumber("-160952.54"),
+ "createNumber(String) LANG-1018 failed");
+ // LANG-1187
+ assertEquals(Double.valueOf("6264583.33"), NumberUtils.createNumber("6264583.33"),
+ "createNumber(String) LANG-1187 failed");
+ // LANG-1215
+ assertEquals(Double.valueOf("193343.82"), NumberUtils.createNumber("193343.82"),
+ "createNumber(String) LANG-1215 failed");
+ // LANG-1060
+ assertEquals(Double.valueOf("001234.5678"), NumberUtils.createNumber("001234.5678"),
+ "createNumber(String) LANG-1060a failed");
+ assertEquals(Double.valueOf("+001234.5678"), NumberUtils.createNumber("+001234.5678"),
+ "createNumber(String) LANG-1060b failed");
+ assertEquals(Double.valueOf("-001234.5678"), NumberUtils.createNumber("-001234.5678"),
+ "createNumber(String) LANG-1060c failed");
+ assertEquals(Double.valueOf("0000.00000"), NumberUtils.createNumber("0000.00000d"),
+ "createNumber(String) LANG-1060d failed");
+ assertEquals(Float.valueOf("001234.56"), NumberUtils.createNumber("001234.56"),
+ "createNumber(String) LANG-1060e failed");
+ assertEquals(Float.valueOf("+001234.56"), NumberUtils.createNumber("+001234.56"),
+ "createNumber(String) LANG-1060f failed");
+ assertEquals(Float.valueOf("-001234.56"), NumberUtils.createNumber("-001234.56"),
+ "createNumber(String) LANG-1060g failed");
+ assertEquals(Float.valueOf("0000.10"), NumberUtils.createNumber("0000.10"),
+ "createNumber(String) LANG-1060h failed");
+ assertEquals(Float.valueOf("001.1E20"), NumberUtils.createNumber("001.1E20"),
+ "createNumber(String) LANG-1060i failed");
+ assertEquals(Float.valueOf("+001.1E20"), NumberUtils.createNumber("+001.1E20"),
+ "createNumber(String) LANG-1060j failed");
+ assertEquals(Float.valueOf("-001.1E20"), NumberUtils.createNumber("-001.1E20"),
+ "createNumber(String) LANG-1060k failed");
+ assertEquals(Double.valueOf("001.1E200"), NumberUtils.createNumber("001.1E200"),
+ "createNumber(String) LANG-1060l failed");
+ assertEquals(Double.valueOf("+001.1E200"), NumberUtils.createNumber("+001.1E200"),
+ "createNumber(String) LANG-1060m failed");
+ assertEquals(Double.valueOf("-001.1E200"), NumberUtils.createNumber("-001.1E200"),
+ "createNumber(String) LANG-1060n failed");
+ // LANG-1645
+ assertEquals(Integer.decode("+0xF"), NumberUtils.createNumber("+0xF"),
+ "createNumber(String) LANG-1645a failed");
+ assertEquals(Long.decode("+0xFFFFFFFF"), NumberUtils.createNumber("+0xFFFFFFFF"),
+ "createNumber(String) LANG-1645b failed");
+ assertEquals(new BigInteger("+FFFFFFFFFFFFFFFF", 16), NumberUtils.createNumber("+0xFFFFFFFFFFFFFFFF"),
+ "createNumber(String) LANG-1645c failed");
+ }
+
+ @Test
+ // Check that the code fails to create a valid number when preceded by -- rather than -
+ public void testCreateNumberFailure_1() {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createNumber("--1.1E-700F"));
+ }
+
+ @Test
+ // Check that the code fails to create a valid number when both e and E are present (with decimal)
+ public void testCreateNumberFailure_2() {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createNumber("-1.1E+0-7e00"));
+ }
+
+ @Test
+ // Check that the code fails to create a valid number when both e and E are present (no decimal)
+ public void testCreateNumberFailure_3() {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createNumber("-11E+0-7e00"));
+ }
+
+ @Test
+ // Check that the code fails to create a valid number when both e and E are present (no decimal)
+ public void testCreateNumberFailure_4() {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createNumber("1eE+00001"));
+ }
+
+ @Test
+ // Check that the code fails to create a valid number when there are multiple trailing 'f' characters (LANG-1205)
+ public void testCreateNumberFailure_5() {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createNumber("1234.5ff"));
+ }
+
+ @Test
+ // Check that the code fails to create a valid number when there are multiple trailing 'F' characters (LANG-1205)
+ public void testCreateNumberFailure_6() {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createNumber("1234.5FF"));
+ }
+
+ @Test
+ // Check that the code fails to create a valid number when there are multiple trailing 'd' characters (LANG-1205)
+ public void testCreateNumberFailure_7() {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createNumber("1234.5dd"));
+ }
+
+ @Test
+ // Check that the code fails to create a valid number when there are multiple trailing 'D' characters (LANG-1205)
+ public void testCreateNumberFailure_8() {
+ assertThrows(NumberFormatException.class, () -> NumberUtils.createNumber("1234.5DD"));
+ }
+
+ // Tests to show when magnitude causes switch to next Number type
+ // Will probably need to be adjusted if code is changed to check precision (LANG-693)
+ @Test
+ public void testCreateNumberMagnitude() {
+ // Test Float.MAX_VALUE, and same with +1 in final digit to check conversion changes to next Number type
+ assertEquals(Float.valueOf(Float.MAX_VALUE), NumberUtils.createNumber("3.4028235e+38"));
+ assertEquals(Double.valueOf(3.4028236e+38), NumberUtils.createNumber("3.4028236e+38"));
+
+ // Test Double.MAX_VALUE
+ assertEquals(Double.valueOf(Double.MAX_VALUE), NumberUtils.createNumber("1.7976931348623157e+308"));
+ // Test with +2 in final digit (+1 does not cause roll-over to BigDecimal)
+ assertEquals(new BigDecimal("1.7976931348623159e+308"), NumberUtils.createNumber("1.7976931348623159e+308"));
+
+ // Requested type is parsed as zero but the value is not zero
+ final Double nonZero1 = Double.valueOf(((double) Float.MIN_VALUE) / 2);
+ assertEquals(nonZero1, NumberUtils.createNumber(nonZero1.toString()));
+ assertEquals(nonZero1, NumberUtils.createNumber(nonZero1.toString() + "F"));
+ // Smallest double is 4.9e-324.
+ // Test a number with zero before and/or after the decimal place to hit edge cases.
+ final BigDecimal nonZero2 = new BigDecimal("4.9e-325");
+ assertEquals(nonZero2, NumberUtils.createNumber("4.9e-325"));
+ assertEquals(nonZero2, NumberUtils.createNumber("4.9e-325D"));
+ final BigDecimal nonZero3 = new BigDecimal("1e-325");
+ assertEquals(nonZero3, NumberUtils.createNumber("1e-325"));
+ assertEquals(nonZero3, NumberUtils.createNumber("1e-325D"));
+ final BigDecimal nonZero4 = new BigDecimal("0.1e-325");
+ assertEquals(nonZero4, NumberUtils.createNumber("0.1e-325"));
+ assertEquals(nonZero4, NumberUtils.createNumber("0.1e-325D"));
+
+ assertEquals(Integer.valueOf(0x12345678), NumberUtils.createNumber("0x12345678"));
+ assertEquals(Long.valueOf(0x123456789L), NumberUtils.createNumber("0x123456789"));
+
+ assertEquals(Long.valueOf(0x7fffffffffffffffL), NumberUtils.createNumber("0x7fffffffffffffff"));
+ // Does not appear to be a way to create a literal BigInteger of this magnitude
+ assertEquals(new BigInteger("7fffffffffffffff0", 16), NumberUtils.createNumber("0x7fffffffffffffff0"));
+
+ assertEquals(Long.valueOf(0x7fffffffffffffffL), NumberUtils.createNumber("#7fffffffffffffff"));
+ assertEquals(new BigInteger("7fffffffffffffff0", 16), NumberUtils.createNumber("#7fffffffffffffff0"));
+
+ assertEquals(Integer.valueOf(017777777777), NumberUtils.createNumber("017777777777")); // 31 bits
+ assertEquals(Long.valueOf(037777777777L), NumberUtils.createNumber("037777777777")); // 32 bits
+
+ // 63 bits
+ assertEquals(Long.valueOf(0777777777777777777777L), NumberUtils.createNumber("0777777777777777777777"));
+ // 64 bits
+ assertEquals(new BigInteger("1777777777777777777777", 8), NumberUtils.createNumber("01777777777777777777777"));
+ }
+
+ /**
+ * LANG-1646: Support the requested Number type (Long, Float, Double) of valid zero input.
+ */
+ @Test
+ public void testCreateNumberZero() {
+ // Handle integers
+ assertEquals(Integer.valueOf(0), NumberUtils.createNumber("0"));
+ assertEquals(Integer.valueOf(0), NumberUtils.createNumber("-0"));
+ assertEquals(Long.valueOf(0), NumberUtils.createNumber("0L"));
+ assertEquals(Long.valueOf(0), NumberUtils.createNumber("-0L"));
+
+ // Handle floating-point with optional leading sign, trailing exponent (eX)
+ // and format specifier (F or D).
+ // This should allow: 0. ; .0 ; 0.0 ; 0 (if exponent or format specifier is present)
+
+ // Exponent does not matter for zero
+ final int[] exponents = {-2345, 0, 13};
+ final String[] zeros = {"0.", ".0", "0.0", "0"};
+ final Float f0 = Float.valueOf(0);
+ final Float fn0 = Float.valueOf(-0F);
+ final Double d0 = Double.valueOf(0);
+ final Double dn0 = Double.valueOf(-0D);
+
+ for (final String zero : zeros) {
+ // Assume float if no preference.
+ // This requires a decimal point if there is no exponent.
+ if (zero.indexOf('.') != -1) {
+ assertCreateNumberZero(zero, f0, fn0);
+ }
+ for (final int exp : exponents) {
+ assertCreateNumberZero(zero + "e" + exp, f0, fn0);
+ }
+ // Type preference
+ assertCreateNumberZero(zero + "F", f0, fn0);
+ assertCreateNumberZero(zero + "D", d0, dn0);
+ for (final int exp : exponents) {
+ final String number = zero + "e" + exp;
+ assertCreateNumberZero(number + "F", f0, fn0);
+ assertCreateNumberZero(number + "D", d0, dn0);
+ }
+ }
+ }
+
+ private static void assertCreateNumberZero(final String number, final Object zero, final Object negativeZero) {
+ assertEquals(zero, NumberUtils.createNumber(number), () -> "Input: " + number);
+ assertEquals(zero, NumberUtils.createNumber("+" + number), () -> "Input: +" + number);
+ assertEquals(negativeZero, NumberUtils.createNumber("-" + number), () -> "Input: -" + number);
+ }
+
+ /**
+ * Tests isCreatable(String) and tests that createNumber(String) returns a valid number iff isCreatable(String)
+ * returns false.
+ */
+ @Test
+ public void testIsCreatable() {
+ compareIsCreatableWithCreateNumber("12345", true);
+ compareIsCreatableWithCreateNumber("1234.5", true);
+ compareIsCreatableWithCreateNumber(".12345", true);
+ compareIsCreatableWithCreateNumber("1234E5", true);
+ compareIsCreatableWithCreateNumber("1234E+5", true);
+ compareIsCreatableWithCreateNumber("1234E-5", true);
+ compareIsCreatableWithCreateNumber("123.4E5", true);
+ compareIsCreatableWithCreateNumber("-1234", true);
+ compareIsCreatableWithCreateNumber("-1234.5", true);
+ compareIsCreatableWithCreateNumber("-.12345", true);
+ compareIsCreatableWithCreateNumber("-1234E5", true);
+ compareIsCreatableWithCreateNumber("0", true);
+ compareIsCreatableWithCreateNumber("0.1", true); // LANG-1216
+ compareIsCreatableWithCreateNumber("-0", true);
+ compareIsCreatableWithCreateNumber("01234", true);
+ compareIsCreatableWithCreateNumber("-01234", true);
+ compareIsCreatableWithCreateNumber("-0xABC123", true);
+ compareIsCreatableWithCreateNumber("-0x0", true);
+ compareIsCreatableWithCreateNumber("123.4E21D", true);
+ compareIsCreatableWithCreateNumber("-221.23F", true);
+ compareIsCreatableWithCreateNumber("22338L", true);
+
+ compareIsCreatableWithCreateNumber(null, false);
+ compareIsCreatableWithCreateNumber("", false);
+ compareIsCreatableWithCreateNumber(" ", false);
+ compareIsCreatableWithCreateNumber("\r\n\t", false);
+ compareIsCreatableWithCreateNumber("--2.3", false);
+ compareIsCreatableWithCreateNumber(".12.3", false);
+ compareIsCreatableWithCreateNumber("-123E", false);
+ compareIsCreatableWithCreateNumber("-123E+-212", false);
+ compareIsCreatableWithCreateNumber("-123E2.12", false);
+ compareIsCreatableWithCreateNumber("0xGF", false);
+ compareIsCreatableWithCreateNumber("0xFAE-1", false);
+ compareIsCreatableWithCreateNumber(".", false);
+ compareIsCreatableWithCreateNumber("-0ABC123", false);
+ compareIsCreatableWithCreateNumber("123.4E-D", false);
+ compareIsCreatableWithCreateNumber("123.4ED", false);
+ compareIsCreatableWithCreateNumber("1234E5l", false);
+ compareIsCreatableWithCreateNumber("11a", false);
+ compareIsCreatableWithCreateNumber("1a", false);
+ compareIsCreatableWithCreateNumber("a", false);
+ compareIsCreatableWithCreateNumber("11g", false);
+ compareIsCreatableWithCreateNumber("11z", false);
+ compareIsCreatableWithCreateNumber("11def", false);
+ compareIsCreatableWithCreateNumber("11d11", false);
+ compareIsCreatableWithCreateNumber("11 11", false);
+ compareIsCreatableWithCreateNumber(" 1111", false);
+ compareIsCreatableWithCreateNumber("1111 ", false);
+
+ compareIsCreatableWithCreateNumber("2.", true); // LANG-521
+ compareIsCreatableWithCreateNumber("1.1L", false); // LANG-664
+ compareIsCreatableWithCreateNumber("+0xF", true); // LANG-1645
+ compareIsCreatableWithCreateNumber("+0xFFFFFFFF", true); // LANG-1645
+ compareIsCreatableWithCreateNumber("+0xFFFFFFFFFFFFFFFF", true); // LANG-1645
+ compareIsCreatableWithCreateNumber(".0", true); // LANG-1646
+ compareIsCreatableWithCreateNumber("0.", true); // LANG-1646
+ compareIsCreatableWithCreateNumber("0.D", true); // LANG-1646
+ compareIsCreatableWithCreateNumber("0e1", true); // LANG-1646
+ compareIsCreatableWithCreateNumber("0e1D", true); // LANG-1646
+ compareIsCreatableWithCreateNumber(".D", false); // LANG-1646
+ compareIsCreatableWithCreateNumber(".e10", false); // LANG-1646
+ compareIsCreatableWithCreateNumber(".e10D", false); // LANG-1646
+ }
+
+ @Test
+ public void testIsDigits() {
+ assertFalse(NumberUtils.isDigits(null), "isDigits(null) failed");
+ assertFalse(NumberUtils.isDigits(""), "isDigits('') failed");
+ assertTrue(NumberUtils.isDigits("12345"), "isDigits(String) failed");
+ assertFalse(NumberUtils.isDigits("1234.5"), "isDigits(String) neg 1 failed");
+ assertFalse(NumberUtils.isDigits("1ab"), "isDigits(String) neg 3 failed");
+ assertFalse(NumberUtils.isDigits("abc"), "isDigits(String) neg 4 failed");
+ }
+
+ /**
+ * Tests isCreatable(String) and tests that createNumber(String) returns a valid number iff isCreatable(String)
+ * returns false.
+ */
+ @Test
+ public void testIsNumber() {
+ compareIsNumberWithCreateNumber("12345", true);
+ compareIsNumberWithCreateNumber("1234.5", true);
+ compareIsNumberWithCreateNumber(".12345", true);
+ compareIsNumberWithCreateNumber("1234E5", true);
+ compareIsNumberWithCreateNumber("1234E+5", true);
+ compareIsNumberWithCreateNumber("1234E-5", true);
+ compareIsNumberWithCreateNumber("123.4E5", true);
+ compareIsNumberWithCreateNumber("-1234", true);
+ compareIsNumberWithCreateNumber("-1234.5", true);
+ compareIsNumberWithCreateNumber("-.12345", true);
+ compareIsNumberWithCreateNumber("-0001.12345", true);
+ compareIsNumberWithCreateNumber("-000.12345", true);
+ compareIsNumberWithCreateNumber("+00.12345", true);
+ compareIsNumberWithCreateNumber("+0002.12345", true);
+ compareIsNumberWithCreateNumber("-1234E5", true);
+ compareIsNumberWithCreateNumber("0", true);
+ compareIsNumberWithCreateNumber("-0", true);
+ compareIsNumberWithCreateNumber("01234", true);
+ compareIsNumberWithCreateNumber("-01234", true);
+ compareIsNumberWithCreateNumber("-0xABC123", true);
+ compareIsNumberWithCreateNumber("-0x0", true);
+ compareIsNumberWithCreateNumber("123.4E21D", true);
+ compareIsNumberWithCreateNumber("-221.23F", true);
+ compareIsNumberWithCreateNumber("22338L", true);
+
+ compareIsNumberWithCreateNumber(null, false);
+ compareIsNumberWithCreateNumber("", false);
+ compareIsNumberWithCreateNumber(" ", false);
+ compareIsNumberWithCreateNumber("\r\n\t", false);
+ compareIsNumberWithCreateNumber("--2.3", false);
+
+ compareIsNumberWithCreateNumber(".12.3", false);
+ compareIsNumberWithCreateNumber("-123E", false);
+ compareIsNumberWithCreateNumber("-123E+-212", false);
+ compareIsNumberWithCreateNumber("-123E2.12", false);
+ compareIsNumberWithCreateNumber("0xGF", false);
+ compareIsNumberWithCreateNumber("0xFAE-1", false);
+ compareIsNumberWithCreateNumber(".", false);
+ compareIsNumberWithCreateNumber("-0ABC123", false);
+ compareIsNumberWithCreateNumber("123.4E-D", false);
+ compareIsNumberWithCreateNumber("123.4ED", false);
+ compareIsNumberWithCreateNumber("+000E.12345", false);
+ compareIsNumberWithCreateNumber("-000E.12345", false);
+ compareIsNumberWithCreateNumber("1234E5l", false);
+ compareIsNumberWithCreateNumber("11a", false);
+ compareIsNumberWithCreateNumber("1a", false);
+ compareIsNumberWithCreateNumber("a", false);
+ compareIsNumberWithCreateNumber("11g", false);
+ compareIsNumberWithCreateNumber("11z", false);
+ compareIsNumberWithCreateNumber("11def", false);
+ compareIsNumberWithCreateNumber("11d11", false);
+ compareIsNumberWithCreateNumber("11 11", false);
+ compareIsNumberWithCreateNumber(" 1111", false);
+ compareIsNumberWithCreateNumber("1111 ", false);
+
+ compareIsNumberWithCreateNumber("2.", true); // LANG-521
+ compareIsNumberWithCreateNumber("1.1L", false); // LANG-664
+ compareIsNumberWithCreateNumber("+0xF", true); // LANG-1645
+ compareIsNumberWithCreateNumber("+0xFFFFFFFF", true); // LANG-1645
+ compareIsNumberWithCreateNumber("+0xFFFFFFFFFFFFFFFF", true); // LANG-1645
+ compareIsNumberWithCreateNumber(".0", true); // LANG-1646
+ compareIsNumberWithCreateNumber("0.", true); // LANG-1646
+ compareIsNumberWithCreateNumber("0.D", true); // LANG-1646
+ compareIsNumberWithCreateNumber("0e1", true); // LANG-1646
+ compareIsNumberWithCreateNumber("0e1D", true); // LANG-1646
+ compareIsNumberWithCreateNumber(".D", false); // LANG-1646
+ compareIsNumberWithCreateNumber(".e10", false); // LANG-1646
+ compareIsNumberWithCreateNumber(".e10D", false); // LANG-1646
+ }
+
+ @Test
+ public void testIsNumberLANG1252() {
+ compareIsNumberWithCreateNumber("+2", true);
+ compareIsNumberWithCreateNumber("+2.0", true);
+ }
+
+ @Test
+ public void testIsNumberLANG1385() {
+ compareIsNumberWithCreateNumber("L", false);
+ }
+
+ @Test
+ public void testIsNumberLANG971() {
+ compareIsNumberWithCreateNumber("0085", false);
+ compareIsNumberWithCreateNumber("085", false);
+ compareIsNumberWithCreateNumber("08", false);
+ compareIsNumberWithCreateNumber("07", true);
+ compareIsNumberWithCreateNumber("00", true);
+ }
+
+ @Test
+ public void testIsNumberLANG972() {
+ compareIsNumberWithCreateNumber("0xABCD", true);
+ compareIsNumberWithCreateNumber("0XABCD", true);
+ }
+
+ @Test
+ public void testIsNumberLANG992() {
+ compareIsNumberWithCreateNumber("0.0", true);
+ compareIsNumberWithCreateNumber("0.4790", true);
+ }
+
+ @Test
+ public void testIsParsable() {
+ assertFalse(NumberUtils.isParsable(null));
+ assertFalse(NumberUtils.isParsable(""));
+ assertFalse(NumberUtils.isParsable("0xC1AB"));
+ assertFalse(NumberUtils.isParsable("65CBA2"));
+ assertFalse(NumberUtils.isParsable("pendro"));
+ assertFalse(NumberUtils.isParsable("64, 2"));
+ assertFalse(NumberUtils.isParsable("64.2.2"));
+ assertFalse(NumberUtils.isParsable("64."));
+ assertFalse(NumberUtils.isParsable("64L"));
+ assertFalse(NumberUtils.isParsable("-"));
+ assertFalse(NumberUtils.isParsable("--2"));
+ assertTrue(NumberUtils.isParsable("64.2"));
+ assertTrue(NumberUtils.isParsable("64"));
+ assertTrue(NumberUtils.isParsable("018"));
+ assertTrue(NumberUtils.isParsable(".18"));
+ assertTrue(NumberUtils.isParsable("-65"));
+ assertTrue(NumberUtils.isParsable("-018"));
+ assertTrue(NumberUtils.isParsable("-018.2"));
+ assertTrue(NumberUtils.isParsable("-.236"));
+ }
+
+ @Test
+ public void testLang1087() {
+ // no sign cases
+ assertEquals(Float.class, NumberUtils.createNumber("0.0").getClass());
+ assertEquals(Float.valueOf("0.0"), NumberUtils.createNumber("0.0"));
+ // explicit positive sign cases
+ assertEquals(Float.class, NumberUtils.createNumber("+0.0").getClass());
+ assertEquals(Float.valueOf("+0.0"), NumberUtils.createNumber("+0.0"));
+ // negative sign cases
+ assertEquals(Float.class, NumberUtils.createNumber("-0.0").getClass());
+ assertEquals(Float.valueOf("-0.0"), NumberUtils.createNumber("-0.0"));
+ }
+
+ @Test
+ public void testLANG1252() {
+ compareIsCreatableWithCreateNumber("+2", true);
+ compareIsCreatableWithCreateNumber("+2.0", true);
+ }
+
+ @Test
+ public void testLang300() {
+ NumberUtils.createNumber("-1l");
+ NumberUtils.createNumber("01l");
+ NumberUtils.createNumber("1l");
+ }
+
+ @Test
+ public void testLang381() {
+ assertTrue(Double.isNaN(NumberUtils.min(1.2, 2.5, Double.NaN)));
+ assertTrue(Double.isNaN(NumberUtils.max(1.2, 2.5, Double.NaN)));
+ assertTrue(Float.isNaN(NumberUtils.min(1.2f, 2.5f, Float.NaN)));
+ assertTrue(Float.isNaN(NumberUtils.max(1.2f, 2.5f, Float.NaN)));
+
+ final double[] a = {1.2, Double.NaN, 3.7, 27.0, 42.0, Double.NaN};
+ assertTrue(Double.isNaN(NumberUtils.max(a)));
+ assertTrue(Double.isNaN(NumberUtils.min(a)));
+
+ final double[] b = {Double.NaN, 1.2, Double.NaN, 3.7, 27.0, 42.0, Double.NaN};
+ assertTrue(Double.isNaN(NumberUtils.max(b)));
+ assertTrue(Double.isNaN(NumberUtils.min(b)));
+
+ final float[] aF = {1.2f, Float.NaN, 3.7f, 27.0f, 42.0f, Float.NaN};
+ assertTrue(Float.isNaN(NumberUtils.max(aF)));
+
+ final float[] bF = {Float.NaN, 1.2f, Float.NaN, 3.7f, 27.0f, 42.0f, Float.NaN};
+ assertTrue(Float.isNaN(NumberUtils.max(bF)));
+ }
+
+ @Test
+ public void TestLang747() {
+ assertEquals(Integer.valueOf(0x8000), NumberUtils.createNumber("0x8000"));
+ assertEquals(Integer.valueOf(0x80000), NumberUtils.createNumber("0x80000"));
+ assertEquals(Integer.valueOf(0x800000), NumberUtils.createNumber("0x800000"));
+ assertEquals(Integer.valueOf(0x8000000), NumberUtils.createNumber("0x8000000"));
+ assertEquals(Integer.valueOf(0x7FFFFFFF), NumberUtils.createNumber("0x7FFFFFFF"));
+ assertEquals(Long.valueOf(0x80000000L), NumberUtils.createNumber("0x80000000"));
+ assertEquals(Long.valueOf(0xFFFFFFFFL), NumberUtils.createNumber("0xFFFFFFFF"));
+
+ // Leading zero tests
+ assertEquals(Integer.valueOf(0x8000000), NumberUtils.createNumber("0x08000000"));
+ assertEquals(Integer.valueOf(0x7FFFFFFF), NumberUtils.createNumber("0x007FFFFFFF"));
+ assertEquals(Long.valueOf(0x80000000L), NumberUtils.createNumber("0x080000000"));
+ assertEquals(Long.valueOf(0xFFFFFFFFL), NumberUtils.createNumber("0x00FFFFFFFF"));
+
+ assertEquals(Long.valueOf(0x800000000L), NumberUtils.createNumber("0x800000000"));
+ assertEquals(Long.valueOf(0x8000000000L), NumberUtils.createNumber("0x8000000000"));
+ assertEquals(Long.valueOf(0x80000000000L), NumberUtils.createNumber("0x80000000000"));
+ assertEquals(Long.valueOf(0x800000000000L), NumberUtils.createNumber("0x800000000000"));
+ assertEquals(Long.valueOf(0x8000000000000L), NumberUtils.createNumber("0x8000000000000"));
+ assertEquals(Long.valueOf(0x80000000000000L), NumberUtils.createNumber("0x80000000000000"));
+ assertEquals(Long.valueOf(0x800000000000000L), NumberUtils.createNumber("0x800000000000000"));
+ assertEquals(Long.valueOf(0x7FFFFFFFFFFFFFFFL), NumberUtils.createNumber("0x7FFFFFFFFFFFFFFF"));
+ // N.B. Cannot use a hex constant such as 0x8000000000000000L here as that is interpreted as a negative long
+ assertEquals(new BigInteger("8000000000000000", 16), NumberUtils.createNumber("0x8000000000000000"));
+ assertEquals(new BigInteger("FFFFFFFFFFFFFFFF", 16), NumberUtils.createNumber("0xFFFFFFFFFFFFFFFF"));
+
+ // Leading zero tests
+ assertEquals(Long.valueOf(0x80000000000000L), NumberUtils.createNumber("0x00080000000000000"));
+ assertEquals(Long.valueOf(0x800000000000000L), NumberUtils.createNumber("0x0800000000000000"));
+ assertEquals(Long.valueOf(0x7FFFFFFFFFFFFFFFL), NumberUtils.createNumber("0x07FFFFFFFFFFFFFFF"));
+ // N.B. Cannot use a hex constant such as 0x8000000000000000L here as that is interpreted as a negative long
+ assertEquals(new BigInteger("8000000000000000", 16), NumberUtils.createNumber("0x00008000000000000000"));
+ assertEquals(new BigInteger("FFFFFFFFFFFFFFFF", 16), NumberUtils.createNumber("0x0FFFFFFFFFFFFFFFF"));
+ }
+
+ @Test
+ public void testLANG971() {
+ compareIsCreatableWithCreateNumber("0085", false);
+ compareIsCreatableWithCreateNumber("085", false);
+ compareIsCreatableWithCreateNumber("08", false);
+ compareIsCreatableWithCreateNumber("07", true);
+ compareIsCreatableWithCreateNumber("00", true);
+ }
+
+ @Test
+ public void testLANG972() {
+ compareIsCreatableWithCreateNumber("0xABCD", true);
+ compareIsCreatableWithCreateNumber("0XABCD", true);
+ }
+
+ @Test
+ public void testLANG992() {
+ compareIsCreatableWithCreateNumber("0.0", true);
+ compareIsCreatableWithCreateNumber("0.4790", true);
+ }
+
+ @Test
+ public void testMaxByte() {
+ assertEquals((byte) 5, NumberUtils.max((byte) 5), "max(byte[]) failed for array length 1");
+ assertEquals((byte) 9, NumberUtils.max((byte) 6, (byte) 9), "max(byte[]) failed for array length 2");
+ assertEquals((byte) 10, NumberUtils.max((byte) -10, (byte) -5, (byte) 0, (byte) 5, (byte) 10),
+ "max(byte[]) failed for array length 5");
+ assertEquals((byte) 10, NumberUtils.max((byte) -10, (byte) -5, (byte) 0, (byte) 5, (byte) 10));
+ assertEquals((byte) 10, NumberUtils.max((byte) -5, (byte) 0, (byte) 10, (byte) 5, (byte) -10));
+ }
+
+ @Test
+ public void testMaxByte_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::max);
+ }
+
+ @Test
+ public void testMaxByte_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.max((byte[]) null));
+ }
+
+ @Test
+ public void testMaxDouble() {
+ final double[] d = null;
+ assertThrows(NullPointerException.class, () -> NumberUtils.max(d), "No exception was thrown for null input.");
+
+ assertThrows(IllegalArgumentException.class, NumberUtils::max, "No exception was thrown for empty input.");
+
+ assertEquals(5.1f, NumberUtils.max(5.1f), "max(double[]) failed for array length 1");
+ assertEquals(9.2f, NumberUtils.max(6.3f, 9.2f), "max(double[]) failed for array length 2");
+ assertEquals(10.4f, NumberUtils.max(-10.5f, -5.6f, 0, 5.7f, 10.4f), "max(double[]) failed for float length 5");
+ assertEquals(10, NumberUtils.max(-10, -5, 0, 5, 10), 0.0001);
+ assertEquals(10, NumberUtils.max(-5, 0, 10, 5, -10), 0.0001);
+ }
+
+ @Test
+ public void testMaxDouble_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::max);
+ }
+
+ @Test
+ public void testMaxDouble_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.max((double[]) null));
+ }
+
+ @Test
+ public void testMaxFloat() {
+ assertEquals(5.1f, NumberUtils.max(5.1f), "max(float[]) failed for array length 1");
+ assertEquals(9.2f, NumberUtils.max(6.3f, 9.2f), "max(float[]) failed for array length 2");
+ assertEquals(10.4f, NumberUtils.max(-10.5f, -5.6f, 0, 5.7f, 10.4f), "max(float[]) failed for float length 5");
+ assertEquals(10, NumberUtils.max(-10, -5, 0, 5, 10), 0.0001f);
+ assertEquals(10, NumberUtils.max(-5, 0, 10, 5, -10), 0.0001f);
+ }
+
+ @Test
+ public void testMaxFloat_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::max);
+ }
+
+ @Test
+ public void testMaxFloat_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.max((float[]) null));
+ }
+
+ @Test
+ public void testMaximumByte() {
+ final byte low = 123;
+ final byte mid = 123 + 1;
+ final byte high = 123 + 2;
+ assertEquals(high, NumberUtils.max(low, mid, high), "maximum(byte, byte, byte) 1 failed");
+ assertEquals(high, NumberUtils.max(mid, low, high), "maximum(byte, byte, byte) 2 failed");
+ assertEquals(high, NumberUtils.max(mid, high, low), "maximum(byte, byte, byte) 3 failed");
+ assertEquals(high, NumberUtils.max(high, mid, high), "maximum(byte, byte, byte) 4 failed");
+ }
+
+ @Test
+ public void testMaximumDouble() {
+ final double low = 12.3;
+ final double mid = 12.3 + 1;
+ final double high = 12.3 + 2;
+ assertEquals(high, NumberUtils.max(low, mid, high), 0.0001);
+ assertEquals(high, NumberUtils.max(mid, low, high), 0.0001);
+ assertEquals(high, NumberUtils.max(mid, high, low), 0.0001);
+ assertEquals(mid, NumberUtils.max(low, mid, low), 0.0001);
+ assertEquals(high, NumberUtils.max(high, mid, high), 0.0001);
+ }
+
+ @Test
+ public void testMaximumFloat() {
+ final float low = 12.3f;
+ final float mid = 12.3f + 1;
+ final float high = 12.3f + 2;
+ assertEquals(high, NumberUtils.max(low, mid, high), 0.0001f);
+ assertEquals(high, NumberUtils.max(mid, low, high), 0.0001f);
+ assertEquals(high, NumberUtils.max(mid, high, low), 0.0001f);
+ assertEquals(mid, NumberUtils.max(low, mid, low), 0.0001f);
+ assertEquals(high, NumberUtils.max(high, mid, high), 0.0001f);
+ }
+
+ @Test
+ public void testMaximumInt() {
+ assertEquals(12345, NumberUtils.max(12345, 12345 - 1, 12345 - 2), "maximum(int, int, int) 1 failed");
+ assertEquals(12345, NumberUtils.max(12345 - 1, 12345, 12345 - 2), "maximum(int, int, int) 2 failed");
+ assertEquals(12345, NumberUtils.max(12345 - 1, 12345 - 2, 12345), "maximum(int, int, int) 3 failed");
+ assertEquals(12345, NumberUtils.max(12345 - 1, 12345, 12345), "maximum(int, int, int) 4 failed");
+ assertEquals(12345, NumberUtils.max(12345, 12345, 12345), "maximum(int, int, int) 5 failed");
+ }
+
+ @Test
+ public void testMaximumLong() {
+ assertEquals(12345L, NumberUtils.max(12345L, 12345L - 1L, 12345L - 2L), "maximum(long, long, long) 1 failed");
+ assertEquals(12345L, NumberUtils.max(12345L - 1L, 12345L, 12345L - 2L), "maximum(long, long, long) 2 failed");
+ assertEquals(12345L, NumberUtils.max(12345L - 1L, 12345L - 2L, 12345L), "maximum(long, long, long) 3 failed");
+ assertEquals(12345L, NumberUtils.max(12345L - 1L, 12345L, 12345L), "maximum(long, long, long) 4 failed");
+ assertEquals(12345L, NumberUtils.max(12345L, 12345L, 12345L), "maximum(long, long, long) 5 failed");
+ }
+
+ @Test
+ public void testMaximumShort() {
+ final short low = 1234;
+ final short mid = 1234 + 1;
+ final short high = 1234 + 2;
+ assertEquals(high, NumberUtils.max(low, mid, high), "maximum(short, short, short) 1 failed");
+ assertEquals(high, NumberUtils.max(mid, low, high), "maximum(short, short, short) 2 failed");
+ assertEquals(high, NumberUtils.max(mid, high, low), "maximum(short, short, short) 3 failed");
+ assertEquals(high, NumberUtils.max(high, mid, high), "maximum(short, short, short) 4 failed");
+ }
+
+ @Test
+ public void testMaxInt() {
+ assertEquals(5, NumberUtils.max(5), "max(int[]) failed for array length 1");
+ assertEquals(9, NumberUtils.max(6, 9), "max(int[]) failed for array length 2");
+ assertEquals(10, NumberUtils.max(-10, -5, 0, 5, 10), "max(int[]) failed for array length 5");
+ assertEquals(10, NumberUtils.max(-10, -5, 0, 5, 10));
+ assertEquals(10, NumberUtils.max(-5, 0, 10, 5, -10));
+ }
+
+ @Test
+ public void testMaxInt_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::max);
+ }
+
+ @Test
+ public void testMaxInt_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.max((int[]) null));
+ }
+
+ @Test
+ public void testMaxLong() {
+ assertEquals(5L, NumberUtils.max(5L), "max(long[]) failed for array length 1");
+ assertEquals(9L, NumberUtils.max(6L, 9L), "max(long[]) failed for array length 2");
+ assertEquals(10L, NumberUtils.max(-10L, -5L, 0L, 5L, 10L), "max(long[]) failed for array length 5");
+ assertEquals(10L, NumberUtils.max(-10L, -5L, 0L, 5L, 10L));
+ assertEquals(10L, NumberUtils.max(-5L, 0L, 10L, 5L, -10L));
+ }
+
+ @Test
+ public void testMaxLong_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::max);
+ }
+
+ @Test
+ public void testMaxLong_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.max((long[]) null));
+ }
+
+ @Test
+ public void testMaxShort() {
+ assertEquals((short) 5, NumberUtils.max((short) 5), "max(short[]) failed for array length 1");
+ assertEquals((short) 9, NumberUtils.max((short) 6, (short) 9), "max(short[]) failed for array length 2");
+ assertEquals((short) 10, NumberUtils.max((short) -10, (short) -5, (short) 0, (short) 5, (short) 10),
+ "max(short[]) failed for array length 5");
+ assertEquals((short) 10, NumberUtils.max((short) -10, (short) -5, (short) 0, (short) 5, (short) 10));
+ assertEquals((short) 10, NumberUtils.max((short) -5, (short) 0, (short) 10, (short) 5, (short) -10));
+ }
+
+ @Test
+ public void testMaxShort_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::max);
+ }
+
+ @Test
+ public void testMaxShort_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.max((short[]) null));
+ }
+
+ @Test
+ public void testMinByte() {
+ assertEquals((byte) 5, NumberUtils.min((byte) 5), "min(byte[]) failed for array length 1");
+ assertEquals((byte) 6, NumberUtils.min((byte) 6, (byte) 9), "min(byte[]) failed for array length 2");
+
+ assertEquals((byte) -10, NumberUtils.min((byte) -10, (byte) -5, (byte) 0, (byte) 5, (byte) 10));
+ assertEquals((byte) -10, NumberUtils.min((byte) -5, (byte) 0, (byte) -10, (byte) 5, (byte) 10));
+ }
+
+ @Test
+ public void testMinByte_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::min);
+ }
+
+ @Test
+ public void testMinByte_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.min((byte[]) null));
+ }
+
+ @Test
+ public void testMinDouble() {
+ assertEquals(5.12, NumberUtils.min(5.12), "min(double[]) failed for array length 1");
+ assertEquals(6.23, NumberUtils.min(6.23, 9.34), "min(double[]) failed for array length 2");
+ assertEquals(-10.45, NumberUtils.min(-10.45, -5.56, 0, 5.67, 10.78), "min(double[]) failed for array length 5");
+ assertEquals(-10, NumberUtils.min(-10, -5, 0, 5, 10), 0.0001);
+ assertEquals(-10, NumberUtils.min(-5, 0, -10, 5, 10), 0.0001);
+ assertEquals(5.12, NumberUtils.min(6.11, 5.12));
+ }
+
+ @Test
+ public void testMinDouble_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::min);
+ }
+
+ @Test
+ public void testMinDouble_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.min((double[]) null));
+ }
+
+ @Test
+ public void testMinFloat() {
+ assertEquals(5.9f, NumberUtils.min(5.9f), "min(float[]) failed for array length 1");
+ assertEquals(6.8f, NumberUtils.min(6.8f, 9.7f), "min(float[]) failed for array length 2");
+ assertEquals(-10.6f, NumberUtils.min(-10.6f, -5.5f, 0, 5.4f, 10.3f), "min(float[]) failed for array length 5");
+ assertEquals(-10, NumberUtils.min(-10, -5, 0, 5, 10), 0.0001f);
+ assertEquals(-10, NumberUtils.min(-5, 0, -10, 5, 10), 0.0001f);
+ assertEquals(Float.NaN, NumberUtils.min(6.8f, Float.NaN));
+ assertEquals(3.7f, NumberUtils.min(6.8f, 3.7f));
+ }
+
+ @Test
+ public void testMinFloat_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::min);
+ }
+
+ @Test
+ public void testMinFloat_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.min((float[]) null));
+ }
+
+ @Test
+ public void testMinimumByte() {
+ final byte low = 123;
+ final byte mid = 123 + 1;
+ final byte high = 123 + 2;
+ assertEquals(low, NumberUtils.min(low, mid, high), "minimum(byte, byte, byte) 1 failed");
+ assertEquals(low, NumberUtils.min(mid, low, high), "minimum(byte, byte, byte) 2 failed");
+ assertEquals(low, NumberUtils.min(mid, high, low), "minimum(byte, byte, byte) 3 failed");
+ assertEquals(low, NumberUtils.min(low, mid, low), "minimum(byte, byte, byte) 4 failed");
+ }
+
+ @Test
+ public void testMinimumDouble() {
+ final double low = 12.3;
+ final double mid = 12.3 + 1;
+ final double high = 12.3 + 2;
+ assertEquals(low, NumberUtils.min(low, mid, high), 0.0001);
+ assertEquals(low, NumberUtils.min(mid, low, high), 0.0001);
+ assertEquals(low, NumberUtils.min(mid, high, low), 0.0001);
+ assertEquals(low, NumberUtils.min(low, mid, low), 0.0001);
+ assertEquals(mid, NumberUtils.min(high, mid, high), 0.0001);
+ }
+
+ @Test
+ public void testMinimumFloat() {
+ final float low = 12.3f;
+ final float mid = 12.3f + 1;
+ final float high = 12.3f + 2;
+ assertEquals(low, NumberUtils.min(low, mid, high), 0.0001f);
+ assertEquals(low, NumberUtils.min(mid, low, high), 0.0001f);
+ assertEquals(low, NumberUtils.min(mid, high, low), 0.0001f);
+ assertEquals(low, NumberUtils.min(low, mid, low), 0.0001f);
+ assertEquals(mid, NumberUtils.min(high, mid, high), 0.0001f);
+ }
+
+ @Test
+ public void testMinimumInt() {
+ assertEquals(12345, NumberUtils.min(12345, 12345 + 1, 12345 + 2), "minimum(int, int, int) 1 failed");
+ assertEquals(12345, NumberUtils.min(12345 + 1, 12345, 12345 + 2), "minimum(int, int, int) 2 failed");
+ assertEquals(12345, NumberUtils.min(12345 + 1, 12345 + 2, 12345), "minimum(int, int, int) 3 failed");
+ assertEquals(12345, NumberUtils.min(12345 + 1, 12345, 12345), "minimum(int, int, int) 4 failed");
+ assertEquals(12345, NumberUtils.min(12345, 12345, 12345), "minimum(int, int, int) 5 failed");
+ }
+
+ @Test
+ public void testMinimumLong() {
+ assertEquals(12345L, NumberUtils.min(12345L, 12345L + 1L, 12345L + 2L), "minimum(long, long, long) 1 failed");
+ assertEquals(12345L, NumberUtils.min(12345L + 1L, 12345L, 12345 + 2L), "minimum(long, long, long) 2 failed");
+ assertEquals(12345L, NumberUtils.min(12345L + 1L, 12345L + 2L, 12345L), "minimum(long, long, long) 3 failed");
+ assertEquals(12345L, NumberUtils.min(12345L + 1L, 12345L, 12345L), "minimum(long, long, long) 4 failed");
+ assertEquals(12345L, NumberUtils.min(12345L, 12345L, 12345L), "minimum(long, long, long) 5 failed");
+ }
+
+ @Test
+ public void testMinimumShort() {
+ final short low = 1234;
+ final short mid = 1234 + 1;
+ final short high = 1234 + 2;
+ assertEquals(low, NumberUtils.min(low, mid, high), "minimum(short, short, short) 1 failed");
+ assertEquals(low, NumberUtils.min(mid, low, high), "minimum(short, short, short) 2 failed");
+ assertEquals(low, NumberUtils.min(mid, high, low), "minimum(short, short, short) 3 failed");
+ assertEquals(low, NumberUtils.min(low, mid, low), "minimum(short, short, short) 4 failed");
+ }
+
+ @Test
+ public void testMinInt() {
+ assertEquals(5, NumberUtils.min(5), "min(int[]) failed for array length 1");
+ assertEquals(6, NumberUtils.min(6, 9), "min(int[]) failed for array length 2");
+
+ assertEquals(-10, NumberUtils.min(-10, -5, 0, 5, 10));
+ assertEquals(-10, NumberUtils.min(-5, 0, -10, 5, 10));
+ }
+
+ @Test
+ public void testMinInt_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::min);
+ }
+
+ @Test
+ public void testMinInt_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.min((int[]) null));
+ }
+
+ @Test
+ public void testMinLong() {
+ assertEquals(5L, NumberUtils.min(5L), "min(long[]) failed for array length 1");
+ assertEquals(6L, NumberUtils.min(6L, 9L), "min(long[]) failed for array length 2");
+
+ assertEquals(-10L, NumberUtils.min(-10L, -5L, 0L, 5L, 10L));
+ assertEquals(-10L, NumberUtils.min(-5L, 0L, -10L, 5L, 10L));
+ }
+
+ @Test
+ public void testMinLong_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::min);
+ }
+
+ @Test
+ public void testMinLong_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.min((long[]) null));
+ }
+
+ @Test
+ public void testMinShort() {
+ assertEquals((short) 5, NumberUtils.min((short) 5), "min(short[]) failed for array length 1");
+ assertEquals((short) 6, NumberUtils.min((short) 6, (short) 9), "min(short[]) failed for array length 2");
+
+ assertEquals((short) -10, NumberUtils.min((short) -10, (short) -5, (short) 0, (short) 5, (short) 10));
+ assertEquals((short) -10, NumberUtils.min((short) -5, (short) 0, (short) -10, (short) 5, (short) 10));
+ }
+
+ @Test
+ public void testMinShort_emptyArray() {
+ assertThrows(IllegalArgumentException.class, NumberUtils::min);
+ }
+
+ @Test
+ public void testMinShort_nullArray() {
+ assertThrows(NullPointerException.class, () -> NumberUtils.min((short[]) null));
+ }
+
+ /**
+ * Test for {(@link NumberUtils#createNumber(String)}
+ */
+ @Test
+ public void testStringCreateNumberEnsureNoPrecisionLoss() {
+ final String shouldBeFloat = "1.23";
+ final String shouldBeDouble = "3.40282354e+38";
+ final String shouldBeBigDecimal = "1.797693134862315759e+308";
+ assertTrue(NumberUtils.createNumber(shouldBeFloat) instanceof Float);
+ assertTrue(NumberUtils.createNumber(shouldBeDouble) instanceof Double);
+ assertTrue(NumberUtils.createNumber(shouldBeBigDecimal) instanceof BigDecimal);
+ // LANG-1060
+ assertTrue(NumberUtils.createNumber("001.12") instanceof Float);
+ assertTrue(NumberUtils.createNumber("-001.12") instanceof Float);
+ assertTrue(NumberUtils.createNumber("+001.12") instanceof Float);
+ assertTrue(NumberUtils.createNumber("003.40282354e+38") instanceof Double);
+ assertTrue(NumberUtils.createNumber("-003.40282354e+38") instanceof Double);
+ assertTrue(NumberUtils.createNumber("+003.40282354e+38") instanceof Double);
+ assertTrue(NumberUtils.createNumber("0001.797693134862315759e+308") instanceof BigDecimal);
+ assertTrue(NumberUtils.createNumber("-001.797693134862315759e+308") instanceof BigDecimal);
+ assertTrue(NumberUtils.createNumber("+001.797693134862315759e+308") instanceof BigDecimal);
+ //LANG-1613
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MIN_NORMAL)) instanceof Double);
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MIN_NORMAL) + "D") instanceof Double);
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MIN_NORMAL) + "F") instanceof Double);
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MIN_VALUE)) instanceof Double);
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MIN_VALUE) + "D") instanceof Double);
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MIN_VALUE) + "F") instanceof Double);
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MAX_VALUE)) instanceof Double);
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MAX_VALUE) + "D") instanceof Double);
+ assertTrue(NumberUtils.createNumber(Double.toString(Double.MAX_VALUE) + "F") instanceof Double);
+ assertTrue(NumberUtils.createNumber("4.9e-324D") instanceof Double);
+ assertTrue(NumberUtils.createNumber("4.9e-324F") instanceof Double);
+ }
+
+ /**
+ * Test for {@link NumberUtils#toDouble(String)}.
+ */
+ @Test
+ public void testStringToDoubleString() {
+ assertEquals(NumberUtils.toDouble("-1.2345"), -1.2345d, "toDouble(String) 1 failed");
+ assertEquals(1.2345d, NumberUtils.toDouble("1.2345"), "toDouble(String) 2 failed");
+ assertEquals(0.0d, NumberUtils.toDouble("abc"), "toDouble(String) 3 failed");
+ // LANG-1060
+ assertEquals(NumberUtils.toDouble("-001.2345"), -1.2345d, "toDouble(String) 4 failed");
+ assertEquals(1.2345d, NumberUtils.toDouble("+001.2345"), "toDouble(String) 5 failed");
+ assertEquals(1.2345d, NumberUtils.toDouble("001.2345"), "toDouble(String) 6 failed");
+ assertEquals(0d, NumberUtils.toDouble("000.00000"), "toDouble(String) 7 failed");
+
+ assertEquals(NumberUtils.toDouble(Double.MAX_VALUE + ""), Double.MAX_VALUE,
+ "toDouble(Double.MAX_VALUE) failed");
+ assertEquals(NumberUtils.toDouble(Double.MIN_VALUE + ""), Double.MIN_VALUE,
+ "toDouble(Double.MIN_VALUE) failed");
+ assertEquals(0.0d, NumberUtils.toDouble(""), "toDouble(empty) failed");
+ assertEquals(0.0d, NumberUtils.toDouble((String) null), "toDouble(null) failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toDouble(String, double)}.
+ */
+ @Test
+ public void testStringToDoubleStringD() {
+ assertEquals(1.2345d, NumberUtils.toDouble("1.2345", 5.1d), "toDouble(String, int) 1 failed");
+ assertEquals(5.0d, NumberUtils.toDouble("a", 5.0d), "toDouble(String, int) 2 failed");
+ // LANG-1060
+ assertEquals(1.2345d, NumberUtils.toDouble("001.2345", 5.1d), "toDouble(String, int) 3 failed");
+ assertEquals(NumberUtils.toDouble("-001.2345", 5.1d), -1.2345d, "toDouble(String, int) 4 failed");
+ assertEquals(1.2345d, NumberUtils.toDouble("+001.2345", 5.1d), "toDouble(String, int) 5 failed");
+ assertEquals(0d, NumberUtils.toDouble("000.00", 5.1d), "toDouble(String, int) 7 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toByte(String)}.
+ */
+ @Test
+ public void testToByteString() {
+ assertEquals(123, NumberUtils.toByte("123"), "toByte(String) 1 failed");
+ assertEquals(0, NumberUtils.toByte("abc"), "toByte(String) 2 failed");
+ assertEquals(0, NumberUtils.toByte(""), "toByte(empty) failed");
+ assertEquals(0, NumberUtils.toByte(null), "toByte(null) failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toByte(String, byte)}.
+ */
+ @Test
+ public void testToByteStringI() {
+ assertEquals(123, NumberUtils.toByte("123", (byte) 5), "toByte(String, byte) 1 failed");
+ assertEquals(5, NumberUtils.toByte("12.3", (byte) 5), "toByte(String, byte) 2 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toFloat(String)}.
+ */
+ @Test
+ public void testToFloatString() {
+ assertEquals(NumberUtils.toFloat("-1.2345"), -1.2345f, "toFloat(String) 1 failed");
+ assertEquals(1.2345f, NumberUtils.toFloat("1.2345"), "toFloat(String) 2 failed");
+ assertEquals(0.0f, NumberUtils.toFloat("abc"), "toFloat(String) 3 failed");
+ // LANG-1060
+ assertEquals(NumberUtils.toFloat("-001.2345"), -1.2345f, "toFloat(String) 4 failed");
+ assertEquals(1.2345f, NumberUtils.toFloat("+001.2345"), "toFloat(String) 5 failed");
+ assertEquals(1.2345f, NumberUtils.toFloat("001.2345"), "toFloat(String) 6 failed");
+ assertEquals(0f, NumberUtils.toFloat("000.00"), "toFloat(String) 7 failed");
+
+ assertEquals(NumberUtils.toFloat(Float.MAX_VALUE + ""), Float.MAX_VALUE, "toFloat(Float.MAX_VALUE) failed");
+ assertEquals(NumberUtils.toFloat(Float.MIN_VALUE + ""), Float.MIN_VALUE, "toFloat(Float.MIN_VALUE) failed");
+ assertEquals(0.0f, NumberUtils.toFloat(""), "toFloat(empty) failed");
+ assertEquals(0.0f, NumberUtils.toFloat(null), "toFloat(null) failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toFloat(String, float)}.
+ */
+ @Test
+ public void testToFloatStringF() {
+ assertEquals(1.2345f, NumberUtils.toFloat("1.2345", 5.1f), "toFloat(String, int) 1 failed");
+ assertEquals(5.0f, NumberUtils.toFloat("a", 5.0f), "toFloat(String, int) 2 failed");
+ // LANG-1060
+ assertEquals(5.0f, NumberUtils.toFloat("-001Z.2345", 5.0f), "toFloat(String, int) 3 failed");
+ assertEquals(5.0f, NumberUtils.toFloat("+001AB.2345", 5.0f), "toFloat(String, int) 4 failed");
+ assertEquals(5.0f, NumberUtils.toFloat("001Z.2345", 5.0f), "toFloat(String, int) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toInt(String)}.
+ */
+ @Test
+ public void testToIntString() {
+ assertEquals(12345, NumberUtils.toInt("12345"), "toInt(String) 1 failed");
+ assertEquals(0, NumberUtils.toInt("abc"), "toInt(String) 2 failed");
+ assertEquals(0, NumberUtils.toInt(""), "toInt(empty) failed");
+ assertEquals(0, NumberUtils.toInt(null), "toInt(null) failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toInt(String, int)}.
+ */
+ @Test
+ public void testToIntStringI() {
+ assertEquals(12345, NumberUtils.toInt("12345", 5), "toInt(String, int) 1 failed");
+ assertEquals(5, NumberUtils.toInt("1234.5", 5), "toInt(String, int) 2 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toLong(String)}.
+ */
+ @Test
+ public void testToLongString() {
+ assertEquals(12345L, NumberUtils.toLong("12345"), "toLong(String) 1 failed");
+ assertEquals(0L, NumberUtils.toLong("abc"), "toLong(String) 2 failed");
+ assertEquals(0L, NumberUtils.toLong("1L"), "toLong(String) 3 failed");
+ assertEquals(0L, NumberUtils.toLong("1l"), "toLong(String) 4 failed");
+ assertEquals(NumberUtils.toLong(Long.MAX_VALUE + ""), Long.MAX_VALUE, "toLong(Long.MAX_VALUE) failed");
+ assertEquals(NumberUtils.toLong(Long.MIN_VALUE + ""), Long.MIN_VALUE, "toLong(Long.MIN_VALUE) failed");
+ assertEquals(0L, NumberUtils.toLong(""), "toLong(empty) failed");
+ assertEquals(0L, NumberUtils.toLong(null), "toLong(null) failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toLong(String, long)}.
+ */
+ @Test
+ public void testToLongStringL() {
+ assertEquals(12345L, NumberUtils.toLong("12345", 5L), "toLong(String, long) 1 failed");
+ assertEquals(5L, NumberUtils.toLong("1234.5", 5L), "toLong(String, long) 2 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toScaledBigDecimal(BigDecimal)}.
+ */
+ @Test
+ public void testToScaledBigDecimalBigDecimal() {
+ assertEquals(NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(123.456)), BigDecimal.valueOf(123.46),
+ "toScaledBigDecimal(BigDecimal) 1 failed");
+ // Test RoundingMode.HALF_EVEN default rounding.
+ assertEquals(NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.515)), BigDecimal.valueOf(23.52),
+ "toScaledBigDecimal(BigDecimal) 2 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.525)), BigDecimal.valueOf(23.52),
+ "toScaledBigDecimal(BigDecimal) 3 failed");
+ assertEquals("2352.00",
+ NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.525)).multiply(BigDecimal.valueOf(100)).toString(),
+ "toScaledBigDecimal(BigDecimal) 4 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal((BigDecimal) null), BigDecimal.ZERO,
+ "toScaledBigDecimal(BigDecimal) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toScaledBigDecimal(BigDecimal, int, RoundingMode)}.
+ */
+ @Test
+ public void testToScaledBigDecimalBigDecimalIRM() {
+ assertEquals(NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(123.456), 1, RoundingMode.CEILING),
+ BigDecimal.valueOf(123.5), "toScaledBigDecimal(BigDecimal, int, RoundingMode) 1 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.5159), 3, RoundingMode.FLOOR),
+ BigDecimal.valueOf(23.515), "toScaledBigDecimal(BigDecimal, int, RoundingMode) 2 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.525), 2, RoundingMode.HALF_UP),
+ BigDecimal.valueOf(23.53), "toScaledBigDecimal(BigDecimal, int, RoundingMode) 3 failed");
+ assertEquals("23521.0000",
+ NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.521), 4, RoundingMode.HALF_EVEN)
+ .multiply(BigDecimal.valueOf(1000)).toString(),
+ "toScaledBigDecimal(BigDecimal, int, RoundingMode) 4 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal((BigDecimal) null, 2, RoundingMode.HALF_UP), BigDecimal.ZERO,
+ "toScaledBigDecimal(BigDecimal, int, RoundingMode) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toScaledBigDecimal(Double)}.
+ */
+ @Test
+ public void testToScaledBigDecimalDouble() {
+ assertEquals(NumberUtils.toScaledBigDecimal(Double.valueOf(123.456d)), BigDecimal.valueOf(123.46),
+ "toScaledBigDecimal(Double) 1 failed");
+ // Test RoundingMode.HALF_EVEN default rounding.
+ assertEquals(NumberUtils.toScaledBigDecimal(Double.valueOf(23.515d)), BigDecimal.valueOf(23.52),
+ "toScaledBigDecimal(Double) 2 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal(Double.valueOf(23.525d)), BigDecimal.valueOf(23.52),
+ "toScaledBigDecimal(Double) 3 failed");
+ assertEquals("2352.00",
+ NumberUtils.toScaledBigDecimal(Double.valueOf(23.525d)).multiply(BigDecimal.valueOf(100)).toString(),
+ "toScaledBigDecimal(Double) 4 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal((Double) null), BigDecimal.ZERO,
+ "toScaledBigDecimal(Double) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toScaledBigDecimal(Double, int, RoundingMode)}.
+ */
+ @Test
+ public void testToScaledBigDecimalDoubleIRM() {
+ assertEquals(NumberUtils.toScaledBigDecimal(Double.valueOf(123.456d), 1, RoundingMode.CEILING),
+ BigDecimal.valueOf(123.5), "toScaledBigDecimal(Double, int, RoundingMode) 1 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal(Double.valueOf(23.5159d), 3, RoundingMode.FLOOR),
+ BigDecimal.valueOf(23.515), "toScaledBigDecimal(Double, int, RoundingMode) 2 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal(Double.valueOf(23.525d), 2, RoundingMode.HALF_UP),
+ BigDecimal.valueOf(23.53), "toScaledBigDecimal(Double, int, RoundingMode) 3 failed");
+ assertEquals("23521.0000",
+ NumberUtils.toScaledBigDecimal(Double.valueOf(23.521d), 4, RoundingMode.HALF_EVEN)
+ .multiply(BigDecimal.valueOf(1000)).toString(),
+ "toScaledBigDecimal(Double, int, RoundingMode) 4 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal((Double) null, 2, RoundingMode.HALF_UP), BigDecimal.ZERO,
+ "toScaledBigDecimal(Double, int, RoundingMode) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toScaledBigDecimal(Float)}.
+ */
+ @Test
+ public void testToScaledBigDecimalFloat() {
+ assertEquals(NumberUtils.toScaledBigDecimal(Float.valueOf(123.456f)), BigDecimal.valueOf(123.46),
+ "toScaledBigDecimal(Float) 1 failed");
+ // Test RoundingMode.HALF_EVEN default rounding.
+ assertEquals(NumberUtils.toScaledBigDecimal(Float.valueOf(23.515f)), BigDecimal.valueOf(23.51),
+ "toScaledBigDecimal(Float) 2 failed");
+ // Note. NumberUtils.toScaledBigDecimal(Float.valueOf(23.515f)).equals(BigDecimal.valueOf(23.51))
+ // because of roundoff error. It is ok.
+ assertEquals(NumberUtils.toScaledBigDecimal(Float.valueOf(23.525f)), BigDecimal.valueOf(23.52),
+ "toScaledBigDecimal(Float) 3 failed");
+ assertEquals("2352.00",
+ NumberUtils.toScaledBigDecimal(Float.valueOf(23.525f)).multiply(BigDecimal.valueOf(100)).toString(),
+ "toScaledBigDecimal(Float) 4 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal((Float) null), BigDecimal.ZERO,
+ "toScaledBigDecimal(Float) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toScaledBigDecimal(Float, int, RoundingMode)}.
+ */
+ @Test
+ public void testToScaledBigDecimalFloatIRM() {
+ assertEquals(NumberUtils.toScaledBigDecimal(Float.valueOf(123.456f), 1, RoundingMode.CEILING),
+ BigDecimal.valueOf(123.5), "toScaledBigDecimal(Float, int, RoundingMode) 1 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal(Float.valueOf(23.5159f), 3, RoundingMode.FLOOR),
+ BigDecimal.valueOf(23.515), "toScaledBigDecimal(Float, int, RoundingMode) 2 failed");
+ // The following happens due to roundoff error. We're ok with this.
+ assertEquals(NumberUtils.toScaledBigDecimal(Float.valueOf(23.525f), 2, RoundingMode.HALF_UP),
+ BigDecimal.valueOf(23.52), "toScaledBigDecimal(Float, int, RoundingMode) 3 failed");
+ assertEquals("23521.0000", NumberUtils.toScaledBigDecimal(Float.valueOf(23.521f), 4, RoundingMode.HALF_EVEN)
+ .multiply(BigDecimal.valueOf(1000)).toString(), "toScaledBigDecimal(Float, int, RoundingMode) 4 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal((Float) null, 2, RoundingMode.HALF_UP), BigDecimal.ZERO,
+ "toScaledBigDecimal(Float, int, RoundingMode) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toScaledBigDecimal(Double)}.
+ */
+ @Test
+ public void testToScaledBigDecimalString() {
+ assertEquals(NumberUtils.toScaledBigDecimal("123.456"), BigDecimal.valueOf(123.46),
+ "toScaledBigDecimal(String) 1 failed");
+ // Test RoundingMode.HALF_EVEN default rounding.
+ assertEquals(NumberUtils.toScaledBigDecimal("23.515"), BigDecimal.valueOf(23.52),
+ "toScaledBigDecimal(String) 2 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal("23.525"), BigDecimal.valueOf(23.52),
+ "toScaledBigDecimal(String) 3 failed");
+ assertEquals("2352.00", NumberUtils.toScaledBigDecimal("23.525").multiply(BigDecimal.valueOf(100)).toString(),
+ "toScaledBigDecimal(String) 4 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal((String) null), BigDecimal.ZERO,
+ "toScaledBigDecimal(String) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toScaledBigDecimal(Double, int, RoundingMode)}.
+ */
+ @Test
+ public void testToScaledBigDecimalStringIRM() {
+ assertEquals(NumberUtils.toScaledBigDecimal("123.456", 1, RoundingMode.CEILING), BigDecimal.valueOf(123.5),
+ "toScaledBigDecimal(String, int, RoundingMode) 1 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal("23.5159", 3, RoundingMode.FLOOR), BigDecimal.valueOf(23.515),
+ "toScaledBigDecimal(String, int, RoundingMode) 2 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal("23.525", 2, RoundingMode.HALF_UP), BigDecimal.valueOf(23.53),
+ "toScaledBigDecimal(String, int, RoundingMode) 3 failed");
+ assertEquals(
+ "23521.0000", NumberUtils.toScaledBigDecimal("23.521", 4, RoundingMode.HALF_EVEN)
+ .multiply(BigDecimal.valueOf(1000)).toString(),
+ "toScaledBigDecimal(String, int, RoundingMode) 4 failed");
+ assertEquals(NumberUtils.toScaledBigDecimal((String) null, 2, RoundingMode.HALF_UP), BigDecimal.ZERO,
+ "toScaledBigDecimal(String, int, RoundingMode) 5 failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toShort(String)}.
+ */
+ @Test
+ public void testToShortString() {
+ assertEquals(12345, NumberUtils.toShort("12345"), "toShort(String) 1 failed");
+ assertEquals(0, NumberUtils.toShort("abc"), "toShort(String) 2 failed");
+ assertEquals(0, NumberUtils.toShort(""), "toShort(empty) failed");
+ assertEquals(0, NumberUtils.toShort(null), "toShort(null) failed");
+ }
+
+ /**
+ * Test for {@link NumberUtils#toShort(String, short)}.
+ */
+ @Test
+ public void testToShortStringI() {
+ assertEquals(12345, NumberUtils.toShort("12345", (short) 5), "toShort(String, short) 1 failed");
+ assertEquals(5, NumberUtils.toShort("1234.5", (short) 5), "toShort(String, short) 2 failed");
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/MutableBooleanTest.java b/src/test/java/org/apache/commons/lang3/mutable/MutableBooleanTest.java
new file mode 100644
index 000000000..1520fa7f2
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/MutableBooleanTest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests.
+ *
+ * @since 2.2
+ * @see MutableBoolean
+ */
+public class MutableBooleanTest extends AbstractLangTest {
+
+ @Test
+ public void testCompareTo() {
+ final MutableBoolean mutBool = new MutableBoolean(false);
+
+ assertEquals(0, mutBool.compareTo(new MutableBoolean(false)));
+ assertEquals(-1, mutBool.compareTo(new MutableBoolean(true)));
+ mutBool.setValue(true);
+ assertEquals(+1, mutBool.compareTo(new MutableBoolean(false)));
+ assertEquals(0, mutBool.compareTo(new MutableBoolean(true)));
+ }
+
+ @Test
+ public void testCompareToNull() {
+ final MutableBoolean mutBool = new MutableBoolean(false);
+ assertThrows(NullPointerException.class, () -> mutBool.compareTo(null));
+ }
+
+ @Test
+ public void testConstructorNull() {
+ assertThrows(NullPointerException.class, () -> new MutableBoolean(null));
+ }
+
+ @Test
+ public void testConstructors() {
+ assertFalse(new MutableBoolean().booleanValue());
+
+ assertTrue(new MutableBoolean(true).booleanValue());
+ assertFalse(new MutableBoolean(false).booleanValue());
+
+ assertTrue(new MutableBoolean(Boolean.TRUE).booleanValue());
+ assertFalse(new MutableBoolean(Boolean.FALSE).booleanValue());
+
+ }
+
+ @Test
+ public void testEquals() {
+ final MutableBoolean mutBoolA = new MutableBoolean(false);
+ final MutableBoolean mutBoolB = new MutableBoolean(false);
+ final MutableBoolean mutBoolC = new MutableBoolean(true);
+
+ assertEquals(mutBoolA, mutBoolA);
+ assertEquals(mutBoolA, mutBoolB);
+ assertEquals(mutBoolB, mutBoolA);
+ assertEquals(mutBoolB, mutBoolB);
+ assertNotEquals(mutBoolA, mutBoolC);
+ assertNotEquals(mutBoolB, mutBoolC);
+ assertEquals(mutBoolC, mutBoolC);
+ assertNotEquals(null, mutBoolA);
+ assertNotEquals(mutBoolA, Boolean.FALSE);
+ assertNotEquals("false", mutBoolA);
+ }
+
+ @Test
+ public void testGetSet() {
+ assertFalse(new MutableBoolean().booleanValue());
+ assertEquals(Boolean.FALSE, new MutableBoolean().getValue());
+
+ final MutableBoolean mutBool = new MutableBoolean(false);
+ assertEquals(Boolean.FALSE, mutBool.toBoolean());
+ assertFalse(mutBool.booleanValue());
+ assertTrue(mutBool.isFalse());
+ assertFalse(mutBool.isTrue());
+
+ mutBool.setValue(Boolean.TRUE);
+ assertEquals(Boolean.TRUE, mutBool.toBoolean());
+ assertTrue(mutBool.booleanValue());
+ assertFalse(mutBool.isFalse());
+ assertTrue(mutBool.isTrue());
+
+ mutBool.setValue(false);
+ assertFalse(mutBool.booleanValue());
+
+ mutBool.setValue(true);
+ assertTrue(mutBool.booleanValue());
+
+ mutBool.setFalse();
+ assertFalse(mutBool.booleanValue());
+
+ mutBool.setTrue();
+ assertTrue(mutBool.booleanValue());
+
+ }
+
+ @Test
+ public void testHashCode() {
+ final MutableBoolean mutBoolA = new MutableBoolean(false);
+ final MutableBoolean mutBoolB = new MutableBoolean(false);
+ final MutableBoolean mutBoolC = new MutableBoolean(true);
+
+ assertEquals(mutBoolA.hashCode(), mutBoolA.hashCode());
+ assertEquals(mutBoolA.hashCode(), mutBoolB.hashCode());
+ assertNotEquals(mutBoolA.hashCode(), mutBoolC.hashCode());
+ assertEquals(mutBoolA.hashCode(), Boolean.FALSE.hashCode());
+ assertEquals(mutBoolC.hashCode(), Boolean.TRUE.hashCode());
+ }
+
+ @Test
+ public void testSetNull() {
+ final MutableBoolean mutBool = new MutableBoolean(false);
+ assertThrows(NullPointerException.class, () -> mutBool.setValue(null));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals(Boolean.FALSE.toString(), new MutableBoolean(false).toString());
+ assertEquals(Boolean.TRUE.toString(), new MutableBoolean(true).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/MutableByteTest.java b/src/test/java/org/apache/commons/lang3/mutable/MutableByteTest.java
new file mode 100644
index 000000000..5ba7aefe6
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/MutableByteTest.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests.
+ *
+ * @see MutableByte
+ */
+public class MutableByteTest extends AbstractLangTest {
+
+ @Test
+ public void testAddAndGetValueObject() {
+ final MutableByte mutableByte = new MutableByte((byte) 0);
+ final byte result = mutableByte.addAndGet(Byte.valueOf((byte) 1));
+
+ assertEquals((byte) 1, result);
+ assertEquals((byte) 1, mutableByte.byteValue());
+ }
+
+ @Test
+ public void testAddAndGetValuePrimitive() {
+ final MutableByte mutableByte = new MutableByte((byte) 0);
+ final byte result = mutableByte.addAndGet((byte) 1);
+
+ assertEquals((byte) 1, result);
+ assertEquals((byte) 1, mutableByte.byteValue());
+ }
+
+ @Test
+ public void testAddValueObject() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ mutNum.add(Integer.valueOf(1));
+
+ assertEquals((byte) 2, mutNum.byteValue());
+ }
+
+ @Test
+ public void testAddValuePrimitive() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ mutNum.add((byte) 1);
+
+ assertEquals((byte) 2, mutNum.byteValue());
+ }
+
+ @Test
+ public void testCompareTo() {
+ final MutableByte mutNum = new MutableByte((byte) 0);
+
+ assertEquals((byte) 0, mutNum.compareTo(new MutableByte((byte) 0)));
+ assertEquals((byte) +1, mutNum.compareTo(new MutableByte((byte) -1)));
+ assertEquals((byte) -1, mutNum.compareTo(new MutableByte((byte) 1)));
+ }
+
+ @Test
+ public void testCompareToNull() {
+ final MutableByte mutNum = new MutableByte((byte) 0);
+ assertThrows(NullPointerException.class, () -> mutNum.compareTo(null));
+ }
+
+ @Test
+ public void testConstructorNull() {
+ assertThrows(NullPointerException.class, () -> new MutableByte((Number) null));
+ }
+
+ @Test
+ public void testConstructors() {
+ assertEquals((byte) 0, new MutableByte().byteValue());
+
+ assertEquals((byte) 1, new MutableByte((byte) 1).byteValue());
+
+ assertEquals((byte) 2, new MutableByte(Byte.valueOf((byte) 2)).byteValue());
+ assertEquals((byte) 3, new MutableByte(new MutableByte((byte) 3)).byteValue());
+
+ assertEquals((byte) 2, new MutableByte("2").byteValue());
+
+ }
+
+ @Test
+ public void testDecrement() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ mutNum.decrement();
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testDecrementAndGet() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ final byte result = mutNum.decrementAndGet();
+
+ assertEquals(0, result);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testEquals() {
+ final MutableByte mutNumA = new MutableByte((byte) 0);
+ final MutableByte mutNumB = new MutableByte((byte) 0);
+ final MutableByte mutNumC = new MutableByte((byte) 1);
+
+ assertEquals(mutNumA, mutNumA);
+ assertEquals(mutNumA, mutNumB);
+ assertEquals(mutNumB, mutNumA);
+ assertEquals(mutNumB, mutNumB);
+ assertNotEquals(mutNumA, mutNumC);
+ assertNotEquals(mutNumB, mutNumC);
+ assertEquals(mutNumC, mutNumC);
+ assertNotEquals(null, mutNumA);
+ assertNotEquals(mutNumA, Byte.valueOf((byte) 0));
+ assertNotEquals("0", mutNumA);
+ }
+
+ @Test
+ public void testGetAndAddValueObject() {
+ final MutableByte mutableByte = new MutableByte((byte) 0);
+ final byte result = mutableByte.getAndAdd(Byte.valueOf((byte) 1));
+
+ assertEquals((byte) 0, result);
+ assertEquals((byte) 1, mutableByte.byteValue());
+ }
+
+ @Test
+ public void testGetAndAddValuePrimitive() {
+ final MutableByte mutableByte = new MutableByte((byte) 0);
+ final byte result = mutableByte.getAndAdd((byte) 1);
+
+ assertEquals((byte) 0, result);
+ assertEquals((byte) 1, mutableByte.byteValue());
+ }
+
+ @Test
+ public void testGetAndDecrement() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ final byte result = mutNum.getAndDecrement();
+
+ assertEquals(1, result);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetAndIncrement() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ final byte result = mutNum.getAndIncrement();
+
+ assertEquals(1, result);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetSet() {
+ final MutableByte mutNum = new MutableByte((byte) 0);
+ assertEquals((byte) 0, new MutableByte().byteValue());
+ assertEquals(Byte.valueOf((byte) 0), new MutableByte().getValue());
+
+ mutNum.setValue((byte) 1);
+ assertEquals((byte) 1, mutNum.byteValue());
+ assertEquals(Byte.valueOf((byte) 1), mutNum.getValue());
+
+ mutNum.setValue(Byte.valueOf((byte) 2));
+ assertEquals((byte) 2, mutNum.byteValue());
+ assertEquals(Byte.valueOf((byte) 2), mutNum.getValue());
+
+ mutNum.setValue(new MutableByte((byte) 3));
+ assertEquals((byte) 3, mutNum.byteValue());
+ assertEquals(Byte.valueOf((byte) 3), mutNum.getValue());
+ }
+
+ @Test
+ public void testHashCode() {
+ final MutableByte mutNumA = new MutableByte((byte) 0);
+ final MutableByte mutNumB = new MutableByte((byte) 0);
+ final MutableByte mutNumC = new MutableByte((byte) 1);
+
+ assertEquals(mutNumA.hashCode(), mutNumA.hashCode());
+ assertEquals(mutNumA.hashCode(), mutNumB.hashCode());
+ assertNotEquals(mutNumA.hashCode(), mutNumC.hashCode());
+ assertEquals(mutNumA.hashCode(), Byte.valueOf((byte) 0).hashCode());
+ }
+
+ @Test
+ public void testIncrement() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ mutNum.increment();
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testIncrementAndGet() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ final byte result = mutNum.incrementAndGet();
+
+ assertEquals(2, result);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testPrimitiveValues() {
+ final MutableByte mutNum = new MutableByte( (byte) 1 );
+ assertEquals(1.0F, mutNum.floatValue());
+ assertEquals(1.0, mutNum.doubleValue());
+ assertEquals( (byte) 1, mutNum.byteValue() );
+ assertEquals( (short) 1, mutNum.shortValue() );
+ assertEquals( 1, mutNum.intValue() );
+ assertEquals( 1L, mutNum.longValue() );
+ }
+
+ @Test
+ public void testSetNull() {
+ final MutableByte mutNum = new MutableByte((byte) 0);
+ assertThrows(NullPointerException.class, () -> mutNum.setValue(null));
+ }
+
+ @Test
+ public void testSubtractValueObject() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ mutNum.subtract(Integer.valueOf(1));
+
+ assertEquals((byte) 0, mutNum.byteValue());
+ }
+
+ @Test
+ public void testSubtractValuePrimitive() {
+ final MutableByte mutNum = new MutableByte((byte) 1);
+ mutNum.subtract((byte) 1);
+
+ assertEquals((byte) 0, mutNum.byteValue());
+ }
+
+ @Test
+ public void testToByte() {
+ assertEquals(Byte.valueOf((byte) 0), new MutableByte((byte) 0).toByte());
+ assertEquals(Byte.valueOf((byte) 123), new MutableByte((byte) 123).toByte());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0", new MutableByte((byte) 0).toString());
+ assertEquals("10", new MutableByte((byte) 10).toString());
+ assertEquals("-123", new MutableByte((byte) -123).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/MutableDoubleTest.java b/src/test/java/org/apache/commons/lang3/mutable/MutableDoubleTest.java
new file mode 100644
index 000000000..982d3884b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/MutableDoubleTest.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests.
+ *
+ * @see MutableDouble
+ */
+public class MutableDoubleTest extends AbstractLangTest {
+
+ @Test
+ public void testAddAndGetValueObject() {
+ final MutableDouble mutableDouble = new MutableDouble(7.5d);
+ final double result = mutableDouble.addAndGet(Double.valueOf(-2.5d));
+
+ assertEquals(5d, result, 0.01d);
+ assertEquals(5d, mutableDouble.doubleValue(), 0.01d);
+ }
+
+ @Test
+ public void testAddAndGetValuePrimitive() {
+ final MutableDouble mutableDouble = new MutableDouble(10.5d);
+ final double result = mutableDouble.addAndGet(-0.5d);
+
+ assertEquals(10d, result, 0.01d);
+ assertEquals(10d, mutableDouble.doubleValue(), 0.01d);
+ }
+
+ @Test
+ public void testAddValueObject() {
+ final MutableDouble mutNum = new MutableDouble(1);
+ mutNum.add(Double.valueOf(1.1d));
+
+ assertEquals(2.1d, mutNum.doubleValue(), 0.01d);
+ }
+
+ @Test
+ public void testAddValuePrimitive() {
+ final MutableDouble mutNum = new MutableDouble(1);
+ mutNum.add(1.1d);
+
+ assertEquals(2.1d, mutNum.doubleValue(), 0.01d);
+ }
+
+ @Test
+ public void testCompareTo() {
+ final MutableDouble mutNum = new MutableDouble(0d);
+
+ assertEquals(0, mutNum.compareTo(new MutableDouble(0d)));
+ assertEquals(+1, mutNum.compareTo(new MutableDouble(-1d)));
+ assertEquals(-1, mutNum.compareTo(new MutableDouble(1d)));
+ }
+
+ @Test
+ public void testCompareToNull() {
+ final MutableDouble mutNum = new MutableDouble(0d);
+ assertThrows(NullPointerException.class, () -> mutNum.compareTo(null));
+ }
+
+ @Test
+ public void testConstructorNull() {
+ assertThrows(NullPointerException.class, () -> new MutableDouble((Number) null));
+ }
+
+ @Test
+ public void testConstructors() {
+ assertEquals(0d, new MutableDouble().doubleValue(), 0.0001d);
+
+ assertEquals(1d, new MutableDouble(1d).doubleValue(), 0.0001d);
+
+ assertEquals(2d, new MutableDouble(Double.valueOf(2d)).doubleValue(), 0.0001d);
+ assertEquals(3d, new MutableDouble(new MutableDouble(3d)).doubleValue(), 0.0001d);
+
+ assertEquals(2d, new MutableDouble("2.0").doubleValue(), 0.0001d);
+
+ }
+
+ @Test
+ public void testDecrement() {
+ final MutableDouble mutNum = new MutableDouble(1);
+ mutNum.decrement();
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testDecrementAndGet() {
+ final MutableDouble mutNum = new MutableDouble(1d);
+ final double result = mutNum.decrementAndGet();
+
+ assertEquals(0d, result, 0.01d);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testEquals() {
+ final MutableDouble mutNumA = new MutableDouble(0d);
+ final MutableDouble mutNumB = new MutableDouble(0d);
+ final MutableDouble mutNumC = new MutableDouble(1d);
+
+ assertEquals(mutNumA, mutNumA);
+ assertEquals(mutNumA, mutNumB);
+ assertEquals(mutNumB, mutNumA);
+ assertEquals(mutNumB, mutNumB);
+ assertNotEquals(mutNumA, mutNumC);
+ assertNotEquals(mutNumB, mutNumC);
+ assertEquals(mutNumC, mutNumC);
+ assertNotEquals(null, mutNumA);
+ assertNotEquals(mutNumA, Double.valueOf(0d));
+ assertNotEquals("0", mutNumA);
+ }
+
+ @Test
+ public void testGetAndAddValueObject() {
+ final MutableDouble mutableDouble = new MutableDouble(0.5d);
+ final double result = mutableDouble.getAndAdd(Double.valueOf(2d));
+
+ assertEquals(0.5d, result, 0.01d);
+ assertEquals(2.5d, mutableDouble.doubleValue(), 0.01d);
+ }
+
+ @Test
+ public void testGetAndAddValuePrimitive() {
+ final MutableDouble mutableDouble = new MutableDouble(0.5d);
+ final double result = mutableDouble.getAndAdd(1d);
+
+ assertEquals(0.5d, result, 0.01d);
+ assertEquals(1.5d, mutableDouble.doubleValue(), 0.01d);
+ }
+
+ @Test
+ public void testGetAndDecrement() {
+ final MutableDouble mutNum = new MutableDouble(1d);
+ final double result = mutNum.getAndDecrement();
+
+ assertEquals(1d, result, 0.01d);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetAndIncrement() {
+ final MutableDouble mutNum = new MutableDouble(1d);
+ final double result = mutNum.getAndIncrement();
+
+ assertEquals(1d, result, 0.01d);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetSet() {
+ final MutableDouble mutNum = new MutableDouble(0d);
+ assertEquals(0d, new MutableDouble().doubleValue(), 0.0001d);
+ assertEquals(Double.valueOf(0), new MutableDouble().getValue());
+
+ mutNum.setValue(1);
+ assertEquals(1d, mutNum.doubleValue(), 0.0001d);
+ assertEquals(Double.valueOf(1d), mutNum.getValue());
+
+ mutNum.setValue(Double.valueOf(2d));
+ assertEquals(2d, mutNum.doubleValue(), 0.0001d);
+ assertEquals(Double.valueOf(2d), mutNum.getValue());
+
+ mutNum.setValue(new MutableDouble(3d));
+ assertEquals(3d, mutNum.doubleValue(), 0.0001d);
+ assertEquals(Double.valueOf(3d), mutNum.getValue());
+ }
+
+ @Test
+ public void testHashCode() {
+ final MutableDouble mutNumA = new MutableDouble(0d);
+ final MutableDouble mutNumB = new MutableDouble(0d);
+ final MutableDouble mutNumC = new MutableDouble(1d);
+
+ assertEquals(mutNumA.hashCode(), mutNumA.hashCode());
+ assertEquals(mutNumA.hashCode(), mutNumB.hashCode());
+ assertNotEquals(mutNumA.hashCode(), mutNumC.hashCode());
+ assertEquals(mutNumA.hashCode(), Double.valueOf(0d).hashCode());
+ }
+
+ @Test
+ public void testIncrement() {
+ final MutableDouble mutNum = new MutableDouble(1);
+ mutNum.increment();
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testIncrementAndGet() {
+ final MutableDouble mutNum = new MutableDouble(1d);
+ final double result = mutNum.incrementAndGet();
+
+ assertEquals(2d, result, 0.01d);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testNanInfinite() {
+ MutableDouble mutNum = new MutableDouble(Double.NaN);
+ assertTrue(mutNum.isNaN());
+
+ mutNum = new MutableDouble(Double.POSITIVE_INFINITY);
+ assertTrue(mutNum.isInfinite());
+
+ mutNum = new MutableDouble(Double.NEGATIVE_INFINITY);
+ assertTrue(mutNum.isInfinite());
+ }
+
+ @Test
+ public void testPrimitiveValues() {
+ final MutableDouble mutNum = new MutableDouble(1.7);
+ assertEquals(1.7F, mutNum.floatValue());
+ assertEquals(1.7, mutNum.doubleValue());
+ assertEquals( (byte) 1, mutNum.byteValue() );
+ assertEquals( (short) 1, mutNum.shortValue() );
+ assertEquals( 1, mutNum.intValue() );
+ assertEquals( 1L, mutNum.longValue() );
+ }
+
+ @Test
+ public void testSetNull() {
+ final MutableDouble mutNum = new MutableDouble(0d);
+ assertThrows(NullPointerException.class, () -> mutNum.setValue(null));
+ }
+
+ @Test
+ public void testSubtractValueObject() {
+ final MutableDouble mutNum = new MutableDouble(1);
+ mutNum.subtract(Double.valueOf(0.9d));
+
+ assertEquals(0.1d, mutNum.doubleValue(), 0.01d);
+ }
+
+ @Test
+ public void testSubtractValuePrimitive() {
+ final MutableDouble mutNum = new MutableDouble(1);
+ mutNum.subtract(0.9d);
+
+ assertEquals(0.1d, mutNum.doubleValue(), 0.01d);
+ }
+
+ @Test
+ public void testToDouble() {
+ assertEquals(Double.valueOf(0d), new MutableDouble(0d).toDouble());
+ assertEquals(Double.valueOf(12.3d), new MutableDouble(12.3d).toDouble());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0.0", new MutableDouble(0d).toString());
+ assertEquals("10.0", new MutableDouble(10d).toString());
+ assertEquals("-123.0", new MutableDouble(-123d).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/MutableFloatTest.java b/src/test/java/org/apache/commons/lang3/mutable/MutableFloatTest.java
new file mode 100644
index 000000000..a7312f2e1
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/MutableFloatTest.java
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests.
+ *
+ * @see MutableFloat
+ */
+public class MutableFloatTest extends AbstractLangTest {
+
+ @Test
+ public void testAddAndGetValueObject() {
+ final MutableFloat mutableFloat = new MutableFloat(5f);
+ final float result = mutableFloat.addAndGet(Float.valueOf(2.5f));
+
+ assertEquals(7.5f, result, 0.01f);
+ assertEquals(7.5f, mutableFloat.floatValue(), 0.01f);
+ }
+
+ @Test
+ public void testAddAndGetValuePrimitive() {
+ final MutableFloat mutableFloat = new MutableFloat(0.5f);
+ final float result = mutableFloat.addAndGet(1f);
+
+ assertEquals(1.5f, result, 0.01f);
+ assertEquals(1.5f, mutableFloat.floatValue(), 0.01f);
+ }
+
+ @Test
+ public void testAddValueObject() {
+ final MutableFloat mutNum = new MutableFloat(1);
+ mutNum.add(Float.valueOf(1.1f));
+
+ assertEquals(2.1f, mutNum.floatValue(), 0.01f);
+ }
+
+ @Test
+ public void testAddValuePrimitive() {
+ final MutableFloat mutNum = new MutableFloat(1);
+ mutNum.add(1.1f);
+
+ assertEquals(2.1f, mutNum.floatValue(), 0.01f);
+ }
+
+ @Test
+ public void testCompareTo() {
+ final MutableFloat mutNum = new MutableFloat(0f);
+
+ assertEquals(0, mutNum.compareTo(new MutableFloat(0f)));
+ assertEquals(+1, mutNum.compareTo(new MutableFloat(-1f)));
+ assertEquals(-1, mutNum.compareTo(new MutableFloat(1f)));
+ }
+
+ @Test
+ public void testCompareToNull() {
+ final MutableFloat mutNum = new MutableFloat(0f);
+ assertThrows(NullPointerException.class, () -> mutNum.compareTo(null));
+ }
+
+ @Test
+ public void testConstructorNull() {
+ assertThrows(NullPointerException.class, () -> new MutableFloat((Number) null));
+ }
+
+ @Test
+ public void testConstructors() {
+ assertEquals(0f, new MutableFloat().floatValue(), 0.0001f);
+
+ assertEquals(1f, new MutableFloat(1f).floatValue(), 0.0001f);
+
+ assertEquals(2f, new MutableFloat(Float.valueOf(2f)).floatValue(), 0.0001f);
+ assertEquals(3f, new MutableFloat(new MutableFloat(3f)).floatValue(), 0.0001f);
+
+ assertEquals(2f, new MutableFloat("2.0").floatValue(), 0.0001f);
+
+ }
+
+ @Test
+ public void testDecrement() {
+ final MutableFloat mutNum = new MutableFloat(1);
+ mutNum.decrement();
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testDecrementAndGet() {
+ final MutableFloat mutNum = new MutableFloat(1f);
+ final float result = mutNum.decrementAndGet();
+
+ assertEquals(0f, result, 0.01f);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testEquals() {
+ final MutableFloat mutNumA = new MutableFloat(0f);
+ final MutableFloat mutNumB = new MutableFloat(0f);
+ final MutableFloat mutNumC = new MutableFloat(1f);
+
+ assertEquals(mutNumA, mutNumA);
+ assertEquals(mutNumA, mutNumB);
+ assertEquals(mutNumB, mutNumA);
+ assertEquals(mutNumB, mutNumB);
+ assertNotEquals(mutNumA, mutNumC);
+ assertNotEquals(mutNumB, mutNumC);
+ assertEquals(mutNumC, mutNumC);
+ assertNotEquals(null, mutNumA);
+ assertNotEquals(mutNumA, Float.valueOf(0f));
+ assertNotEquals("0", mutNumA);
+ }
+
+ @Test
+ public void testGetAndAddValueObject() {
+ final MutableFloat mutableFloat = new MutableFloat(7.75f);
+ final float result = mutableFloat.getAndAdd(Float.valueOf(2.25f));
+
+ assertEquals(7.75f, result, 0.01f);
+ assertEquals(10f, mutableFloat.floatValue(), 0.01f);
+ }
+
+ @Test
+ public void testGetAndAddValuePrimitive() {
+ final MutableFloat mutableFloat = new MutableFloat(1.25f);
+ final float result = mutableFloat.getAndAdd(0.75f);
+
+ assertEquals(1.25f, result, 0.01f);
+ assertEquals(2f, mutableFloat.floatValue(), 0.01f);
+ }
+
+ @Test
+ public void testGetAndDecrement() {
+ final MutableFloat mutNum = new MutableFloat(1f);
+ final float result = mutNum.getAndDecrement();
+
+ assertEquals(1f, result, 0.01f);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetAndIncrement() {
+ final MutableFloat mutNum = new MutableFloat(1f);
+ final float result = mutNum.getAndIncrement();
+
+ assertEquals(1f, result, 0.01f);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetSet() {
+ final MutableFloat mutNum = new MutableFloat(0f);
+ assertEquals(0f, new MutableFloat().floatValue(), 0.0001f);
+ assertEquals(Float.valueOf(0), new MutableFloat().getValue());
+
+ mutNum.setValue(1);
+ assertEquals(1f, mutNum.floatValue(), 0.0001f);
+ assertEquals(Float.valueOf(1f), mutNum.getValue());
+
+ mutNum.setValue(Float.valueOf(2f));
+ assertEquals(2f, mutNum.floatValue(), 0.0001f);
+ assertEquals(Float.valueOf(2f), mutNum.getValue());
+
+ mutNum.setValue(new MutableFloat(3f));
+ assertEquals(3f, mutNum.floatValue(), 0.0001f);
+ assertEquals(Float.valueOf(3f), mutNum.getValue());
+ }
+
+ @Test
+ public void testHashCode() {
+ final MutableFloat mutNumA = new MutableFloat(0f);
+ final MutableFloat mutNumB = new MutableFloat(0f);
+ final MutableFloat mutNumC = new MutableFloat(1f);
+
+ assertEquals(mutNumA.hashCode(), mutNumA.hashCode());
+ assertEquals(mutNumA.hashCode(), mutNumB.hashCode());
+ assertNotEquals(mutNumA.hashCode(), mutNumC.hashCode());
+ assertEquals(mutNumA.hashCode(), Float.valueOf(0f).hashCode());
+ }
+
+ @Test
+ public void testIncrement() {
+ final MutableFloat mutNum = new MutableFloat(1);
+ mutNum.increment();
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testIncrementAndGet() {
+ final MutableFloat mutNum = new MutableFloat(1f);
+ final float result = mutNum.incrementAndGet();
+
+ assertEquals(2f, result, 0.01f);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testNanInfinite() {
+ MutableFloat mutNum = new MutableFloat(Float.NaN);
+ assertTrue(mutNum.isNaN());
+
+ mutNum = new MutableFloat(Float.POSITIVE_INFINITY);
+ assertTrue(mutNum.isInfinite());
+
+ mutNum = new MutableFloat(Float.NEGATIVE_INFINITY);
+ assertTrue(mutNum.isInfinite());
+ }
+
+ @Test
+ public void testPrimitiveValues() {
+ final MutableFloat mutNum = new MutableFloat(1.7F);
+
+ assertEquals( 1, mutNum.intValue() );
+ assertEquals( 1.7, mutNum.doubleValue(), 0.00001 );
+ assertEquals( (byte) 1, mutNum.byteValue() );
+ assertEquals( (short) 1, mutNum.shortValue() );
+ assertEquals( 1, mutNum.intValue() );
+ assertEquals( 1L, mutNum.longValue() );
+ }
+
+ @Test
+ public void testSetNull() {
+ final MutableFloat mutNum = new MutableFloat(0f);
+ assertThrows(NullPointerException.class, () -> mutNum.setValue(null));
+ }
+
+ @Test
+ public void testSubtractValueObject() {
+ final MutableFloat mutNum = new MutableFloat(1);
+ mutNum.subtract(Float.valueOf(0.9f));
+
+ assertEquals(0.1f, mutNum.floatValue(), 0.01f);
+ }
+
+ @Test
+ public void testSubtractValuePrimitive() {
+ final MutableFloat mutNum = new MutableFloat(1);
+ mutNum.subtract(0.9f);
+
+ assertEquals(0.1f, mutNum.floatValue(), 0.01f);
+ }
+
+ @Test
+ public void testToFloat() {
+ assertEquals(Float.valueOf(0f), new MutableFloat(0f).toFloat());
+ assertEquals(Float.valueOf(12.3f), new MutableFloat(12.3f).toFloat());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0.0", new MutableFloat(0f).toString());
+ assertEquals("10.0", new MutableFloat(10f).toString());
+ assertEquals("-123.0", new MutableFloat(-123f).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/MutableIntTest.java b/src/test/java/org/apache/commons/lang3/mutable/MutableIntTest.java
new file mode 100644
index 000000000..dcae63245
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/MutableIntTest.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.mutable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests.
+ *
+ * @see MutableInt
+ */
+public class MutableIntTest extends AbstractLangTest {
+
+ @Test
+ public void testAddAndGetValueObject() {
+ final MutableInt mutableInteger = new MutableInt(0);
+ final int result = mutableInteger.addAndGet(Integer.valueOf(1));
+
+ assertEquals(1, result);
+ assertEquals(1, mutableInteger.intValue());
+ }
+
+ @Test
+ public void testAddAndGetValuePrimitive() {
+ final MutableInt mutableInteger = new MutableInt(0);
+ final int result = mutableInteger.addAndGet(1);
+
+ assertEquals(1, result);
+ assertEquals(1, mutableInteger.intValue());
+ }
+
+ @Test
+ public void testAddValueObject() {
+ final MutableInt mutNum = new MutableInt(1);
+ mutNum.add(Integer.valueOf(1));
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testAddValuePrimitive() {
+ final MutableInt mutNum = new MutableInt(1);
+ mutNum.add(1);
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testCompareTo() {
+ final MutableInt mutNum = new MutableInt(0);
+
+ assertEquals(0, mutNum.compareTo(new MutableInt(0)));
+ assertEquals(+1, mutNum.compareTo(new MutableInt(-1)));
+ assertEquals(-1, mutNum.compareTo(new MutableInt(1)));
+ }
+
+ @Test
+ public void testCompareToNull() {
+ final MutableInt mutNum = new MutableInt(0);
+ assertThrows(NullPointerException.class, () -> mutNum.compareTo(null));
+ }
+
+ @Test
+ public void testConstructorNull() {
+ assertThrows(NullPointerException.class, () -> new MutableInt((Number) null));
+ }
+
+ @Test
+ public void testConstructors() {
+ assertEquals(0, new MutableInt().intValue());
+
+ assertEquals(1, new MutableInt(1).intValue());
+
+ assertEquals(2, new MutableInt(Integer.valueOf(2)).intValue());
+ assertEquals(3, new MutableInt(new MutableLong(3)).intValue());
+
+ assertEquals(2, new MutableInt("2").intValue());
+
+ }
+
+ @Test
+ public void testDecrement() {
+ final MutableInt mutNum = new MutableInt(1);
+ mutNum.decrement();
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testDecrementAndGet() {
+ final MutableInt mutNum = new MutableInt(1);
+ final int result = mutNum.decrementAndGet();
+
+ assertEquals(0, result);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testEquals() {
+ this.testEquals(new MutableInt(0), new MutableInt(0), new MutableInt(1));
+ // Should Numbers be supported? GaryG July-21-2005.
+ //this.testEquals(mutNumA, Integer.valueOf(0), mutNumC);
+ }
+
+ /**
+ * @param numA must not be a 0 Integer; must not equal numC.
+ * @param numB must equal numA; must not equal numC.
+ * @param numC must not equal numA; must not equal numC.
+ */
+ void testEquals(final Number numA, final Number numB, final Number numC) {
+ assertEquals(numA, numA);
+ assertEquals(numA, numB);
+ assertEquals(numB, numA);
+ assertEquals(numB, numB);
+ assertNotEquals(numA, numC);
+ assertNotEquals(numB, numC);
+ assertEquals(numC, numC);
+ assertNotEquals(null, numA);
+ assertNotEquals(numA, Integer.valueOf(0));
+ assertNotEquals("0", numA);
+ }
+
+ @Test
+ public void testGetAndAddValueObject() {
+ final MutableInt mutableInteger = new MutableInt(0);
+ final int result = mutableInteger.getAndAdd(Integer.valueOf(1));
+
+ assertEquals(0, result);
+ assertEquals(1, mutableInteger.intValue());
+ }
+
+ @Test
+ public void testGetAndAddValuePrimitive() {
+ final MutableInt mutableInteger = new MutableInt(0);
+ final int result = mutableInteger.getAndAdd(1);
+
+ assertEquals(0, result);
+ assertEquals(1, mutableInteger.intValue());
+ }
+
+ @Test
+ public void testGetAndDecrement() {
+ final MutableInt mutNum = new MutableInt(1);
+ final int result = mutNum.getAndDecrement();
+
+ assertEquals(1, result);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetAndIncrement() {
+ final MutableInt mutNum = new MutableInt(1);
+ final int result = mutNum.getAndIncrement();
+
+ assertEquals(1, result);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetSet() {
+ final MutableInt mutNum = new MutableInt(0);
+ assertEquals(0, new MutableInt().intValue());
+ assertEquals(Integer.valueOf(0), new MutableInt().getValue());
+
+ mutNum.setValue(1);
+ assertEquals(1, mutNum.intValue());
+ assertEquals(Integer.valueOf(1), mutNum.getValue());
+
+ mutNum.setValue(Integer.valueOf(2));
+ assertEquals(2, mutNum.intValue());
+ assertEquals(Integer.valueOf(2), mutNum.getValue());
+
+ mutNum.setValue(new MutableLong(3));
+ assertEquals(3, mutNum.intValue());
+ assertEquals(Integer.valueOf(3), mutNum.getValue());
+ }
+
+ @Test
+ public void testHashCode() {
+ final MutableInt mutNumA = new MutableInt(0);
+ final MutableInt mutNumB = new MutableInt(0);
+ final MutableInt mutNumC = new MutableInt(1);
+
+ assertEquals(mutNumA.hashCode(), mutNumA.hashCode());
+ assertEquals(mutNumA.hashCode(), mutNumB.hashCode());
+ assertNotEquals(mutNumA.hashCode(), mutNumC.hashCode());
+ assertEquals(mutNumA.hashCode(), Integer.valueOf(0).hashCode());
+ }
+
+ @Test
+ public void testIncrement() {
+ final MutableInt mutNum = new MutableInt(1);
+ mutNum.increment();
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testIncrementAndGet() {
+ final MutableInt mutNum = new MutableInt(1);
+ final int result = mutNum.incrementAndGet();
+
+ assertEquals(2, result);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testPrimitiveValues() {
+ final MutableInt mutNum = new MutableInt(1);
+ assertEquals( (byte) 1, mutNum.byteValue() );
+ assertEquals( (short) 1, mutNum.shortValue() );
+ assertEquals(1.0F, mutNum.floatValue());
+ assertEquals(1.0, mutNum.doubleValue());
+ assertEquals( 1L, mutNum.longValue() );
+ }
+
+ @Test
+ public void testSetNull() {
+ final MutableInt mutNum = new MutableInt(0);
+ assertThrows(NullPointerException.class, () -> mutNum.setValue(null));
+ }
+
+ @Test
+ public void testSubtractValueObject() {
+ final MutableInt mutNum = new MutableInt(1);
+ mutNum.subtract(Integer.valueOf(1));
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testSubtractValuePrimitive() {
+ final MutableInt mutNum = new MutableInt(1);
+ mutNum.subtract(1);
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testToInteger() {
+ assertEquals(Integer.valueOf(0), new MutableInt(0).toInteger());
+ assertEquals(Integer.valueOf(123), new MutableInt(123).toInteger());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0", new MutableInt(0).toString());
+ assertEquals("10", new MutableInt(10).toString());
+ assertEquals("-123", new MutableInt(-123).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/MutableLongTest.java b/src/test/java/org/apache/commons/lang3/mutable/MutableLongTest.java
new file mode 100644
index 000000000..32ee0d803
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/MutableLongTest.java
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests.
+ *
+ * @see MutableLong
+ */
+public class MutableLongTest extends AbstractLangTest {
+
+ @Test
+ public void testAddAndGetValueObject() {
+ final MutableLong mutableLong = new MutableLong(0L);
+ final long result = mutableLong.addAndGet(Long.valueOf(1L));
+
+ assertEquals(1L, result);
+ assertEquals(1L, mutableLong.longValue());
+ }
+
+ @Test
+ public void testAddAndGetValuePrimitive() {
+ final MutableLong mutableLong = new MutableLong(0L);
+ final long result = mutableLong.addAndGet(1L);
+
+ assertEquals(1L, result);
+ assertEquals(1L, mutableLong.longValue());
+ }
+
+ @Test
+ public void testAddValueObject() {
+ final MutableLong mutNum = new MutableLong(1);
+ mutNum.add(Long.valueOf(1));
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testAddValuePrimitive() {
+ final MutableLong mutNum = new MutableLong(1);
+ mutNum.add(1);
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testCompareTo() {
+ final MutableLong mutNum = new MutableLong(0);
+
+ assertEquals(0, mutNum.compareTo(new MutableLong(0)));
+ assertEquals(+1, mutNum.compareTo(new MutableLong(-1)));
+ assertEquals(-1, mutNum.compareTo(new MutableLong(1)));
+ }
+
+ @Test
+ public void testCompareToNull() {
+ final MutableLong mutNum = new MutableLong(0);
+ assertThrows(NullPointerException.class, () -> mutNum.compareTo(null));
+ }
+
+ @Test
+ public void testConstructorNull() {
+ assertThrows(NullPointerException.class, () -> new MutableLong((Number) null));
+ }
+
+ @Test
+ public void testConstructors() {
+ assertEquals(0, new MutableLong().longValue());
+
+ assertEquals(1, new MutableLong(1).longValue());
+
+ assertEquals(2, new MutableLong(Long.valueOf(2)).longValue());
+ assertEquals(3, new MutableLong(new MutableLong(3)).longValue());
+
+ assertEquals(2, new MutableLong("2").longValue());
+
+ }
+
+ @Test
+ public void testDecrement() {
+ final MutableLong mutNum = new MutableLong(1);
+ mutNum.decrement();
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testDecrementAndGet() {
+ final MutableLong mutNum = new MutableLong(1L);
+ final long result = mutNum.decrementAndGet();
+
+ assertEquals(0, result);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testEquals() {
+ final MutableLong mutNumA = new MutableLong(0);
+ final MutableLong mutNumB = new MutableLong(0);
+ final MutableLong mutNumC = new MutableLong(1);
+
+ assertEquals(mutNumA, mutNumA);
+ assertEquals(mutNumA, mutNumB);
+ assertEquals(mutNumB, mutNumA);
+ assertEquals(mutNumB, mutNumB);
+ assertNotEquals(mutNumA, mutNumC);
+ assertNotEquals(mutNumB, mutNumC);
+ assertEquals(mutNumC, mutNumC);
+ assertNotEquals(null, mutNumA);
+ assertNotEquals(mutNumA, Long.valueOf(0));
+ assertNotEquals("0", mutNumA);
+ }
+
+ @Test
+ public void testGetAndAddValueObject() {
+ final MutableLong mutableLong = new MutableLong(0L);
+ final long result = mutableLong.getAndAdd(Long.valueOf(1L));
+
+ assertEquals(0L, result);
+ assertEquals(1L, mutableLong.longValue());
+ }
+
+ @Test
+ public void testGetAndAddValuePrimitive() {
+ final MutableLong mutableLong = new MutableLong(0L);
+ final long result = mutableLong.getAndAdd(1L);
+
+ assertEquals(0L, result);
+ assertEquals(1L, mutableLong.longValue());
+ }
+
+ @Test
+ public void testGetAndDecrement() {
+ final MutableLong mutNum = new MutableLong(1L);
+ final long result = mutNum.getAndDecrement();
+
+ assertEquals(1, result);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetAndIncrement() {
+ final MutableLong mutNum = new MutableLong(1L);
+ final long result = mutNum.getAndIncrement();
+
+ assertEquals(1, result);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetSet() {
+ final MutableLong mutNum = new MutableLong(0);
+ assertEquals(0, new MutableLong().longValue());
+ assertEquals(Long.valueOf(0), new MutableLong().getValue());
+
+ mutNum.setValue(1);
+ assertEquals(1, mutNum.longValue());
+ assertEquals(Long.valueOf(1), mutNum.getValue());
+
+ mutNum.setValue(Long.valueOf(2));
+ assertEquals(2, mutNum.longValue());
+ assertEquals(Long.valueOf(2), mutNum.getValue());
+
+ mutNum.setValue(new MutableLong(3));
+ assertEquals(3, mutNum.longValue());
+ assertEquals(Long.valueOf(3), mutNum.getValue());
+ }
+
+ @Test
+ public void testHashCode() {
+ final MutableLong mutNumA = new MutableLong(0);
+ final MutableLong mutNumB = new MutableLong(0);
+ final MutableLong mutNumC = new MutableLong(1);
+
+ assertEquals(mutNumA.hashCode(), mutNumA.hashCode());
+ assertEquals(mutNumA.hashCode(), mutNumB.hashCode());
+ assertNotEquals(mutNumA.hashCode(), mutNumC.hashCode());
+ assertEquals(mutNumA.hashCode(), Long.valueOf(0).hashCode());
+ }
+
+ @Test
+ public void testIncrement() {
+ final MutableLong mutNum = new MutableLong(1);
+ mutNum.increment();
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testIncrementAndGet() {
+ final MutableLong mutNum = new MutableLong(1L);
+ final long result = mutNum.incrementAndGet();
+
+ assertEquals(2, result);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testPrimitiveValues() {
+ final MutableLong mutNum = new MutableLong(1L);
+ assertEquals(1.0F, mutNum.floatValue());
+ assertEquals(1.0, mutNum.doubleValue());
+ assertEquals( (byte) 1, mutNum.byteValue() );
+ assertEquals( (short) 1, mutNum.shortValue() );
+ assertEquals( 1, mutNum.intValue() );
+ assertEquals( 1L, mutNum.longValue() );
+ }
+
+ @Test
+ public void testSetNull() {
+ final MutableLong mutNum = new MutableLong(0);
+ assertThrows(NullPointerException.class, () -> mutNum.setValue(null));
+ }
+
+ @Test
+ public void testSubtractValueObject() {
+ final MutableLong mutNum = new MutableLong(1);
+ mutNum.subtract(Long.valueOf(1));
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testSubtractValuePrimitive() {
+ final MutableLong mutNum = new MutableLong(1);
+ mutNum.subtract(1);
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testToLong() {
+ assertEquals(Long.valueOf(0L), new MutableLong(0L).toLong());
+ assertEquals(Long.valueOf(123L), new MutableLong(123L).toLong());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0", new MutableLong(0).toString());
+ assertEquals("10", new MutableLong(10).toString());
+ assertEquals("-123", new MutableLong(-123).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/MutableObjectTest.java b/src/test/java/org/apache/commons/lang3/mutable/MutableObjectTest.java
new file mode 100644
index 000000000..c02d9d97e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/MutableObjectTest.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.mutable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * JUnit tests.
+ *
+ * @see MutableShort
+ */
+public class MutableObjectTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructors() {
+ assertNull(new MutableObject<String>().getValue());
+
+ final Integer i = Integer.valueOf(6);
+ assertSame(i, new MutableObject<>(i).getValue());
+ assertSame("HI", new MutableObject<>("HI").getValue());
+ assertSame(null, new MutableObject<>(null).getValue());
+ }
+
+ @Test
+ public void testEquals() {
+ final MutableObject<String> mutNumA = new MutableObject<>("ALPHA");
+ final MutableObject<String> mutNumB = new MutableObject<>("ALPHA");
+ final MutableObject<String> mutNumC = new MutableObject<>("BETA");
+ final MutableObject<String> mutNumD = new MutableObject<>(null);
+
+ assertEquals(mutNumA, mutNumA);
+ assertEquals(mutNumA, mutNumB);
+ assertEquals(mutNumB, mutNumA);
+ assertEquals(mutNumB, mutNumB);
+ assertNotEquals(mutNumA, mutNumC);
+ assertNotEquals(mutNumB, mutNumC);
+ assertEquals(mutNumC, mutNumC);
+ assertNotEquals(mutNumA, mutNumD);
+ assertEquals(mutNumD, mutNumD);
+
+ assertNotEquals(null, mutNumA);
+ assertNotEquals(mutNumA, new Object());
+ assertNotEquals("0", mutNumA);
+ }
+
+ @Test
+ public void testGetSet() {
+ final MutableObject<String> mutNum = new MutableObject<>();
+ assertNull(new MutableObject<>().getValue());
+
+ mutNum.setValue("HELLO");
+ assertSame("HELLO", mutNum.getValue());
+
+ mutNum.setValue(null);
+ assertSame(null, mutNum.getValue());
+ }
+
+ @Test
+ public void testHashCode() {
+ final MutableObject<String> mutNumA = new MutableObject<>("ALPHA");
+ final MutableObject<String> mutNumB = new MutableObject<>("ALPHA");
+ final MutableObject<String> mutNumC = new MutableObject<>("BETA");
+ final MutableObject<String> mutNumD = new MutableObject<>(null);
+
+ assertEquals(mutNumA.hashCode(), mutNumA.hashCode());
+ assertEquals(mutNumA.hashCode(), mutNumB.hashCode());
+ assertNotEquals(mutNumA.hashCode(), mutNumC.hashCode());
+ assertNotEquals(mutNumA.hashCode(), mutNumD.hashCode());
+ assertEquals(mutNumA.hashCode(), "ALPHA".hashCode());
+ assertEquals(0, mutNumD.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("HI", new MutableObject<>("HI").toString());
+ assertEquals("10.0", new MutableObject<>(Double.valueOf(10)).toString());
+ assertEquals("null", new MutableObject<>(null).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/MutableShortTest.java b/src/test/java/org/apache/commons/lang3/mutable/MutableShortTest.java
new file mode 100644
index 000000000..8b6a4a139
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/MutableShortTest.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit tests.
+ *
+ * @see MutableShort
+ */
+public class MutableShortTest extends AbstractLangTest {
+
+ @Test
+ public void testAddAndGetValueObject() {
+ final MutableShort mutableShort = new MutableShort((short) 0);
+ final short result = mutableShort.addAndGet(Short.valueOf((short) 1));
+
+ assertEquals((short) 1, result);
+ assertEquals((short) 1, mutableShort.shortValue());
+ }
+
+ @Test
+ public void testAddAndGetValuePrimitive() {
+ final MutableShort mutableShort = new MutableShort((short) 0);
+ final short result = mutableShort.addAndGet((short) 1);
+
+ assertEquals((short) 1, result);
+ assertEquals((short) 1, mutableShort.shortValue());
+ }
+
+ @Test
+ public void testAddValueObject() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ mutNum.add(Short.valueOf((short) 1));
+
+ assertEquals((short) 2, mutNum.shortValue());
+ }
+
+ @Test
+ public void testAddValuePrimitive() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ mutNum.add((short) 1);
+
+ assertEquals((short) 2, mutNum.shortValue());
+ }
+
+ @Test
+ public void testCompareTo() {
+ final MutableShort mutNum = new MutableShort((short) 0);
+
+ assertEquals((short) 0, mutNum.compareTo(new MutableShort((short) 0)));
+ assertEquals((short) +1, mutNum.compareTo(new MutableShort((short) -1)));
+ assertEquals((short) -1, mutNum.compareTo(new MutableShort((short) 1)));
+ assertThrows(NullPointerException.class, () -> mutNum.compareTo(null));
+ }
+
+ @Test
+ public void testConstructors() {
+ assertEquals((short) 0, new MutableShort().shortValue());
+
+ assertEquals((short) 1, new MutableShort((short) 1).shortValue());
+
+ assertEquals((short) 2, new MutableShort(Short.valueOf((short) 2)).shortValue());
+ assertEquals((short) 3, new MutableShort(new MutableShort((short) 3)).shortValue());
+
+ assertEquals((short) 2, new MutableShort("2").shortValue());
+
+ assertThrows(NullPointerException.class, () -> new MutableShort((Number) null));
+ }
+
+ @Test
+ public void testDecrement() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ mutNum.decrement();
+
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testDecrementAndGet() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ final short result = mutNum.decrementAndGet();
+
+ assertEquals(0, result);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testEquals() {
+ final MutableShort mutNumA = new MutableShort((short) 0);
+ final MutableShort mutNumB = new MutableShort((short) 0);
+ final MutableShort mutNumC = new MutableShort((short) 1);
+
+ assertEquals(mutNumA, mutNumA);
+ assertEquals(mutNumA, mutNumB);
+ assertEquals(mutNumB, mutNumA);
+ assertEquals(mutNumB, mutNumB);
+ assertNotEquals(mutNumA, mutNumC);
+ assertNotEquals(mutNumB, mutNumC);
+ assertEquals(mutNumC, mutNumC);
+ assertNotEquals(null, mutNumA);
+ assertNotEquals(mutNumA, Short.valueOf((short) 0));
+ assertNotEquals("0", mutNumA);
+ }
+
+ @Test
+ public void testGetAndAddValueObject() {
+ final MutableShort mutableShort = new MutableShort((short) 0);
+ final short result = mutableShort.getAndAdd(Short.valueOf((short) 1));
+
+ assertEquals((short) 0, result);
+ assertEquals((short) 1, mutableShort.shortValue());
+ }
+
+ @Test
+ public void testGetAndAddValuePrimitive() {
+ final MutableShort mutableShort = new MutableShort((short) 0);
+ final short result = mutableShort.getAndAdd((short) 1);
+
+ assertEquals((short) 0, result);
+ assertEquals((short) 1, mutableShort.shortValue());
+ }
+
+ @Test
+ public void testGetAndDecrement() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ final short result = mutNum.getAndDecrement();
+
+ assertEquals(1, result);
+ assertEquals(0, mutNum.intValue());
+ assertEquals(0L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetAndIncrement() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ final short result = mutNum.getAndIncrement();
+
+ assertEquals(1, result);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testGetSet() {
+ final MutableShort mutNum = new MutableShort((short) 0);
+ assertEquals((short) 0, new MutableShort().shortValue());
+ assertEquals(Short.valueOf((short) 0), new MutableShort().getValue());
+
+ mutNum.setValue((short) 1);
+ assertEquals((short) 1, mutNum.shortValue());
+ assertEquals(Short.valueOf((short) 1), mutNum.getValue());
+
+ mutNum.setValue(Short.valueOf((short) 2));
+ assertEquals((short) 2, mutNum.shortValue());
+ assertEquals(Short.valueOf((short) 2), mutNum.getValue());
+
+ mutNum.setValue(new MutableShort((short) 3));
+ assertEquals((short) 3, mutNum.shortValue());
+ assertEquals(Short.valueOf((short) 3), mutNum.getValue());
+ assertThrows(NullPointerException.class, () -> mutNum.setValue(null));
+ }
+
+ @Test
+ public void testHashCode() {
+ final MutableShort mutNumA = new MutableShort((short) 0);
+ final MutableShort mutNumB = new MutableShort((short) 0);
+ final MutableShort mutNumC = new MutableShort((short) 1);
+
+ assertEquals(mutNumA.hashCode(), mutNumA.hashCode());
+ assertEquals(mutNumA.hashCode(), mutNumB.hashCode());
+ assertNotEquals(mutNumA.hashCode(), mutNumC.hashCode());
+ assertEquals(mutNumA.hashCode(), Short.valueOf((short) 0).hashCode());
+ }
+
+ @Test
+ public void testIncrement() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ mutNum.increment();
+
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testIncrementAndGet() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ final short result = mutNum.incrementAndGet();
+
+ assertEquals(2, result);
+ assertEquals(2, mutNum.intValue());
+ assertEquals(2L, mutNum.longValue());
+ }
+
+ @Test
+ public void testPrimitiveValues() {
+ final MutableShort mutNum = new MutableShort( (short) 1 );
+ assertEquals(1.0F, mutNum.floatValue());
+ assertEquals(1.0, mutNum.doubleValue());
+ assertEquals( (byte) 1, mutNum.byteValue() );
+ assertEquals( (short) 1, mutNum.shortValue() );
+ assertEquals( 1, mutNum.intValue() );
+ assertEquals( 1L, mutNum.longValue() );
+ }
+
+ @Test
+ public void testSubtractValueObject() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ mutNum.subtract(Short.valueOf((short) 1));
+
+ assertEquals((short) 0, mutNum.shortValue());
+ }
+
+ @Test
+ public void testSubtractValuePrimitive() {
+ final MutableShort mutNum = new MutableShort((short) 1);
+ mutNum.subtract((short) 1);
+
+ assertEquals((short) 0, mutNum.shortValue());
+ }
+
+ @Test
+ public void testToShort() {
+ assertEquals(Short.valueOf((short) 0), new MutableShort((short) 0).toShort());
+ assertEquals(Short.valueOf((short) 123), new MutableShort((short) 123).toShort());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0", new MutableShort((short) 0).toString());
+ assertEquals("10", new MutableShort((short) 10).toString());
+ assertEquals("-123", new MutableShort((short) -123).toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/mutable/PrintAtomicVsMutable.java b/src/test/java/org/apache/commons/lang3/mutable/PrintAtomicVsMutable.java
new file mode 100644
index 000000000..de9555f60
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/mutable/PrintAtomicVsMutable.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.mutable;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.lang3.time.DurationUtils;
+
+/**
+ * Prints out the difference time working with {@link MutableInt} and {@link AtomicInteger}.
+ */
+public class PrintAtomicVsMutable {
+
+ public static void main(String[] args) {
+ MutableInt mInt = new MutableInt();
+ final int max = 100_000_000;
+ System.out.println("MutableInt " + DurationUtils.of(() -> {
+ for (int i = 0; i < max; i++) {
+ mInt.incrementAndGet();
+ }
+ }));
+ AtomicInteger aInt = new AtomicInteger();
+ System.out.println("AtomicInteger " + DurationUtils.of(() -> {
+ for (int i = 0; i < max; i++) {
+ aInt.incrementAndGet();
+ }
+ }));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java
new file mode 100644
index 000000000..04cc93bcd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java
@@ -0,0 +1,296 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests ConstructorUtils
+ */
+public class ConstructorUtilsTest extends AbstractLangTest {
+ public static class TestBean {
+ private final String toString;
+ final String[] varArgs;
+
+ public TestBean() {
+ toString = "()";
+ varArgs = null;
+ }
+
+ public TestBean(final int i) {
+ toString = "(int)";
+ varArgs = null;
+ }
+
+ public TestBean(final Integer i) {
+ toString = "(Integer)";
+ varArgs = null;
+ }
+
+ public TestBean(final double d) {
+ toString = "(double)";
+ varArgs = null;
+ }
+
+ public TestBean(final String s) {
+ toString = "(String)";
+ varArgs = null;
+ }
+
+ public TestBean(final Object o) {
+ toString = "(Object)";
+ varArgs = null;
+ }
+
+ public TestBean(final String... s) {
+ toString = "(String...)";
+ varArgs = s;
+ }
+
+ public TestBean(final BaseClass bc, final String... s) {
+ toString = "(BaseClass, String...)";
+ varArgs = s;
+ }
+
+ public TestBean(final Integer i, final String... s) {
+ toString = "(Integer, String...)";
+ varArgs = s;
+ }
+
+ public TestBean(final Integer first, final int... args) {
+ toString = "(Integer, String...)";
+ varArgs = new String[args.length];
+ for (int i = 0; i < args.length; ++i) {
+ varArgs[i] = Integer.toString(args[i]);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return toString;
+ }
+
+ void verify(final String str, final String[] args) {
+ assertEquals(str, toString);
+ assertArrayEquals(args, varArgs);
+ }
+ }
+
+ private static class BaseClass {}
+
+ private static class SubClass extends BaseClass {}
+
+ static class PrivateClass {
+ @SuppressWarnings("unused")
+ public PrivateClass() {
+ }
+
+ @SuppressWarnings("unused")
+ public static class PublicInnerClass {
+ public PublicInnerClass() {
+ }
+ }
+ }
+
+ private final Map<Class<?>, Class<?>[]> classCache;
+
+ public ConstructorUtilsTest() {
+ classCache = new HashMap<>();
+ }
+
+
+ @BeforeEach
+ public void setUp() {
+ classCache.clear();
+ }
+
+ @Test
+ public void testConstructor() throws Exception {
+ assertNotNull(MethodUtils.class.newInstance());
+ }
+
+ @Test
+ public void testInvokeConstructor() throws Exception {
+ assertEquals("()", ConstructorUtils.invokeConstructor(TestBean.class,
+ (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY).toString());
+ assertEquals("()", ConstructorUtils.invokeConstructor(TestBean.class,
+ (Object[]) null).toString());
+ assertEquals("()", ConstructorUtils.invokeConstructor(TestBean.class).toString());
+ assertEquals("(String)", ConstructorUtils.invokeConstructor(
+ TestBean.class, "").toString());
+ assertEquals("(Object)", ConstructorUtils.invokeConstructor(
+ TestBean.class, new Object()).toString());
+ assertEquals("(Object)", ConstructorUtils.invokeConstructor(
+ TestBean.class, Boolean.TRUE).toString());
+ assertEquals("(Integer)", ConstructorUtils.invokeConstructor(
+ TestBean.class, NumberUtils.INTEGER_ONE).toString());
+ assertEquals("(int)", ConstructorUtils.invokeConstructor(
+ TestBean.class, NumberUtils.BYTE_ONE).toString());
+ assertEquals("(double)", ConstructorUtils.invokeConstructor(
+ TestBean.class, NumberUtils.LONG_ONE).toString());
+ assertEquals("(double)", ConstructorUtils.invokeConstructor(
+ TestBean.class, NumberUtils.DOUBLE_ONE).toString());
+ ConstructorUtils.invokeConstructor(TestBean.class, NumberUtils.INTEGER_ONE)
+ .verify("(Integer)", null);
+ ConstructorUtils.invokeConstructor(TestBean.class, "a", "b")
+ .verify("(String...)", new String[]{"a", "b"});
+ ConstructorUtils.invokeConstructor(TestBean.class, NumberUtils.INTEGER_ONE, "a", "b")
+ .verify("(Integer, String...)", new String[]{"a", "b"});
+ ConstructorUtils.invokeConstructor(TestBean.class, new SubClass(), new String[]{"a", "b"})
+ .verify("(BaseClass, String...)", new String[]{"a", "b"});
+ }
+
+ @Test
+ public void testInvokeExactConstructor() throws Exception {
+ assertEquals("()", ConstructorUtils.invokeExactConstructor(
+ TestBean.class, (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY).toString());
+ assertEquals("()", ConstructorUtils.invokeExactConstructor(
+ TestBean.class, (Object[]) null).toString());
+ assertEquals("(String)", ConstructorUtils.invokeExactConstructor(
+ TestBean.class, "").toString());
+ assertEquals("(Object)", ConstructorUtils.invokeExactConstructor(
+ TestBean.class, new Object()).toString());
+ assertEquals("(Integer)", ConstructorUtils.invokeExactConstructor(
+ TestBean.class, NumberUtils.INTEGER_ONE).toString());
+ assertEquals("(double)", ConstructorUtils.invokeExactConstructor(
+ TestBean.class, new Object[] { NumberUtils.DOUBLE_ONE },
+ new Class[] { Double.TYPE }).toString());
+
+ assertThrows(
+ NoSuchMethodException.class,
+ () -> ConstructorUtils.invokeExactConstructor(TestBean.class, NumberUtils.BYTE_ONE));
+ assertThrows(
+ NoSuchMethodException.class,
+ () -> ConstructorUtils.invokeExactConstructor(TestBean.class, NumberUtils.LONG_ONE));
+ assertThrows(
+ NoSuchMethodException.class,
+ () -> ConstructorUtils.invokeExactConstructor(TestBean.class, Boolean.TRUE));
+ }
+
+ @Test
+ public void testGetAccessibleConstructor() throws Exception {
+ assertNotNull(ConstructorUtils.getAccessibleConstructor(Object.class
+ .getConstructor(ArrayUtils.EMPTY_CLASS_ARRAY)));
+ assertNull(ConstructorUtils.getAccessibleConstructor(PrivateClass.class
+ .getConstructor(ArrayUtils.EMPTY_CLASS_ARRAY)));
+ assertNull(ConstructorUtils.getAccessibleConstructor(PrivateClass.PublicInnerClass.class));
+ }
+
+ @Test
+ public void testGetAccessibleConstructorFromDescription() {
+ assertNotNull(ConstructorUtils.getAccessibleConstructor(Object.class,
+ ArrayUtils.EMPTY_CLASS_ARRAY));
+ assertNull(ConstructorUtils.getAccessibleConstructor(
+ PrivateClass.class, ArrayUtils.EMPTY_CLASS_ARRAY));
+ }
+
+ @Test
+ public void testGetMatchingAccessibleMethod() {
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ ArrayUtils.EMPTY_CLASS_ARRAY, ArrayUtils.EMPTY_CLASS_ARRAY);
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class, null,
+ ArrayUtils.EMPTY_CLASS_ARRAY);
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(String.class), singletonArray(String.class));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Object.class), singletonArray(Object.class));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Boolean.class), singletonArray(Object.class));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Byte.class), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Byte.TYPE), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Short.class), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Short.TYPE), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Character.class), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Character.TYPE), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Integer.class), singletonArray(Integer.class));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Integer.TYPE), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Long.class), singletonArray(Double.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Long.TYPE), singletonArray(Double.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Float.class), singletonArray(Double.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Float.TYPE), singletonArray(Double.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Double.class), singletonArray(Double.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ singletonArray(Double.TYPE), singletonArray(Double.TYPE));
+ expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+ new Class<?>[]{SubClass.class, String[].class},
+ new Class<?>[]{BaseClass.class, String[].class});
+ }
+
+ @Test
+ public void testNullArgument() {
+ expectMatchingAccessibleConstructorParameterTypes(MutableObject.class,
+ singletonArray(null), singletonArray(Object.class));
+ }
+
+ private void expectMatchingAccessibleConstructorParameterTypes(final Class<?> cls,
+ final Class<?>[] requestTypes, final Class<?>[] actualTypes) {
+ final Constructor<?> c = ConstructorUtils.getMatchingAccessibleConstructor(cls,
+ requestTypes);
+ assertArrayEquals(actualTypes, c.getParameterTypes(), toString(c.getParameterTypes()) + " not equals " + toString(actualTypes));
+ }
+
+ private String toString(final Class<?>[] c) {
+ return Arrays.asList(c).toString();
+ }
+
+ private Class<?>[] singletonArray(final Class<?> c) {
+ Class<?>[] result = classCache.get(c);
+ if (result == null) {
+ result = new Class[] { c };
+ classCache.put(c, result);
+ }
+ return result;
+ }
+
+ @Test
+ public void testVarArgsUnboxing() throws Exception {
+ final TestBean testBean = ConstructorUtils.invokeConstructor(
+ TestBean.class, Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3));
+
+ assertArrayEquals(new String[]{"2", "3"}, testBean.varArgs);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/FieldUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/FieldUtilsTest.java
new file mode 100644
index 000000000..b68a994f3
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/FieldUtilsTest.java
@@ -0,0 +1,1071 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArraySorter;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.commons.lang3.compare.ObjectToStringComparator;
+import org.apache.commons.lang3.reflect.testbed.Ambig;
+import org.apache.commons.lang3.reflect.testbed.Annotated;
+import org.apache.commons.lang3.reflect.testbed.Foo;
+import org.apache.commons.lang3.reflect.testbed.PrivatelyShadowedChild;
+import org.apache.commons.lang3.reflect.testbed.PublicChild;
+import org.apache.commons.lang3.reflect.testbed.PubliclyShadowedChild;
+import org.apache.commons.lang3.reflect.testbed.StaticContainer;
+import org.apache.commons.lang3.reflect.testbed.StaticContainerChild;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests FieldUtils
+ */
+public class FieldUtilsTest extends AbstractLangTest {
+
+ private static final String JACOCO_DATA_FIELD_NAME = "$jacocoData";
+ static final Integer I0 = Integer.valueOf(0);
+ static final Integer I1 = Integer.valueOf(1);
+ static final Double D0 = Double.valueOf(0.0);
+ static final Double D1 = Double.valueOf(1.0);
+
+ @Annotated
+ private PublicChild publicChild;
+ private PubliclyShadowedChild publiclyShadowedChild;
+ @Annotated
+ private PrivatelyShadowedChild privatelyShadowedChild;
+ private final Class<? super PublicChild> parentClass = PublicChild.class.getSuperclass();
+
+ @BeforeEach
+ public void setUp() {
+ StaticContainer.reset();
+ publicChild = new PublicChild();
+ publiclyShadowedChild = new PubliclyShadowedChild();
+ privatelyShadowedChild = new PrivatelyShadowedChild();
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new FieldUtils());
+ final Constructor<?>[] cons = FieldUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(FieldUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(FieldUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testGetField() {
+ assertEquals(Foo.class, FieldUtils.getField(PublicChild.class, "VALUE").getDeclaringClass());
+ assertEquals(parentClass, FieldUtils.getField(PublicChild.class, "s").getDeclaringClass());
+ assertNull(FieldUtils.getField(PublicChild.class, "b"));
+ assertNull(FieldUtils.getField(PublicChild.class, "i"));
+ assertNull(FieldUtils.getField(PublicChild.class, "d"));
+ assertEquals(Foo.class, FieldUtils.getField(PubliclyShadowedChild.class, "VALUE").getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getField(PubliclyShadowedChild.class, "s").getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getField(PubliclyShadowedChild.class, "b").getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getField(PubliclyShadowedChild.class, "i").getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getField(PubliclyShadowedChild.class, "d").getDeclaringClass());
+ assertEquals(Foo.class, FieldUtils.getField(PrivatelyShadowedChild.class, "VALUE").getDeclaringClass());
+ assertEquals(parentClass, FieldUtils.getField(PrivatelyShadowedChild.class, "s").getDeclaringClass());
+ assertNull(FieldUtils.getField(PrivatelyShadowedChild.class, "b"));
+ assertNull(FieldUtils.getField(PrivatelyShadowedChild.class, "i"));
+ assertNull(FieldUtils.getField(PrivatelyShadowedChild.class, "d"));
+ }
+
+ @Test
+ public void testGetFieldIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getField(null, "none"));
+ }
+
+ @Test
+ public void testGetFieldIllegalArgumentException2() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getField(PublicChild.class, null));
+ }
+
+ @Test
+ public void testGetFieldIllegalArgumentException3() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getField(PublicChild.class, ""));
+ }
+
+ @Test
+ public void testGetFieldIllegalArgumentException4() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getField(PublicChild.class, " "));
+ }
+
+ @Test
+ public void testGetFieldForceAccess() {
+ assertEquals(PublicChild.class, FieldUtils.getField(PublicChild.class, "VALUE", true).getDeclaringClass());
+ assertEquals(parentClass, FieldUtils.getField(PublicChild.class, "s", true).getDeclaringClass());
+ assertEquals(parentClass, FieldUtils.getField(PublicChild.class, "b", true).getDeclaringClass());
+ assertEquals(parentClass, FieldUtils.getField(PublicChild.class, "i", true).getDeclaringClass());
+ assertEquals(parentClass, FieldUtils.getField(PublicChild.class, "d", true).getDeclaringClass());
+ assertEquals(Foo.class, FieldUtils.getField(PubliclyShadowedChild.class, "VALUE", true).getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getField(PubliclyShadowedChild.class, "s", true).getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getField(PubliclyShadowedChild.class, "b", true).getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getField(PubliclyShadowedChild.class, "i", true).getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getField(PubliclyShadowedChild.class, "d", true).getDeclaringClass());
+ assertEquals(Foo.class, FieldUtils.getField(PrivatelyShadowedChild.class, "VALUE", true).getDeclaringClass());
+ assertEquals(PrivatelyShadowedChild.class, FieldUtils.getField(PrivatelyShadowedChild.class, "s", true).getDeclaringClass());
+ assertEquals(PrivatelyShadowedChild.class, FieldUtils.getField(PrivatelyShadowedChild.class, "b", true).getDeclaringClass());
+ assertEquals(PrivatelyShadowedChild.class, FieldUtils.getField(PrivatelyShadowedChild.class, "i", true).getDeclaringClass());
+ assertEquals(PrivatelyShadowedChild.class, FieldUtils.getField(PrivatelyShadowedChild.class, "d", true).getDeclaringClass());
+ }
+
+ @Test
+ public void testGetFieldForceAccessIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getField(null, "none", true));
+ }
+
+ @Test
+ public void testGetFieldForceAccessIllegalArgumentException2() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getField(PublicChild.class, null, true));
+ }
+
+ @Test
+ public void testGetFieldForceAccessIllegalArgumentException3() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getField(PublicChild.class, "", true));
+ }
+
+ @Test
+ public void testGetFieldForceAccessIllegalArgumentException4() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getField(PublicChild.class, " ", true));
+ }
+
+ @Test
+ public void testGetAllFields() {
+ assertArrayEquals(new Field[0], FieldUtils.getAllFields(Object.class));
+ final Field[] fieldsNumber = sort(Number.class.getDeclaredFields());
+ assertArrayEquals(fieldsNumber, sort(FieldUtils.getAllFields(Number.class)));
+ final Field[] fieldsInteger = Integer.class.getDeclaredFields();
+ assertArrayEquals(sort(ArrayUtils.addAll(fieldsInteger, fieldsNumber)), sort(FieldUtils.getAllFields(Integer.class)));
+ final Field[] allFields = FieldUtils.getAllFields(PublicChild.class);
+ // Under Jacoco,0.8.1 and Java 10, the field count is 7.
+ int expected = 5;
+ for (final Field field : allFields) {
+ if (field.getName().equals(JACOCO_DATA_FIELD_NAME)) {
+ expected++;
+ }
+ }
+ assertEquals(expected, allFields.length, Arrays.toString(allFields));
+ }
+
+ private Field[] sort(final Field[] fields) {
+ // Field does not implement Comparable, so we use a KISS solution here.
+ return ArraySorter.sort(fields, ObjectToStringComparator.INSTANCE);
+ }
+
+ @Test
+ public void testGetAllFieldsList() {
+ assertEquals(0, FieldUtils.getAllFieldsList(Object.class).size());
+ final List<Field> fieldsNumber = Arrays.asList(Number.class.getDeclaredFields());
+ assertEquals(fieldsNumber, FieldUtils.getAllFieldsList(Number.class));
+ final List<Field> fieldsInteger = Arrays.asList(Integer.class.getDeclaredFields());
+ final List<Field> allFieldsInteger = new ArrayList<>(fieldsInteger);
+ allFieldsInteger.addAll(fieldsNumber);
+ assertEquals(new HashSet<>(allFieldsInteger), new HashSet<>(FieldUtils.getAllFieldsList(Integer.class)));
+ final List<Field> allFields = FieldUtils.getAllFieldsList(PublicChild.class);
+ // Under Jacoco,0.8.1 and Java 10, the field count is 7.
+ int expected = 5;
+ for (final Field field : allFields) {
+ if (field.getName().equals(JACOCO_DATA_FIELD_NAME)) {
+ expected++;
+ }
+ }
+ assertEquals(expected, allFields.size(), allFields.toString());
+
+ }
+
+ @Test
+ public void testGetFieldsWithAnnotation() throws NoSuchFieldException {
+ assertArrayEquals(new Field[0], FieldUtils.getFieldsWithAnnotation(Object.class, Annotated.class));
+ final Field[] annotatedFields = sort(new Field[] {
+ FieldUtilsTest.class.getDeclaredField("publicChild"),
+ FieldUtilsTest.class.getDeclaredField("privatelyShadowedChild") });
+ assertArrayEquals(annotatedFields,
+ sort(FieldUtils.getFieldsWithAnnotation(FieldUtilsTest.class, Annotated.class)));
+ }
+
+ @Test
+ public void testGetFieldsWithAnnotationIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getFieldsWithAnnotation(FieldUtilsTest.class, null));
+ }
+
+ @Test
+ public void testGetFieldsWithAnnotationIllegalArgumentException2() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getFieldsWithAnnotation(null, Annotated.class));
+ }
+
+ @Test
+ public void testGetFieldsWithAnnotationIllegalArgumentException3() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getFieldsWithAnnotation(null, null));
+ }
+
+ @Test
+ public void testGetFieldsListWithAnnotation() throws NoSuchFieldException {
+ assertEquals(0, FieldUtils.getFieldsListWithAnnotation(Object.class, Annotated.class).size());
+ final List<Field> annotatedFields = Arrays.asList(
+ FieldUtilsTest.class.getDeclaredField("publicChild"),
+ FieldUtilsTest.class.getDeclaredField("privatelyShadowedChild")
+ );
+ final List<Field> fieldUtilsTestAnnotatedFields = FieldUtils.getFieldsListWithAnnotation(FieldUtilsTest.class, Annotated.class);
+ assertEquals(annotatedFields.size(), fieldUtilsTestAnnotatedFields.size());
+ assertTrue(fieldUtilsTestAnnotatedFields.contains(annotatedFields.get(0)));
+ assertTrue(fieldUtilsTestAnnotatedFields.contains(annotatedFields.get(1)));
+ }
+
+ @Test
+ public void testGetFieldsListWithAnnotationIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getFieldsListWithAnnotation(FieldUtilsTest.class, null));
+ }
+
+ @Test
+ public void testGetFieldsListWithAnnotationIllegalArgumentException2() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getFieldsListWithAnnotation(null, Annotated.class));
+ }
+
+ @Test
+ public void testGetFieldsListWithAnnotationIllegalArgumentException3() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getFieldsListWithAnnotation(null, null));
+ }
+
+ @Test
+ public void testGetDeclaredField() {
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "VALUE"));
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "s"));
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "b"));
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "i"));
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "d"));
+ assertNull(FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "VALUE"));
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "s").getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "b").getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "i").getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "d").getDeclaringClass());
+ assertNull(FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "VALUE"));
+ assertNull(FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "s"));
+ assertNull(FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "b"));
+ assertNull(FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "i"));
+ assertNull(FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "d"));
+ }
+
+ @Test
+ public void testGetDeclaredFieldAccessIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getDeclaredField(null, "none"));
+ }
+
+ @Test
+ public void testGetDeclaredFieldAccessIllegalArgumentException2() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getDeclaredField(PublicChild.class, null));
+ }
+
+ @Test
+ public void testGetDeclaredFieldAccessIllegalArgumentException3() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getDeclaredField(PublicChild.class, ""));
+ }
+
+ @Test
+ public void testGetDeclaredFieldAccessIllegalArgumentException4() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getDeclaredField(PublicChild.class, " "));
+ }
+
+ @Test
+ public void testGetDeclaredFieldForceAccess() {
+ assertEquals(PublicChild.class, FieldUtils.getDeclaredField(PublicChild.class, "VALUE", true).getDeclaringClass());
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "s", true));
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "b", true));
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "i", true));
+ assertNull(FieldUtils.getDeclaredField(PublicChild.class, "d", true));
+ assertNull(FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "VALUE", true));
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "s", true).getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "b", true).getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "i", true).getDeclaringClass());
+ assertEquals(PubliclyShadowedChild.class, FieldUtils.getDeclaredField(PubliclyShadowedChild.class, "d", true).getDeclaringClass());
+ assertNull(FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "VALUE", true));
+ assertEquals(PrivatelyShadowedChild.class, FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "s", true).getDeclaringClass());
+ assertEquals(PrivatelyShadowedChild.class, FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "b", true).getDeclaringClass());
+ assertEquals(PrivatelyShadowedChild.class, FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "i", true).getDeclaringClass());
+ assertEquals(PrivatelyShadowedChild.class, FieldUtils.getDeclaredField(PrivatelyShadowedChild.class, "d", true).getDeclaringClass());
+ }
+
+ @Test
+ public void testGetDeclaredFieldForceAccessIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.getDeclaredField(null, "none", true));
+ }
+
+ @Test
+ public void testGetDeclaredFieldForceAccessIllegalArgumentException2() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getDeclaredField(PublicChild.class, null, true));
+ }
+
+ @Test
+ public void testGetDeclaredFieldForceAccessIllegalArgumentException3() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getDeclaredField(PublicChild.class, "", true));
+ }
+
+ @Test
+ public void testGetDeclaredFieldForceAccessIllegalArgumentException4() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getDeclaredField(PublicChild.class, " ", true));
+ }
+
+ @Test
+ public void testReadStaticField() throws Exception {
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(FieldUtils.getField(Foo.class, "VALUE")));
+ }
+
+ @Test
+ public void testReadStaticFieldIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.readStaticField(null));
+ }
+
+ @Test
+ public void testReadStaticFieldIllegalArgumentException2() throws Exception {
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(FieldUtils.getField(Foo.class, "VALUE")));
+ final Field nonStaticField = FieldUtils.getField(PublicChild.class, "s");
+ assumeTrue(nonStaticField != null);
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readStaticField(nonStaticField));
+ }
+
+ @Test
+ public void testReadStaticFieldForceAccess() throws Exception {
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(FieldUtils.getField(Foo.class, "VALUE")));
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(FieldUtils.getField(PublicChild.class, "VALUE")));
+ }
+
+ @Test
+ public void testReadStaticFieldForceAccessIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> FieldUtils.readStaticField(null, true));
+ }
+
+ @Test
+ public void testReadStaticFieldForceAccessIllegalArgumentException2() {
+ final Field nonStaticField = FieldUtils.getField(PublicChild.class, "s", true);
+ assumeTrue(nonStaticField != null);
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readStaticField(nonStaticField));
+ }
+
+ @Test
+ public void testReadNamedStaticField() throws Exception {
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(Foo.class, "VALUE"));
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(PubliclyShadowedChild.class, "VALUE"));
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(PrivatelyShadowedChild.class, "VALUE"));
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(PublicChild.class, "VALUE"));
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readStaticField(null, "none"),
+ "null class should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readStaticField(Foo.class, null),
+ "null field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readStaticField(Foo.class, ""),
+ "empty field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readStaticField(Foo.class, " "),
+ "blank field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readStaticField(Foo.class, "does_not_exist"),
+ "a field that doesn't exist should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readStaticField(PublicChild.class, "s"),
+ "non-static field should cause an IllegalArgumentException");
+ }
+
+ @Test
+ public void testReadNamedStaticFieldForceAccess() throws Exception {
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(Foo.class, "VALUE", true));
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(PubliclyShadowedChild.class, "VALUE", true));
+ assertEquals(Foo.VALUE, FieldUtils.readStaticField(PrivatelyShadowedChild.class, "VALUE", true));
+ assertEquals("child", FieldUtils.readStaticField(PublicChild.class, "VALUE", true));
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readStaticField(null, "none", true),
+ "null class should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readStaticField(Foo.class, null, true),
+ "null field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readStaticField(Foo.class, "", true),
+ "empty field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readStaticField(Foo.class, " ", true),
+ "blank field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readStaticField(Foo.class, "does_not_exist", true),
+ "a field that doesn't exist should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readStaticField(PublicChild.class, "s", false),
+ "non-static field should cause an IllegalArgumentException");
+ }
+
+ @Test
+ public void testReadDeclaredNamedStaticField() throws Exception {
+ assertEquals(Foo.VALUE, FieldUtils.readDeclaredStaticField(Foo.class, "VALUE"));
+ assertThrows(
+ NullPointerException.class, () -> FieldUtils.readDeclaredStaticField(PublicChild.class, "VALUE"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readDeclaredStaticField(PubliclyShadowedChild.class, "VALUE"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readDeclaredStaticField(PrivatelyShadowedChild.class, "VALUE"));
+ }
+
+ @Test
+ public void testReadDeclaredNamedStaticFieldForceAccess() throws Exception {
+ assertEquals(Foo.VALUE, FieldUtils.readDeclaredStaticField(Foo.class, "VALUE", true));
+ assertEquals("child", FieldUtils.readDeclaredStaticField(PublicChild.class, "VALUE", true));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readDeclaredStaticField(PubliclyShadowedChild.class, "VALUE", true));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readDeclaredStaticField(PrivatelyShadowedChild.class, "VALUE", true));
+ }
+
+ @Test
+ public void testReadField() throws Exception {
+ final Field parentS = FieldUtils.getDeclaredField(parentClass, "s");
+ assertEquals("s", FieldUtils.readField(parentS, publicChild));
+ assertEquals("s", FieldUtils.readField(parentS, publiclyShadowedChild));
+ assertEquals("s", FieldUtils.readField(parentS, privatelyShadowedChild));
+ final Field parentB = FieldUtils.getDeclaredField(parentClass, "b", true);
+ assertEquals(Boolean.FALSE, FieldUtils.readField(parentB, publicChild));
+ assertEquals(Boolean.FALSE, FieldUtils.readField(parentB, publiclyShadowedChild));
+ assertEquals(Boolean.FALSE, FieldUtils.readField(parentB, privatelyShadowedChild));
+ final Field parentI = FieldUtils.getDeclaredField(parentClass, "i", true);
+ assertEquals(I0, FieldUtils.readField(parentI, publicChild));
+ assertEquals(I0, FieldUtils.readField(parentI, publiclyShadowedChild));
+ assertEquals(I0, FieldUtils.readField(parentI, privatelyShadowedChild));
+ final Field parentD = FieldUtils.getDeclaredField(parentClass, "d", true);
+ assertEquals(D0, FieldUtils.readField(parentD, publicChild));
+ assertEquals(D0, FieldUtils.readField(parentD, publiclyShadowedChild));
+ assertEquals(D0, FieldUtils.readField(parentD, privatelyShadowedChild));
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readField(null, publicChild),
+ "a null field should cause an IllegalArgumentException");
+ }
+
+ @Test
+ public void testReadFieldForceAccess() throws Exception {
+ final Field parentS = FieldUtils.getDeclaredField(parentClass, "s");
+ parentS.setAccessible(false);
+ assertEquals("s", FieldUtils.readField(parentS, publicChild, true));
+ assertEquals("s", FieldUtils.readField(parentS, publiclyShadowedChild, true));
+ assertEquals("s", FieldUtils.readField(parentS, privatelyShadowedChild, true));
+ final Field parentB = FieldUtils.getDeclaredField(parentClass, "b", true);
+ parentB.setAccessible(false);
+ assertEquals(Boolean.FALSE, FieldUtils.readField(parentB, publicChild, true));
+ assertEquals(Boolean.FALSE, FieldUtils.readField(parentB, publiclyShadowedChild, true));
+ assertEquals(Boolean.FALSE, FieldUtils.readField(parentB, privatelyShadowedChild, true));
+ final Field parentI = FieldUtils.getDeclaredField(parentClass, "i", true);
+ parentI.setAccessible(false);
+ assertEquals(I0, FieldUtils.readField(parentI, publicChild, true));
+ assertEquals(I0, FieldUtils.readField(parentI, publiclyShadowedChild, true));
+ assertEquals(I0, FieldUtils.readField(parentI, privatelyShadowedChild, true));
+ final Field parentD = FieldUtils.getDeclaredField(parentClass, "d", true);
+ parentD.setAccessible(false);
+ assertEquals(D0, FieldUtils.readField(parentD, publicChild, true));
+ assertEquals(D0, FieldUtils.readField(parentD, publiclyShadowedChild, true));
+ assertEquals(D0, FieldUtils.readField(parentD, privatelyShadowedChild, true));
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readField(null, publicChild, true),
+ "a null field should cause an IllegalArgumentException");
+ }
+
+ @Test
+ public void testReadNamedField() throws Exception {
+ assertEquals("s", FieldUtils.readField(publicChild, "s"));
+ assertEquals("ss", FieldUtils.readField(publiclyShadowedChild, "s"));
+ assertEquals("s", FieldUtils.readField(privatelyShadowedChild, "s"));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readField(publicChild, null),
+ "a null field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readField(publicChild, ""),
+ "an empty field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readField(publicChild, " "),
+ "a blank field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readField((Object) null, "none"),
+ "a null target should cause an IllegalArgumentException");
+
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readField(publicChild, "b"));
+
+ assertEquals(Boolean.TRUE, FieldUtils.readField(publiclyShadowedChild, "b"));
+ assertThrows( IllegalArgumentException.class, () -> FieldUtils.readField(privatelyShadowedChild, "b"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readField(publicChild, "i"));
+ assertEquals(I1, FieldUtils.readField(publiclyShadowedChild, "i"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readField(privatelyShadowedChild, "i"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readField(publicChild, "d"));
+ assertEquals(D1, FieldUtils.readField(publiclyShadowedChild, "d"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readField(privatelyShadowedChild, "d"));
+ }
+
+ @Test
+ public void testReadNamedFieldForceAccess() throws Exception {
+ assertEquals("s", FieldUtils.readField(publicChild, "s", true));
+ assertEquals("ss", FieldUtils.readField(publiclyShadowedChild, "s", true));
+ assertEquals("ss", FieldUtils.readField(privatelyShadowedChild, "s", true));
+ assertEquals(Boolean.FALSE, FieldUtils.readField(publicChild, "b", true));
+ assertEquals(Boolean.TRUE, FieldUtils.readField(publiclyShadowedChild, "b", true));
+ assertEquals(Boolean.TRUE, FieldUtils.readField(privatelyShadowedChild, "b", true));
+ assertEquals(I0, FieldUtils.readField(publicChild, "i", true));
+ assertEquals(I1, FieldUtils.readField(publiclyShadowedChild, "i", true));
+ assertEquals(I1, FieldUtils.readField(privatelyShadowedChild, "i", true));
+ assertEquals(D0, FieldUtils.readField(publicChild, "d", true));
+ assertEquals(D1, FieldUtils.readField(publiclyShadowedChild, "d", true));
+ assertEquals(D1, FieldUtils.readField(privatelyShadowedChild, "d", true));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readField(publicChild, null, true),
+ "a null field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readField(publicChild, "", true),
+ "an empty field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readField(publicChild, " ", true),
+ "a blank field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readField((Object) null, "none", true),
+ "a null target should cause an IllegalArgumentException");
+ }
+
+ @Test
+ public void testReadDeclaredNamedField() throws Exception {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readDeclaredField(publicChild, null),
+ "a null field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readDeclaredField(publicChild, ""),
+ "an empty field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readDeclaredField(publicChild, " "),
+ "a blank field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readDeclaredField(null, "none"),
+ "a null target should cause an IllegalArgumentException");
+
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(publicChild, "s"));
+ assertEquals("ss", FieldUtils.readDeclaredField(publiclyShadowedChild, "s"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(privatelyShadowedChild, "s"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(publicChild, "b"));
+ assertEquals(Boolean.TRUE, FieldUtils.readDeclaredField(publiclyShadowedChild, "b"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(privatelyShadowedChild, "b"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(publicChild, "i"));
+ assertEquals(I1, FieldUtils.readDeclaredField(publiclyShadowedChild, "i"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(privatelyShadowedChild, "i"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(publicChild, "d"));
+ assertEquals(D1, FieldUtils.readDeclaredField(publiclyShadowedChild, "d"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(privatelyShadowedChild, "d"));
+ }
+
+ @Test
+ public void testReadDeclaredNamedFieldForceAccess() throws Exception {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readDeclaredField(publicChild, null, true),
+ "a null field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readDeclaredField(publicChild, "", true),
+ "an empty field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.readDeclaredField(publicChild, " ", true),
+ "a blank field name should cause an IllegalArgumentException");
+
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.readDeclaredField(null, "none", true),
+ "a null target should cause an IllegalArgumentException");
+
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(publicChild, "s", true));
+ assertEquals("ss", FieldUtils.readDeclaredField(publiclyShadowedChild, "s", true));
+ assertEquals("ss", FieldUtils.readDeclaredField(privatelyShadowedChild, "s", true));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(publicChild, "b", true));
+ assertEquals(Boolean.TRUE, FieldUtils.readDeclaredField(publiclyShadowedChild, "b", true));
+ assertEquals(Boolean.TRUE, FieldUtils.readDeclaredField(privatelyShadowedChild, "b", true));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(publicChild, "i", true));
+ assertEquals(I1, FieldUtils.readDeclaredField(publiclyShadowedChild, "i", true));
+ assertEquals(I1, FieldUtils.readDeclaredField(privatelyShadowedChild, "i", true));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.readDeclaredField(publicChild, "d", true));
+ assertEquals(D1, FieldUtils.readDeclaredField(publiclyShadowedChild, "d", true));
+ assertEquals(D1, FieldUtils.readDeclaredField(privatelyShadowedChild, "d", true));
+ }
+
+ @Test
+ public void testWriteStaticField() throws Exception {
+ final Field field = StaticContainer.class.getDeclaredField("mutablePublic");
+ FieldUtils.writeStaticField(field, "new");
+ assertEquals("new", StaticContainer.mutablePublic);
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("mutableProtected"), "new"));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("mutablePackage"), "new"));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("mutablePrivate"), "new"));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("IMMUTABLE_PUBLIC"), "new"));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("IMMUTABLE_PROTECTED"), "new"));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("IMMUTABLE_PACKAGE"), "new"));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("IMMUTABLE_PRIVATE"), "new"));
+ }
+
+ @Test
+ public void testWriteStaticFieldForceAccess() throws Exception {
+ Field field = StaticContainer.class.getDeclaredField("mutablePublic");
+ FieldUtils.writeStaticField(field, "new", true);
+ assertEquals("new", StaticContainer.mutablePublic);
+ field = StaticContainer.class.getDeclaredField("mutableProtected");
+ FieldUtils.writeStaticField(field, "new", true);
+ assertEquals("new", StaticContainer.getMutableProtected());
+ field = StaticContainer.class.getDeclaredField("mutablePackage");
+ FieldUtils.writeStaticField(field, "new", true);
+ assertEquals("new", StaticContainer.getMutablePackage());
+ field = StaticContainer.class.getDeclaredField("mutablePrivate");
+ FieldUtils.writeStaticField(field, "new", true);
+ assertEquals("new", StaticContainer.getMutablePrivate());
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("IMMUTABLE_PUBLIC"), "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("IMMUTABLE_PROTECTED"), "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("IMMUTABLE_PACKAGE"), "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainer.class.getDeclaredField("IMMUTABLE_PRIVATE"), "new", true));
+ }
+
+ @Test
+ public void testWriteNamedStaticField() throws Exception {
+ FieldUtils.writeStaticField(StaticContainerChild.class, "mutablePublic", "new");
+ assertEquals("new", StaticContainer.mutablePublic);
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "mutableProtected", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "mutablePackage", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "mutablePrivate", "new"));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "IMMUTABLE_PUBLIC", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "IMMUTABLE_PROTECTED", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "IMMUTABLE_PACKAGE", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "IMMUTABLE_PRIVATE", "new"));
+ }
+
+ @Test
+ public void testWriteNamedStaticFieldForceAccess() throws Exception {
+ FieldUtils.writeStaticField(StaticContainerChild.class, "mutablePublic", "new", true);
+ assertEquals("new", StaticContainer.mutablePublic);
+ FieldUtils.writeStaticField(StaticContainerChild.class, "mutableProtected", "new", true);
+ assertEquals("new", StaticContainer.getMutableProtected());
+ FieldUtils.writeStaticField(StaticContainerChild.class, "mutablePackage", "new", true);
+ assertEquals("new", StaticContainer.getMutablePackage());
+ FieldUtils.writeStaticField(StaticContainerChild.class, "mutablePrivate", "new", true);
+ assertEquals("new", StaticContainer.getMutablePrivate());
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "IMMUTABLE_PUBLIC", "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "IMMUTABLE_PROTECTED", "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "IMMUTABLE_PACKAGE", "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeStaticField(StaticContainerChild.class, "IMMUTABLE_PRIVATE", "new", true));
+ }
+
+ @Test
+ public void testWriteDeclaredNamedStaticField() throws Exception {
+ FieldUtils.writeStaticField(StaticContainer.class, "mutablePublic", "new");
+ assertEquals("new", StaticContainer.mutablePublic);
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "mutableProtected", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "mutablePackage", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "mutablePrivate", "new"));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "IMMUTABLE_PUBLIC", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "IMMUTABLE_PROTECTED", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "IMMUTABLE_PACKAGE", "new"));
+ assertThrows(
+ NullPointerException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "IMMUTABLE_PRIVATE", "new"));
+ }
+
+ @Test
+ public void testWriteDeclaredNamedStaticFieldForceAccess() throws Exception {
+ FieldUtils.writeDeclaredStaticField(StaticContainer.class, "mutablePublic", "new", true);
+ assertEquals("new", StaticContainer.mutablePublic);
+ FieldUtils.writeDeclaredStaticField(StaticContainer.class, "mutableProtected", "new", true);
+ assertEquals("new", StaticContainer.getMutableProtected());
+ FieldUtils.writeDeclaredStaticField(StaticContainer.class, "mutablePackage", "new", true);
+ assertEquals("new", StaticContainer.getMutablePackage());
+ FieldUtils.writeDeclaredStaticField(StaticContainer.class, "mutablePrivate", "new", true);
+ assertEquals("new", StaticContainer.getMutablePrivate());
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "IMMUTABLE_PUBLIC", "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "IMMUTABLE_PROTECTED", "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "IMMUTABLE_PACKAGE", "new", true));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeDeclaredStaticField(StaticContainer.class, "IMMUTABLE_PRIVATE", "new", true));
+ }
+
+ @Test
+ public void testWriteField() throws Exception {
+ final Field field = parentClass.getDeclaredField("s");
+ FieldUtils.writeField(field, publicChild, "S");
+ assertEquals("S", field.get(publicChild));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeField(parentClass.getDeclaredField("b"), publicChild, Boolean.TRUE));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeField(parentClass.getDeclaredField("i"), publicChild, Integer.valueOf(Integer.MAX_VALUE)));
+ assertThrows(
+ IllegalAccessException.class,
+ () -> FieldUtils.writeField(parentClass.getDeclaredField("d"), publicChild, Double.valueOf(Double.MAX_VALUE)));
+ }
+
+ @Test
+ public void testWriteFieldForceAccess() throws Exception {
+ Field field = parentClass.getDeclaredField("s");
+ FieldUtils.writeField(field, publicChild, "S", true);
+ assertEquals("S", field.get(publicChild));
+ field = parentClass.getDeclaredField("b");
+ FieldUtils.writeField(field, publicChild, Boolean.TRUE, true);
+ assertEquals(Boolean.TRUE, field.get(publicChild));
+ field = parentClass.getDeclaredField("i");
+ FieldUtils.writeField(field, publicChild, Integer.valueOf(Integer.MAX_VALUE), true);
+ assertEquals(Integer.valueOf(Integer.MAX_VALUE), field.get(publicChild));
+ field = parentClass.getDeclaredField("d");
+ FieldUtils.writeField(field, publicChild, Double.valueOf(Double.MAX_VALUE), true);
+ assertEquals(Double.valueOf(Double.MAX_VALUE), field.get(publicChild));
+ }
+
+ @Test
+ public void testWriteNamedField() throws Exception {
+ FieldUtils.writeField(publicChild, "s", "S");
+ assertEquals("S", FieldUtils.readField(publicChild, "s"));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.writeField(publicChild, "b", Boolean.TRUE));
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.writeField(publicChild, "i", Integer.valueOf(1)));
+ assertThrows(
+ IllegalArgumentException.class, () -> FieldUtils.writeField(publicChild, "d", Double.valueOf(1.0)));
+
+ FieldUtils.writeField(publiclyShadowedChild, "s", "S");
+ assertEquals("S", FieldUtils.readField(publiclyShadowedChild, "s"));
+ FieldUtils.writeField(publiclyShadowedChild, "b", Boolean.FALSE);
+ assertEquals(Boolean.FALSE, FieldUtils.readField(publiclyShadowedChild, "b"));
+ FieldUtils.writeField(publiclyShadowedChild, "i", Integer.valueOf(0));
+ assertEquals(Integer.valueOf(0), FieldUtils.readField(publiclyShadowedChild, "i"));
+ FieldUtils.writeField(publiclyShadowedChild, "d", Double.valueOf(0.0));
+ assertEquals(Double.valueOf(0.0), FieldUtils.readField(publiclyShadowedChild, "d"));
+
+ FieldUtils.writeField(privatelyShadowedChild, "s", "S");
+ assertEquals("S", FieldUtils.readField(privatelyShadowedChild, "s"));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeField(privatelyShadowedChild, "b", Boolean.TRUE));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeField(privatelyShadowedChild, "i", Integer.valueOf(1)));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeField(privatelyShadowedChild, "d", Double.valueOf(1.0)));
+ }
+
+ @Test
+ public void testWriteNamedFieldForceAccess() throws Exception {
+ FieldUtils.writeField(publicChild, "s", "S", true);
+ assertEquals("S", FieldUtils.readField(publicChild, "s", true));
+ FieldUtils.writeField(publicChild, "b", Boolean.TRUE, true);
+ assertEquals(Boolean.TRUE, FieldUtils.readField(publicChild, "b", true));
+ FieldUtils.writeField(publicChild, "i", Integer.valueOf(1), true);
+ assertEquals(Integer.valueOf(1), FieldUtils.readField(publicChild, "i", true));
+ FieldUtils.writeField(publicChild, "d", Double.valueOf(1.0), true);
+ assertEquals(Double.valueOf(1.0), FieldUtils.readField(publicChild, "d", true));
+
+ FieldUtils.writeField(publiclyShadowedChild, "s", "S", true);
+ assertEquals("S", FieldUtils.readField(publiclyShadowedChild, "s", true));
+ FieldUtils.writeField(publiclyShadowedChild, "b", Boolean.FALSE, true);
+ assertEquals(Boolean.FALSE, FieldUtils.readField(publiclyShadowedChild, "b", true));
+ FieldUtils.writeField(publiclyShadowedChild, "i", Integer.valueOf(0), true);
+ assertEquals(Integer.valueOf(0), FieldUtils.readField(publiclyShadowedChild, "i", true));
+ FieldUtils.writeField(publiclyShadowedChild, "d", Double.valueOf(0.0), true);
+ assertEquals(Double.valueOf(0.0), FieldUtils.readField(publiclyShadowedChild, "d", true));
+
+ FieldUtils.writeField(privatelyShadowedChild, "s", "S", true);
+ assertEquals("S", FieldUtils.readField(privatelyShadowedChild, "s", true));
+ FieldUtils.writeField(privatelyShadowedChild, "b", Boolean.FALSE, true);
+ assertEquals(Boolean.FALSE, FieldUtils.readField(privatelyShadowedChild, "b", true));
+ FieldUtils.writeField(privatelyShadowedChild, "i", Integer.valueOf(0), true);
+ assertEquals(Integer.valueOf(0), FieldUtils.readField(privatelyShadowedChild, "i", true));
+ FieldUtils.writeField(privatelyShadowedChild, "d", Double.valueOf(0.0), true);
+ assertEquals(Double.valueOf(0.0), FieldUtils.readField(privatelyShadowedChild, "d", true));
+ }
+
+ @Test
+ public void testWriteDeclaredNamedField() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.writeDeclaredField(publicChild, "s", "S"));
+ assertThrows(
+ IllegalArgumentException.class, () -> FieldUtils.writeDeclaredField(publicChild, "b", Boolean.TRUE));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeDeclaredField(publicChild, "i", Integer.valueOf(1)));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeDeclaredField(publicChild, "d", Double.valueOf(1.0)));
+
+ FieldUtils.writeDeclaredField(publiclyShadowedChild, "s", "S");
+ assertEquals("S", FieldUtils.readDeclaredField(publiclyShadowedChild, "s"));
+ FieldUtils.writeDeclaredField(publiclyShadowedChild, "b", Boolean.FALSE);
+ assertEquals(Boolean.FALSE, FieldUtils.readDeclaredField(publiclyShadowedChild, "b"));
+ FieldUtils.writeDeclaredField(publiclyShadowedChild, "i", Integer.valueOf(0));
+ assertEquals(Integer.valueOf(0), FieldUtils.readDeclaredField(publiclyShadowedChild, "i"));
+ FieldUtils.writeDeclaredField(publiclyShadowedChild, "d", Double.valueOf(0.0));
+ assertEquals(Double.valueOf(0.0), FieldUtils.readDeclaredField(publiclyShadowedChild, "d"));
+
+ assertThrows(
+ IllegalArgumentException.class, () -> FieldUtils.writeDeclaredField(privatelyShadowedChild, "s", "S"));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeDeclaredField(privatelyShadowedChild, "b", Boolean.TRUE));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeDeclaredField(privatelyShadowedChild, "i", Integer.valueOf(1)));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeDeclaredField(privatelyShadowedChild, "d", Double.valueOf(1.0)));
+ }
+
+ @Test
+ public void testWriteDeclaredNamedFieldForceAccess() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.writeDeclaredField(publicChild, "s", "S", true));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeDeclaredField(publicChild, "b", Boolean.TRUE, true));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeDeclaredField(publicChild, "i", Integer.valueOf(1), true));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldUtils.writeDeclaredField(publicChild, "d", Double.valueOf(1.0), true));
+
+ FieldUtils.writeDeclaredField(publiclyShadowedChild, "s", "S", true);
+ assertEquals("S", FieldUtils.readDeclaredField(publiclyShadowedChild, "s", true));
+ FieldUtils.writeDeclaredField(publiclyShadowedChild, "b", Boolean.FALSE, true);
+ assertEquals(Boolean.FALSE, FieldUtils.readDeclaredField(publiclyShadowedChild, "b", true));
+ FieldUtils.writeDeclaredField(publiclyShadowedChild, "i", Integer.valueOf(0), true);
+ assertEquals(Integer.valueOf(0), FieldUtils.readDeclaredField(publiclyShadowedChild, "i", true));
+ FieldUtils.writeDeclaredField(publiclyShadowedChild, "d", Double.valueOf(0.0), true);
+ assertEquals(Double.valueOf(0.0), FieldUtils.readDeclaredField(publiclyShadowedChild, "d", true));
+
+ FieldUtils.writeDeclaredField(privatelyShadowedChild, "s", "S", true);
+ assertEquals("S", FieldUtils.readDeclaredField(privatelyShadowedChild, "s", true));
+ FieldUtils.writeDeclaredField(privatelyShadowedChild, "b", Boolean.FALSE, true);
+ assertEquals(Boolean.FALSE, FieldUtils.readDeclaredField(privatelyShadowedChild, "b", true));
+ FieldUtils.writeDeclaredField(privatelyShadowedChild, "i", Integer.valueOf(0), true);
+ assertEquals(Integer.valueOf(0), FieldUtils.readDeclaredField(privatelyShadowedChild, "i", true));
+ FieldUtils.writeDeclaredField(privatelyShadowedChild, "d", Double.valueOf(0.0), true);
+ assertEquals(Double.valueOf(0.0), FieldUtils.readDeclaredField(privatelyShadowedChild, "d", true));
+ }
+
+ @Test
+ public void testAmbig() {
+ assertThrows(IllegalArgumentException.class, () -> FieldUtils.getField(Ambig.class, "VALUE"));
+ }
+
+ @Test
+ public void testRemoveFinalModifier() throws Exception {
+ final Field field = StaticContainer.class.getDeclaredField("IMMUTABLE_PRIVATE_2");
+ assertFalse(field.isAccessible());
+ assertTrue(Modifier.isFinal(field.getModifiers()));
+ callRemoveFinalModifierCheckForException(field, true);
+ if (SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_11)) {
+ assertFalse(Modifier.isFinal(field.getModifiers()));
+ assertFalse(field.isAccessible());
+ }
+ }
+
+ @Test
+ public void testRemoveFinalModifierWithAccess() throws Exception {
+ final Field field = StaticContainer.class.getDeclaredField("IMMUTABLE_PRIVATE_2");
+ assertFalse(field.isAccessible());
+ assertTrue(Modifier.isFinal(field.getModifiers()));
+ callRemoveFinalModifierCheckForException(field, true);
+ if (SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_11)) {
+ assertFalse(Modifier.isFinal(field.getModifiers()));
+ assertFalse(field.isAccessible());
+ }
+ }
+
+ @Test
+ public void testRemoveFinalModifierWithoutAccess() throws Exception {
+ final Field field = StaticContainer.class.getDeclaredField("IMMUTABLE_PRIVATE_2");
+ assertFalse(field.isAccessible());
+ assertTrue(Modifier.isFinal(field.getModifiers()));
+ callRemoveFinalModifierCheckForException(field, false);
+ if (SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_11)) {
+ assertTrue(Modifier.isFinal(field.getModifiers()));
+ assertFalse(field.isAccessible());
+ }
+ }
+
+ @Test
+ public void testRemoveFinalModifierAccessNotNeeded() throws Exception {
+ final Field field = StaticContainer.class.getDeclaredField("IMMUTABLE_PACKAGE");
+ assertFalse(field.isAccessible());
+ assertTrue(Modifier.isFinal(field.getModifiers()));
+ callRemoveFinalModifierCheckForException(field, false);
+ if (SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_11)) {
+ assertTrue(Modifier.isFinal(field.getModifiers()));
+ assertFalse(field.isAccessible());
+ }
+ }
+
+ /**
+ * Read the {@code @deprecated} notice on
+ * {@link FieldUtils#removeFinalModifier(Field, boolean)}.
+ *
+ * @param field {@link Field} to be curried into
+ * {@link FieldUtils#removeFinalModifier(Field, boolean)}.
+ * @param forceAccess {@link Boolean} to be curried into
+ * {@link FieldUtils#removeFinalModifier(Field, boolean)}.
+ */
+ private void callRemoveFinalModifierCheckForException(final Field field, final Boolean forceAccess) {
+ try {
+ FieldUtils.removeFinalModifier(field, forceAccess);
+ } catch (final UnsupportedOperationException exception) {
+ if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_12)) {
+ assertTrue(exception.getCause() instanceof NoSuchFieldException);
+ } else {
+ fail("No exception should be thrown for java prior to 12.0");
+ }
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/InheritanceUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/InheritanceUtilsTest.java
new file mode 100644
index 000000000..bb32c48b9
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/InheritanceUtilsTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.reflect.testbed.AnotherChild;
+import org.apache.commons.lang3.reflect.testbed.AnotherParent;
+import org.apache.commons.lang3.reflect.testbed.Grandchild;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests InheritanceUtils
+ */
+public class InheritanceUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructor() throws Exception {
+ assertNotNull(InheritanceUtils.class.newInstance());
+ }
+
+ @Test
+ public void testDistanceGreaterThanZero() {
+ assertEquals(1, InheritanceUtils.distance(AnotherChild.class, AnotherParent.class));
+ assertEquals(1, InheritanceUtils.distance(Grandchild.class, AnotherChild.class));
+ assertEquals(2, InheritanceUtils.distance(Grandchild.class, AnotherParent.class));
+ assertEquals(3, InheritanceUtils.distance(Grandchild.class, Object.class));
+ }
+
+ @Test
+ public void testDistanceEqual() {
+ assertEquals(0, InheritanceUtils.distance(AnotherChild.class, AnotherChild.class));
+ }
+
+ @Test
+ public void testDistanceEqualObject() {
+ assertEquals(0, InheritanceUtils.distance(Object.class, Object.class));
+ }
+
+ @Test
+ public void testDistanceNullChild() {
+ assertEquals(-1, InheritanceUtils.distance(null, Object.class));
+ }
+
+ @Test
+ public void testDistanceNullParent() {
+ assertEquals(-1, InheritanceUtils.distance(Object.class, null));
+ }
+
+ @Test
+ public void testDistanceNullParentNullChild() {
+ assertEquals(-1, InheritanceUtils.distance(null, null));
+ }
+
+ @Test
+ public void testDistanceDisjoint() {
+ assertEquals(-1, InheritanceUtils.distance(Boolean.class, String.class));
+ }
+
+ @Test
+ public void testDistanceReverseParentChild() {
+ assertEquals(-1, InheritanceUtils.distance(Object.class, Grandchild.class));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
new file mode 100644
index 000000000..21535a5c5
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
@@ -0,0 +1,1092 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItemInArray;
+import static org.hamcrest.Matchers.hasItems;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.awt.Color;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.ClassUtils.Interfaces;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.commons.lang3.reflect.testbed.Annotated;
+import org.apache.commons.lang3.reflect.testbed.GenericConsumer;
+import org.apache.commons.lang3.reflect.testbed.GenericParent;
+import org.apache.commons.lang3.reflect.testbed.PublicChild;
+import org.apache.commons.lang3.reflect.testbed.StringParameterizedChild;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests MethodUtils
+ */
+public class MethodUtilsTest extends AbstractLangTest {
+
+ private interface PrivateInterface {
+ }
+
+ static class TestBeanWithInterfaces implements PrivateInterface {
+ public String foo() {
+ return "foo()";
+ }
+ }
+
+ public static class TestBean {
+
+ public static String bar() {
+ return "bar()";
+ }
+
+ public static String bar(final int i) {
+ return "bar(int)";
+ }
+
+ public static String bar(final Integer i) {
+ return "bar(Integer)";
+ }
+
+ public static String bar(final double d) {
+ return "bar(double)";
+ }
+
+ public static String bar(final String s) {
+ return "bar(String)";
+ }
+
+ public static String bar(final Object o) {
+ return "bar(Object)";
+ }
+
+ public static String bar(final String... s) {
+ return "bar(String...)";
+ }
+
+ public static String bar(final long... s) {
+ return "bar(long...)";
+ }
+
+ public static String bar(final Integer i, final String... s) {
+ return "bar(int, String...)";
+ }
+
+ public static void oneParameterStatic(final String s) {
+ // empty
+ }
+
+ @SuppressWarnings("unused")
+ private void privateStuff() {
+ }
+
+ @SuppressWarnings("unused")
+ private String privateStringStuff() {
+ return "privateStringStuff()";
+ }
+
+ @SuppressWarnings("unused")
+ private String privateStringStuff(final int i) {
+ return "privateStringStuff(int)";
+ }
+
+ @SuppressWarnings("unused")
+ private String privateStringStuff(final Integer i) {
+ return "privateStringStuff(Integer)";
+ }
+
+ @SuppressWarnings("unused")
+ private String privateStringStuff(final double d) {
+ return "privateStringStuff(double)";
+ }
+
+ @SuppressWarnings("unused")
+ private String privateStringStuff(final String s) {
+ return "privateStringStuff(String)";
+ }
+
+ @SuppressWarnings("unused")
+ private String privateStringStuff(final Object s) {
+ return "privateStringStuff(Object)";
+ }
+
+ public String foo() {
+ return "foo()";
+ }
+
+ public String foo(final int i) {
+ return "foo(int)";
+ }
+
+ public String foo(final Integer i) {
+ return "foo(Integer)";
+ }
+
+ public String foo(final double d) {
+ return "foo(double)";
+ }
+
+ public String foo(final long l) {
+ return "foo(long)";
+ }
+
+ public String foo(final String s) {
+ return "foo(String)";
+ }
+
+ public String foo(final Object o) {
+ return "foo(Object)";
+ }
+
+ public String foo(final String... s) {
+ return "foo(String...)";
+ }
+
+ public String foo(final long... l) {
+ return "foo(long...)";
+ }
+
+ public String foo(final Integer i, final String... s) {
+ return "foo(int, String...)";
+ }
+
+ public void oneParameter(final String s) {
+ // empty
+ }
+
+ public String foo(final Object... s) {
+ return "foo(Object...)";
+ }
+
+ public int[] unboxing(final int... values) {
+ return values;
+ }
+
+ // This method is overloaded for the wrapper class for every primitive type, plus the common supertypes
+ // Number and Object. This is an acid test since it easily leads to ambiguous methods.
+ public static String varOverload(final Byte... args) {
+ return "Byte...";
+ }
+
+ public static String varOverload(final Character... args) {
+ return "Character...";
+ }
+
+ public static String varOverload(final Short... args) {
+ return "Short...";
+ }
+
+ public static String varOverload(final Boolean... args) {
+ return "Boolean...";
+ }
+
+ public static String varOverload(final Float... args) {
+ return "Float...";
+ }
+
+ public static String varOverload(final Double... args) {
+ return "Double...";
+ }
+
+ public static String varOverload(final Integer... args) {
+ return "Integer...";
+ }
+
+ public static String varOverload(final Long... args) {
+ return "Long...";
+ }
+
+ public static String varOverload(final Number... args) {
+ return "Number...";
+ }
+
+ public static String varOverload(final Object... args) {
+ return "Object...";
+ }
+
+ public static String varOverload(final String... args) {
+ return "String...";
+ }
+
+ // This method is overloaded for the wrapper class for every numeric primitive type, plus the common
+ // supertype Number
+ public static String numOverload(final Byte... args) {
+ return "Byte...";
+ }
+
+ public static String numOverload(final Short... args) {
+ return "Short...";
+ }
+
+ public static String numOverload(final Float... args) {
+ return "Float...";
+ }
+
+ public static String numOverload(final Double... args) {
+ return "Double...";
+ }
+
+ public static String numOverload(final Integer... args) {
+ return "Integer...";
+ }
+
+ public static String numOverload(final Long... args) {
+ return "Long...";
+ }
+
+ public static String numOverload(final Number... args) {
+ return "Number...";
+ }
+
+ // These varOverloadEcho and varOverloadEchoStatic methods are designed to verify that
+ // not only is the correct overloaded variant invoked, but that the varags arguments
+ // are also delivered correctly to the method.
+ public ImmutablePair<String, Object[]> varOverloadEcho(final String... args) {
+ return new ImmutablePair<>("String...", args);
+ }
+
+ public ImmutablePair<String, Object[]> varOverloadEcho(final Number... args) {
+ return new ImmutablePair<>("Number...", args);
+ }
+
+ public static ImmutablePair<String, Object[]> varOverloadEchoStatic(final String... args) {
+ return new ImmutablePair<>("String...", args);
+ }
+
+ public static ImmutablePair<String, Object[]> varOverloadEchoStatic(final Number... args) {
+ return new ImmutablePair<>("Number...", args);
+ }
+
+ static void verify(final ImmutablePair<String, Object[]> a, final ImmutablePair<String, Object[]> b) {
+ assertEquals(a.getLeft(), b.getLeft());
+ assertArrayEquals(a.getRight(), b.getRight());
+ }
+
+ static void verify(final ImmutablePair<String, Object[]> a, final Object obj) {
+ @SuppressWarnings("unchecked")
+ final ImmutablePair<String, Object[]> pair = (ImmutablePair<String, Object[]>) obj;
+ verify(a, pair);
+ }
+
+ }
+
+ private static class TestMutable implements Mutable<Object> {
+ @Override
+ public Object getValue() {
+ return null;
+ }
+
+ @Override
+ public void setValue(final Object value) {
+ }
+ }
+
+ private TestBean testBean;
+ private final Map<Class<?>, Class<?>[]> classCache = new HashMap<>();
+
+ @BeforeEach
+ public void setUp() {
+ testBean = new TestBean();
+ classCache.clear();
+ }
+
+ @Test
+ public void testConstructor() throws Exception {
+ assertNotNull(MethodUtils.class.newInstance());
+ }
+
+ @Test
+ public void verifyJavaVarargsOverloadingResolution() {
+ // This code is not a test of MethodUtils.
+ // Rather it makes explicit the behavior of the Java specification for
+ // various cases of overload resolution.
+ assertEquals("Byte...", TestBean.varOverload((byte) 1, (byte) 2));
+ assertEquals("Short...", TestBean.varOverload((short) 1, (short) 2));
+ assertEquals("Integer...", TestBean.varOverload(1, 2));
+ assertEquals("Long...", TestBean.varOverload(1L, 2L));
+ assertEquals("Float...", TestBean.varOverload(1f, 2f));
+ assertEquals("Double...", TestBean.varOverload(1d, 2d));
+ assertEquals("Character...", TestBean.varOverload('a', 'b'));
+ assertEquals("String...", TestBean.varOverload("a", "b"));
+ assertEquals("Boolean...", TestBean.varOverload(true, false));
+
+ assertEquals("Object...", TestBean.varOverload(1, "s"));
+ assertEquals("Object...", TestBean.varOverload(1, true));
+ assertEquals("Object...", TestBean.varOverload(1.1, true));
+ assertEquals("Object...", TestBean.varOverload('c', true));
+ assertEquals("Number...", TestBean.varOverload(1, 1.1));
+ assertEquals("Number...", TestBean.varOverload(1, 1L));
+ assertEquals("Number...", TestBean.varOverload(1d, 1f));
+ assertEquals("Number...", TestBean.varOverload((short) 1, (byte) 1));
+ assertEquals("Object...", TestBean.varOverload(1, 'c'));
+ assertEquals("Object...", TestBean.varOverload('c', "s"));
+ }
+
+ @Test
+ public void testInvokeJavaVarargsOverloadingResolution() throws Exception {
+ assertEquals("Byte...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", (byte) 1, (byte) 2));
+ assertEquals("Short...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", (short) 1, (short) 2));
+ assertEquals("Integer...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, 2));
+ assertEquals("Long...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1L, 2L));
+ assertEquals("Float...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1f, 2f));
+ assertEquals("Double...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1d, 2d));
+ assertEquals("Character...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 'a', 'b'));
+ assertEquals("String...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", "a", "b"));
+ assertEquals("Boolean...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", true, false));
+
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, "s"));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, true));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1.1, true));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 'c', true));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, 1.1));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, 1L));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1d, 1f));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", (short) 1, (byte) 1));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, 'c'));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 'c', "s"));
+
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, "varOverload",
+ (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class, "numOverload",
+ (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
+ }
+
+ @Test
+ public void testInvokeMethod() throws Exception {
+ assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
+ (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
+ assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo"));
+ assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
+ (Object[]) null));
+ assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
+ null, null));
+ assertEquals("foo(String)", MethodUtils.invokeMethod(testBean, "foo",
+ ""));
+ assertEquals("foo(Object)", MethodUtils.invokeMethod(testBean, "foo",
+ new Object()));
+ assertEquals("foo(Object)", MethodUtils.invokeMethod(testBean, "foo",
+ Boolean.TRUE));
+ assertEquals("foo(Integer)", MethodUtils.invokeMethod(testBean, "foo",
+ NumberUtils.INTEGER_ONE));
+ assertEquals("foo(int)", MethodUtils.invokeMethod(testBean, "foo",
+ NumberUtils.BYTE_ONE));
+ assertEquals("foo(long)", MethodUtils.invokeMethod(testBean, "foo",
+ NumberUtils.LONG_ONE));
+ assertEquals("foo(double)", MethodUtils.invokeMethod(testBean, "foo",
+ NumberUtils.DOUBLE_ONE));
+ assertEquals("foo(String...)", MethodUtils.invokeMethod(testBean, "foo",
+ "a", "b", "c"));
+ assertEquals("foo(String...)", MethodUtils.invokeMethod(testBean, "foo",
+ "a", "b", "c"));
+ assertEquals("foo(int, String...)", MethodUtils.invokeMethod(testBean, "foo",
+ 5, "a", "b", "c"));
+ assertEquals("foo(long...)", MethodUtils.invokeMethod(testBean, "foo",
+ 1L, 2L));
+
+ assertThrows(NoSuchMethodException.class, () -> MethodUtils.invokeMethod(testBean, "foo", 1, 2));
+
+ TestBean.verify(new ImmutablePair<>("String...", new String[]{"x", "y"}),
+ MethodUtils.invokeMethod(testBean, "varOverloadEcho", "x", "y"));
+ TestBean.verify(new ImmutablePair<>("Number...", new Number[]{17, 23, 42}),
+ MethodUtils.invokeMethod(testBean, "varOverloadEcho", 17, 23, 42));
+ TestBean.verify(new ImmutablePair<>("String...", new String[]{"x", "y"}),
+ MethodUtils.invokeMethod(testBean, "varOverloadEcho", "x", "y"));
+ TestBean.verify(new ImmutablePair<>("Number...", new Number[]{17, 23, 42}),
+ MethodUtils.invokeMethod(testBean, "varOverloadEcho", 17, 23, 42));
+ }
+
+ @Test
+ public void testInvokeMethod_VarArgsWithNullValues() throws Exception {
+ assertEquals("String...", MethodUtils.invokeMethod(testBean, "varOverload",
+ "a", null, "c"));
+ assertEquals("String...", MethodUtils.invokeMethod(testBean, "varOverload",
+ "a", "b", null));
+ }
+
+ @Test
+ public void testInvokeMethod_VarArgsNotUniqueResolvable() throws Exception {
+ assertEquals("Boolean...", MethodUtils.invokeMethod(testBean, "varOverload",
+ new Object[] {null}));
+ assertEquals("Object...", MethodUtils.invokeMethod(testBean, "varOverload",
+ (Object[]) null));
+ }
+
+ @Test
+ public void testInvokeExactMethod() throws Exception {
+ assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",
+ (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
+ assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo"));
+ assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",
+ (Object[]) null));
+ assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",
+ null, null));
+ assertEquals("foo(String)", MethodUtils.invokeExactMethod(testBean,
+ "foo", ""));
+ assertEquals("foo(Object)", MethodUtils.invokeExactMethod(testBean,
+ "foo", new Object()));
+ assertEquals("foo(Integer)", MethodUtils.invokeExactMethod(testBean,
+ "foo", NumberUtils.INTEGER_ONE));
+ assertEquals("foo(double)", MethodUtils.invokeExactMethod(testBean,
+ "foo", new Object[]{NumberUtils.DOUBLE_ONE},
+ new Class[]{Double.TYPE}));
+
+ assertThrows(
+ NoSuchMethodException.class,
+ () -> MethodUtils.invokeExactMethod(testBean, "foo", NumberUtils.BYTE_ONE));
+
+ assertThrows(
+ NoSuchMethodException.class,
+ () -> MethodUtils.invokeExactMethod(testBean, "foo", NumberUtils.LONG_ONE));
+ assertThrows(NoSuchMethodException.class, () -> MethodUtils.invokeExactMethod(testBean, "foo", Boolean.TRUE));
+ }
+
+ @Test
+ public void testInvokeStaticMethod() throws Exception {
+ assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class,
+ "bar", (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
+ assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class,
+ "bar", (Object[]) null));
+ assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class,
+ "bar", null, null));
+ assertEquals("bar(String)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", ""));
+ assertEquals("bar(Object)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", new Object()));
+ assertEquals("bar(Object)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", Boolean.TRUE));
+ assertEquals("bar(Integer)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", NumberUtils.INTEGER_ONE));
+ assertEquals("bar(int)", MethodUtils.invokeStaticMethod(TestBean.class,
+ "bar", NumberUtils.BYTE_ONE));
+ assertEquals("bar(double)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", NumberUtils.DOUBLE_ONE));
+ assertEquals("bar(String...)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", "a", "b"));
+ assertEquals("bar(long...)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", 1L, 2L));
+ assertEquals("bar(int, String...)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", NumberUtils.INTEGER_ONE, "a", "b"));
+
+ TestBean.verify(new ImmutablePair<>("String...", new String[]{"x", "y"}),
+ MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", "x", "y"));
+ TestBean.verify(new ImmutablePair<>("Number...", new Number[]{17, 23, 42}),
+ MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", 17, 23, 42));
+ TestBean.verify(new ImmutablePair<>("String...", new String[]{"x", "y"}),
+ MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", "x", "y"));
+ TestBean.verify(new ImmutablePair<>("Number...", new Number[]{17, 23, 42}),
+ MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", 17, 23, 42));
+
+ assertThrows(
+ NoSuchMethodException.class, () -> MethodUtils.invokeStaticMethod(TestBean.class, "does_not_exist"));
+ }
+
+ @Test
+ public void testInvokeExactStaticMethod() throws Exception {
+ assertEquals("bar()", MethodUtils.invokeExactStaticMethod(TestBean.class,
+ "bar", (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
+ assertEquals("bar()", MethodUtils.invokeExactStaticMethod(TestBean.class,
+ "bar", (Object[]) null));
+ assertEquals("bar()", MethodUtils.invokeExactStaticMethod(TestBean.class,
+ "bar", null, null));
+ assertEquals("bar(String)", MethodUtils.invokeExactStaticMethod(
+ TestBean.class, "bar", ""));
+ assertEquals("bar(Object)", MethodUtils.invokeExactStaticMethod(
+ TestBean.class, "bar", new Object()));
+ assertEquals("bar(Integer)", MethodUtils.invokeExactStaticMethod(
+ TestBean.class, "bar", NumberUtils.INTEGER_ONE));
+ assertEquals("bar(double)", MethodUtils.invokeExactStaticMethod(
+ TestBean.class, "bar", new Object[]{NumberUtils.DOUBLE_ONE},
+ new Class[]{Double.TYPE}));
+
+ assertThrows(
+ NoSuchMethodException.class,
+ () -> MethodUtils.invokeExactStaticMethod(TestBean.class, "bar", NumberUtils.BYTE_ONE));
+ assertThrows(
+ NoSuchMethodException.class,
+ () -> MethodUtils.invokeExactStaticMethod(TestBean.class, "bar", NumberUtils.LONG_ONE));
+ assertThrows(
+ NoSuchMethodException.class,
+ () -> MethodUtils.invokeExactStaticMethod(TestBean.class, "bar", Boolean.TRUE));
+ }
+
+ @Test
+ public void testGetAccessibleInterfaceMethod() throws Exception {
+ final Class<?>[][] p = {ArrayUtils.EMPTY_CLASS_ARRAY, null};
+ for (final Class<?>[] element : p) {
+ final Method method = TestMutable.class.getMethod("getValue", element);
+ final Method accessibleMethod = MethodUtils.getAccessibleMethod(method);
+ assertNotSame(accessibleMethod, method);
+ assertSame(Mutable.class, accessibleMethod.getDeclaringClass());
+ }
+ }
+
+ @Test
+ public void testGetAccessibleMethodPrivateInterface() throws Exception {
+ final Method expected = TestBeanWithInterfaces.class.getMethod("foo");
+ assertNotNull(expected);
+ final Method actual = MethodUtils.getAccessibleMethod(TestBeanWithInterfaces.class, "foo");
+ assertNull(actual);
+ }
+
+ @Test
+ public void testGetAccessibleInterfaceMethodFromDescription() {
+ final Class<?>[][] p = {ArrayUtils.EMPTY_CLASS_ARRAY, null};
+ for (final Class<?>[] element : p) {
+ final Method accessibleMethod = MethodUtils.getAccessibleMethod(
+ TestMutable.class, "getValue", element);
+ assertSame(Mutable.class, accessibleMethod.getDeclaringClass());
+ }
+ }
+
+ @Test
+ public void testGetAccessiblePublicMethod() throws Exception {
+ assertSame(MutableObject.class, MethodUtils.getAccessibleMethod(
+ MutableObject.class.getMethod("getValue",
+ ArrayUtils.EMPTY_CLASS_ARRAY)).getDeclaringClass());
+ }
+
+ @Test
+ public void testGetAccessiblePublicMethodFromDescription() {
+ assertSame(MutableObject.class, MethodUtils.getAccessibleMethod(
+ MutableObject.class, "getValue", ArrayUtils.EMPTY_CLASS_ARRAY)
+ .getDeclaringClass());
+ }
+
+ @Test
+ public void testGetAccessibleMethodInaccessible() throws Exception {
+ final Method expected = TestBean.class.getDeclaredMethod("privateStuff");
+ final Method actual = MethodUtils.getAccessibleMethod(expected);
+ assertNull(actual);
+ }
+
+ @Test
+ public void testGetMatchingAccessibleMethod() {
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ ArrayUtils.EMPTY_CLASS_ARRAY, ArrayUtils.EMPTY_CLASS_ARRAY);
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ null, ArrayUtils.EMPTY_CLASS_ARRAY);
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(String.class), singletonArray(String.class));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Object.class), singletonArray(Object.class));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Boolean.class), singletonArray(Object.class));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Byte.class), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Byte.TYPE), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Short.class), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Short.TYPE), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Character.class), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Character.TYPE), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Integer.class), singletonArray(Integer.class));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Integer.TYPE), singletonArray(Integer.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Long.class), singletonArray(Long.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Long.TYPE), singletonArray(Long.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Float.class), singletonArray(Double.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Float.TYPE), singletonArray(Double.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Double.class), singletonArray(Double.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Double.TYPE), singletonArray(Double.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ singletonArray(Double.TYPE), singletonArray(Double.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ new Class[]{String.class, String.class}, new Class[]{String[].class});
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ new Class[]{Integer.TYPE, String.class, String.class}, new Class[]{Integer.class, String[].class});
+ expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testOne",
+ singletonArray(ParentObject.class), singletonArray(ParentObject.class));
+ expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testOne",
+ singletonArray(ChildObject.class), singletonArray(ParentObject.class));
+ expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testTwo",
+ singletonArray(ParentObject.class), singletonArray(GrandParentObject.class));
+ expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testTwo",
+ singletonArray(ChildObject.class), singletonArray(ChildInterface.class));
+ }
+
+ @Test
+ public void testNullArgument() {
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "oneParameter",
+ singletonArray(null), singletonArray(String.class));
+ }
+
+ @Test
+ public void testGetOverrideHierarchyIncludingInterfaces() {
+ final Method method = MethodUtils.getAccessibleMethod(StringParameterizedChild.class, "consume", String.class);
+ final Iterator<MethodDescriptor> expected =
+ Arrays.asList(new MethodDescriptor(StringParameterizedChild.class, "consume", String.class),
+ new MethodDescriptor(GenericParent.class, "consume", GenericParent.class.getTypeParameters()[0]),
+ new MethodDescriptor(GenericConsumer.class, "consume", GenericConsumer.class.getTypeParameters()[0]))
+ .iterator();
+ for (final Method m : MethodUtils.getOverrideHierarchy(method, Interfaces.INCLUDE)) {
+ assertTrue(expected.hasNext());
+ final MethodDescriptor md = expected.next();
+ assertEquals(md.declaringClass, m.getDeclaringClass());
+ assertEquals(md.name, m.getName());
+ assertEquals(md.parameterTypes.length, m.getParameterTypes().length);
+ for (int i = 0; i < md.parameterTypes.length; i++) {
+ assertTrue(TypeUtils.equals(md.parameterTypes[i], m.getGenericParameterTypes()[i]));
+ }
+ }
+ assertFalse(expected.hasNext());
+ }
+
+ @Test
+ public void testGetOverrideHierarchyExcludingInterfaces() {
+ final Method method = MethodUtils.getAccessibleMethod(StringParameterizedChild.class, "consume", String.class);
+ final Iterator<MethodDescriptor> expected =
+ Arrays.asList(new MethodDescriptor(StringParameterizedChild.class, "consume", String.class),
+ new MethodDescriptor(GenericParent.class, "consume", GenericParent.class.getTypeParameters()[0]))
+ .iterator();
+ for (final Method m : MethodUtils.getOverrideHierarchy(method, Interfaces.EXCLUDE)) {
+ assertTrue(expected.hasNext());
+ final MethodDescriptor md = expected.next();
+ assertEquals(md.declaringClass, m.getDeclaringClass());
+ assertEquals(md.name, m.getName());
+ assertEquals(md.parameterTypes.length, m.getParameterTypes().length);
+ for (int i = 0; i < md.parameterTypes.length; i++) {
+ assertTrue(TypeUtils.equals(md.parameterTypes[i], m.getGenericParameterTypes()[i]));
+ }
+ }
+ assertFalse(expected.hasNext());
+ }
+
+ @Test
+ @Annotated
+ public void testGetMethodsWithAnnotation() throws NoSuchMethodException {
+ assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class));
+
+ final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(MethodUtilsTest.class, Annotated.class);
+ assertEquals(2, methodsWithAnnotation.length);
+ assertThat(methodsWithAnnotation, hasItemInArray(MethodUtilsTest.class.getMethod("testGetMethodsWithAnnotation")));
+ assertThat(methodsWithAnnotation, hasItemInArray(MethodUtilsTest.class.getMethod("testGetMethodsListWithAnnotation")));
+ }
+
+ @Test
+ public void testGetMethodsWithAnnotationSearchSupersAndIgnoreAccess() {
+ assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class,
+ true, true));
+
+ final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class,
+ true, true);
+ assertEquals(4, methodsWithAnnotation.length);
+ assertEquals("PublicChild", methodsWithAnnotation[0].getDeclaringClass().getSimpleName());
+ assertEquals("PublicChild", methodsWithAnnotation[1].getDeclaringClass().getSimpleName());
+ assertTrue(methodsWithAnnotation[0].getName().endsWith("AnnotatedMethod"));
+ assertTrue(methodsWithAnnotation[1].getName().endsWith("AnnotatedMethod"));
+ assertEquals("Foo.doIt",
+ methodsWithAnnotation[2].getDeclaringClass().getSimpleName() + '.' +
+ methodsWithAnnotation[2].getName());
+ assertEquals("Parent.parentProtectedAnnotatedMethod",
+ methodsWithAnnotation[3].getDeclaringClass().getSimpleName() + '.' +
+ methodsWithAnnotation[3].getName());
+ }
+
+ @Test
+ public void testGetMethodsWithAnnotationNotSearchSupersButIgnoreAccess() {
+ assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class,
+ false, true));
+
+ final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class,
+ false, true);
+ assertEquals(2, methodsWithAnnotation.length);
+ assertEquals("PublicChild", methodsWithAnnotation[0].getDeclaringClass().getSimpleName());
+ assertEquals("PublicChild", methodsWithAnnotation[1].getDeclaringClass().getSimpleName());
+ assertTrue(methodsWithAnnotation[0].getName().endsWith("AnnotatedMethod"));
+ assertTrue(methodsWithAnnotation[1].getName().endsWith("AnnotatedMethod"));
+ }
+
+ @Test
+ public void testGetMethodsWithAnnotationSearchSupersButNotIgnoreAccess() {
+ assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class,
+ true, false));
+
+ final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class,
+ true, false);
+ assertEquals(2, methodsWithAnnotation.length);
+ assertEquals("PublicChild.publicAnnotatedMethod",
+ methodsWithAnnotation[0].getDeclaringClass().getSimpleName() + '.' +
+ methodsWithAnnotation[0].getName());
+ assertEquals("Foo.doIt",
+ methodsWithAnnotation[1].getDeclaringClass().getSimpleName() + '.' +
+ methodsWithAnnotation[1].getName());
+ }
+
+ @Test
+ public void testGetMethodsWithAnnotationNotSearchSupersAndNotIgnoreAccess() {
+ assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class,
+ false, false));
+
+ final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class,
+ false, false);
+ assertEquals(1, methodsWithAnnotation.length);
+ assertEquals("PublicChild.publicAnnotatedMethod",
+ methodsWithAnnotation[0].getDeclaringClass().getSimpleName() + '.' +
+ methodsWithAnnotation[0].getName());
+ }
+
+ @Test
+ public void testGetAnnotationSearchSupersAndIgnoreAccess() throws NoSuchMethodException {
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"),
+ Annotated.class, true, true));
+ assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class,
+ true, true));
+ assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"),
+ Annotated.class, true, true));
+ assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"),
+ Annotated.class, true, true));
+ assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"),
+ Annotated.class, true, true));
+
+ assertNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("parentNotAnnotatedMethod", String.class),
+ Annotated.class, true, true));
+ assertNotNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("parentProtectedAnnotatedMethod", String.class),
+ Annotated.class, true, true));
+ assertNotNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getDeclaredMethod("privateAnnotatedMethod", String.class),
+ Annotated.class, true, true));
+ assertNotNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("publicAnnotatedMethod", String.class),
+ Annotated.class, true, true));
+ }
+
+ @Test
+ public void testGetAnnotationNotSearchSupersButIgnoreAccess() throws NoSuchMethodException {
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"),
+ Annotated.class, false, true));
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class,
+ false, true));
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"),
+ Annotated.class, false, true));
+ assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"),
+ Annotated.class, false, true));
+ assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"),
+ Annotated.class, false, true));
+ }
+
+ @Test
+ public void testGetAnnotationSearchSupersButNotIgnoreAccess() throws NoSuchMethodException {
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"),
+ Annotated.class, true, false));
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class,
+ true, false));
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"),
+ Annotated.class, true, false));
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"),
+ Annotated.class, true, false));
+ assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"),
+ Annotated.class, true, false));
+
+ assertNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("parentNotAnnotatedMethod", String.class),
+ Annotated.class, true, false));
+ assertNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("parentProtectedAnnotatedMethod", String.class),
+ Annotated.class, true, false));
+ assertNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getDeclaredMethod("privateAnnotatedMethod", String.class),
+ Annotated.class, true, false));
+ assertNotNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("publicAnnotatedMethod", String.class),
+ Annotated.class, true, false));
+ }
+
+ @Test
+ public void testGetAnnotationNotSearchSupersAndNotIgnoreAccess() throws NoSuchMethodException {
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"),
+ Annotated.class, false, false));
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class,
+ false, false));
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"),
+ Annotated.class, false, false));
+ assertNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"),
+ Annotated.class, false, false));
+ assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"),
+ Annotated.class, false, false));
+ }
+
+ @Test
+ public void testGetMethodsWithAnnotationIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsWithAnnotation(FieldUtilsTest.class, null));
+ }
+
+ @Test
+ public void testGetMethodsWithAnnotationIllegalArgumentException2() {
+ assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsWithAnnotation(null, Annotated.class));
+ }
+
+ @Test
+ public void testGetMethodsWithAnnotationIllegalArgumentException3() {
+ assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsWithAnnotation(null, null));
+ }
+
+ @Test
+ @Annotated
+ public void testGetMethodsListWithAnnotation() throws NoSuchMethodException {
+ assertEquals(0, MethodUtils.getMethodsListWithAnnotation(Object.class, Annotated.class).size());
+
+ final List<Method> methodWithAnnotation = MethodUtils.getMethodsListWithAnnotation(MethodUtilsTest.class, Annotated.class);
+ assertEquals(2, methodWithAnnotation.size());
+ assertThat(methodWithAnnotation, hasItems(
+ MethodUtilsTest.class.getMethod("testGetMethodsWithAnnotation"),
+ MethodUtilsTest.class.getMethod("testGetMethodsListWithAnnotation")
+ ));
+ }
+
+ @Test
+ public void testGetMethodsListWithAnnotationIllegalArgumentException1() {
+ assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsListWithAnnotation(FieldUtilsTest.class, null));
+ }
+
+ @Test
+ public void testGetMethodsListWithAnnotationIllegalArgumentException2() {
+ assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsListWithAnnotation(null, Annotated.class));
+ }
+
+ @Test
+ public void testGetMethodsListWithAnnotationIllegalArgumentException3() {
+ assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsListWithAnnotation(null, null));
+ }
+
+ @Test
+ public void testGetAnnotationIllegalArgumentException1() {
+ assertThrows(NullPointerException.class,
+ () -> MethodUtils.getAnnotation(FieldUtilsTest.class.getDeclaredMethods()[0], null, true, true));
+ }
+
+ @Test
+ public void testGetAnnotationIllegalArgumentException2() {
+ assertThrows(NullPointerException.class, () -> MethodUtils.getAnnotation(null, Annotated.class, true, true));
+ }
+
+ @Test
+ public void testGetAnnotationIllegalArgumentException3() {
+ assertThrows(NullPointerException.class, () -> MethodUtils.getAnnotation(null, null, true, true));
+ }
+
+ private void expectMatchingAccessibleMethodParameterTypes(final Class<?> cls,
+ final String methodName, final Class<?>[] requestTypes, final Class<?>[] actualTypes) {
+ final Method m = MethodUtils.getMatchingAccessibleMethod(cls, methodName,
+ requestTypes);
+ assertNotNull(m, "could not find any matches for " + methodName
+ + " (" + (requestTypes == null ? null : toString(requestTypes)) + ")");
+ assertArrayEquals(actualTypes, m.getParameterTypes(), toString(m.getParameterTypes()) + " not equals " + toString(actualTypes));
+ }
+
+ private String toString(final Class<?>[] c) {
+ return Arrays.asList(c).toString();
+ }
+
+ private Class<?>[] singletonArray(final Class<?> c) {
+ Class<?>[] result = classCache.get(c);
+ if (result == null) {
+ result = new Class[]{c};
+ classCache.put(c, result);
+ }
+ return result;
+ }
+
+ public static class InheritanceBean {
+ public void testOne(final Object obj) {
+ }
+
+ public void testOne(final GrandParentObject obj) {
+ }
+
+ public void testOne(final ParentObject obj) {
+ }
+
+ public void testTwo(final Object obj) {
+ }
+
+ public void testTwo(final GrandParentObject obj) {
+ }
+
+ public void testTwo(final ChildInterface obj) {
+ }
+ }
+
+ interface ChildInterface {
+ }
+
+ public static class GrandParentObject {
+ }
+
+ public static class ParentObject extends GrandParentObject {
+ }
+
+ public static class ChildObject extends ParentObject implements ChildInterface {
+ }
+
+ private static class MethodDescriptor {
+ final Class<?> declaringClass;
+ final String name;
+ final Type[] parameterTypes;
+
+ MethodDescriptor(final Class<?> declaringClass, final String name, final Type... parameterTypes) {
+ this.declaringClass = declaringClass;
+ this.name = name;
+ this.parameterTypes = parameterTypes;
+ }
+ }
+
+ @Test
+ public void testVarArgsUnboxing() throws Exception {
+ final TestBean testBean = new TestBean();
+ final int[] actual = (int[]) MethodUtils.invokeMethod(testBean, "unboxing", Integer.valueOf(1), Integer.valueOf(2));
+ assertArrayEquals(new int[]{1, 2}, actual);
+ }
+
+ @Test
+ public void testInvokeMethodForceAccessNoArgs() throws Exception {
+ assertEquals("privateStringStuff()", MethodUtils.invokeMethod(testBean, true, "privateStringStuff"));
+ }
+
+ @Test
+ public void testInvokeMethodForceAccessWithArgs() throws Exception {
+ assertEquals("privateStringStuff(Integer)", MethodUtils.invokeMethod(testBean, true, "privateStringStuff", 5));
+ assertEquals("privateStringStuff(double)", MethodUtils.invokeMethod(testBean, true, "privateStringStuff", 5.0d));
+ assertEquals("privateStringStuff(String)", MethodUtils.invokeMethod(testBean, true, "privateStringStuff", "Hi There"));
+ assertEquals("privateStringStuff(Object)", MethodUtils.invokeMethod(testBean, true, "privateStringStuff", new Date()));
+ }
+
+ @Test
+ public void testDistance() throws Exception {
+ final Method distanceMethod = MethodUtils.getMatchingMethod(MethodUtils.class, "distance", Class[].class, Class[].class);
+ distanceMethod.setAccessible(true);
+
+ assertEquals(-1, distanceMethod.invoke(null, new Class[]{String.class}, new Class[]{Date.class}));
+ assertEquals(0, distanceMethod.invoke(null, new Class[]{Date.class}, new Class[]{Date.class}));
+ assertEquals(1, distanceMethod.invoke(null, new Class[]{Integer.class}, new Class[]{ClassUtils.wrapperToPrimitive(Integer.class)}));
+ assertEquals(2, distanceMethod.invoke(null, new Class[]{Integer.class}, new Class[]{Object.class}));
+
+ distanceMethod.setAccessible(false);
+ }
+
+ @Test
+ public void testGetMatchingMethod() throws NoSuchMethodException {
+ assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod"),
+ GetMatchingMethodClass.class.getMethod("testMethod"));
+
+ assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod", Long.TYPE),
+ GetMatchingMethodClass.class.getMethod("testMethod", Long.TYPE));
+
+ assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod", Long.class),
+ GetMatchingMethodClass.class.getMethod("testMethod", Long.class));
+
+ assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod", (Class<?>) null),
+ GetMatchingMethodClass.class.getMethod("testMethod", Long.class));
+
+ assertThrows(IllegalStateException.class,
+ () -> MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod2", (Class<?>) null));
+
+ assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod3", Long.TYPE, Long.class),
+ GetMatchingMethodClass.class.getMethod("testMethod3", Long.TYPE, Long.class));
+
+ assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod3", Long.class, Long.TYPE),
+ GetMatchingMethodClass.class.getMethod("testMethod3", Long.class, Long.TYPE));
+
+ assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod3", null, Long.TYPE),
+ GetMatchingMethodClass.class.getMethod("testMethod3", Long.class, Long.TYPE));
+
+ assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod3", Long.TYPE, null),
+ GetMatchingMethodClass.class.getMethod("testMethod3", Long.TYPE, Long.class));
+
+ assertThrows(IllegalStateException.class,
+ () -> MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod4", null, null));
+ }
+
+ private static final class GetMatchingMethodClass {
+ public void testMethod() {
+ }
+
+ public void testMethod(final Long aLong) {
+ }
+
+ public void testMethod(final long aLong) {
+ }
+
+ public void testMethod2(final Long aLong) {
+ }
+
+ public void testMethod2(final Color aColor) {
+ }
+
+ public void testMethod2(final long aLong) {
+ }
+
+ public void testMethod3(final long aLong, final Long anotherLong) {
+ }
+
+ public void testMethod3(final Long aLong, final long anotherLong) {
+ }
+
+ public void testMethod3(final Long aLong, final Long anotherLong) {
+ }
+
+ public void testMethod4(final Long aLong, final Long anotherLong) {
+ }
+
+ public void testMethod4(final Color aColor1, final Color aColor2) {
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/TypeLiteralTest.java b/src/test/java/org/apache/commons/lang3/reflect/TypeLiteralTest.java
new file mode 100644
index 000000000..6293ef8e1
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/TypeLiteralTest.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.reflect;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+public class TypeLiteralTest extends AbstractLangTest {
+
+ @Test
+ public void testBasic() {
+ assertTrue(TypeUtils.equals(String.class, new TypeLiteral<String>() {}.value));
+ assertTrue(TypeUtils.equals(TypeUtils.parameterize(List.class, String.class),
+ new TypeLiteral<List<String>>() {}.value));
+ }
+
+ @Test
+ public void testTyped() {
+ final Typed<String> stringType = new TypeLiteral<String>() {};
+ assertTrue(TypeUtils.equals(String.class, stringType.getType()));
+ final Typed<List<String>> listOfStringType = new TypeLiteral<List<String>>() {};
+ assertTrue(TypeUtils.equals(TypeUtils.parameterize(List.class, String.class), listOfStringType.getType()));
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(new TypeLiteral<String>() {}, new TypeLiteral<String>() {});
+ assertEquals(new TypeLiteral<List<String>>() {}, new TypeLiteral<List<String>>() {});
+ assertNotEquals(new TypeLiteral<String>() {}, new TypeLiteral<List<String>>() {});
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void testRaw() {
+ assertThrows(NullPointerException.class, () -> new TypeLiteral() {});
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
new file mode 100644
index 000000000..49f2c0d7d
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
@@ -0,0 +1,1039 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.awt.Insets;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeSet;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.reflect.testbed.Foo;
+import org.apache.commons.lang3.reflect.testbed.GenericParent;
+import org.apache.commons.lang3.reflect.testbed.GenericTypeHolder;
+import org.apache.commons.lang3.reflect.testbed.StringParameterizedChild;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+class AAAClass extends AAClass<String> {
+ public class BBBClass extends BBClass<String> {
+ // empty
+ }
+}
+
+@SuppressWarnings("unused") // Unused type parameter for test
+class AAClass<T> {
+
+ public class BBClass<S> {
+ // empty
+ }
+}
+
+@SuppressWarnings("rawtypes")
+//raw types, where used, are used purposely
+class AClass extends AAClass<String>.BBClass<Number> {
+
+ @SuppressWarnings("unused") // Unused type parameter for test
+ public interface AInterface<T> {
+ // empty
+ }
+
+ @SuppressWarnings("unused") // Unused type parameter for test
+ public class BClass<T> {
+ // empty
+ }
+
+ @SuppressWarnings("unused") // Unused type parameter for test
+ public class CClass<T> extends BClass {
+ // empty
+ }
+
+ @SuppressWarnings("unused") // Unused type parameter for test
+ public class DClass<T> extends CClass<T> {
+ // empty
+ }
+
+ @SuppressWarnings("unused") // Unused type parameter for test
+ public class EClass<T> extends DClass {
+ // empty
+ }
+
+ public class FClass extends EClass<String> {
+ // empty
+ }
+
+ public class GClass<T extends BClass<? extends T> & AInterface<AInterface<? super T>>> {
+ // empty
+ }
+
+ public BClass<Number> bClass;
+
+ public CClass<? extends String> cClass;
+
+ public DClass<String> dClass;
+
+ public EClass<String> eClass;
+
+ public FClass fClass;
+
+ public GClass gClass;
+
+ AClass(final AAClass<String> enclosingInstance) {
+ enclosingInstance.super();
+ }
+}
+@SuppressWarnings("rawtypes")
+abstract class Test1<G> {
+ public abstract Object m0();
+ public abstract String[] m1();
+ public abstract <E> E[] m2();
+ public abstract <E> List<? extends E> m3();
+ public abstract <E extends Enum<E>> List<? extends Enum<E>> m4();
+ public abstract List<? extends Enum<?>> m5();
+ public abstract List<? super Enum<?>> m6();
+ public abstract List<?> m7();
+ public abstract Map<? extends Enum<?>, ? super Enum<?>> m8();
+ public abstract <K, V> Map<? extends K, ? super V[]> m9();
+ public abstract <K, V> Map<? extends K, V[]> m10();
+ public abstract <K, V> Map<? extends K, List<V[]>> m11();
+ public abstract List m12();
+ public abstract Map m13();
+ public abstract Properties m14();
+ public abstract G m15();
+ public abstract List<G> m16();
+ public abstract Enum m17();
+}
+
+/**
+ * Test TypeUtils
+ */
+@SuppressWarnings({ "unchecked", "unused", "rawtypes" })
+//raw types, where used, are used purposely
+public class TypeUtilsTest<B> extends AbstractLangTest {
+
+ public interface And<K, V> extends This<Number, Number> {
+ }
+
+ public static class ClassWithSuperClassWithGenericType extends ArrayList<Object> {
+ private static final long serialVersionUID = 1L;
+
+ public static <U> Iterable<U> methodWithGenericReturnType() {
+ return null;
+ }
+ }
+
+ public class Other<T> implements This<String, T> {
+ }
+
+ public class Tester implements This<String, B> {
+ }
+
+ public class That<K, V> implements This<K, V> {
+ }
+
+ public class The<K, V> extends That<Number, Number> implements And<String, String> {
+ }
+
+ public class Thing<Q> extends Other<B> {
+ }
+
+ public interface This<K, V> {
+ }
+
+ public static Comparable<String> stringComparable;
+
+ public static Comparable<URI> uriComparable;
+
+ public static Comparable<Integer> intComparable;
+
+ public static Comparable<Long> longComparable;
+
+ public static Comparable<?> wildcardComparable;
+
+ public static URI uri;
+
+ public static List<String>[] stringListArray;
+
+ public static <G extends Comparable<G>> G stub() {
+ return null;
+ }
+
+ public static <G extends Comparable<? super G>> G stub2() {
+ return null;
+ }
+
+ public static <T extends Comparable<? extends T>> T stub3() {
+ return null;
+ }
+
+ public This<String, String> dis;
+
+ public That<String, String> dat;
+
+ public The<String, String> da;
+
+ public Other<String> uhder;
+
+ public Thing ding;
+
+ public TypeUtilsTest<String>.Tester tester;
+
+ public Tester tester2;
+
+ public TypeUtilsTest<String>.That<String, String> dat2;
+
+ public TypeUtilsTest<Number>.That<String, String> dat3;
+
+ public Comparable<? extends Integer>[] intWildcardComparable;
+
+ public Iterable<? extends Map<Integer, ? extends Collection<?>>> iterable;
+
+ public void delegateBooleanAssertion(final Type[] types, final int i2, final int i1, final boolean expected) {
+ final Type type1 = types[i1];
+ final Type type2 = types[i2];
+ final boolean isAssignable = TypeUtils.isAssignable(type2, type1);
+
+ if (expected) {
+ assertTrue(isAssignable,
+ "[" + i1 + ", " + i2 + "]: From "
+ + String.valueOf(type2) + " to "
+ + String.valueOf(type1));
+ } else {
+ assertFalse(isAssignable,
+ "[" + i1 + ", " + i2 + "]: From "
+ + String.valueOf(type2) + " to "
+ + String.valueOf(type1));
+ }
+ }
+
+ public void dummyMethod(final List list0, final List<Object> list1, final List<?> list2,
+ final List<? super Object> list3, final List<String> list4, final List<? extends String> list5,
+ final List<? super String> list6, final List[] list7, final List<Object>[] list8, final List<?>[] list9,
+ final List<? super Object>[] list10, final List<String>[] list11, final List<? extends String>[] list12,
+ final List<? super String>[] list13) {
+ }
+
+ @Test
+ public void testContainsTypeVariables() throws Exception {
+ assertFalse(TypeUtils.containsTypeVariables(Test1.class.getMethod("m0").getGenericReturnType()));
+ assertFalse(TypeUtils.containsTypeVariables(Test1.class.getMethod("m1").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m2").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m3").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m4").getGenericReturnType()));
+ assertFalse(TypeUtils.containsTypeVariables(Test1.class.getMethod("m5").getGenericReturnType()));
+ assertFalse(TypeUtils.containsTypeVariables(Test1.class.getMethod("m6").getGenericReturnType()));
+ assertFalse(TypeUtils.containsTypeVariables(Test1.class.getMethod("m7").getGenericReturnType()));
+ assertFalse(TypeUtils.containsTypeVariables(Test1.class.getMethod("m8").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m9").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m10").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m11").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m12").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m13").getGenericReturnType()));
+ assertFalse(TypeUtils.containsTypeVariables(Test1.class.getMethod("m14").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m15").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m16").getGenericReturnType()));
+ assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m17").getGenericReturnType()));
+ }
+
+ @Test
+ public void testDetermineTypeVariableAssignments() throws SecurityException,
+ NoSuchFieldException {
+ final ParameterizedType iterableType = (ParameterizedType) getClass().getField("iterable")
+ .getGenericType();
+ final Map<TypeVariable<?>, Type> typeVarAssigns = TypeUtils.determineTypeArguments(TreeSet.class,
+ iterableType);
+ final TypeVariable<?> treeSetTypeVar = TreeSet.class.getTypeParameters()[0];
+ assertTrue(typeVarAssigns.containsKey(treeSetTypeVar));
+ assertEquals(iterableType.getActualTypeArguments()[0], typeVarAssigns
+ .get(treeSetTypeVar));
+ }
+
+ @Test
+ public void testGenericArrayType() throws Exception {
+ final Type expected = getClass().getField("intWildcardComparable").getGenericType();
+ final GenericArrayType actual =
+ TypeUtils.genericArrayType(TypeUtils.parameterize(Comparable.class, TypeUtils.wildcardType()
+ .withUpperBounds(Integer.class).build()));
+ assertTrue(TypeUtils.equals(expected, actual));
+ assertEquals("java.lang.Comparable<? extends java.lang.Integer>[]", actual.toString());
+ }
+
+ @Test
+ public void testGetArrayComponentType() throws Exception {
+ final Method method = getClass().getMethod("dummyMethod", List.class, List.class, List.class,
+ List.class, List.class, List.class, List.class, List[].class, List[].class,
+ List[].class, List[].class, List[].class, List[].class, List[].class);
+
+ final Type[] types = method.getGenericParameterTypes();
+
+ assertNull(TypeUtils.getArrayComponentType(types[0]));
+ assertNull(TypeUtils.getArrayComponentType(types[1]));
+ assertNull(TypeUtils.getArrayComponentType(types[2]));
+ assertNull(TypeUtils.getArrayComponentType(types[3]));
+ assertNull(TypeUtils.getArrayComponentType(types[4]));
+ assertNull(TypeUtils.getArrayComponentType(types[5]));
+ assertNull(TypeUtils.getArrayComponentType(types[6]));
+ assertEquals(types[0], TypeUtils.getArrayComponentType(types[7]));
+ assertEquals(types[1], TypeUtils.getArrayComponentType(types[8]));
+ assertEquals(types[2], TypeUtils.getArrayComponentType(types[9]));
+ assertEquals(types[3], TypeUtils.getArrayComponentType(types[10]));
+ assertEquals(types[4], TypeUtils.getArrayComponentType(types[11]));
+ assertEquals(types[5], TypeUtils.getArrayComponentType(types[12]));
+ assertEquals(types[6], TypeUtils.getArrayComponentType(types[13]));
+ }
+
+ @Test
+ public void testGetPrimitiveArrayComponentType() {
+ assertEquals(boolean.class, TypeUtils.getArrayComponentType(boolean[].class));
+ assertEquals(byte.class, TypeUtils.getArrayComponentType(byte[].class));
+ assertEquals(short.class, TypeUtils.getArrayComponentType(short[].class));
+ assertEquals(int.class, TypeUtils.getArrayComponentType(int[].class));
+ assertEquals(char.class, TypeUtils.getArrayComponentType(char[].class));
+ assertEquals(long.class, TypeUtils.getArrayComponentType(long[].class));
+ assertEquals(float.class, TypeUtils.getArrayComponentType(float[].class));
+ assertEquals(double.class, TypeUtils.getArrayComponentType(double[].class));
+
+ assertNull(TypeUtils.getArrayComponentType(boolean.class));
+ assertNull(TypeUtils.getArrayComponentType(byte.class));
+ assertNull(TypeUtils.getArrayComponentType(short.class));
+ assertNull(TypeUtils.getArrayComponentType(int.class));
+ assertNull(TypeUtils.getArrayComponentType(char.class));
+ assertNull(TypeUtils.getArrayComponentType(long.class));
+ assertNull(TypeUtils.getArrayComponentType(float.class));
+ assertNull(TypeUtils.getArrayComponentType(double.class));
+ }
+
+ @Test
+ public void testGetRawType() throws SecurityException, NoSuchFieldException {
+ final Type stringParentFieldType = GenericTypeHolder.class.getDeclaredField("stringParent")
+ .getGenericType();
+ final Type integerParentFieldType = GenericTypeHolder.class.getDeclaredField("integerParent")
+ .getGenericType();
+ final Type foosFieldType = GenericTypeHolder.class.getDeclaredField("foos").getGenericType();
+ final Type genericParentT = GenericParent.class.getTypeParameters()[0];
+ assertEquals(GenericParent.class, TypeUtils.getRawType(stringParentFieldType, null));
+ assertEquals(GenericParent.class, TypeUtils.getRawType(integerParentFieldType,
+ null));
+ assertEquals(List.class, TypeUtils.getRawType(foosFieldType, null));
+ assertEquals(String.class, TypeUtils.getRawType(genericParentT,
+ StringParameterizedChild.class));
+ assertEquals(String.class, TypeUtils.getRawType(genericParentT,
+ stringParentFieldType));
+ assertEquals(Foo.class, TypeUtils.getRawType(Iterable.class.getTypeParameters()[0],
+ foosFieldType));
+ assertEquals(Foo.class, TypeUtils.getRawType(List.class.getTypeParameters()[0],
+ foosFieldType));
+ assertNull(TypeUtils.getRawType(genericParentT, GenericParent.class));
+ assertEquals(GenericParent[].class, TypeUtils.getRawType(GenericTypeHolder.class
+ .getDeclaredField("barParents").getGenericType(), null));
+ }
+
+ @Test
+ public void testGetTypeArguments() {
+ Map<TypeVariable<?>, Type> typeVarAssigns;
+ TypeVariable<?> treeSetTypeVar;
+ Type typeArg;
+
+ typeVarAssigns = TypeUtils.getTypeArguments(Integer.class, Comparable.class);
+ treeSetTypeVar = Comparable.class.getTypeParameters()[0];
+ assertTrue(typeVarAssigns.containsKey(treeSetTypeVar),
+ "Type var assigns for Comparable from Integer: " + typeVarAssigns);
+ typeArg = typeVarAssigns.get(treeSetTypeVar);
+ assertEquals(Integer.class, typeVarAssigns.get(treeSetTypeVar),
+ "Type argument of Comparable from Integer: " + typeArg);
+
+ typeVarAssigns = TypeUtils.getTypeArguments(int.class, Comparable.class);
+ treeSetTypeVar = Comparable.class.getTypeParameters()[0];
+ assertTrue(typeVarAssigns.containsKey(treeSetTypeVar),
+ "Type var assigns for Comparable from int: " + typeVarAssigns);
+ typeArg = typeVarAssigns.get(treeSetTypeVar);
+ assertEquals(Integer.class, typeVarAssigns.get(treeSetTypeVar),
+ "Type argument of Comparable from int: " + typeArg);
+
+ final Collection<Integer> col = Collections.emptyList();
+ typeVarAssigns = TypeUtils.getTypeArguments(List.class, Collection.class);
+ treeSetTypeVar = Comparable.class.getTypeParameters()[0];
+ assertFalse(typeVarAssigns.containsKey(treeSetTypeVar),
+ "Type var assigns for Collection from List: " + typeVarAssigns);
+
+ typeVarAssigns = TypeUtils.getTypeArguments(AAAClass.BBBClass.class, AAClass.BBClass.class);
+ assertEquals(2, typeVarAssigns.size());
+ assertEquals(String.class, typeVarAssigns.get(AAClass.class.getTypeParameters()[0]));
+ assertEquals(String.class, typeVarAssigns.get(AAClass.BBClass.class.getTypeParameters()[0]));
+
+ typeVarAssigns = TypeUtils.getTypeArguments(Other.class, This.class);
+ assertEquals(2, typeVarAssigns.size());
+ assertEquals(String.class, typeVarAssigns.get(This.class.getTypeParameters()[0]));
+ assertEquals(Other.class.getTypeParameters()[0], typeVarAssigns.get(This.class.getTypeParameters()[1]));
+
+ typeVarAssigns = TypeUtils.getTypeArguments(And.class, This.class);
+ assertEquals(2, typeVarAssigns.size());
+ assertEquals(Number.class, typeVarAssigns.get(This.class.getTypeParameters()[0]));
+ assertEquals(Number.class, typeVarAssigns.get(This.class.getTypeParameters()[1]));
+
+ typeVarAssigns = TypeUtils.getTypeArguments(Thing.class, Other.class);
+ assertEquals(2, typeVarAssigns.size());
+ assertEquals(getClass().getTypeParameters()[0], typeVarAssigns.get(getClass().getTypeParameters()[0]));
+ assertEquals(getClass().getTypeParameters()[0], typeVarAssigns.get(Other.class.getTypeParameters()[0]));
+ }
+
+ @Test
+ public void testIsArrayGenericTypes() throws Exception {
+ final Method method = getClass().getMethod("dummyMethod", List.class, List.class, List.class,
+ List.class, List.class, List.class, List.class, List[].class, List[].class,
+ List[].class, List[].class, List[].class, List[].class, List[].class);
+
+ final Type[] types = method.getGenericParameterTypes();
+
+ assertFalse(TypeUtils.isArrayType(types[0]));
+ assertFalse(TypeUtils.isArrayType(types[1]));
+ assertFalse(TypeUtils.isArrayType(types[2]));
+ assertFalse(TypeUtils.isArrayType(types[3]));
+ assertFalse(TypeUtils.isArrayType(types[4]));
+ assertFalse(TypeUtils.isArrayType(types[5]));
+ assertFalse(TypeUtils.isArrayType(types[6]));
+ assertTrue(TypeUtils.isArrayType(types[7]));
+ assertTrue(TypeUtils.isArrayType(types[8]));
+ assertTrue(TypeUtils.isArrayType(types[9]));
+ assertTrue(TypeUtils.isArrayType(types[10]));
+ assertTrue(TypeUtils.isArrayType(types[11]));
+ assertTrue(TypeUtils.isArrayType(types[12]));
+ assertTrue(TypeUtils.isArrayType(types[13]));
+ }
+
+ @Test
+ public void testIsArrayTypeClasses() {
+ assertTrue(TypeUtils.isArrayType(boolean[].class));
+ assertTrue(TypeUtils.isArrayType(byte[].class));
+ assertTrue(TypeUtils.isArrayType(short[].class));
+ assertTrue(TypeUtils.isArrayType(int[].class));
+ assertTrue(TypeUtils.isArrayType(char[].class));
+ assertTrue(TypeUtils.isArrayType(long[].class));
+ assertTrue(TypeUtils.isArrayType(float[].class));
+ assertTrue(TypeUtils.isArrayType(double[].class));
+ assertTrue(TypeUtils.isArrayType(Object[].class));
+ assertTrue(TypeUtils.isArrayType(String[].class));
+
+ assertFalse(TypeUtils.isArrayType(boolean.class));
+ assertFalse(TypeUtils.isArrayType(byte.class));
+ assertFalse(TypeUtils.isArrayType(short.class));
+ assertFalse(TypeUtils.isArrayType(int.class));
+ assertFalse(TypeUtils.isArrayType(char.class));
+ assertFalse(TypeUtils.isArrayType(long.class));
+ assertFalse(TypeUtils.isArrayType(float.class));
+ assertFalse(TypeUtils.isArrayType(double.class));
+ assertFalse(TypeUtils.isArrayType(Object.class));
+ assertFalse(TypeUtils.isArrayType(String.class));
+ }
+
+ @SuppressWarnings("boxing") // deliberately used here
+ @Test
+ public void testIsAssignable() throws SecurityException, NoSuchMethodException,
+ NoSuchFieldException {
+ List list0 = null;
+ List<Object> list1;
+ List<?> list2;
+ List<? super Object> list3;
+ List<String> list4;
+ List<? extends String> list5;
+ List<? super String> list6;
+ List[] list7 = null;
+ List<Object>[] list8;
+ List<?>[] list9;
+ List<? super Object>[] list10;
+ List<String>[] list11;
+ List<? extends String>[] list12;
+ List<? super String>[] list13;
+ final Class<?> clazz = getClass();
+ final Method method = clazz.getMethod("dummyMethod", List.class, List.class, List.class,
+ List.class, List.class, List.class, List.class, List[].class, List[].class,
+ List[].class, List[].class, List[].class, List[].class, List[].class);
+ final Type[] types = method.getGenericParameterTypes();
+// list0 = list0;
+ delegateBooleanAssertion(types, 0, 0, true);
+ list1 = list0;
+ delegateBooleanAssertion(types, 0, 1, true);
+ list0 = list1;
+ delegateBooleanAssertion(types, 1, 0, true);
+ list2 = list0;
+ delegateBooleanAssertion(types, 0, 2, true);
+ list0 = list2;
+ delegateBooleanAssertion(types, 2, 0, true);
+ list3 = list0;
+ delegateBooleanAssertion(types, 0, 3, true);
+ list0 = list3;
+ delegateBooleanAssertion(types, 3, 0, true);
+ list4 = list0;
+ delegateBooleanAssertion(types, 0, 4, true);
+ list0 = list4;
+ delegateBooleanAssertion(types, 4, 0, true);
+ list5 = list0;
+ delegateBooleanAssertion(types, 0, 5, true);
+ list0 = list5;
+ delegateBooleanAssertion(types, 5, 0, true);
+ list6 = list0;
+ delegateBooleanAssertion(types, 0, 6, true);
+ list0 = list6;
+ delegateBooleanAssertion(types, 6, 0, true);
+// list1 = list1;
+ delegateBooleanAssertion(types, 1, 1, true);
+ list2 = list1;
+ delegateBooleanAssertion(types, 1, 2, true);
+ list1 = (List<Object>) list2;
+ delegateBooleanAssertion(types, 2, 1, false);
+ list3 = list1;
+ delegateBooleanAssertion(types, 1, 3, true);
+ list1 = (List<Object>) list3;
+ delegateBooleanAssertion(types, 3, 1, false);
+ // list4 = list1;
+ delegateBooleanAssertion(types, 1, 4, false);
+ // list1 = list4;
+ delegateBooleanAssertion(types, 4, 1, false);
+ // list5 = list1;
+ delegateBooleanAssertion(types, 1, 5, false);
+ // list1 = list5;
+ delegateBooleanAssertion(types, 5, 1, false);
+ list6 = list1;
+ delegateBooleanAssertion(types, 1, 6, true);
+ list1 = (List<Object>) list6;
+ delegateBooleanAssertion(types, 6, 1, false);
+// list2 = list2;
+ delegateBooleanAssertion(types, 2, 2, true);
+ list2 = list3;
+ delegateBooleanAssertion(types, 2, 3, false);
+ list2 = list4;
+ delegateBooleanAssertion(types, 3, 2, true);
+ list3 = (List<? super Object>) list2;
+ delegateBooleanAssertion(types, 2, 4, false);
+ list2 = list5;
+ delegateBooleanAssertion(types, 4, 2, true);
+ list4 = (List<String>) list2;
+ delegateBooleanAssertion(types, 2, 5, false);
+ list2 = list6;
+ delegateBooleanAssertion(types, 5, 2, true);
+ list5 = (List<? extends String>) list2;
+ delegateBooleanAssertion(types, 2, 6, false);
+// list3 = list3;
+ delegateBooleanAssertion(types, 6, 2, true);
+ list6 = (List<? super String>) list2;
+ delegateBooleanAssertion(types, 3, 3, true);
+ // list4 = list3;
+ delegateBooleanAssertion(types, 3, 4, false);
+ // list3 = list4;
+ delegateBooleanAssertion(types, 4, 3, false);
+ // list5 = list3;
+ delegateBooleanAssertion(types, 3, 5, false);
+ // list3 = list5;
+ delegateBooleanAssertion(types, 5, 3, false);
+ list6 = list3;
+ delegateBooleanAssertion(types, 3, 6, true);
+ list3 = (List<? super Object>) list6;
+ delegateBooleanAssertion(types, 6, 3, false);
+// list4 = list4;
+ delegateBooleanAssertion(types, 4, 4, true);
+ list5 = list4;
+ delegateBooleanAssertion(types, 4, 5, true);
+ list4 = (List<String>) list5;
+ delegateBooleanAssertion(types, 5, 4, false);
+ list6 = list4;
+ delegateBooleanAssertion(types, 4, 6, true);
+ list4 = (List<String>) list6;
+ delegateBooleanAssertion(types, 6, 4, false);
+// list5 = list5;
+ delegateBooleanAssertion(types, 5, 5, true);
+ list6 = (List<? super String>) list5;
+ delegateBooleanAssertion(types, 5, 6, false);
+ list5 = (List<? extends String>) list6;
+ delegateBooleanAssertion(types, 6, 5, false);
+// list6 = list6;
+ delegateBooleanAssertion(types, 6, 6, true);
+
+// list7 = list7;
+ delegateBooleanAssertion(types, 7, 7, true);
+ list8 = list7;
+ delegateBooleanAssertion(types, 7, 8, true);
+ list7 = list8;
+ delegateBooleanAssertion(types, 8, 7, true);
+ list9 = list7;
+ delegateBooleanAssertion(types, 7, 9, true);
+ list7 = list9;
+ delegateBooleanAssertion(types, 9, 7, true);
+ list10 = list7;
+ delegateBooleanAssertion(types, 7, 10, true);
+ list7 = list10;
+ delegateBooleanAssertion(types, 10, 7, true);
+ list11 = list7;
+ delegateBooleanAssertion(types, 7, 11, true);
+ list7 = list11;
+ delegateBooleanAssertion(types, 11, 7, true);
+ list12 = list7;
+ delegateBooleanAssertion(types, 7, 12, true);
+ list7 = list12;
+ delegateBooleanAssertion(types, 12, 7, true);
+ list13 = list7;
+ delegateBooleanAssertion(types, 7, 13, true);
+ list7 = list13;
+ delegateBooleanAssertion(types, 13, 7, true);
+// list8 = list8;
+ delegateBooleanAssertion(types, 8, 8, true);
+ list9 = list8;
+ delegateBooleanAssertion(types, 8, 9, true);
+ list8 = (List<Object>[]) list9;
+ delegateBooleanAssertion(types, 9, 8, false);
+ list10 = list8;
+ delegateBooleanAssertion(types, 8, 10, true);
+ list8 = (List<Object>[]) list10; // NOTE cast is required by Sun Java, but not by Eclipse
+ delegateBooleanAssertion(types, 10, 8, false);
+ // list11 = list8;
+ delegateBooleanAssertion(types, 8, 11, false);
+ // list8 = list11;
+ delegateBooleanAssertion(types, 11, 8, false);
+ // list12 = list8;
+ delegateBooleanAssertion(types, 8, 12, false);
+ // list8 = list12;
+ delegateBooleanAssertion(types, 12, 8, false);
+ list13 = list8;
+ delegateBooleanAssertion(types, 8, 13, true);
+ list8 = (List<Object>[]) list13;
+ delegateBooleanAssertion(types, 13, 8, false);
+// list9 = list9;
+ delegateBooleanAssertion(types, 9, 9, true);
+ list10 = (List<? super Object>[]) list9;
+ delegateBooleanAssertion(types, 9, 10, false);
+ list9 = list10;
+ delegateBooleanAssertion(types, 10, 9, true);
+ list11 = (List<String>[]) list9;
+ delegateBooleanAssertion(types, 9, 11, false);
+ list9 = list11;
+ delegateBooleanAssertion(types, 11, 9, true);
+ list12 = (List<? extends String>[]) list9;
+ delegateBooleanAssertion(types, 9, 12, false);
+ list9 = list12;
+ delegateBooleanAssertion(types, 12, 9, true);
+ list13 = (List<? super String>[]) list9;
+ delegateBooleanAssertion(types, 9, 13, false);
+ list9 = list13;
+ delegateBooleanAssertion(types, 13, 9, true);
+// list10 = list10;
+ delegateBooleanAssertion(types, 10, 10, true);
+ // list11 = list10;
+ delegateBooleanAssertion(types, 10, 11, false);
+ // list10 = list11;
+ delegateBooleanAssertion(types, 11, 10, false);
+ // list12 = list10;
+ delegateBooleanAssertion(types, 10, 12, false);
+ // list10 = list12;
+ delegateBooleanAssertion(types, 12, 10, false);
+ list13 = list10;
+ delegateBooleanAssertion(types, 10, 13, true);
+ list10 = (List<? super Object>[]) list13;
+ delegateBooleanAssertion(types, 13, 10, false);
+// list11 = list11;
+ delegateBooleanAssertion(types, 11, 11, true);
+ list12 = list11;
+ delegateBooleanAssertion(types, 11, 12, true);
+ list11 = (List<String>[]) list12;
+ delegateBooleanAssertion(types, 12, 11, false);
+ list13 = list11;
+ delegateBooleanAssertion(types, 11, 13, true);
+ list11 = (List<String>[]) list13;
+ delegateBooleanAssertion(types, 13, 11, false);
+// list12 = list12;
+ delegateBooleanAssertion(types, 12, 12, true);
+ list13 = (List<? super String>[]) list12;
+ delegateBooleanAssertion(types, 12, 13, false);
+ list12 = (List<? extends String>[]) list13;
+ delegateBooleanAssertion(types, 13, 12, false);
+// list13 = list13;
+ delegateBooleanAssertion(types, 13, 13, true);
+ final Type disType = getClass().getField("dis").getGenericType();
+ // Reporter.log( ( ( ParameterizedType ) disType
+ // ).getOwnerType().getClass().toString() );
+ final Type datType = getClass().getField("dat").getGenericType();
+ final Type daType = getClass().getField("da").getGenericType();
+ final Type uhderType = getClass().getField("uhder").getGenericType();
+ final Type dingType = getClass().getField("ding").getGenericType();
+ final Type testerType = getClass().getField("tester").getGenericType();
+ final Type tester2Type = getClass().getField("tester2").getGenericType();
+ final Type dat2Type = getClass().getField("dat2").getGenericType();
+ final Type dat3Type = getClass().getField("dat3").getGenericType();
+ dis = dat;
+ assertTrue(TypeUtils.isAssignable(datType, disType));
+ // dis = da;
+ assertFalse(TypeUtils.isAssignable(daType, disType));
+ dis = uhder;
+ assertTrue(TypeUtils.isAssignable(uhderType, disType));
+ dis = ding;
+ assertFalse(TypeUtils.isAssignable(dingType, disType),
+ String.format("type %s not assignable to %s!", dingType, disType));
+ dis = tester;
+ assertTrue(TypeUtils.isAssignable(testerType, disType));
+ // dis = tester2;
+ assertFalse(TypeUtils.isAssignable(tester2Type, disType));
+ // dat = dat2;
+ assertFalse(TypeUtils.isAssignable(dat2Type, datType));
+ // dat2 = dat;
+ assertFalse(TypeUtils.isAssignable(datType, dat2Type));
+ // dat = dat3;
+ assertFalse(TypeUtils.isAssignable(dat3Type, datType));
+ final char ch = 0;
+ final boolean bo = false;
+ final byte by = 0;
+ final short sh = 0;
+ int in = 0;
+ long lo = 0;
+ final float fl = 0;
+ double du;
+ du = ch;
+ assertTrue(TypeUtils.isAssignable(char.class, double.class));
+ du = by;
+ assertTrue(TypeUtils.isAssignable(byte.class, double.class));
+ du = sh;
+ assertTrue(TypeUtils.isAssignable(short.class, double.class));
+ du = in;
+ assertTrue(TypeUtils.isAssignable(int.class, double.class));
+ du = lo;
+ assertTrue(TypeUtils.isAssignable(long.class, double.class));
+ du = fl;
+ assertTrue(TypeUtils.isAssignable(float.class, double.class));
+ lo = in;
+ assertTrue(TypeUtils.isAssignable(int.class, long.class));
+ lo = Integer.valueOf(0);
+ assertTrue(TypeUtils.isAssignable(Integer.class, long.class));
+ // Long lngW = 1;
+ assertFalse(TypeUtils.isAssignable(int.class, Long.class));
+ // lngW = Integer.valueOf( 0 );
+ assertFalse(TypeUtils.isAssignable(Integer.class, Long.class));
+ in = Integer.valueOf(0);
+ assertTrue(TypeUtils.isAssignable(Integer.class, int.class));
+ final Integer inte = in;
+ assertTrue(TypeUtils.isAssignable(int.class, Integer.class));
+ assertTrue(TypeUtils.isAssignable(int.class, Number.class));
+ assertTrue(TypeUtils.isAssignable(int.class, Object.class));
+ final Type intComparableType = getClass().getField("intComparable").getGenericType();
+ intComparable = 1;
+ assertTrue(TypeUtils.isAssignable(int.class, intComparableType));
+ assertTrue(TypeUtils.isAssignable(int.class, Comparable.class));
+ final Serializable ser = 1;
+ assertTrue(TypeUtils.isAssignable(int.class, Serializable.class));
+ final Type longComparableType = getClass().getField("longComparable").getGenericType();
+ // longComparable = 1;
+ assertFalse(TypeUtils.isAssignable(int.class, longComparableType));
+ // longComparable = Integer.valueOf( 0 );
+ assertFalse(TypeUtils.isAssignable(Integer.class, longComparableType));
+ // int[] ia;
+ // long[] la = ia;
+ assertFalse(TypeUtils.isAssignable(int[].class, long[].class));
+ final Integer[] ia = null;
+ final Type caType = getClass().getField("intWildcardComparable").getGenericType();
+ intWildcardComparable = ia;
+ assertTrue(TypeUtils.isAssignable(Integer[].class, caType));
+ // int[] ina = ia;
+ assertFalse(TypeUtils.isAssignable(Integer[].class, int[].class));
+ final int[] ina = null;
+ final Object[] oa;
+ // oa = ina;
+ assertFalse(TypeUtils.isAssignable(int[].class, Object[].class));
+ oa = new Integer[0];
+ assertTrue(TypeUtils.isAssignable(Integer[].class, Object[].class));
+ final Type bClassType = AClass.class.getField("bClass").getGenericType();
+ final Type cClassType = AClass.class.getField("cClass").getGenericType();
+ final Type dClassType = AClass.class.getField("dClass").getGenericType();
+ final Type eClassType = AClass.class.getField("eClass").getGenericType();
+ final Type fClassType = AClass.class.getField("fClass").getGenericType();
+ final AClass aClass = new AClass(new AAClass<>());
+ aClass.bClass = aClass.cClass;
+ assertTrue(TypeUtils.isAssignable(cClassType, bClassType));
+ aClass.bClass = aClass.dClass;
+ assertTrue(TypeUtils.isAssignable(dClassType, bClassType));
+ aClass.bClass = aClass.eClass;
+ assertTrue(TypeUtils.isAssignable(eClassType, bClassType));
+ aClass.bClass = aClass.fClass;
+ assertTrue(TypeUtils.isAssignable(fClassType, bClassType));
+ aClass.cClass = aClass.dClass;
+ assertTrue(TypeUtils.isAssignable(dClassType, cClassType));
+ aClass.cClass = aClass.eClass;
+ assertTrue(TypeUtils.isAssignable(eClassType, cClassType));
+ aClass.cClass = aClass.fClass;
+ assertTrue(TypeUtils.isAssignable(fClassType, cClassType));
+ aClass.dClass = aClass.eClass;
+ assertTrue(TypeUtils.isAssignable(eClassType, dClassType));
+ aClass.dClass = aClass.fClass;
+ assertTrue(TypeUtils.isAssignable(fClassType, dClassType));
+ aClass.eClass = aClass.fClass;
+ assertTrue(TypeUtils.isAssignable(fClassType, eClassType));
+ }
+
+ private void testIsAssignable(final Class testUnassignableClass) {
+ final Class<Constructor> rawClass = Constructor.class;
+ final Class<Insets> typeArgClass = Insets.class;
+ // Builds a ParameterizedType for Constructor<Insets>
+ final ParameterizedType paramType = TypeUtils.parameterize(rawClass, typeArgClass);
+ assertEquals(rawClass, paramType.getRawType());
+ assertEquals(typeArgClass, paramType.getActualTypeArguments()[0]);
+
+ assertFalse(testUnassignableClass.isAssignableFrom(paramType.getClass()));
+ assertFalse(paramType.getClass().isAssignableFrom(testUnassignableClass));
+
+ final GenericArrayType arrayType = TypeUtils.genericArrayType(paramType);
+ assertFalse(TypeUtils.isAssignable(arrayType, paramType),
+ () -> String.format("TypeUtils.isAssignable(%s, %s)", arrayType, paramType));
+ assertFalse(TypeUtils.isAssignable(paramType, arrayType),
+ () -> String.format("TypeUtils.isAssignable(%s, %s)", paramType, arrayType));
+ }
+
+ @Test
+ public void testIsAssignableGenericArrayTypeToParameterizedType() {
+ final Class<Constructor> rawClass = Constructor.class;
+ final Class<Insets> typeArgClass = Insets.class;
+ // Builds a ParameterizedType for Constructor<Insets>
+ final ParameterizedType paramType = TypeUtils.parameterize(rawClass, typeArgClass);
+ assertEquals(rawClass, paramType.getRawType());
+ assertEquals(typeArgClass, paramType.getActualTypeArguments()[0]);
+
+ assertFalse(GenericArrayType.class.isAssignableFrom(paramType.getClass()));
+ assertFalse(paramType.getClass().isAssignableFrom(GenericArrayType.class));
+
+ final GenericArrayType testType = TypeUtils.genericArrayType(paramType);
+ assertFalse(TypeUtils.isAssignable(paramType, testType),
+ () -> String.format("TypeUtils.isAssignable(%s, %s)", paramType, testType));
+ assertFalse(TypeUtils.isAssignable(testType, paramType),
+ () -> String.format("TypeUtils.isAssignable(%s, %s)", testType, paramType));
+ }
+
+ @Test
+ @Disabled("TODO")
+ public void testIsAssignableGenericArrayTypeToWildercardType() {
+ final Class<Constructor> rawClass = Constructor.class;
+ final Class<Insets> typeArgClass = Insets.class;
+ // Builds a ParameterizedType for Constructor<Insets>
+ final ParameterizedType paramType = TypeUtils.parameterize(rawClass, typeArgClass);
+ assertEquals(rawClass, paramType.getRawType());
+ assertEquals(typeArgClass, paramType.getActualTypeArguments()[0]);
+
+ assertFalse(WildcardType.class.isAssignableFrom(paramType.getClass()));
+ assertFalse(paramType.getClass().isAssignableFrom(WildcardType.class));
+
+ final WildcardType testType = TypeUtils.WILDCARD_ALL;
+ // TODO This test returns true unlike the test above.
+ // Is this a bug in this test or in the main code?
+ assertFalse(TypeUtils.isAssignable(paramType, testType),
+ () -> String.format("TypeUtils.isAssignable(%s, %s)", paramType, testType));
+ assertFalse(TypeUtils.isAssignable(testType, paramType),
+ () -> String.format("TypeUtils.isAssignable(%s, %s)", testType, paramType));
+ }
+
+ @Test
+ public void testIsAssignableGenericArrayTypeToObject() {
+ final Class<Constructor> rawClass = Constructor.class;
+ final Class<Insets> typeArgClass = Insets.class;
+ // Builds a ParameterizedType for Constructor<Insets>
+ final ParameterizedType paramType = TypeUtils.parameterize(rawClass, typeArgClass);
+ assertEquals(rawClass, paramType.getRawType());
+ assertEquals(typeArgClass, paramType.getActualTypeArguments()[0]);
+
+ assertTrue(Object.class.isAssignableFrom(paramType.getClass()));
+ assertFalse(paramType.getClass().isAssignableFrom(Object.class));
+
+ final Type testType = Object.class;
+ assertTrue(TypeUtils.isAssignable(paramType, testType),
+ () -> String.format("TypeUtils.isAssignable(%s, %s)", paramType, testType));
+ assertFalse(TypeUtils.isAssignable(testType, paramType),
+ () -> String.format("TypeUtils.isAssignable(%s, %s)", testType, paramType));
+ }
+
+ @SuppressWarnings("boxing") // boxing is deliberate here
+ @Test
+ public void testIsInstance() throws SecurityException, NoSuchFieldException {
+ final Type intComparableType = getClass().getField("intComparable").getGenericType();
+ final Type uriComparableType = getClass().getField("uriComparable").getGenericType();
+ intComparable = 1;
+ assertTrue(TypeUtils.isInstance(1, intComparableType));
+ // uriComparable = 1;
+ assertFalse(TypeUtils.isInstance(1, uriComparableType));
+ }
+
+ @Test
+ public void testLang1114() throws Exception {
+ final Type nonWildcardType = getClass().getDeclaredField("wildcardComparable").getGenericType();
+ final Type wildcardType = ((ParameterizedType) nonWildcardType).getActualTypeArguments()[0];
+
+ assertFalse(TypeUtils.equals(wildcardType, nonWildcardType));
+ assertFalse(TypeUtils.equals(nonWildcardType, wildcardType));
+ }
+
+ @Test
+ public void testLANG1190() throws Exception {
+ final Type fromType = ClassWithSuperClassWithGenericType.class.getDeclaredMethod("methodWithGenericReturnType").getGenericReturnType();
+ final Type failingToType = TypeUtils.wildcardType().withLowerBounds(ClassWithSuperClassWithGenericType.class).build();
+
+ assertTrue(TypeUtils.isAssignable(fromType, failingToType));
+ }
+
+ @Test
+ public void testLANG1348() throws Exception {
+ final Method method = Enum.class.getMethod("valueOf", Class.class, String.class);
+ assertEquals("T extends java.lang.Enum<T>", TypeUtils.toString(method.getGenericReturnType()));
+ }
+
+ @Test
+ public void testLang820() {
+ final Type[] typeArray = {String.class, String.class};
+ final Type[] expectedArray = {String.class};
+ assertArrayEquals(expectedArray, TypeUtils.normalizeUpperBounds(typeArray));
+ }
+
+ @Test
+ public void testLowerBoundedWildcardType() {
+ final WildcardType lowerBounded = TypeUtils.wildcardType().withLowerBounds(java.sql.Date.class).build();
+ assertEquals(String.format("? super %s", java.sql.Date.class.getName()), TypeUtils.toString(lowerBounded));
+ assertEquals(String.format("? super %s", java.sql.Date.class.getName()), lowerBounded.toString());
+
+ final TypeVariable<Class<Iterable>> iterableT0 = Iterable.class.getTypeParameters()[0];
+ final WildcardType lowerTypeVariable = TypeUtils.wildcardType().withLowerBounds(iterableT0).build();
+ assertEquals(String.format("? super %s", iterableT0.getName()), TypeUtils.toString(lowerTypeVariable));
+ assertEquals(String.format("? super %s", iterableT0.getName()), lowerTypeVariable.toString());
+ }
+
+ @Test
+ public void testParameterize() throws Exception {
+ final ParameterizedType stringComparableType = TypeUtils.parameterize(Comparable.class, String.class);
+ assertTrue(TypeUtils.equals(getClass().getField("stringComparable").getGenericType(),
+ stringComparableType));
+ assertEquals("java.lang.Comparable<java.lang.String>", stringComparableType.toString());
+ }
+
+ @Test
+ public void testParameterizeNarrowerTypeArray() {
+ final TypeVariable<?>[] variables = ArrayList.class.getTypeParameters();
+ final ParameterizedType parameterizedType = TypeUtils.parameterize(ArrayList.class, variables);
+ final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(variables[0], String.class);
+ final Type unrolled = TypeUtils.unrollVariables(mapping, parameterizedType);
+ assertEquals(TypeUtils.parameterize(ArrayList.class, String.class), unrolled);
+ }
+
+ @Test
+ public void testParameterizeNullPointerException() {
+ assertThrows(NullPointerException.class, () -> TypeUtils.parameterize(null, Collections.emptyMap()));
+ final Map<TypeVariable<?>, Type> nullTypeVariableMap = null;
+ assertThrows(NullPointerException.class, () -> TypeUtils.parameterize(String.class, nullTypeVariableMap));
+ }
+
+ @Test
+ public void testParameterizeVarArgsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> TypeUtils.parameterize(null));
+ }
+
+ @Test
+ public void testParameterizeWithOwner() throws Exception {
+ final Type owner = TypeUtils.parameterize(TypeUtilsTest.class, String.class);
+ final ParameterizedType dat2Type = TypeUtils.parameterizeWithOwner(owner, That.class, String.class, String.class);
+ assertTrue(TypeUtils.equals(getClass().getField("dat2").getGenericType(), dat2Type));
+ }
+
+ @Test
+ public void testParameterizeWithOwner3ArgsNullPointerException() {
+ final Type owner = TypeUtils.parameterize(TypeUtilsTest.class, String.class);
+ assertThrows(NullPointerException.class, () -> TypeUtils.parameterizeWithOwner(owner, null, String.class));
+ final Map<TypeVariable<?>, Type> nullTypeVariableMap = null;
+ assertThrows(NullPointerException.class, () -> TypeUtils.parameterizeWithOwner(owner, That.class, nullTypeVariableMap));
+ }
+
+ @Test
+ public void testParameterizeWithOwnerVarArgsNullPointerException() {
+ final Type owner = TypeUtils.parameterize(TypeUtilsTest.class, String.class);
+ assertThrows(NullPointerException.class, () -> TypeUtils.parameterizeWithOwner(owner, null));
+ }
+
+ @Test
+ public void testToLongString() {
+ assertEquals(getClass().getName() + ":B", TypeUtils.toLongString(getClass().getTypeParameters()[0]));
+ }
+
+ @Test
+ public void testToStringLang1311() {
+ assertEquals("int[]", TypeUtils.toString(int[].class));
+ assertEquals("java.lang.Integer[]", TypeUtils.toString(Integer[].class));
+ final Field stringListField = FieldUtils.getDeclaredField(getClass(), "stringListArray");
+ assertEquals("java.util.List<java.lang.String>[]", TypeUtils.toString(stringListField.getGenericType()));
+ }
+
+ @Test
+ public void testTypesSatisfyVariables() throws SecurityException,
+ NoSuchMethodException {
+ final Map<TypeVariable<?>, Type> typeVarAssigns = new HashMap<>();
+ final Integer max = TypeUtilsTest.<Integer>stub();
+ typeVarAssigns.put(getClass().getMethod("stub").getTypeParameters()[0], Integer.class);
+ assertTrue(TypeUtils.typesSatisfyVariables(typeVarAssigns));
+ typeVarAssigns.clear();
+ typeVarAssigns.put(getClass().getMethod("stub2").getTypeParameters()[0], Integer.class);
+ assertTrue(TypeUtils.typesSatisfyVariables(typeVarAssigns));
+ typeVarAssigns.clear();
+ typeVarAssigns.put(getClass().getMethod("stub3").getTypeParameters()[0], Integer.class);
+ assertTrue(TypeUtils.typesSatisfyVariables(typeVarAssigns));
+ }
+
+ @Test
+ public void testUnboundedWildcardType() {
+ final WildcardType unbounded = TypeUtils.wildcardType().withLowerBounds((Type) null).withUpperBounds().build();
+ assertTrue(TypeUtils.equals(TypeUtils.WILDCARD_ALL, unbounded));
+ assertArrayEquals(new Type[] { Object.class }, TypeUtils.getImplicitUpperBounds(unbounded));
+ assertArrayEquals(new Type[] { null }, TypeUtils.getImplicitLowerBounds(unbounded));
+ assertEquals("?", TypeUtils.toString(unbounded));
+ assertEquals("?", unbounded.toString());
+ }
+
+ @Test
+ public void testWildcardType() throws Exception {
+ final WildcardType simpleWildcard = TypeUtils.wildcardType().withUpperBounds(String.class).build();
+ final Field cClass = AClass.class.getField("cClass");
+ assertTrue(TypeUtils.equals(((ParameterizedType) cClass.getGenericType()).getActualTypeArguments()[0],
+ simpleWildcard));
+ assertEquals(String.format("? extends %s", String.class.getName()), TypeUtils.toString(simpleWildcard));
+ assertEquals(String.format("? extends %s", String.class.getName()), simpleWildcard.toString());
+ }
+
+ @Test
+ public void testWrap() {
+ final Type t = getClass().getTypeParameters()[0];
+ assertTrue(TypeUtils.equals(t, TypeUtils.wrap(t).getType()));
+
+ assertEquals(String.class, TypeUtils.wrap(String.class).getType());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/Ambig.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/Ambig.java
new file mode 100644
index 000000000..198a8cf9c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/Ambig.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+/**
+ */
+public class Ambig implements Foo, Bar {
+
+ @Override
+ public void doIt() {
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/Annotated.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/Annotated.java
new file mode 100644
index 000000000..5cc9868f9
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/Annotated.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.
+ */
+
+package org.apache.commons.lang3.reflect.testbed;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface Annotated {
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/AnotherChild.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/AnotherChild.java
new file mode 100644
index 000000000..590d4188c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/AnotherChild.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+/**
+ *
+ */
+public class AnotherChild extends AnotherParent {
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/AnotherParent.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/AnotherParent.java
new file mode 100644
index 000000000..795e46a05
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/AnotherParent.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+/**
+ *
+ */
+public class AnotherParent {
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/Bar.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/Bar.java
new file mode 100644
index 000000000..8ac873d3a
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/Bar.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.testbed;
+
+/**
+ */
+public interface Bar {
+ String VALUE = "bar";
+
+ void doIt();
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/Foo.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/Foo.java
new file mode 100644
index 000000000..f07a43377
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/Foo.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+/**
+ */
+public interface Foo {
+ String VALUE = "foo";
+
+ @Annotated
+ void doIt();
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericConsumer.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericConsumer.java
new file mode 100644
index 000000000..02fe80e7b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericConsumer.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+public interface GenericConsumer<T> {
+ void consume(T t);
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericParent.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericParent.java
new file mode 100644
index 000000000..73b1446f0
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericParent.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.testbed;
+
+/**
+ * Class declaring a parameter variable.
+ */
+public class GenericParent<T> implements GenericConsumer<T> {
+
+ @Override
+ public void consume(final T t) {
+ }
+
+ @Annotated
+ protected void parentProtectedAnnotatedMethod(final T t) {
+ }
+
+ public void parentNotAnnotatedMethod(final T t) {
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericTypeHolder.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericTypeHolder.java
new file mode 100644
index 000000000..390044cdd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/GenericTypeHolder.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+import java.util.List;
+
+/**
+ * Holds generic testbed types.
+ */
+public class GenericTypeHolder {
+ public GenericParent<String> stringParent;
+ public GenericParent<Integer> integerParent;
+ public List<Foo> foos;
+ public GenericParent<Bar>[] barParents;
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/Grandchild.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/Grandchild.java
new file mode 100644
index 000000000..06b9c614f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/Grandchild.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+/**
+ *
+ */
+public class Grandchild extends AnotherChild {
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/Parent.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/Parent.java
new file mode 100644
index 000000000..eeee5e398
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/Parent.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.reflect.testbed;
+
+/**
+ */
+class Parent implements Foo {
+ public String s = "s";
+ protected boolean b = false;
+ int i = 0;
+ @SuppressWarnings("unused")
+ private final double d = 0.0;
+
+ @Override
+ public void doIt() {
+ }
+
+ @Annotated
+ protected void parentProtectedAnnotatedMethod() {
+ }
+
+ public void parentNotAnnotatedMethod() {
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/PrivatelyShadowedChild.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/PrivatelyShadowedChild.java
new file mode 100644
index 000000000..9e9bf9eba
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/PrivatelyShadowedChild.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+/**
+ */
+@SuppressWarnings({ "unused" }) // deliberate re-use of variable names
+public class PrivatelyShadowedChild extends Parent {
+ private final String s = "ss";
+ private final boolean b = true;
+ private final int i = 1;
+ private final double d = 1.0;
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/PublicChild.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/PublicChild.java
new file mode 100644
index 000000000..ce2a1ec04
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/PublicChild.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.reflect.testbed;
+
+/**
+ */
+public class PublicChild extends Parent {
+ static final String VALUE = "child";
+
+ @Override
+ public void parentProtectedAnnotatedMethod() {
+ }
+
+ @Override
+ public void parentNotAnnotatedMethod() {
+ }
+
+ @Annotated
+ private void privateAnnotatedMethod() {
+ }
+
+ @Annotated
+ public void publicAnnotatedMethod() {
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/PubliclyShadowedChild.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/PubliclyShadowedChild.java
new file mode 100644
index 000000000..a95ec350f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/PubliclyShadowedChild.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+/**
+ */
+public class PubliclyShadowedChild extends Parent {
+ public String s = "ss";
+ public boolean b = true;
+ public int i = 1;
+ public double d = 1.0;
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/StaticContainer.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/StaticContainer.java
new file mode 100644
index 000000000..75f1ca203
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/StaticContainer.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.reflect.testbed;
+
+/**
+ */
+public class StaticContainer {
+ public static final Object IMMUTABLE_PUBLIC = "public";
+ protected static final Object IMMUTABLE_PROTECTED = "protected";
+ static final Object IMMUTABLE_PACKAGE = "";
+ @SuppressWarnings("unused")
+ private static final Object IMMUTABLE_PRIVATE = "private";
+
+ /**
+ * This final modifier of this field is meant to be removed by a test.
+ * Using this field may produce unpredictable results.
+ */
+ @SuppressWarnings("unused")
+ private static final Object IMMUTABLE_PRIVATE_2 = "private";
+
+ public static Object mutablePublic;
+ protected static Object mutableProtected;
+ static Object mutablePackage;
+ private static Object mutablePrivate;
+
+ public static void reset() {
+ mutablePublic = null;
+ mutableProtected = null;
+ mutablePackage = null;
+ mutablePrivate = null;
+ }
+
+ public static Object getMutableProtected() {
+ return mutableProtected;
+ }
+
+ public static Object getMutablePackage() {
+ return mutablePackage;
+ }
+
+ public static Object getMutablePrivate() {
+ return mutablePrivate;
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/StaticContainerChild.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/StaticContainerChild.java
new file mode 100644
index 000000000..18af79d7e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/StaticContainerChild.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.
+ */
+package org.apache.commons.lang3.reflect.testbed;
+
+/**
+ */
+public class StaticContainerChild extends StaticContainer {
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/testbed/StringParameterizedChild.java b/src/test/java/org/apache/commons/lang3/reflect/testbed/StringParameterizedChild.java
new file mode 100644
index 000000000..18555e290
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/reflect/testbed/StringParameterizedChild.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.reflect.testbed;
+
+/**
+ * {@link GenericParent} subclass that explicitly specifies &lt;T&gt; as {@link String}.
+ */
+public class StringParameterizedChild extends GenericParent<String> {
+ @Override
+ public void consume(final String t) {
+ super.consume(t);
+ }
+
+ @Override
+ public void parentProtectedAnnotatedMethod(final String t) {
+ }
+
+ @Override
+ public void parentNotAnnotatedMethod(final String t) {
+ }
+
+ @Annotated
+ private void privateAnnotatedMethod(final String t) {
+ }
+
+ @Annotated
+ public void publicAnnotatedMethod(final String t) {
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/stream/IntStreamsTest.java b/src/test/java/org/apache/commons/lang3/stream/IntStreamsTest.java
new file mode 100644
index 000000000..ecb87d68b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/stream/IntStreamsTest.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.stream;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IntStreams}.
+ */
+public class IntStreamsTest extends AbstractLangTest {
+
+ @Test
+ public void testRange() {
+ assertArrayEquals(new int[] {0, 1}, IntStreams.range(2).toArray());
+ }
+
+ @Test
+ public void testRangeClosed() {
+ assertArrayEquals(new int[] {0, 1, 2}, IntStreams.rangeClosed(2).toArray());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/stream/LangCollectorsTest.java b/src/test/java/org/apache/commons/lang3/stream/LangCollectorsTest.java
new file mode 100644
index 000000000..e8f408498
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/stream/LangCollectorsTest.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.stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link LangCollectors}
+ */
+public class LangCollectorsTest {
+
+ private static class Fixture {
+ int value;
+
+ private Fixture(final int value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(value);
+ }
+ }
+
+ private static final Long _1L = Long.valueOf(1);
+ private static final Long _2L = Long.valueOf(2);
+ private static final Long _3L = Long.valueOf(3);
+
+ private static final Function<Object, String> TO_STRING = Objects::toString;
+
+ private static final Collector<Object, ?, String> JOINING_0 = LangCollectors.joining();
+ private static final Collector<Object, ?, String> JOINING_1 = LangCollectors.joining("-");
+ private static final Collector<Object, ?, String> JOINING_3 = LangCollectors.joining("-", "<", ">");
+ private static final Collector<Object, ?, String> JOINING_4 = LangCollectors.joining("-", "<", ">", TO_STRING);
+ private static final Collector<Object, ?, String> JOINING_4_NUL = LangCollectors.joining("-", "<", ">", o -> Objects.toString(o, "NUL"));
+
+ @Test
+ public void testJoiningNonStrings0Arg() {
+ assertEquals("", Stream.of().collect(JOINING_0));
+ assertEquals("1", Stream.of(_1L).collect(JOINING_0));
+ assertEquals("12", Stream.of(_1L, _2L).collect(JOINING_0));
+ assertEquals("123", Stream.of(_1L, _2L, _3L).collect(JOINING_0));
+ assertEquals("1null3", Stream.of(_1L, null, _3L).collect(JOINING_0));
+ assertEquals("12", Stream.of(new AtomicLong(1), new AtomicLong(2)).collect(JOINING_0));
+ assertEquals("12", Stream.of(new Fixture(1), new Fixture(2)).collect(JOINING_0));
+ }
+
+ @Test
+ public void testJoiningNonStrings1Arg() {
+ assertEquals("", Stream.of().collect(JOINING_1));
+ assertEquals("1", Stream.of(_1L).collect(JOINING_1));
+ assertEquals("1-2", Stream.of(_1L, _2L).collect(JOINING_1));
+ assertEquals("1-2-3", Stream.of(_1L, _2L, _3L).collect(JOINING_1));
+ assertEquals("1-null-3", Stream.of(_1L, null, _3L).collect(JOINING_1));
+ assertEquals("1-2", Stream.of(new AtomicLong(1), new AtomicLong(2)).collect(JOINING_1));
+ assertEquals("1-2", Stream.of(new Fixture(1), new Fixture(2)).collect(JOINING_1));
+ }
+
+ @Test
+ public void testJoiningNonStrings3Args() {
+ assertEquals("<>", Stream.of().collect(JOINING_3));
+ assertEquals("<1>", Stream.of(_1L).collect(JOINING_3));
+ assertEquals("<1-2>", Stream.of(_1L, _2L).collect(JOINING_3));
+ assertEquals("<1-2-3>", Stream.of(_1L, _2L, _3L).collect(JOINING_3));
+ assertEquals("<1-null-3>", Stream.of(_1L, null, _3L).collect(JOINING_3));
+ assertEquals("<1-2>", Stream.of(new AtomicLong(1), new AtomicLong(2)).collect(JOINING_3));
+ assertEquals("<1-2>", Stream.of(new Fixture(1), new Fixture(2)).collect(JOINING_3));
+ }
+
+ @Test
+ public void testJoiningNonStrings4Args() {
+ assertEquals("<>", Stream.of().collect(JOINING_4));
+ assertEquals("<1>", Stream.of(_1L).collect(JOINING_4));
+ assertEquals("<1-2>", Stream.of(_1L, _2L).collect(JOINING_4));
+ assertEquals("<1-2-3>", Stream.of(_1L, _2L, _3L).collect(JOINING_4));
+ assertEquals("<1-null-3>", Stream.of(_1L, null, _3L).collect(JOINING_4));
+ assertEquals("<1-NUL-3>", Stream.of(_1L, null, _3L).collect(JOINING_4_NUL));
+ assertEquals("<1-2>", Stream.of(new AtomicLong(1), new AtomicLong(2)).collect(JOINING_4));
+ assertEquals("<1-2>", Stream.of(new Fixture(1), new Fixture(2)).collect(JOINING_4));
+ }
+
+ @Test
+ public void testJoiningStrings0Arg() {
+ assertEquals("", Stream.of().collect(JOINING_0));
+ assertEquals("1", Stream.of("1").collect(JOINING_0));
+ assertEquals("12", Stream.of("1", "2").collect(JOINING_0));
+ assertEquals("123", Stream.of("1", "2", "3").collect(JOINING_0));
+ assertEquals("1null3", Stream.of("1", null, "3").collect(JOINING_0));
+ }
+
+ @Test
+ public void testJoiningStrings1Arg() {
+ assertEquals("", Stream.of().collect(JOINING_1));
+ assertEquals("1", Stream.of("1").collect(JOINING_1));
+ assertEquals("1-2", Stream.of("1", "2").collect(JOINING_1));
+ assertEquals("1-2-3", Stream.of("1", "2", "3").collect(JOINING_1));
+ assertEquals("1-null-3", Stream.of("1", null, "3").collect(JOINING_1));
+ }
+
+ @Test
+ public void testJoiningStrings3Args() {
+ assertEquals("<>", Stream.of().collect(JOINING_3));
+ assertEquals("<1>", Stream.of("1").collect(JOINING_3));
+ assertEquals("<1-2>", Stream.of("1", "2").collect(JOINING_3));
+ assertEquals("<1-2-3>", Stream.of("1", "2", "3").collect(JOINING_3));
+ assertEquals("<1-null-3>", Stream.of("1", null, "3").collect(JOINING_3));
+ }
+
+ @Test
+ public void testJoiningStrings4Args() {
+ assertEquals("<>", Stream.of().collect(JOINING_4));
+ assertEquals("<1>", Stream.of("1").collect(JOINING_4));
+ assertEquals("<1-2>", Stream.of("1", "2").collect(JOINING_4));
+ assertEquals("<1-2-3>", Stream.of("1", "2", "3").collect(JOINING_4));
+ assertEquals("<1-null-3>", Stream.of("1", null, "3").collect(JOINING_4));
+ assertEquals("<1-NUL-3>", Stream.of("1", null, "3").collect(JOINING_4_NUL));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/stream/StreamsTest.java b/src/test/java/org/apache/commons/lang3/stream/StreamsTest.java
new file mode 100644
index 000000000..c15f95999
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/stream/StreamsTest.java
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.function.Failable;
+import org.apache.commons.lang3.function.FailableConsumer;
+import org.apache.commons.lang3.function.FailablePredicate;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.function.Executable;
+import org.xml.sax.SAXException;
+
+/**
+ * Tests {@link Streams}.
+ */
+public class StreamsTest extends AbstractLangTest {
+
+ protected <T extends Throwable> FailableConsumer<String, T> asIntConsumer(final T pThrowable) {
+ return s -> {
+ final int i = Integer.parseInt(s);
+ if (i == 4) {
+ throw pThrowable;
+ }
+ };
+ }
+
+ protected <T extends Throwable> FailablePredicate<Integer, T> asIntPredicate(final T pThrowable) {
+ return i -> {
+ if (i.intValue() == 5 && pThrowable != null) {
+ throw pThrowable;
+ }
+ return i % 2 == 0;
+ };
+ }
+
+ private void assertEvenNumbers(final List<Integer> output) {
+ assertEquals(3, output.size());
+ for (int i = 0; i < 3; i++) {
+ assertEquals((i + 1) * 2, output.get(i).intValue());
+ }
+ }
+
+ @TestFactory
+ public Stream<DynamicTest> simpleStreamFilterFailing() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ final List<Integer> output = Failable.stream(input).map(Integer::valueOf).filter(asIntPredicate(null)).collect(Collectors.toList());
+ assertEvenNumbers(output);
+
+ return Stream.of(
+
+ dynamicTest("IllegalArgumentException", () -> {
+ final IllegalArgumentException iae = new IllegalArgumentException("Invalid argument: " + 5);
+ final Executable testMethod = () -> Failable.stream(input).map(Integer::valueOf).filter(asIntPredicate(iae)).collect(Collectors.toList());
+ final IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, testMethod);
+ assertThat(thrown.getMessage(), is(equalTo("Invalid argument: " + 5)));
+ }),
+
+ dynamicTest("OutOfMemoryError", () -> {
+ final OutOfMemoryError oome = new OutOfMemoryError();
+ final Executable testMethod = () -> Failable.stream(input).map(Integer::valueOf).filter(asIntPredicate(oome)).collect(Collectors.toList());
+ final OutOfMemoryError thrown = assertThrows(OutOfMemoryError.class, testMethod);
+ assertThat(thrown.getMessage(), is(nullValue()));
+ }),
+
+ dynamicTest("SAXException", () -> {
+ final SAXException se = new SAXException();
+ final Executable testMethod = () -> Failable.stream(input).map(Integer::valueOf).filter(asIntPredicate(se)).collect(Collectors.toList());
+ final UndeclaredThrowableException thrown = assertThrows(UndeclaredThrowableException.class, testMethod);
+ assertAll(() -> assertThat(thrown.getMessage(), is(nullValue())), () -> assertThat(thrown.getCause(), is(equalTo(se))));
+ }));
+ }
+
+ @TestFactory
+ public Stream<DynamicTest> simpleStreamForEachFailing() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+
+ return Stream.of(
+
+ dynamicTest("IllegalArgumentException", () -> {
+ final IllegalArgumentException ise = new IllegalArgumentException();
+ final Executable testMethod = () -> Failable.stream(input).forEach(asIntConsumer(ise));
+ final IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, testMethod);
+ assertThat(thrown.getMessage(), is(nullValue()));
+ }),
+
+ dynamicTest("OutOfMemoryError", () -> {
+ final OutOfMemoryError oome = new OutOfMemoryError();
+ final Executable oomeTestMethod = () -> Failable.stream(input).forEach(asIntConsumer(oome));
+ final OutOfMemoryError oomeThrown = assertThrows(OutOfMemoryError.class, oomeTestMethod);
+ assertThat(oomeThrown.getMessage(), is(nullValue()));
+ }),
+
+ dynamicTest("SAXException", () -> {
+ final SAXException se = new SAXException();
+ final Executable seTestMethod = () -> Failable.stream(input).forEach(asIntConsumer(se));
+ final UndeclaredThrowableException seThrown = assertThrows(UndeclaredThrowableException.class, seTestMethod);
+ assertAll(() -> assertThat(seThrown.getMessage(), is(nullValue())), () -> assertThat(seThrown.getCause(), is(equalTo(se))));
+ }));
+ }
+
+ @Test
+ public void testInstanceOfStream() {
+ assertEquals(2, Streams.instancesOf(String.class, Arrays.asList("A", "B")).collect(Collectors.toList()).size());
+ assertEquals(2, Streams.instancesOf(String.class, Arrays.asList(null, "A", null, "B", null)).collect(Collectors.toList()).size());
+ assertEquals(0, Streams.instancesOf(String.class, Arrays.asList(null, null)).collect(Collectors.toList()).size());
+ //
+ final List<Object> objects = Arrays.asList("A", "B");
+ assertEquals(2, Streams.instancesOf(String.class, objects).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testNullSafeStreamNotNull() {
+ assertEquals(2, Streams.nonNull(Arrays.asList("A", "B")).collect(Collectors.toList()).size());
+ assertEquals(2, Streams.nonNull(Arrays.asList(null, "A", null, "B", null)).collect(Collectors.toList()).size());
+ assertEquals(0, Streams.nonNull(Arrays.asList(null, null)).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testNullSafeStreamNull() {
+ final List<String> input = null;
+ assertEquals(0, Streams.nonNull(input).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testOfArray() {
+ assertEquals(0, Streams.of((Object[]) null).count());
+ assertEquals(1, Streams.of("foo").count());
+ assertEquals(2, Streams.of("foo", "bar").count());
+ }
+
+ @Test
+ public void testOfCollectionNotNull() {
+ assertEquals(2, Streams.of(Arrays.asList("A", "B")).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testOfCollectionNull() {
+ final List<String> input = null;
+ assertEquals(0, Streams.of(input).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testOfEnumeration() {
+ final Hashtable<String, Integer> table = new Hashtable<>();
+ assertEquals(0, Streams.of(table.elements()).count());
+ table.put("One", 1);
+ assertEquals(1, Streams.of(table.elements()).count());
+ table.put("Two", 2);
+ assertEquals(2, Streams.of(table.elements()).count());
+ final List<String> collect = Streams.of(table.keys()).collect(Collectors.toList());
+ assertTrue(collect.contains("One"));
+ assertTrue(collect.contains("Two"));
+ assertEquals(2, collect.size());
+ }
+
+ @Test
+ public void testOfIterableNotNull() {
+ assertEquals(2, Streams.of((Iterable<String>) Arrays.asList("A", "B")).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testOfIterableNull() {
+ final Iterable<String> input = null;
+ assertEquals(0, Streams.of(input).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testOfIteratorNotNull() {
+ assertEquals(2, Streams.of(Arrays.asList("A", "B").iterator()).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testOfIteratorNull() {
+ final Iterator<String> input = null;
+ assertEquals(0, Streams.of(input).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testSimpleStreamFilter() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ final List<Integer> output = Failable.stream(input).map(Integer::valueOf).filter(i -> (i.intValue() % 2 == 0)).collect(Collectors.toList());
+ assertEvenNumbers(output);
+ }
+
+ @Test
+ public void testSimpleStreamForEach() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ final List<Integer> output = new ArrayList<>();
+ Failable.stream(input).forEach(s -> output.add(Integer.valueOf(s)));
+ assertEquals(6, output.size());
+ for (int i = 0; i < 6; i++) {
+ assertEquals(i + 1, output.get(i).intValue());
+ }
+ }
+
+ @Test
+ public void testSimpleStreamMap() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ final List<Integer> output = Failable.stream(input).map(Integer::valueOf).collect(Collectors.toList());
+ assertEquals(6, output.size());
+ for (int i = 0; i < 6; i++) {
+ assertEquals(i + 1, output.get(i).intValue());
+ }
+ }
+
+ @Test
+ public void testSimpleStreamMapFailing() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4 ", "5", "6");
+ final Executable testMethod = () -> Failable.stream(input).map(Integer::valueOf).collect(Collectors.toList());
+ final NumberFormatException thrown = assertThrows(NumberFormatException.class, testMethod);
+ assertEquals("For input string: \"4 \"", thrown.getMessage());
+ }
+
+ @Test
+ public void testStreamCollection() {
+ final List<String> input = Arrays.asList("1", "2", "3", "4", "5", "6");
+ assertEquals(6, Streams.stream(input).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testStreamCollectionNull() {
+ final List<String> input = null;
+ assertEquals(0, Streams.stream(input).collect(Collectors.toList()).size());
+ }
+
+ @Test
+ public void testToArray() {
+ final String[] array = Arrays.asList("2", "3", "1").stream().collect(Streams.toArray(String.class));
+ assertNotNull(array);
+ assertEquals(3, array.length);
+ assertEquals("2", array[0]);
+ assertEquals("3", array[1]);
+ assertEquals("1", array[2]);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/test/NotVisibleExceptionFactory.java b/src/test/java/org/apache/commons/lang3/test/NotVisibleExceptionFactory.java
new file mode 100644
index 000000000..79e077f78
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/test/NotVisibleExceptionFactory.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.test;
+
+/**
+ * Allows for testing an exception that is not visible to
+ * {@link org.apache.commons.lang3.exception.ExceptionUtils}
+ */
+public class NotVisibleExceptionFactory {
+
+ private NotVisibleExceptionFactory() {}
+
+ /**
+ * Create a new Exception whose getCause method returns the
+ * provided cause.
+ * @param cause the cause of the exception
+ * @return a new {@link Exception}
+ */
+ public static Exception createException(final Throwable cause) {
+ return new NotVisibleException(cause);
+ }
+
+ private static class NotVisibleException extends Exception {
+
+ private static final long serialVersionUID = 1L; // avoid warning
+
+ private final Throwable cause;
+
+ private NotVisibleException(final Throwable cause) {
+ this.cause = cause;
+ }
+
+ @Override
+ public synchronized Throwable getCause() {
+ return cause;
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/CompositeFormatTest.java b/src/test/java/org/apache/commons/lang3/text/CompositeFormatTest.java
new file mode 100644
index 000000000..8f23bc95c
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/CompositeFormatTest.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.text;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.CompositeFormat}.
+ */
+@Deprecated
+public class CompositeFormatTest extends AbstractLangTest {
+
+ /**
+ * Ensures that the parse/format separation is correctly maintained.
+ */
+ @Test
+ public void testCompositeFormat() {
+
+ final Format parser = new Format() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ return null; // do nothing
+ }
+ };
+
+ final Format formatter = new Format() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+ return null; // do nothing
+ }
+
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+ };
+
+ final CompositeFormat composite = new CompositeFormat(parser, formatter);
+
+ composite.parseObject("", null);
+ composite.format(new Object(), new StringBuffer(), null);
+ assertEquals(parser, composite.getParser(), "Parser get method incorrectly implemented");
+ assertEquals(formatter, composite.getFormatter(), "Formatter get method incorrectly implemented");
+ }
+
+ @Test
+ public void testUsage() throws Exception {
+ final Format f1 = new SimpleDateFormat("MMddyyyy", Locale.ENGLISH);
+ final Format f2 = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH);
+ final CompositeFormat c = new CompositeFormat(f1, f2);
+ final String testString = "January 3, 2005";
+ assertEquals(testString, c.format(c.parseObject("01032005")));
+ assertEquals(testString, c.reformat("01032005"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/ExtendedMessageFormatTest.java b/src/test/java/org/apache/commons/lang3/text/ExtendedMessageFormatTest.java
new file mode 100644
index 000000000..f773b0697
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/ExtendedMessageFormatTest.java
@@ -0,0 +1,483 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.MessageFormat;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test case for {@link ExtendedMessageFormat}.
+ *
+ * @since 2.4
+ */
+@Deprecated
+public class ExtendedMessageFormatTest extends AbstractLangTest {
+
+ private final Map<String, FormatFactory> registry = new HashMap<>();
+
+ @BeforeEach
+ public void setUp() {
+ registry.put("lower", new LowerCaseFormatFactory());
+ registry.put("upper", new UpperCaseFormatFactory());
+ }
+
+ /**
+ * Test extended formats.
+ */
+ @Test
+ public void testExtendedFormats() {
+ final String pattern = "Lower: {0,lower} Upper: {1,upper}";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+ assertEquals(pattern, emf.toPattern(), "TOPATTERN");
+ assertEquals(emf.format(new Object[] {"foo", "bar"}), "Lower: foo Upper: BAR");
+ assertEquals(emf.format(new Object[] {"Foo", "Bar"}), "Lower: foo Upper: BAR");
+ assertEquals(emf.format(new Object[] {"FOO", "BAR"}), "Lower: foo Upper: BAR");
+ assertEquals(emf.format(new Object[] {"FOO", "bar"}), "Lower: foo Upper: BAR");
+ assertEquals(emf.format(new Object[] {"foo", "BAR"}), "Lower: foo Upper: BAR");
+ }
+
+ /**
+ * Test Bug LANG-477 - out of memory error with escaped quote
+ */
+ @Test
+ public void testEscapedQuote_LANG_477() {
+ final String pattern = "it''s a {0,lower} 'test'!";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+ assertEquals("it's a dummy test!", emf.format(new Object[] {"DUMMY"}));
+ }
+
+ /**
+ * Test Bug LANG-917 - IndexOutOfBoundsException and/or infinite loop when using a choice pattern
+ */
+ @Test
+ public void testEmbeddedPatternInChoice() {
+ final String pattern = "Hi {0,lower}, got {1,choice,0#none|1#one|1<{1,number}}, {2,upper}!";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+ assertEquals(emf.format(new Object[] {"there", 3, "great"}), "Hi there, got 3, GREAT!");
+ }
+
+ /**
+ * Test Bug LANG-948 - Exception while using ExtendedMessageFormat and escaping braces
+ */
+ @Test
+ public void testEscapedBraces_LANG_948() {
+ // message without placeholder because braces are escaped by quotes
+ final String pattern = "Message without placeholders '{}'";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+ assertEquals("Message without placeholders {}", emf.format(new Object[] {"DUMMY"}));
+
+ // message with placeholder because quotes are escaped by quotes
+ final String pattern2 = "Message with placeholder ''{0}''";
+ final ExtendedMessageFormat emf2 = new ExtendedMessageFormat(pattern2, registry);
+ assertEquals("Message with placeholder 'DUMMY'", emf2.format(new Object[] {"DUMMY"}));
+ }
+
+ /**
+ * Test extended and built in formats.
+ */
+ @Test
+ public void testExtendedAndBuiltInFormats() {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2007, Calendar.JANUARY, 23, 18, 33, 5);
+ final Object[] args = {"John Doe", cal.getTime(), Double.valueOf("12345.67")};
+ final String builtinsPattern = "DOB: {1,date,short} Salary: {2,number,currency}";
+ final String extendedPattern = "Name: {0,upper} ";
+ final String pattern = extendedPattern + builtinsPattern;
+
+ final HashSet<Locale> testLocales = new HashSet<>(Arrays.asList(DateFormat.getAvailableLocales()));
+ testLocales.retainAll(Arrays.asList(NumberFormat.getAvailableLocales()));
+ testLocales.add(null);
+
+ for (final Locale locale : testLocales) {
+ final MessageFormat builtins = createMessageFormat(builtinsPattern, locale);
+ final String expectedPattern = extendedPattern + builtins.toPattern();
+ DateFormat df = null;
+ NumberFormat nf = null;
+ ExtendedMessageFormat emf = null;
+ if (locale == null) {
+ df = DateFormat.getDateInstance(DateFormat.SHORT);
+ nf = NumberFormat.getCurrencyInstance();
+ emf = new ExtendedMessageFormat(pattern, registry);
+ } else {
+ df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+ nf = NumberFormat.getCurrencyInstance(locale);
+ emf = new ExtendedMessageFormat(pattern, locale, registry);
+ }
+ final StringBuilder expected = new StringBuilder();
+ expected.append("Name: ");
+ expected.append(args[0].toString().toUpperCase(Locale.ROOT));
+ expected.append(" DOB: ");
+ expected.append(df.format(args[1]));
+ expected.append(" Salary: ");
+ expected.append(nf.format(args[2]));
+ assertEquals(expectedPattern, emf.toPattern(), "pattern comparison for locale " + locale);
+ assertEquals(expected.toString(), emf.format(args), String.valueOf(locale));
+ }
+ }
+
+// /**
+// * Test extended formats with choice format.
+// *
+// * NOTE: FAILING - currently sub-formats not supported
+// */
+// public void testExtendedWithChoiceFormat() {
+// String pattern = "Choice: {0,choice,1.0#{1,lower}|2.0#{1,upper}}";
+// ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+// assertPatterns(null, pattern, emf.toPattern());
+// try {
+// assertEquals("one", emf.format(new Object[] {Integer.valueOf(1), "ONE"}));
+// assertEquals("TWO", emf.format(new Object[] {Integer.valueOf(2), "two"}));
+// } catch (IllegalArgumentException e) {
+// // currently sub-formats not supported
+// }
+// }
+
+// /**
+// * Test mixed extended and built-in formats with choice format.
+// *
+// * NOTE: FAILING - currently sub-formats not supported
+// */
+// public void testExtendedAndBuiltInWithChoiceFormat() {
+// String pattern = "Choice: {0,choice,1.0#{0} {1,lower} {2,number}|2.0#{0} {1,upper} {2,number,currency}}";
+// Object[] lowArgs = new Object[] {Integer.valueOf(1), "Low", Double.valueOf("1234.56")};
+// Object[] highArgs = new Object[] {Integer.valueOf(2), "High", Double.valueOf("9876.54")};
+// Locale[] availableLocales = ChoiceFormat.getAvailableLocales();
+// Locale[] testLocales = new Locale[availableLocales.length + 1];
+// testLocales[0] = null;
+// System.arraycopy(availableLocales, 0, testLocales, 1, availableLocales.length);
+// for (int i = 0; i < testLocales.length; i++) {
+// NumberFormat nf = null;
+// NumberFormat cf = null;
+// ExtendedMessageFormat emf = null;
+// if (testLocales[i] == null) {
+// nf = NumberFormat.getNumberInstance();
+// cf = NumberFormat.getCurrencyInstance();
+// emf = new ExtendedMessageFormat(pattern, registry);
+// } else {
+// nf = NumberFormat.getNumberInstance(testLocales[i]);
+// cf = NumberFormat.getCurrencyInstance(testLocales[i]);
+// emf = new ExtendedMessageFormat(pattern, testLocales[i], registry);
+// }
+// assertPatterns(null, pattern, emf.toPattern());
+// try {
+// String lowExpected = lowArgs[0] + " low " + nf.format(lowArgs[2]);
+// String highExpected = highArgs[0] + " HIGH " + cf.format(highArgs[2]);
+// assertEquals(lowExpected, emf.format(lowArgs));
+// assertEquals(highExpected, emf.format(highArgs));
+// } catch (IllegalArgumentException e) {
+// // currently sub-formats not supported
+// }
+// }
+// }
+
+ /**
+ * Test the built-in choice format.
+ */
+ @Test
+ public void testBuiltInChoiceFormat() {
+ final Object[] values = new Number[] {Integer.valueOf(1), Double.valueOf("2.2"), Double.valueOf("1234.5")};
+ String choicePattern;
+ final Locale[] availableLocales = NumberFormat.getAvailableLocales();
+
+ choicePattern = "{0,choice,1#One|2#Two|3#Many {0,number}}";
+ for (final Object value : values) {
+ checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
+ }
+
+ choicePattern = "{0,choice,1#''One''|2#\"Two\"|3#''{Many}'' {0,number}}";
+ for (final Object value : values) {
+ checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
+ }
+ }
+
+ /**
+ * Test the built-in date/time formats
+ */
+ @Test
+ public void testBuiltInDateTimeFormat() {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2007, Calendar.JANUARY, 23, 18, 33, 5);
+ final Object[] args = {cal.getTime()};
+ final Locale[] availableLocales = DateFormat.getAvailableLocales();
+
+ checkBuiltInFormat("1: {0,date,short}", args, availableLocales);
+ checkBuiltInFormat("2: {0,date,medium}", args, availableLocales);
+ checkBuiltInFormat("3: {0,date,long}", args, availableLocales);
+ checkBuiltInFormat("4: {0,date,full}", args, availableLocales);
+ checkBuiltInFormat("5: {0,date,d MMM yy}", args, availableLocales);
+ checkBuiltInFormat("6: {0,time,short}", args, availableLocales);
+ checkBuiltInFormat("7: {0,time,medium}", args, availableLocales);
+ checkBuiltInFormat("8: {0,time,long}", args, availableLocales);
+ checkBuiltInFormat("9: {0,time,full}", args, availableLocales);
+ checkBuiltInFormat("10: {0,time,HH:mm}", args, availableLocales);
+ checkBuiltInFormat("11: {0,date}", args, availableLocales);
+ checkBuiltInFormat("12: {0,time}", args, availableLocales);
+ }
+
+ @Test
+ public void testOverriddenBuiltinFormat() {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2007, Calendar.JANUARY, 23);
+ final Object[] args = {cal.getTime()};
+ final Locale[] availableLocales = DateFormat.getAvailableLocales();
+ final Map<String, ? extends FormatFactory> dateRegistry = Collections.singletonMap("date", new OverrideShortDateFormatFactory());
+
+ //check the non-overridden builtins:
+ checkBuiltInFormat("1: {0,date}", dateRegistry, args, availableLocales);
+ checkBuiltInFormat("2: {0,date,medium}", dateRegistry, args, availableLocales);
+ checkBuiltInFormat("3: {0,date,long}", dateRegistry, args, availableLocales);
+ checkBuiltInFormat("4: {0,date,full}", dateRegistry, args, availableLocales);
+ checkBuiltInFormat("5: {0,date,d MMM yy}", dateRegistry, args, availableLocales);
+
+ //check the overridden format:
+ for (int i = -1; i < availableLocales.length; i++) {
+ final Locale locale = i < 0 ? null : availableLocales[i];
+ final MessageFormat dateDefault = createMessageFormat("{0,date}", locale);
+ final String pattern = "{0,date,short}";
+ final ExtendedMessageFormat dateShort = new ExtendedMessageFormat(pattern, locale, dateRegistry);
+ assertEquals(dateDefault.format(args), dateShort.format(args), "overridden date,short format");
+ assertEquals(pattern, dateShort.toPattern(), "overridden date,short pattern");
+ }
+ }
+
+ /**
+ * Test the built-in number formats.
+ */
+ @Test
+ public void testBuiltInNumberFormat() {
+ final Object[] args = {Double.valueOf("6543.21")};
+ final Locale[] availableLocales = NumberFormat.getAvailableLocales();
+ checkBuiltInFormat("1: {0,number}", args, availableLocales);
+ checkBuiltInFormat("2: {0,number,integer}", args, availableLocales);
+ checkBuiltInFormat("3: {0,number,currency}", args, availableLocales);
+ checkBuiltInFormat("4: {0,number,percent}", args, availableLocales);
+ checkBuiltInFormat("5: {0,number,00000.000}", args, availableLocales);
+ }
+
+ /**
+ * Test equals() and hashcode.
+ */
+ @Test
+ public void testEqualsHashcode() {
+ final Map<String, ? extends FormatFactory> fmtRegistry = Collections.singletonMap("testfmt", new LowerCaseFormatFactory());
+ final Map<String, ? extends FormatFactory> otherRegistry = Collections.singletonMap("testfmt", new UpperCaseFormatFactory());
+
+ final String pattern = "Pattern: {0,testfmt}";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
+
+ ExtendedMessageFormat other;
+
+ // Same object
+ assertEquals(emf, emf, "same, equals()");
+ assertEquals(emf.hashCode(), emf.hashCode(), "same, hashcode()");
+
+ // Equal Object
+ other = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
+ assertEquals(emf, other, "equal, equals()");
+ assertEquals(emf.hashCode(), other.hashCode(), "equal, hashcode()");
+
+ // Different Class
+ other = new OtherExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
+ assertNotEquals(emf, other, "class, equals()");
+ assertEquals(emf.hashCode(), other.hashCode(), "class, hashcode()"); // same hashcode
+
+ // Different pattern
+ other = new ExtendedMessageFormat("X" + pattern, Locale.US, fmtRegistry);
+ assertNotEquals(emf, other, "pattern, equals()");
+ assertNotEquals(emf.hashCode(), other.hashCode(), "pattern, hashcode()");
+
+ // Different registry
+ other = new ExtendedMessageFormat(pattern, Locale.US, otherRegistry);
+ assertNotEquals(emf, other, "registry, equals()");
+ assertNotEquals(emf.hashCode(), other.hashCode(), "registry, hashcode()");
+
+ // Different Locale
+ other = new ExtendedMessageFormat(pattern, Locale.FRANCE, fmtRegistry);
+ assertNotEquals(emf, other, "locale, equals()");
+ assertEquals(emf.hashCode(), other.hashCode(), "locale, hashcode()"); // same hashcode
+ }
+
+ /**
+ * Test a built-in format for the specified Locales, plus {@code null} Locale.
+ * @param pattern MessageFormat pattern
+ * @param args MessageFormat arguments
+ * @param locales to test
+ */
+ private void checkBuiltInFormat(final String pattern, final Object[] args, final Locale[] locales) {
+ checkBuiltInFormat(pattern, null, args, locales);
+ }
+
+ /**
+ * Test a built-in format for the specified Locales, plus {@code null} Locale.
+ * @param pattern MessageFormat pattern
+ * @param fmtRegistry FormatFactory registry to use
+ * @param args MessageFormat arguments
+ * @param locales to test
+ */
+ private void checkBuiltInFormat(final String pattern, final Map<String, ?> fmtRegistry, final Object[] args, final Locale[] locales) {
+ checkBuiltInFormat(pattern, fmtRegistry, args, (Locale) null);
+ for (final Locale locale : locales) {
+ checkBuiltInFormat(pattern, fmtRegistry, args, locale);
+ }
+ }
+
+ /**
+ * Create an ExtendedMessageFormat for the specified pattern and locale and check the
+ * formatted output matches the expected result for the parameters.
+ * @param pattern string
+ * @param registryUnused map (currently unused)
+ * @param args Object[]
+ * @param locale Locale
+ */
+ private void checkBuiltInFormat(final String pattern, final Map<String, ?> registryUnused, final Object[] args, final Locale locale) {
+ final StringBuilder buffer = new StringBuilder();
+ buffer.append("Pattern=[");
+ buffer.append(pattern);
+ buffer.append("], locale=[");
+ buffer.append(locale);
+ buffer.append("]");
+ final MessageFormat mf = createMessageFormat(pattern, locale);
+ ExtendedMessageFormat emf = null;
+ if (locale == null) {
+ emf = new ExtendedMessageFormat(pattern);
+ } else {
+ emf = new ExtendedMessageFormat(pattern, locale);
+ }
+ assertEquals(mf.format(args), emf.format(args), "format " + buffer.toString());
+ assertEquals(mf.toPattern(), emf.toPattern(), "toPattern " + buffer.toString());
+ }
+
+ /**
+ * Replace MessageFormat(String, Locale) constructor (not available until JDK 1.4).
+ * @param pattern string
+ * @param locale Locale
+ * @return MessageFormat
+ */
+ private MessageFormat createMessageFormat(final String pattern, final Locale locale) {
+ final MessageFormat result = new MessageFormat(pattern);
+ if (locale != null) {
+ result.setLocale(locale);
+ result.applyPattern(pattern);
+ }
+ return result;
+ }
+
+ // ------------------------ Test Formats ------------------------
+
+ /**
+ * {@link Format} implementation which converts to lower case.
+ */
+ private static class LowerCaseFormat extends Format {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+ return toAppendTo.append(((String) obj).toLowerCase(Locale.ROOT));
+ }
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * {@link Format} implementation which converts to upper case.
+ */
+ private static class UpperCaseFormat extends Format {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+ return toAppendTo.append(((String) obj).toUpperCase(Locale.ROOT));
+ }
+
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+
+ // ------------------------ Test Format Factories ---------------
+ /**
+ * {@link FormatFactory} implementation for lower case format.
+ */
+ private static class LowerCaseFormatFactory implements FormatFactory {
+ private static final Format LOWER_INSTANCE = new LowerCaseFormat();
+
+ @Override
+ public Format getFormat(final String name, final String arguments, final Locale locale) {
+ return LOWER_INSTANCE;
+ }
+ }
+ /**
+ * {@link FormatFactory} implementation for upper case format.
+ */
+ private static class UpperCaseFormatFactory implements FormatFactory {
+ private static final Format UPPER_INSTANCE = new UpperCaseFormat();
+
+ @Override
+ public Format getFormat(final String name, final String arguments, final Locale locale) {
+ return UPPER_INSTANCE;
+ }
+ }
+ /**
+ * {@link FormatFactory} implementation to override date format "short" to "default".
+ */
+ private static class OverrideShortDateFormatFactory implements FormatFactory {
+
+ @Override
+ public Format getFormat(final String name, final String arguments, final Locale locale) {
+ return !"short".equals(arguments) ? null
+ : locale == null ? DateFormat
+ .getDateInstance(DateFormat.DEFAULT) : DateFormat
+ .getDateInstance(DateFormat.DEFAULT, locale);
+ }
+ }
+
+ /**
+ * Alternative ExtendedMessageFormat impl.
+ */
+ private static class OtherExtendedMessageFormat extends ExtendedMessageFormat {
+ private static final long serialVersionUID = 1L;
+
+ OtherExtendedMessageFormat(final String pattern, final Locale locale,
+ final Map<String, ? extends FormatFactory> registry) {
+ super(pattern, locale, registry);
+ }
+
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/FormattableUtilsTest.java b/src/test/java/org/apache/commons/lang3/text/FormattableUtilsTest.java
new file mode 100644
index 000000000..f0c715f5b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/FormattableUtilsTest.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 static java.util.FormattableFlags.LEFT_JUSTIFY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Formatter;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests {@link FormattableUtils}.
+ */
+@Deprecated
+public class FormattableUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testDefaultAppend() {
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1).toString());
+ assertEquals("fo", FormattableUtils.append("foo", new Formatter(), 0, -1, 2).toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1).toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1).toString());
+ assertEquals(" fo", FormattableUtils.append("foo", new Formatter(), 0, 3, 2).toString());
+ assertEquals(" fo", FormattableUtils.append("foo", new Formatter(), 0, 5, 2).toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1).toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1).toString());
+ assertEquals("fo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2).toString());
+ assertEquals("fo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2).toString());
+ }
+
+ @Test
+ public void testAlternatePadCharacter() {
+ final char pad='_';
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, pad).toString());
+ assertEquals("fo", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, pad).toString());
+ assertEquals("_foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, pad).toString());
+ assertEquals("___foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, pad).toString());
+ assertEquals("_fo", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, pad).toString());
+ assertEquals("___fo", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, pad).toString());
+ assertEquals("foo_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, pad).toString());
+ assertEquals("foo___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, pad).toString());
+ assertEquals("fo_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, pad).toString());
+ assertEquals("fo___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, pad).toString());
+ }
+
+ @Test
+ public void testEllipsis() {
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, "*").toString());
+ assertEquals("f*", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, "*").toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, "*").toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, "*").toString());
+ assertEquals(" f*", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, "*").toString());
+ assertEquals(" f*", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, "*").toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, "*").toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, "*").toString());
+ assertEquals("f* ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, "*").toString());
+ assertEquals("f* ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, "*").toString());
+
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, "+*").toString());
+ assertEquals("+*", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, "+*").toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, "+*").toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, "+*").toString());
+ assertEquals(" +*", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, "+*").toString());
+ assertEquals(" +*", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, "+*").toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, "+*").toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, "+*").toString());
+ assertEquals("+* ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, "+*").toString());
+ assertEquals("+* ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, "+*").toString());
+ }
+
+ @Test
+ public void testIllegalEllipsis() {
+ assertThrows(IllegalArgumentException.class, () -> FormattableUtils.append("foo", new Formatter(), 0, -1, 1, "xx"));
+ }
+
+ @Test
+ public void testAlternatePadCharAndEllipsis() {
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, '_', "*").toString());
+ assertEquals("f*", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, '_', "*").toString());
+ assertEquals("_foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, '_', "*").toString());
+ assertEquals("___foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, '_', "*").toString());
+ assertEquals("_f*", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, '_', "*").toString());
+ assertEquals("___f*", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, '_', "*").toString());
+ assertEquals("foo_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, '_', "*").toString());
+ assertEquals("foo___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, '_', "*").toString());
+ assertEquals("f*_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, '_', "*").toString());
+ assertEquals("f*___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, '_', "*").toString());
+
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, '_', "+*").toString());
+ assertEquals("+*", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, '_', "+*").toString());
+ assertEquals("_foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, '_', "+*").toString());
+ assertEquals("___foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, '_', "+*").toString());
+ assertEquals("_+*", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, '_', "+*").toString());
+ assertEquals("___+*", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, '_', "+*").toString());
+ assertEquals("foo_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, '_', "+*").toString());
+ assertEquals("foo___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, '_', "+*").toString());
+ assertEquals("+*_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, '_', "+*").toString());
+ assertEquals("+*___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, '_', "+*").toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/StrBuilderAppendInsertTest.java b/src/test/java/org/apache/commons/lang3/text/StrBuilderAppendInsertTest.java
new file mode 100644
index 000000000..66539ea49
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/StrBuilderAppendInsertTest.java
@@ -0,0 +1,1449 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.text.DecimalFormatSymbols;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.StrBuilder}.
+ */
+@Deprecated
+public class StrBuilderAppendInsertTest extends AbstractLangTest {
+
+ /** The system line separator. */
+ private static final String SEP = System.lineSeparator();
+
+ /** Test subclass of Object, with a toString method. */
+ private static final Object FOO = new Object() {
+ @Override
+ public String toString() {
+ return "foo";
+ }
+ };
+
+ @Test
+ public void testAppendNewLine() {
+ StrBuilder sb = new StrBuilder("---");
+ sb.appendNewLine().append("+++");
+ assertEquals("---" + SEP + "+++", sb.toString());
+
+ sb = new StrBuilder("---");
+ sb.setNewLineText("#").appendNewLine().setNewLineText(null).appendNewLine();
+ assertEquals("---#" + SEP, sb.toString());
+ }
+
+ @Test
+ public void testAppendWithNullText() {
+ final StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL");
+ assertEquals("", sb.toString());
+
+ sb.appendNull();
+ assertEquals("NULL", sb.toString());
+
+ sb.append((Object) null);
+ assertEquals("NULLNULL", sb.toString());
+
+ sb.append(FOO);
+ assertEquals("NULLNULLfoo", sb.toString());
+
+ sb.append((String) null);
+ assertEquals("NULLNULLfooNULL", sb.toString());
+
+ sb.append("");
+ assertEquals("NULLNULLfooNULL", sb.toString());
+
+ sb.append("bar");
+ assertEquals("NULLNULLfooNULLbar", sb.toString());
+
+ sb.append((StringBuffer) null);
+ assertEquals("NULLNULLfooNULLbarNULL", sb.toString());
+
+ sb.append(new StringBuffer("baz"));
+ assertEquals("NULLNULLfooNULLbarNULLbaz", sb.toString());
+ }
+
+ @Test
+ public void testAppend_Object() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendNull();
+ assertEquals("", sb.toString());
+
+ sb.append((Object) null);
+ assertEquals("", sb.toString());
+
+ sb.append(FOO);
+ assertEquals("foo", sb.toString());
+
+ sb.append((StringBuffer) null);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuffer("baz"));
+ assertEquals("foobaz", sb.toString());
+
+ sb.append(new StrBuilder("yes"));
+ assertEquals("foobazyes", sb.toString());
+
+ sb.append((CharSequence) "Seq");
+ assertEquals("foobazyesSeq", sb.toString());
+
+ sb.append(new StringBuilder("bld")); // Check it supports StringBuilder
+ assertEquals("foobazyesSeqbld", sb.toString());
+ }
+
+ @Test
+ public void testAppend_StringBuilder() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((String) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StringBuilder("foo"));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuilder(""));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuilder("bar"));
+ assertEquals("foobar", sb.toString());
+ }
+
+ @Test
+ public void testAppend_String() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((String) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+
+ sb.append("");
+ assertEquals("foo", sb.toString());
+
+ sb.append("bar");
+ assertEquals("foobar", sb.toString());
+ }
+
+ @Test
+ public void testAppend_String_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((String) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("foo", 0, 3);
+ assertEquals("foo", sb.toString());
+
+ final StrBuilder sb1 = sb;
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append("bar", -1, 1),
+ "append(char[], -1,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append("bar", 3, 1),
+ "append(char[], 3,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append("bar", 1, -1),
+ "append(char[],, -1) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append("bar", 1, 3),
+ "append(char[], 1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append("bar", -1, 3),
+ "append(char[], -1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append("bar", 4, 0),
+ "append(char[], 4, 0) expected IndexOutOfBoundsException");
+
+ sb.append("bar", 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append("abcbardef", 3, 3);
+ assertEquals("foobar", sb.toString());
+
+ sb.append((CharSequence) "abcbardef", 4, 3);
+ assertEquals("foobarard", sb.toString());
+ }
+
+ @Test
+ public void testAppend_StringBuilder_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((String) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StringBuilder("foo"), 0, 3);
+ assertEquals("foo", sb.toString());
+
+ final StrBuilder sb1 = sb;
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuilder("bar"), -1, 1),
+ "append(StringBuilder, -1,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuilder("bar"), 3, 1),
+ "append(StringBuilder, 3,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuilder("bar"), 1, -1),
+ "append(StringBuilder,, -1) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuilder("bar"), 1, 3),
+ "append(StringBuilder, 1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuilder("bar"), -1, 3),
+ "append(StringBuilder, -1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuilder("bar"), 4, 0),
+ "append(StringBuilder, 4, 0) expected IndexOutOfBoundsException");
+
+ sb.append(new StringBuilder("bar"), 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuilder("abcbardef"), 3, 3);
+ assertEquals("foobar", sb.toString());
+
+ sb.append( new StringBuilder("abcbardef"), 4, 3);
+ assertEquals("foobarard", sb.toString());
+ }
+
+ @Test
+ public void testAppend_StringBuffer() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((StringBuffer) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StringBuffer("foo"));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuffer(""));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuffer("bar"));
+ assertEquals("foobar", sb.toString());
+ }
+
+ @Test
+ public void testAppend_StringBuffer_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((StringBuffer) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StringBuffer("foo"), 0, 3);
+ assertEquals("foo", sb.toString());
+
+ final StrBuilder sb1 = sb;
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuffer("bar"), -1, 1),
+ "append(char[], -1,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuffer("bar"), 3, 1),
+ "append(char[], 3,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuffer("bar"), 1, -1),
+ "append(char[],, -1) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuffer("bar"), 1, 3),
+ "append(char[], 1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuffer("bar"), -1, 3),
+ "append(char[], -1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StringBuffer("bar"), 4, 0),
+ "append(char[], 4, 0) expected IndexOutOfBoundsException");
+
+ sb.append(new StringBuffer("bar"), 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuffer("abcbardef"), 3, 3);
+ assertEquals("foobar", sb.toString());
+ }
+
+ @Test
+ public void testAppend_StrBuilder() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((StrBuilder) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StrBuilder("foo"));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StrBuilder(""));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StrBuilder("bar"));
+ assertEquals("foobar", sb.toString());
+ }
+
+ @Test
+ public void testAppend_StrBuilder_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((StrBuilder) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StrBuilder("foo"), 0, 3);
+ assertEquals("foo", sb.toString());
+
+ final StrBuilder sb1 = sb;
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StrBuilder("bar"), -1, 1),
+ "append(char[], -1,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StrBuilder("bar"), 3, 1),
+ "append(char[], 3,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StrBuilder("bar"), 1, -1),
+ "append(char[],, -1) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StrBuilder("bar"), 1, 3),
+ "append(char[], 1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StrBuilder("bar"), -1, 3),
+ "append(char[], -1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new StrBuilder("bar"), 4, 0),
+ "append(char[], 4, 0) expected IndexOutOfBoundsException");
+
+ sb.append(new StrBuilder("bar"), 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StrBuilder("abcbardef"), 3, 3);
+ assertEquals("foobar", sb.toString());
+ }
+
+ @Test
+ public void testAppend_CharArray() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((char[]) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new char[0]);
+ assertEquals("", sb.toString());
+
+ sb.append(new char[]{'f', 'o', 'o'});
+ assertEquals("foo", sb.toString());
+ }
+
+ @Test
+ public void testAppend_CharArray_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((char[]) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new char[]{'f', 'o', 'o'}, 0, 3);
+ assertEquals("foo", sb.toString());
+
+ final StrBuilder sb1 = sb;
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new char[]{'b', 'a', 'r'}, -1, 1),
+ "append(char[], -1,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new char[]{'b', 'a', 'r'}, 3, 1),
+ "append(char[], 3,) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new char[]{'b', 'a', 'r'}, 1, -1),
+ "append(char[],, -1) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new char[]{'b', 'a', 'r'}, 1, 3),
+ "append(char[], 1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new char[]{'b', 'a', 'r'}, -1, 3),
+ "append(char[], -1, 3) expected IndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.append(new char[]{'b', 'a', 'r'}, 4, 0),
+ "append(char[], 4, 0) expected IndexOutOfBoundsException");
+
+ sb.append(new char[]{'b', 'a', 'r'}, 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new char[]{'a', 'b', 'c', 'b', 'a', 'r', 'd', 'e', 'f'}, 3, 3);
+ assertEquals("foobar", sb.toString());
+ }
+
+ @Test
+ public void testAppend_Boolean() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append(true);
+ assertEquals("true", sb.toString());
+
+ sb.append(false);
+ assertEquals("truefalse", sb.toString());
+
+ sb.append('!');
+ assertEquals("truefalse!", sb.toString());
+ }
+
+ @Test
+ public void testAppend_PrimitiveNumber() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append(0);
+ assertEquals("0", sb.toString());
+
+ sb.append(1L);
+ assertEquals("01", sb.toString());
+
+ sb.append(2.3f);
+ assertEquals("012.3", sb.toString());
+
+ sb.append(4.5d);
+ assertEquals("012.34.5", sb.toString());
+ }
+
+ @Test
+ public void testAppendln_FormattedString() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final String str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("Hello %s", "Alice");
+ assertEquals("Hello Alice" + SEP, sb.toString());
+ assertEquals(2, count[0]); // appendNewLine() calls append(String)
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_Object() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendln((Object) null);
+ assertEquals("" + SEP, sb.toString());
+
+ sb.appendln(FOO);
+ assertEquals(SEP + "foo" + SEP, sb.toString());
+
+ sb.appendln(Integer.valueOf(6));
+ assertEquals(SEP + "foo" + SEP + "6" + SEP, sb.toString());
+ }
+
+ @Test
+ public void testAppendln_String() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final String str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("foo");
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(2, count[0]); // appendNewLine() calls append(String)
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_String_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final String str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("foo", 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_StringBuffer() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StringBuffer str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StringBuffer("foo"));
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_StringBuilder() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StringBuilder str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StringBuilder("foo"));
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_StringBuffer_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StringBuffer str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StringBuffer("foo"), 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_StringBuilder_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StringBuilder str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StringBuilder("foo"), 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_StrBuilder() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StrBuilder str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StrBuilder("foo"));
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_StrBuilder_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StrBuilder str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StrBuilder("foo"), 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_CharArray() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final char[] str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("foo".toCharArray());
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_CharArray_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final char[] str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("foo".toCharArray(), 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ @Test
+ public void testAppendln_Boolean() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendln(true);
+ assertEquals("true" + SEP, sb.toString());
+
+ sb.clear();
+ sb.appendln(false);
+ assertEquals("false" + SEP, sb.toString());
+ }
+
+ @Test
+ public void testAppendln_PrimitiveNumber() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendln(0);
+ assertEquals("0" + SEP, sb.toString());
+
+ sb.clear();
+ sb.appendln(1L);
+ assertEquals("1" + SEP, sb.toString());
+
+ sb.clear();
+ sb.appendln(2.3f);
+ assertEquals("2.3" + SEP, sb.toString());
+
+ sb.clear();
+ sb.appendln(4.5d);
+ assertEquals("4.5" + SEP, sb.toString());
+ }
+
+ @Test
+ public void testAppendPadding() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+
+ sb.appendPadding(-1, '-');
+ assertEquals("foo", sb.toString());
+
+ sb.appendPadding(0, '-');
+ assertEquals("foo", sb.toString());
+
+ sb.appendPadding(1, '-');
+ assertEquals("foo-", sb.toString());
+
+ sb.appendPadding(16, '-');
+ assertEquals(20, sb.length());
+ // 12345678901234567890
+ assertEquals("foo-----------------", sb.toString());
+ }
+
+ @Test
+ public void testAppendFixedWidthPadLeft() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadLeft("foo", -1, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 0, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 1, '-');
+ assertEquals("o", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 2, '-');
+ assertEquals("oo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 3, '-');
+ assertEquals("foo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 4, '-');
+ assertEquals("-foo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 10, '-');
+ assertEquals(10, sb.length());
+ // 1234567890
+ assertEquals("-------foo", sb.toString());
+
+ sb.clear();
+ sb.setNullText("null");
+ sb.appendFixedWidthPadLeft(null, 5, '-');
+ assertEquals("-null", sb.toString());
+ }
+
+ @Test
+ public void testAppendFixedWidthPadLeft_int() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadLeft(123, -1, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 0, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 1, '-');
+ assertEquals("3", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 2, '-');
+ assertEquals("23", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 3, '-');
+ assertEquals("123", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 4, '-');
+ assertEquals("-123", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 10, '-');
+ assertEquals(10, sb.length());
+ // 1234567890
+ assertEquals("-------123", sb.toString());
+ }
+
+ @Test
+ public void testAppendFixedWidthPadRight() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadRight("foo", -1, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 0, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 1, '-');
+ assertEquals("f", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 2, '-');
+ assertEquals("fo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 3, '-');
+ assertEquals("foo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 4, '-');
+ assertEquals("foo-", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 10, '-');
+ assertEquals(10, sb.length());
+ // 1234567890
+ assertEquals("foo-------", sb.toString());
+
+ sb.clear();
+ sb.setNullText("null");
+ sb.appendFixedWidthPadRight(null, 5, '-');
+ assertEquals("null-", sb.toString());
+ }
+
+ // See: https://issues.apache.org/jira/browse/LANG-299
+ @Test
+ public void testLang299() {
+ final StrBuilder sb = new StrBuilder(1);
+ sb.appendFixedWidthPadRight("foo", 1, '-');
+ assertEquals("f", sb.toString());
+ }
+
+ @Test
+ public void testAppendFixedWidthPadRight_int() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadRight(123, -1, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 0, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 1, '-');
+ assertEquals("1", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 2, '-');
+ assertEquals("12", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 3, '-');
+ assertEquals("123", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 4, '-');
+ assertEquals("123-", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 10, '-');
+ assertEquals(10, sb.length());
+ // 1234567890
+ assertEquals("123-------", sb.toString());
+ }
+
+ @Test
+ public void testAppend_FormattedString() {
+ StrBuilder sb;
+
+ sb = new StrBuilder();
+ sb.append("Hi", (Object[]) null);
+ assertEquals("Hi", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("Hi", "Alice");
+ assertEquals("Hi", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("Hi %s", "Alice");
+ assertEquals("Hi Alice", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("Hi %s %,d", "Alice", 5000);
+ // group separator depends on system locale
+ final char groupingSeparator = DecimalFormatSymbols.getInstance().getGroupingSeparator();
+ final String expected = "Hi Alice 5" + groupingSeparator + "000";
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testAppendAll_Array() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendAll((Object[]) null);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll();
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll("foo", "bar", "baz");
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.appendAll("foo", "bar", "baz");
+ assertEquals("foobarbaz", sb.toString());
+ }
+
+ @Test
+ public void testAppendAll_Collection() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendAll((Collection<?>) null);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(Collections.EMPTY_LIST);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(Arrays.asList("foo", "bar", "baz"));
+ assertEquals("foobarbaz", sb.toString());
+ }
+
+ @Test
+ public void testAppendAll_Iterator() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendAll((Iterator<?>) null);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(Collections.EMPTY_LIST.iterator());
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(Arrays.asList("foo", "bar", "baz").iterator());
+ assertEquals("foobarbaz", sb.toString());
+ }
+
+ @Test
+ public void testAppendWithSeparators_Array() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendWithSeparators((Object[]) null, ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(new Object[0], ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(new Object[]{"foo", "bar", "baz"}, ",");
+ assertEquals("foo,bar,baz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(new Object[]{"foo", "bar", "baz"}, null);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(new Object[]{"foo", null, "baz"}, ",");
+ assertEquals("foo,,baz", sb.toString());
+ }
+
+ @Test
+ public void testAppendWithSeparators_Collection() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendWithSeparators((Collection<?>) null, ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Collections.EMPTY_LIST, ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList("foo", "bar", "baz"), ",");
+ assertEquals("foo,bar,baz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList("foo", "bar", "baz"), null);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList("foo", null, "baz"), ",");
+ assertEquals("foo,,baz", sb.toString());
+ }
+
+ @Test
+ public void testAppendWithSeparators_Iterator() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendWithSeparators((Iterator<?>) null, ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Collections.EMPTY_LIST.iterator(), ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList("foo", "bar", "baz").iterator(), ",");
+ assertEquals("foo,bar,baz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList("foo", "bar", "baz").iterator(), null);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList("foo", null, "baz").iterator(), ",");
+ assertEquals("foo,,baz", sb.toString());
+ }
+
+ @Test
+ public void testAppendWithSeparatorsWithNullText() {
+ final StrBuilder sb = new StrBuilder();
+ sb.setNullText("null");
+ sb.appendWithSeparators(new Object[]{"foo", null, "baz"}, ",");
+ assertEquals("foo,null,baz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList("foo", null, "baz"), ",");
+ assertEquals("foo,null,baz", sb.toString());
+ }
+
+ @Test
+ public void testAppendSeparator_String() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendSeparator(","); // no effect
+ assertEquals("", sb.toString());
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+ sb.appendSeparator(",");
+ assertEquals("foo,", sb.toString());
+ }
+
+ @Test
+ public void testAppendSeparator_String_String() {
+ final StrBuilder sb = new StrBuilder();
+ final String startSeparator = "order by ";
+ final String standardSeparator = ",";
+ final String foo = "foo";
+ sb.appendSeparator(null, null);
+ assertEquals("", sb.toString());
+ sb.appendSeparator(standardSeparator, null);
+ assertEquals("", sb.toString());
+ sb.appendSeparator(standardSeparator, startSeparator);
+ assertEquals(startSeparator, sb.toString());
+ sb.appendSeparator(null, null);
+ assertEquals(startSeparator, sb.toString());
+ sb.appendSeparator(null, startSeparator);
+ assertEquals(startSeparator, sb.toString());
+ sb.append(foo);
+ assertEquals(startSeparator + foo, sb.toString());
+ sb.appendSeparator(standardSeparator, startSeparator);
+ assertEquals(startSeparator + foo + standardSeparator, sb.toString());
+ }
+
+ @Test
+ public void testAppendSeparator_char() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendSeparator(','); // no effect
+ assertEquals("", sb.toString());
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+ sb.appendSeparator(',');
+ assertEquals("foo,", sb.toString());
+ }
+ @Test
+ public void testAppendSeparator_char_char() {
+ final StrBuilder sb = new StrBuilder();
+ final char startSeparator = ':';
+ final char standardSeparator = ',';
+ final String foo = "foo";
+ sb.appendSeparator(standardSeparator, startSeparator); // no effect
+ assertEquals(String.valueOf(startSeparator), sb.toString());
+ sb.append(foo);
+ assertEquals(String.valueOf(startSeparator) + foo, sb.toString());
+ sb.appendSeparator(standardSeparator, startSeparator);
+ assertEquals(String.valueOf(startSeparator) + foo + standardSeparator, sb.toString());
+ }
+
+ @Test
+ public void testAppendSeparator_String_int() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendSeparator(",", 0); // no effect
+ assertEquals("", sb.toString());
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+ sb.appendSeparator(",", 1);
+ assertEquals("foo,", sb.toString());
+
+ sb.appendSeparator(",", -1); // no effect
+ assertEquals("foo,", sb.toString());
+ }
+
+ @Test
+ public void testAppendSeparator_char_int() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendSeparator(',', 0); // no effect
+ assertEquals("", sb.toString());
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+ sb.appendSeparator(',', 1);
+ assertEquals("foo,", sb.toString());
+
+ sb.appendSeparator(',', -1); // no effect
+ assertEquals("foo,", sb.toString());
+ }
+
+ @Test
+ public void testInsert() {
+
+ final StrBuilder sb = new StrBuilder();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, FOO),
+ "insert(-1, Object) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, FOO),
+ "insert(7, Object) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, (Object) null);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, FOO);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, "foo"),
+ "insert(-1, String) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, "foo"),
+ "insert(7, String) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, (String) null);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, "foo");
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, new char[]{'f', 'o', 'o'}),
+ "insert(-1, char[]) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, new char[]{'f', 'o', 'o'}),
+ "insert(7, char[]) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, (char[]) null);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, new char[0]);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, new char[]{'f', 'o', 'o'});
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 3, 3),
+ "insert(-1, char[], 3, 3) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 3, 3),
+ "insert(7, char[], 3, 3) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, null, 0, 0);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, new char[0], 0, 0);
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, -1, 3),
+ "insert(0, char[], -1, 3) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 10, 3),
+ "insert(0, char[], 10, 3) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 0, -1),
+ "insert(0, char[], 0, -1) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 0, 10),
+ "insert(0, char[], 0, 10) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 0, 0);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 3, 3);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, true),
+ "insert(-1, boolean) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, true),
+ "insert(7, boolean) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, true);
+ assertEquals("truebarbaz", sb.toString());
+
+ sb.insert(0, false);
+ assertEquals("falsetruebarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, '!'),
+ "insert(-1, char) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, '!'),
+ "insert(7, char) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, '!');
+ assertEquals("!barbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, 0),
+ "insert(-1, int) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, 0),
+ "insert(7, int) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, '0');
+ assertEquals("0barbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, 1L),
+ "insert(-1, long) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, 1L),
+ "insert(7, long) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, 1L);
+ assertEquals("1barbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, 2.3F),
+ "insert(-1, float) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, 2.3F),
+ "insert(7, float) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, 2.3F);
+ assertEquals("2.3barbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, 4.5D),
+ "insert(-1, double) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, 4.5D),
+ "insert(7, double) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, 4.5D);
+ assertEquals("4.5barbaz", sb.toString());
+ }
+
+ @Test
+ public void testInsertWithNullText() {
+ final StrBuilder sb = new StrBuilder();
+ sb.setNullText("null");
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, FOO),
+ "insert(-1, Object) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, FOO),
+ "insert(7, Object) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, (Object) null);
+ assertEquals("nullbarbaz", sb.toString());
+
+ sb.insert(0, FOO);
+ assertEquals("foonullbarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(-1, "foo"),
+ "insert(-1, String) expected StringIndexOutOfBoundsException");
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.insert(7, "foo"),
+ "insert(7, String) expected StringIndexOutOfBoundsException");
+
+ sb.insert(0, (String) null);
+ assertEquals("nullbarbaz", sb.toString());
+
+ sb.insert(0, "foo");
+ assertEquals("foonullbarbaz", sb.toString());
+
+ sb.insert(0, (char[]) null);
+ assertEquals("nullfoonullbarbaz", sb.toString());
+
+ sb.insert(0, null, 0, 0);
+ assertEquals("nullnullfoonullbarbaz", sb.toString());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/StrBuilderTest.java b/src/test/java/org/apache/commons/lang3/text/StrBuilderTest.java
new file mode 100644
index 000000000..0ebf53c21
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/StrBuilderTest.java
@@ -0,0 +1,1853 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.CharBuffer;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.StrBuilder}.
+ */
+@Deprecated
+public class StrBuilderTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructors() {
+ final StrBuilder sb0 = new StrBuilder();
+ assertEquals(32, sb0.capacity());
+ assertEquals(0, sb0.length());
+ assertEquals(0, sb0.size());
+
+ final StrBuilder sb1 = new StrBuilder(32);
+ assertEquals(32, sb1.capacity());
+ assertEquals(0, sb1.length());
+ assertEquals(0, sb1.size());
+
+ final StrBuilder sb2 = new StrBuilder(0);
+ assertEquals(32, sb2.capacity());
+ assertEquals(0, sb2.length());
+ assertEquals(0, sb2.size());
+
+ final StrBuilder sb3 = new StrBuilder(-1);
+ assertEquals(32, sb3.capacity());
+ assertEquals(0, sb3.length());
+ assertEquals(0, sb3.size());
+
+ final StrBuilder sb4 = new StrBuilder(1);
+ assertEquals(1, sb4.capacity());
+ assertEquals(0, sb4.length());
+ assertEquals(0, sb4.size());
+
+ final StrBuilder sb5 = new StrBuilder(null);
+ assertEquals(32, sb5.capacity());
+ assertEquals(0, sb5.length());
+ assertEquals(0, sb5.size());
+
+ final StrBuilder sb6 = new StrBuilder("");
+ assertEquals(32, sb6.capacity());
+ assertEquals(0, sb6.length());
+ assertEquals(0, sb6.size());
+
+ final StrBuilder sb7 = new StrBuilder("foo");
+ assertEquals(35, sb7.capacity());
+ assertEquals(3, sb7.length());
+ assertEquals(3, sb7.size());
+ }
+
+ @Test
+ public void testChaining() {
+ final StrBuilder sb = new StrBuilder();
+ assertSame(sb, sb.setNewLineText(null));
+ assertSame(sb, sb.setNullText(null));
+ assertSame(sb, sb.setLength(1));
+ assertSame(sb, sb.setCharAt(0, 'a'));
+ assertSame(sb, sb.ensureCapacity(0));
+ assertSame(sb, sb.minimizeCapacity());
+ assertSame(sb, sb.clear());
+ assertSame(sb, sb.reverse());
+ assertSame(sb, sb.trim());
+ }
+
+ @Test
+ public void testReadFromReader() throws Exception {
+ String s = "";
+ for (int i = 0; i < 100; ++i) {
+ final StrBuilder sb = new StrBuilder();
+ final int len = sb.readFrom(new StringReader(s));
+
+ assertEquals(s.length(), len);
+ assertEquals(s, sb.toString());
+
+ s += Integer.toString(i);
+ }
+ }
+
+ @Test
+ public void testReadFromReaderAppendsToEnd() throws Exception {
+ final StrBuilder sb = new StrBuilder("Test");
+ sb.readFrom(new StringReader(" 123"));
+ assertEquals("Test 123", sb.toString());
+ }
+
+ @Test
+ public void testReadFromCharBuffer() throws Exception {
+ String s = "";
+ for (int i = 0; i < 100; ++i) {
+ final StrBuilder sb = new StrBuilder();
+ final int len = sb.readFrom(CharBuffer.wrap(s));
+
+ assertEquals(s.length(), len);
+ assertEquals(s, sb.toString());
+
+ s += Integer.toString(i);
+ }
+ }
+
+ @Test
+ public void testReadFromCharBufferAppendsToEnd() throws Exception {
+ final StrBuilder sb = new StrBuilder("Test");
+ sb.readFrom(CharBuffer.wrap(" 123"));
+ assertEquals("Test 123", sb.toString());
+ }
+
+ @Test
+ public void testReadFromReadable() throws Exception {
+ String s = "";
+ for (int i = 0; i < 100; ++i) {
+ final StrBuilder sb = new StrBuilder();
+ final int len = sb.readFrom(new MockReadable(s));
+
+ assertEquals(s.length(), len);
+ assertEquals(s, sb.toString());
+
+ s += Integer.toString(i);
+ }
+ }
+
+ @Test
+ public void testReadFromReadableAppendsToEnd() throws Exception {
+ final StrBuilder sb = new StrBuilder("Test");
+ sb.readFrom(new MockReadable(" 123"));
+ assertEquals("Test 123", sb.toString());
+ }
+
+ private static class MockReadable implements Readable {
+
+ private final CharBuffer src;
+
+ MockReadable(final String src) {
+ this.src = CharBuffer.wrap(src);
+ }
+
+ @Override
+ public int read(final CharBuffer cb) throws IOException {
+ return src.read(cb);
+ }
+ }
+
+ @Test
+ public void testGetSetNewLineText() {
+ final StrBuilder sb = new StrBuilder();
+ assertNull(sb.getNewLineText());
+
+ sb.setNewLineText("#");
+ assertEquals("#", sb.getNewLineText());
+
+ sb.setNewLineText("");
+ assertEquals("", sb.getNewLineText());
+
+ sb.setNewLineText(null);
+ assertNull(sb.getNewLineText());
+ }
+
+ @Test
+ public void testGetSetNullText() {
+ final StrBuilder sb = new StrBuilder();
+ assertNull(sb.getNullText());
+
+ sb.setNullText("null");
+ assertEquals("null", sb.getNullText());
+
+ sb.setNullText("");
+ assertNull(sb.getNullText());
+
+ sb.setNullText("NULL");
+ assertEquals("NULL", sb.getNullText());
+
+ sb.setNullText(null);
+ assertNull(sb.getNullText());
+ }
+
+ @Test
+ public void testCapacityAndLength() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(32, sb.capacity());
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+
+ sb.minimizeCapacity();
+ assertEquals(0, sb.capacity());
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+
+ sb.ensureCapacity(32);
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+
+ sb.append("foo");
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(3, sb.length());
+ assertEquals(3, sb.size());
+ assertFalse(sb.isEmpty());
+
+ sb.clear();
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+
+ sb.append("123456789012345678901234567890123");
+ assertTrue(sb.capacity() > 32);
+ assertEquals(33, sb.length());
+ assertEquals(33, sb.size());
+ assertFalse(sb.isEmpty());
+
+ sb.ensureCapacity(16);
+ assertTrue(sb.capacity() > 16);
+ assertEquals(33, sb.length());
+ assertEquals(33, sb.size());
+ assertFalse(sb.isEmpty());
+
+ sb.minimizeCapacity();
+ assertEquals(33, sb.capacity());
+ assertEquals(33, sb.length());
+ assertEquals(33, sb.size());
+ assertFalse(sb.isEmpty());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.setLength(-1),
+ "setLength(-1) expected StringIndexOutOfBoundsException");
+
+ sb.setLength(33);
+ assertEquals(33, sb.capacity());
+ assertEquals(33, sb.length());
+ assertEquals(33, sb.size());
+ assertFalse(sb.isEmpty());
+
+ sb.setLength(16);
+ assertTrue(sb.capacity() >= 16);
+ assertEquals(16, sb.length());
+ assertEquals(16, sb.size());
+ assertEquals("1234567890123456", sb.toString());
+ assertFalse(sb.isEmpty());
+
+ sb.setLength(32);
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(32, sb.length());
+ assertEquals(32, sb.size());
+ assertEquals("1234567890123456\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", sb.toString());
+ assertFalse(sb.isEmpty());
+
+ sb.setLength(0);
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+ }
+
+ @Test
+ public void testLength() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(0, sb.length());
+
+ sb.append("Hello");
+ assertEquals(5, sb.length());
+ }
+
+ @Test
+ public void testSetLength() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append("Hello");
+ sb.setLength(2); // shorten
+ assertEquals("He", sb.toString());
+ sb.setLength(2); // no change
+ assertEquals("He", sb.toString());
+ sb.setLength(3); // lengthen
+ assertEquals("He\0", sb.toString());
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.setLength(-1),
+ "setLength(-1) expected StringIndexOutOfBoundsException");
+ }
+
+ @Test
+ public void testCapacity() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(sb.buffer.length, sb.capacity());
+
+ sb.append("HelloWorldHelloWorldHelloWorldHelloWorld");
+ assertEquals(sb.buffer.length, sb.capacity());
+ }
+
+ @Test
+ public void testEnsureCapacity() {
+ final StrBuilder sb = new StrBuilder();
+ sb.ensureCapacity(2);
+ assertTrue(sb.capacity() >= 2);
+
+ sb.ensureCapacity(-1);
+ assertTrue(sb.capacity() >= 0);
+
+ sb.append("HelloWorld");
+ sb.ensureCapacity(40);
+ assertTrue(sb.capacity() >= 40);
+ }
+
+ @Test
+ public void testMinimizeCapacity() {
+ final StrBuilder sb = new StrBuilder();
+ sb.minimizeCapacity();
+ assertEquals(0, sb.capacity());
+
+ sb.append("HelloWorld");
+ sb.minimizeCapacity();
+ assertEquals(10, sb.capacity());
+ }
+
+ @Test
+ public void testSize() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(0, sb.size());
+
+ sb.append("Hello");
+ assertEquals(5, sb.size());
+ }
+
+ @Test
+ public void testIsEmpty() {
+ final StrBuilder sb = new StrBuilder();
+ assertTrue(sb.isEmpty());
+
+ sb.append("Hello");
+ assertFalse(sb.isEmpty());
+
+ sb.clear();
+ assertTrue(sb.isEmpty());
+ }
+
+ @Test
+ public void testClear() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append("Hello");
+ sb.clear();
+ assertEquals(0, sb.length());
+ assertTrue(sb.buffer.length >= 5);
+ }
+
+ @Test
+ public void testCharAt() {
+ final StrBuilder sb = new StrBuilder();
+ assertThrows(
+ IndexOutOfBoundsException.class, () -> sb.charAt(0), "charAt(0) expected IndexOutOfBoundsException");
+ assertThrows(
+ IndexOutOfBoundsException.class, () -> sb.charAt(-1), "charAt(-1) expected IndexOutOfBoundsException");
+ sb.append("foo");
+ assertEquals('f', sb.charAt(0));
+ assertEquals('o', sb.charAt(1));
+ assertEquals('o', sb.charAt(2));
+ assertThrows(
+ IndexOutOfBoundsException.class, () -> sb.charAt(-1), "charAt(-1) expected IndexOutOfBoundsException");
+ assertThrows(
+ IndexOutOfBoundsException.class, () -> sb.charAt(3), "charAt(3) expected IndexOutOfBoundsException");
+ }
+
+ @Test
+ public void testSetCharAt() {
+ final StrBuilder sb = new StrBuilder();
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.setCharAt(0, 'f'),
+ "setCharAt(0, ) expected IndexOutOfBoundsException");
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.setCharAt(-1, 'f'),
+ "setCharAt(-1, ) expected IndexOutOfBoundsException");
+ sb.append("foo");
+ sb.setCharAt(0, 'b');
+ sb.setCharAt(1, 'a');
+ sb.setCharAt(2, 'r');
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb.setCharAt(3, '!'),
+ "setCharAt(3, ) expected IndexOutOfBoundsException");
+ assertEquals("bar", sb.toString());
+ }
+
+ @Test
+ public void testDeleteCharAt() {
+ final StrBuilder sb = new StrBuilder("abc");
+ sb.deleteCharAt(0);
+ assertEquals("bc", sb.toString());
+
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.deleteCharAt(1000));
+ }
+
+ @Test
+ public void testToCharArray() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, sb.toCharArray());
+
+ char[] a = sb.toCharArray();
+ assertNotNull(a, "toCharArray() result is null");
+ assertEquals(0, a.length, "toCharArray() result is too large");
+
+ sb.append("junit");
+ a = sb.toCharArray();
+ assertEquals(5, a.length, "toCharArray() result incorrect length");
+ assertArrayEquals("junit".toCharArray(), a, "toCharArray() result does not match");
+ }
+
+ @Test
+ public void testToCharArrayIntInt() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, sb.toCharArray(0, 0));
+
+ sb.append("junit");
+ char[] a = sb.toCharArray(0, 20); // too large test
+ assertEquals(5, a.length, "toCharArray(int, int) result incorrect length");
+ assertArrayEquals("junit".toCharArray(), a, "toCharArray(int, int) result does not match");
+
+ a = sb.toCharArray(0, 4);
+ assertEquals(4, a.length, "toCharArray(int, int) result incorrect length");
+ assertArrayEquals("juni".toCharArray(), a, "toCharArray(int, int) result does not match");
+
+ a = sb.toCharArray(0, 4);
+ assertEquals(4, a.length, "toCharArray(int, int) result incorrect length");
+ assertArrayEquals("juni".toCharArray(), a, "toCharArray(int, int) result does not match");
+
+ a = sb.toCharArray(0, 1);
+ assertNotNull(a, "toCharArray(int, int) result is null");
+
+ assertThrows(
+ IndexOutOfBoundsException.class, () -> sb.toCharArray(-1, 5), "no string index out of bound on -1");
+
+ assertThrows(
+ IndexOutOfBoundsException.class, () -> sb.toCharArray(6, 5), "no string index out of bound on -1");
+ }
+
+ @Test
+ public void testGetChars ( ) {
+ final StrBuilder sb = new StrBuilder();
+
+ char[] input = new char[10];
+ char[] a = sb.getChars(input);
+ assertSame (input, a);
+ assertArrayEquals(new char[10], a);
+
+ sb.append("junit");
+ a = sb.getChars(input);
+ assertSame(input, a);
+ assertArrayEquals(new char[]{'j', 'u', 'n', 'i', 't', 0, 0, 0, 0, 0}, a);
+
+ a = sb.getChars(null);
+ assertNotSame(input, a);
+ assertEquals(5, a.length);
+ assertArrayEquals("junit".toCharArray(), a);
+
+ input = new char[5];
+ a = sb.getChars(input);
+ assertSame(input, a);
+
+ input = new char[4];
+ a = sb.getChars(input);
+ assertNotSame(input, a);
+ }
+
+ @Test
+ public void testGetCharsIntIntCharArrayInt( ) {
+ final StrBuilder sb = new StrBuilder();
+
+ sb.append("junit");
+ final char[] a = new char[5];
+ sb.getChars(0, 5, a, 0);
+ assertArrayEquals(new char[]{'j', 'u', 'n', 'i', 't'}, a);
+
+ final char[] b = new char[5];
+ sb.getChars(0, 2, b, 3);
+ assertArrayEquals(new char[]{0, 0, 0, 'j', 'u'}, b);
+
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.getChars(-1, 0, b, 0));
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.getChars(0, -1, b, 0));
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.getChars(0, 20, b, 0));
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.getChars(4, 2, b, 0));
+ }
+
+ @Test
+ public void testDeleteIntInt() {
+ final StrBuilder sb = new StrBuilder("abc");
+ sb.delete(0, 1);
+ assertEquals("bc", sb.toString());
+ sb.delete(1, 2);
+ assertEquals("b", sb.toString());
+ sb.delete(0, 1);
+ assertEquals("", sb.toString());
+ sb.delete(0, 1000);
+ assertEquals("", sb.toString());
+
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.delete(1, 2));
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.delete(-1, 1));
+ assertThrows(IndexOutOfBoundsException.class, () -> new StrBuilder("anything").delete(2, 1));
+ }
+
+ @Test
+ public void testDeleteAll_char() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.deleteAll('X');
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteAll('a');
+ assertEquals("bcbccb", sb.toString());
+ sb.deleteAll('c');
+ assertEquals("bbb", sb.toString());
+ sb.deleteAll('b');
+ assertEquals("", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteAll('b');
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testDeleteFirst_char() {
+ StrBuilder sb = new StrBuilder("abcba");
+ sb.deleteFirst('X');
+ assertEquals("abcba", sb.toString());
+ sb.deleteFirst('a');
+ assertEquals("bcba", sb.toString());
+ sb.deleteFirst('c');
+ assertEquals("bba", sb.toString());
+ sb.deleteFirst('b');
+ assertEquals("ba", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteFirst('b');
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testDeleteAll_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.deleteAll((String) null);
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteAll("");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.deleteAll("X");
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteAll("a");
+ assertEquals("bcbccb", sb.toString());
+ sb.deleteAll("c");
+ assertEquals("bbb", sb.toString());
+ sb.deleteAll("b");
+ assertEquals("", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.deleteAll("bc");
+ assertEquals("acba", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteAll("bc");
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testDeleteFirst_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.deleteFirst((String) null);
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteFirst("");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.deleteFirst("X");
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteFirst("a");
+ assertEquals("bcbccba", sb.toString());
+ sb.deleteFirst("c");
+ assertEquals("bbccba", sb.toString());
+ sb.deleteFirst("b");
+ assertEquals("bccba", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.deleteFirst("bc");
+ assertEquals("abccba", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteFirst("bc");
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testDeleteAll_StrMatcher() {
+ StrBuilder sb = new StrBuilder("A0xA1A2yA3");
+ sb.deleteAll((StrMatcher) null);
+ assertEquals("A0xA1A2yA3", sb.toString());
+ sb.deleteAll(A_NUMBER_MATCHER);
+ assertEquals("xy", sb.toString());
+
+ sb = new StrBuilder("Ax1");
+ sb.deleteAll(A_NUMBER_MATCHER);
+ assertEquals("Ax1", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteAll(A_NUMBER_MATCHER);
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testDeleteFirst_StrMatcher() {
+ StrBuilder sb = new StrBuilder("A0xA1A2yA3");
+ sb.deleteFirst((StrMatcher) null);
+ assertEquals("A0xA1A2yA3", sb.toString());
+ sb.deleteFirst(A_NUMBER_MATCHER);
+ assertEquals("xA1A2yA3", sb.toString());
+
+ sb = new StrBuilder("Ax1");
+ sb.deleteFirst(A_NUMBER_MATCHER);
+ assertEquals("Ax1", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteFirst(A_NUMBER_MATCHER);
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testReplace_int_int_String() {
+ final StrBuilder sb = new StrBuilder("abc");
+ sb.replace(0, 1, "d");
+ assertEquals("dbc", sb.toString());
+ sb.replace(0, 1, "aaa");
+ assertEquals("aaabc", sb.toString());
+ sb.replace(0, 3, "");
+ assertEquals("bc", sb.toString());
+ sb.replace(1, 2, null);
+ assertEquals("b", sb.toString());
+ sb.replace(1, 1000, "text");
+ assertEquals("btext", sb.toString());
+ sb.replace(0, 1000, "text");
+ assertEquals("text", sb.toString());
+
+ final StrBuilder sb1 = new StrBuilder("atext");
+ sb1.replace(1, 1, "ny");
+ assertEquals("anytext", sb1.toString());
+ assertThrows(IndexOutOfBoundsException.class, () -> sb1.replace(2, 1, "anything"));
+
+ final StrBuilder sb2 = new StrBuilder();
+ assertThrows(IndexOutOfBoundsException.class, () -> sb2.replace(1, 2, "anything"));
+ assertThrows(IndexOutOfBoundsException.class, () -> sb2.replace(-1, 1, "anything"));
+ }
+
+ @Test
+ public void testReplaceAll_char_char() {
+ final StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceAll('x', 'y');
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll('a', 'd');
+ assertEquals("dbcbccbd", sb.toString());
+ sb.replaceAll('b', 'e');
+ assertEquals("dececced", sb.toString());
+ sb.replaceAll('c', 'f');
+ assertEquals("defeffed", sb.toString());
+ sb.replaceAll('d', 'd');
+ assertEquals("defeffed", sb.toString());
+ }
+
+ @Test
+ public void testReplaceFirst_char_char() {
+ final StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceFirst('x', 'y');
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst('a', 'd');
+ assertEquals("dbcbccba", sb.toString());
+ sb.replaceFirst('b', 'e');
+ assertEquals("decbccba", sb.toString());
+ sb.replaceFirst('c', 'f');
+ assertEquals("defbccba", sb.toString());
+ sb.replaceFirst('d', 'd');
+ assertEquals("defbccba", sb.toString());
+ }
+
+ @Test
+ public void testReplaceAll_String_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceAll((String) null, null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll((String) null, "anything");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll("", null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll("", "anything");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replaceAll("x", "y");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll("a", "d");
+ assertEquals("dbcbccbd", sb.toString());
+ sb.replaceAll("d", null);
+ assertEquals("bcbccb", sb.toString());
+ sb.replaceAll("cb", "-");
+ assertEquals("b-c-", sb.toString());
+
+ sb = new StrBuilder("abcba");
+ sb.replaceAll("b", "xbx");
+ assertEquals("axbxcxbxa", sb.toString());
+
+ sb = new StrBuilder("bb");
+ sb.replaceAll("b", "xbx");
+ assertEquals("xbxxbx", sb.toString());
+ }
+
+ @Test
+ public void testReplaceFirst_String_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceFirst((String) null, null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst((String) null, "anything");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst("", null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst("", "anything");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replaceFirst("x", "y");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst("a", "d");
+ assertEquals("dbcbccba", sb.toString());
+ sb.replaceFirst("d", null);
+ assertEquals("bcbccba", sb.toString());
+ sb.replaceFirst("cb", "-");
+ assertEquals("b-ccba", sb.toString());
+
+ sb = new StrBuilder("abcba");
+ sb.replaceFirst("b", "xbx");
+ assertEquals("axbxcba", sb.toString());
+
+ sb = new StrBuilder("bb");
+ sb.replaceFirst("b", "xbx");
+ assertEquals("xbxb", sb.toString());
+ }
+
+ @Test
+ public void testReplaceAll_StrMatcher_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceAll((StrMatcher) null, null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll((StrMatcher) null, "anything");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll(StrMatcher.noneMatcher(), null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll(StrMatcher.noneMatcher(), "anything");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replaceAll(StrMatcher.charMatcher('x'), "y");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll(StrMatcher.charMatcher('a'), "d");
+ assertEquals("dbcbccbd", sb.toString());
+ sb.replaceAll(StrMatcher.charMatcher('d'), null);
+ assertEquals("bcbccb", sb.toString());
+ sb.replaceAll(StrMatcher.stringMatcher("cb"), "-");
+ assertEquals("b-c-", sb.toString());
+
+ sb = new StrBuilder("abcba");
+ sb.replaceAll(StrMatcher.charMatcher('b'), "xbx");
+ assertEquals("axbxcxbxa", sb.toString());
+
+ sb = new StrBuilder("bb");
+ sb.replaceAll(StrMatcher.charMatcher('b'), "xbx");
+ assertEquals("xbxxbx", sb.toString());
+
+ sb = new StrBuilder("A1-A2A3-A4");
+ sb.replaceAll(A_NUMBER_MATCHER, "***");
+ assertEquals("***-******-***", sb.toString());
+
+ sb = new StrBuilder("Dear X, hello X.");
+ sb.replaceAll(StrMatcher.stringMatcher("X"), "012345678901234567");
+ assertEquals("Dear 012345678901234567, hello 012345678901234567.", sb.toString());
+ }
+
+ @Test
+ public void testReplaceFirst_StrMatcher_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceFirst((StrMatcher) null, null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst((StrMatcher) null, "anything");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.noneMatcher(), null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.noneMatcher(), "anything");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replaceFirst(StrMatcher.charMatcher('x'), "y");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.charMatcher('a'), "d");
+ assertEquals("dbcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.charMatcher('d'), null);
+ assertEquals("bcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.stringMatcher("cb"), "-");
+ assertEquals("b-ccba", sb.toString());
+
+ sb = new StrBuilder("abcba");
+ sb.replaceFirst(StrMatcher.charMatcher('b'), "xbx");
+ assertEquals("axbxcba", sb.toString());
+
+ sb = new StrBuilder("bb");
+ sb.replaceFirst(StrMatcher.charMatcher('b'), "xbx");
+ assertEquals("xbxb", sb.toString());
+
+ sb = new StrBuilder("A1-A2A3-A4");
+ sb.replaceFirst(A_NUMBER_MATCHER, "***");
+ assertEquals("***-A2A3-A4", sb.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryMatcher() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replace(null, "x", 0, sb.length(), -1);
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replace(StrMatcher.charMatcher('a'), "x", 0, sb.length(), -1);
+ assertEquals("xbcbccbx", sb.toString());
+
+ sb.replace(StrMatcher.stringMatcher("cb"), "x", 0, sb.length(), -1);
+ assertEquals("xbxcxx", sb.toString());
+
+ sb = new StrBuilder("A1-A2A3-A4");
+ sb.replace(A_NUMBER_MATCHER, "***", 0, sb.length(), -1);
+ assertEquals("***-******-***", sb.toString());
+
+ sb = new StrBuilder();
+ sb.replace(A_NUMBER_MATCHER, "***", 0, sb.length(), -1);
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryReplace() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), "cb", 0, sb.length(), -1);
+ assertEquals("abcbccba", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), "-", 0, sb.length(), -1);
+ assertEquals("ab-c-a", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), "+++", 0, sb.length(), -1);
+ assertEquals("ab+++c+++a", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), "", 0, sb.length(), -1);
+ assertEquals("abca", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), null, 0, sb.length(), -1);
+ assertEquals("abca", sb.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryStartIndex() {
+ StrBuilder sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, sb.length(), -1);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 1, sb.length(), -1);
+ assertEquals("aax--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 2, sb.length(), -1);
+ assertEquals("aax--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 3, sb.length(), -1);
+ assertEquals("aax--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 4, sb.length(), -1);
+ assertEquals("aaxa-ay-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 5, sb.length(), -1);
+ assertEquals("aaxaa-y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 6, sb.length(), -1);
+ assertEquals("aaxaaaay-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 7, sb.length(), -1);
+ assertEquals("aaxaaaay-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 8, sb.length(), -1);
+ assertEquals("aaxaaaay-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 9, sb.length(), -1);
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 10, sb.length(), -1);
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ final StrBuilder sb1 = new StrBuilder("aaxaaaayaa");
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.replace(StrMatcher.stringMatcher("aa"), "-", 11, sb1.length(), -1));
+ assertEquals("aaxaaaayaa", sb1.toString());
+
+ final StrBuilder sb2 = new StrBuilder("aaxaaaayaa");
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb2.replace(StrMatcher.stringMatcher("aa"), "-", -1, sb2.length(), -1));
+ assertEquals("aaxaaaayaa", sb2.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryEndIndex() {
+ StrBuilder sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 0, -1);
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 2, -1);
+ assertEquals("-xaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 3, -1);
+ assertEquals("-xaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 4, -1);
+ assertEquals("-xaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 5, -1);
+ assertEquals("-x-aayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 6, -1);
+ assertEquals("-x-aayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 7, -1);
+ assertEquals("-x--yaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 8, -1);
+ assertEquals("-x--yaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 9, -1);
+ assertEquals("-x--yaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, -1);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 1000, -1);
+ assertEquals("-x--y-", sb.toString());
+
+ final StrBuilder sb1 = new StrBuilder("aaxaaaayaa");
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> sb1.replace(StrMatcher.stringMatcher("aa"), "-", 2, 1, -1));
+ assertEquals("aaxaaaayaa", sb1.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryCount() {
+ StrBuilder sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, -1);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 0);
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 1);
+ assertEquals("-xaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 2);
+ assertEquals("-x-aayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 3);
+ assertEquals("-x--yaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 4);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 5);
+ assertEquals("-x--y-", sb.toString());
+ }
+
+ @Test
+ public void testReverse() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals("", sb.reverse().toString());
+
+ sb.clear().append(true);
+ assertEquals("eurt", sb.reverse().toString());
+ assertEquals("true", sb.reverse().toString());
+ }
+
+ @Test
+ public void testTrim() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals("", sb.reverse().toString());
+
+ sb.clear().append(" \u0000 ");
+ assertEquals("", sb.trim().toString());
+
+ sb.clear().append(" \u0000 a b c");
+ assertEquals("a b c", sb.trim().toString());
+
+ sb.clear().append("a b c \u0000 ");
+ assertEquals("a b c", sb.trim().toString());
+
+ sb.clear().append(" \u0000 a b c \u0000 ");
+ assertEquals("a b c", sb.trim().toString());
+
+ sb.clear().append("a b c");
+ assertEquals("a b c", sb.trim().toString());
+ }
+
+ @Test
+ public void testStartsWith() {
+ final StrBuilder sb = new StrBuilder();
+ assertFalse(sb.startsWith("a"));
+ assertFalse(sb.startsWith(null));
+ assertTrue(sb.startsWith(""));
+ sb.append("abc");
+ assertTrue(sb.startsWith("a"));
+ assertTrue(sb.startsWith("ab"));
+ assertTrue(sb.startsWith("abc"));
+ assertFalse(sb.startsWith("cba"));
+ }
+
+ @Test
+ public void testEndsWith() {
+ final StrBuilder sb = new StrBuilder();
+ assertFalse(sb.endsWith("a"));
+ assertFalse(sb.endsWith("c"));
+ assertTrue(sb.endsWith(""));
+ assertFalse(sb.endsWith(null));
+ sb.append("abc");
+ assertTrue(sb.endsWith("c"));
+ assertTrue(sb.endsWith("bc"));
+ assertTrue(sb.endsWith("abc"));
+ assertFalse(sb.endsWith("cba"));
+ assertFalse(sb.endsWith("abcd"));
+ assertFalse(sb.endsWith(" abc"));
+ assertFalse(sb.endsWith("abc "));
+ }
+
+ @Test
+ public void testSubSequenceIntInt() {
+ final StrBuilder sb = new StrBuilder ("hello goodbye");
+ // Start index is negative
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.subSequence(-1, 5));
+
+ // End index is negative
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.subSequence(2, -1));
+
+ // End index greater than length()
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.subSequence(2, sb.length() + 1));
+
+ // Start index greater then end index
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.subSequence(3, 2));
+
+ // Normal cases
+ assertEquals ("hello", sb.subSequence(0, 5));
+ assertEquals ("hello goodbye".subSequence(0, 6), sb.subSequence(0, 6));
+ assertEquals ("goodbye", sb.subSequence(6, 13));
+ assertEquals ("hello goodbye".subSequence(6, 13), sb.subSequence(6, 13));
+ }
+
+ @Test
+ public void testSubstringInt() {
+ final StrBuilder sb = new StrBuilder ("hello goodbye");
+ assertEquals ("goodbye", sb.substring(6));
+ assertEquals ("hello goodbye".substring(6), sb.substring(6));
+ assertEquals ("hello goodbye", sb.substring(0));
+ assertEquals ("hello goodbye".substring(0), sb.substring(0));
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.substring(-1));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.substring(15));
+ }
+
+ @Test
+ public void testSubstringIntInt() {
+ final StrBuilder sb = new StrBuilder ("hello goodbye");
+ assertEquals ("hello", sb.substring(0, 5));
+ assertEquals ("hello goodbye".substring(0, 6), sb.substring(0, 6));
+
+ assertEquals ("goodbye", sb.substring(6, 13));
+ assertEquals ("hello goodbye".substring(6, 13), sb.substring(6, 13));
+
+ assertEquals ("goodbye", sb.substring(6, 20));
+
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.substring(-1, 5));
+ assertThrows(IndexOutOfBoundsException.class, () -> sb.substring(15, 20));
+ }
+
+ @Test
+ public void testMidString() {
+ final StrBuilder sb = new StrBuilder("hello goodbye hello");
+ assertEquals("goodbye", sb.midString(6, 7));
+ assertEquals("hello", sb.midString(0, 5));
+ assertEquals("hello", sb.midString(-5, 5));
+ assertEquals("", sb.midString(0, -1));
+ assertEquals("", sb.midString(20, 2));
+ assertEquals("hello", sb.midString(14, 22));
+ }
+
+ @Test
+ public void testRightString() {
+ final StrBuilder sb = new StrBuilder("left right");
+ assertEquals("right", sb.rightString(5));
+ assertEquals("", sb.rightString(0));
+ assertEquals("", sb.rightString(-5));
+ assertEquals("left right", sb.rightString(15));
+ }
+
+ @Test
+ public void testLeftString() {
+ final StrBuilder sb = new StrBuilder("left right");
+ assertEquals("left", sb.leftString(4));
+ assertEquals("", sb.leftString(0));
+ assertEquals("", sb.leftString(-5));
+ assertEquals("left right", sb.leftString(15));
+ }
+
+ @Test
+ public void testContains_char() {
+ final StrBuilder sb = new StrBuilder("abcdefghijklmnopqrstuvwxyz");
+ assertTrue(sb.contains('a'));
+ assertTrue(sb.contains('o'));
+ assertTrue(sb.contains('z'));
+ assertFalse(sb.contains('1'));
+ }
+
+ @Test
+ public void testContains_String() {
+ final StrBuilder sb = new StrBuilder("abcdefghijklmnopqrstuvwxyz");
+ assertTrue(sb.contains("a"));
+ assertTrue(sb.contains("pq"));
+ assertTrue(sb.contains("z"));
+ assertFalse(sb.contains("zyx"));
+ assertFalse(sb.contains((String) null));
+ }
+
+ @Test
+ public void testContains_StrMatcher() {
+ StrBuilder sb = new StrBuilder("abcdefghijklmnopqrstuvwxyz");
+ assertTrue(sb.contains(StrMatcher.charMatcher('a')));
+ assertTrue(sb.contains(StrMatcher.stringMatcher("pq")));
+ assertTrue(sb.contains(StrMatcher.charMatcher('z')));
+ assertFalse(sb.contains(StrMatcher.stringMatcher("zy")));
+ assertFalse(sb.contains((StrMatcher) null));
+
+ sb = new StrBuilder();
+ assertFalse(sb.contains(A_NUMBER_MATCHER));
+ sb.append("B A1 C");
+ assertTrue(sb.contains(A_NUMBER_MATCHER));
+ }
+
+ @Test
+ public void testIndexOf_char() {
+ final StrBuilder sb = new StrBuilder("abab");
+ assertEquals(0, sb.indexOf('a'));
+
+ // should work like String#indexOf
+ assertEquals("abab".indexOf('a'), sb.indexOf('a'));
+
+ assertEquals(1, sb.indexOf('b'));
+ assertEquals("abab".indexOf('b'), sb.indexOf('b'));
+
+ assertEquals(-1, sb.indexOf('z'));
+ }
+
+ @Test
+ public void testIndexOf_char_int() {
+ StrBuilder sb = new StrBuilder("abab");
+ assertEquals(0, sb.indexOf('a', -1));
+ assertEquals(0, sb.indexOf('a', 0));
+ assertEquals(2, sb.indexOf('a', 1));
+ assertEquals(-1, sb.indexOf('a', 4));
+ assertEquals(-1, sb.indexOf('a', 5));
+
+ // should work like String#indexOf
+ assertEquals("abab".indexOf('a', 1), sb.indexOf('a', 1));
+
+ assertEquals(3, sb.indexOf('b', 2));
+ assertEquals("abab".indexOf('b', 2), sb.indexOf('b', 2));
+
+ assertEquals(-1, sb.indexOf('z', 2));
+
+ sb = new StrBuilder("xyzabc");
+ assertEquals(2, sb.indexOf('z', 0));
+ assertEquals(-1, sb.indexOf('z', 3));
+ }
+
+ @Test
+ public void testLastIndexOf_char() {
+ final StrBuilder sb = new StrBuilder("abab");
+
+ assertEquals (2, sb.lastIndexOf('a'));
+ //should work like String#lastIndexOf
+ assertEquals ("abab".lastIndexOf('a'), sb.lastIndexOf('a'));
+
+ assertEquals(3, sb.lastIndexOf('b'));
+ assertEquals ("abab".lastIndexOf('b'), sb.lastIndexOf('b'));
+
+ assertEquals (-1, sb.lastIndexOf('z'));
+ }
+
+ @Test
+ public void testLastIndexOf_char_int() {
+ StrBuilder sb = new StrBuilder("abab");
+ assertEquals(-1, sb.lastIndexOf('a', -1));
+ assertEquals(0, sb.lastIndexOf('a', 0));
+ assertEquals(0, sb.lastIndexOf('a', 1));
+
+ // should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf('a', 1), sb.lastIndexOf('a', 1));
+
+ assertEquals(1, sb.lastIndexOf('b', 2));
+ assertEquals("abab".lastIndexOf('b', 2), sb.lastIndexOf('b', 2));
+
+ assertEquals(-1, sb.lastIndexOf('z', 2));
+
+ sb = new StrBuilder("xyzabc");
+ assertEquals(2, sb.lastIndexOf('z', sb.length()));
+ assertEquals(-1, sb.lastIndexOf('z', 1));
+ }
+
+ @Test
+ public void testIndexOf_String() {
+ final StrBuilder sb = new StrBuilder("abab");
+
+ assertEquals(0, sb.indexOf("a"));
+ //should work like String#indexOf
+ assertEquals("abab".indexOf("a"), sb.indexOf("a"));
+
+ assertEquals(0, sb.indexOf("ab"));
+ //should work like String#indexOf
+ assertEquals("abab".indexOf("ab"), sb.indexOf("ab"));
+
+ assertEquals(1, sb.indexOf("b"));
+ assertEquals("abab".indexOf("b"), sb.indexOf("b"));
+
+ assertEquals(1, sb.indexOf("ba"));
+ assertEquals("abab".indexOf("ba"), sb.indexOf("ba"));
+
+ assertEquals(-1, sb.indexOf("z"));
+
+ assertEquals(-1, sb.indexOf((String) null));
+ }
+
+ @Test
+ public void testIndexOf_String_int() {
+ StrBuilder sb = new StrBuilder("abab");
+ assertEquals(0, sb.indexOf("a", -1));
+ assertEquals(0, sb.indexOf("a", 0));
+ assertEquals(2, sb.indexOf("a", 1));
+ assertEquals(2, sb.indexOf("a", 2));
+ assertEquals(-1, sb.indexOf("a", 3));
+ assertEquals(-1, sb.indexOf("a", 4));
+ assertEquals(-1, sb.indexOf("a", 5));
+
+ assertEquals(-1, sb.indexOf("abcdef", 0));
+ assertEquals(0, sb.indexOf("", 0));
+ assertEquals(1, sb.indexOf("", 1));
+
+ //should work like String#indexOf
+ assertEquals ("abab".indexOf("a", 1), sb.indexOf("a", 1));
+
+ assertEquals(2, sb.indexOf("ab", 1));
+ //should work like String#indexOf
+ assertEquals("abab".indexOf("ab", 1), sb.indexOf("ab", 1));
+
+ assertEquals(3, sb.indexOf("b", 2));
+ assertEquals("abab".indexOf("b", 2), sb.indexOf("b", 2));
+
+ assertEquals(1, sb.indexOf("ba", 1));
+ assertEquals("abab".indexOf("ba", 2), sb.indexOf("ba", 2));
+
+ assertEquals(-1, sb.indexOf("z", 2));
+
+ sb = new StrBuilder("xyzabc");
+ assertEquals(2, sb.indexOf("za", 0));
+ assertEquals(-1, sb.indexOf("za", 3));
+
+ assertEquals(-1, sb.indexOf((String) null, 2));
+ }
+
+ @Test
+ public void testLastIndexOf_String() {
+ final StrBuilder sb = new StrBuilder("abab");
+
+ assertEquals(2, sb.lastIndexOf("a"));
+ //should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf("a"), sb.lastIndexOf("a"));
+
+ assertEquals(2, sb.lastIndexOf("ab"));
+ //should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf("ab"), sb.lastIndexOf("ab"));
+
+ assertEquals(3, sb.lastIndexOf("b"));
+ assertEquals("abab".lastIndexOf("b"), sb.lastIndexOf("b"));
+
+ assertEquals(1, sb.lastIndexOf("ba"));
+ assertEquals("abab".lastIndexOf("ba"), sb.lastIndexOf("ba"));
+
+ assertEquals(-1, sb.lastIndexOf("z"));
+
+ assertEquals(-1, sb.lastIndexOf((String) null));
+ }
+
+ @Test
+ public void testLastIndexOf_String_int() {
+ StrBuilder sb = new StrBuilder("abab");
+ assertEquals(-1, sb.lastIndexOf("a", -1));
+ assertEquals(0, sb.lastIndexOf("a", 0));
+ assertEquals(0, sb.lastIndexOf("a", 1));
+ assertEquals(2, sb.lastIndexOf("a", 2));
+ assertEquals(2, sb.lastIndexOf("a", 3));
+ assertEquals(2, sb.lastIndexOf("a", 4));
+ assertEquals(2, sb.lastIndexOf("a", 5));
+
+ assertEquals(-1, sb.lastIndexOf("abcdef", 3));
+ assertEquals("abab".lastIndexOf("", 3), sb.lastIndexOf("", 3));
+ assertEquals("abab".lastIndexOf("", 1), sb.lastIndexOf("", 1));
+
+ //should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf("a", 1), sb.lastIndexOf("a", 1));
+
+ assertEquals(0, sb.lastIndexOf("ab", 1));
+ //should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf("ab", 1), sb.lastIndexOf("ab", 1));
+
+ assertEquals(1, sb.lastIndexOf("b", 2));
+ assertEquals("abab".lastIndexOf("b", 2), sb.lastIndexOf("b", 2));
+
+ assertEquals(1, sb.lastIndexOf("ba", 2));
+ assertEquals("abab".lastIndexOf("ba", 2), sb.lastIndexOf("ba", 2));
+
+ assertEquals(-1, sb.lastIndexOf("z", 2));
+
+ sb = new StrBuilder("xyzabc");
+ assertEquals(2, sb.lastIndexOf("za", sb.length()));
+ assertEquals(-1, sb.lastIndexOf("za", 1));
+
+ assertEquals(-1, sb.lastIndexOf((String) null, 2));
+ }
+
+ @Test
+ public void testIndexOf_StrMatcher() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(-1, sb.indexOf((StrMatcher) null));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a')));
+
+ sb.append("ab bd");
+ assertEquals(0, sb.indexOf(StrMatcher.charMatcher('a')));
+ assertEquals(1, sb.indexOf(StrMatcher.charMatcher('b')));
+ assertEquals(2, sb.indexOf(StrMatcher.spaceMatcher()));
+ assertEquals(4, sb.indexOf(StrMatcher.charMatcher('d')));
+ assertEquals(-1, sb.indexOf(StrMatcher.noneMatcher()));
+ assertEquals(-1, sb.indexOf((StrMatcher) null));
+
+ sb.append(" A1 junction");
+ assertEquals(6, sb.indexOf(A_NUMBER_MATCHER));
+ }
+
+ @Test
+ public void testIndexOf_StrMatcher_int() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(-1, sb.indexOf((StrMatcher) null, 2));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a'), 2));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a'), 0));
+
+ sb.append("ab bd");
+ assertEquals(0, sb.indexOf(StrMatcher.charMatcher('a'), -2));
+ assertEquals(0, sb.indexOf(StrMatcher.charMatcher('a'), 0));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a'), 2));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a'), 20));
+
+ assertEquals(1, sb.indexOf(StrMatcher.charMatcher('b'), -1));
+ assertEquals(1, sb.indexOf(StrMatcher.charMatcher('b'), 0));
+ assertEquals(1, sb.indexOf(StrMatcher.charMatcher('b'), 1));
+ assertEquals(3, sb.indexOf(StrMatcher.charMatcher('b'), 2));
+ assertEquals(3, sb.indexOf(StrMatcher.charMatcher('b'), 3));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('b'), 4));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('b'), 5));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('b'), 6));
+
+ assertEquals(2, sb.indexOf(StrMatcher.spaceMatcher(), -2));
+ assertEquals(2, sb.indexOf(StrMatcher.spaceMatcher(), 0));
+ assertEquals(2, sb.indexOf(StrMatcher.spaceMatcher(), 2));
+ assertEquals(-1, sb.indexOf(StrMatcher.spaceMatcher(), 4));
+ assertEquals(-1, sb.indexOf(StrMatcher.spaceMatcher(), 20));
+
+ assertEquals(-1, sb.indexOf(StrMatcher.noneMatcher(), 0));
+ assertEquals(-1, sb.indexOf((StrMatcher) null, 0));
+
+ sb.append(" A1 junction with A2");
+ assertEquals(6, sb.indexOf(A_NUMBER_MATCHER, 5));
+ assertEquals(6, sb.indexOf(A_NUMBER_MATCHER, 6));
+ assertEquals(23, sb.indexOf(A_NUMBER_MATCHER, 7));
+ assertEquals(23, sb.indexOf(A_NUMBER_MATCHER, 22));
+ assertEquals(23, sb.indexOf(A_NUMBER_MATCHER, 23));
+ assertEquals(-1, sb.indexOf(A_NUMBER_MATCHER, 24));
+ }
+
+ @Test
+ public void testLastIndexOf_StrMatcher() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(-1, sb.lastIndexOf((StrMatcher) null));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a')));
+
+ sb.append("ab bd");
+ assertEquals(0, sb.lastIndexOf(StrMatcher.charMatcher('a')));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b')));
+ assertEquals(2, sb.lastIndexOf(StrMatcher.spaceMatcher()));
+ assertEquals(4, sb.lastIndexOf(StrMatcher.charMatcher('d')));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.noneMatcher()));
+ assertEquals(-1, sb.lastIndexOf((StrMatcher) null));
+
+ sb.append(" A1 junction");
+ assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER));
+ }
+
+ @Test
+ public void testLastIndexOf_StrMatcher_int() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(-1, sb.lastIndexOf((StrMatcher) null, 2));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a'), 2));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a'), 0));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a'), -1));
+
+ sb.append("ab bd");
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a'), -2));
+ assertEquals(0, sb.lastIndexOf(StrMatcher.charMatcher('a'), 0));
+ assertEquals(0, sb.lastIndexOf(StrMatcher.charMatcher('a'), 2));
+ assertEquals(0, sb.lastIndexOf(StrMatcher.charMatcher('a'), 20));
+
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('b'), -1));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('b'), 0));
+ assertEquals(1, sb.lastIndexOf(StrMatcher.charMatcher('b'), 1));
+ assertEquals(1, sb.lastIndexOf(StrMatcher.charMatcher('b'), 2));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b'), 3));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b'), 4));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b'), 5));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b'), 6));
+
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.spaceMatcher(), -2));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.spaceMatcher(), 0));
+ assertEquals(2, sb.lastIndexOf(StrMatcher.spaceMatcher(), 2));
+ assertEquals(2, sb.lastIndexOf(StrMatcher.spaceMatcher(), 4));
+ assertEquals(2, sb.lastIndexOf(StrMatcher.spaceMatcher(), 20));
+
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.noneMatcher(), 0));
+ assertEquals(-1, sb.lastIndexOf((StrMatcher) null, 0));
+
+ sb.append(" A1 junction with A2");
+ assertEquals(-1, sb.lastIndexOf(A_NUMBER_MATCHER, 5));
+ assertEquals(-1, sb.lastIndexOf(A_NUMBER_MATCHER, 6)); // A matches, 1 is outside bounds
+ assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER, 7));
+ assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER, 22));
+ assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER, 23)); // A matches, 2 is outside bounds
+ assertEquals(23, sb.lastIndexOf(A_NUMBER_MATCHER, 24));
+ }
+
+ static final StrMatcher A_NUMBER_MATCHER = new StrMatcher() {
+ @Override
+ public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
+ if (buffer[pos] == 'A') {
+ pos++;
+ if (pos < bufferEnd && buffer[pos] >= '0' && buffer[pos] <= '9') {
+ return 2;
+ }
+ }
+ return 0;
+ }
+ };
+
+ @Test
+ public void testAsTokenizer() {
+ // from Javadoc
+ final StrBuilder b = new StrBuilder();
+ b.append("a b ");
+ final StrTokenizer t = b.asTokenizer();
+
+ final String[] tokens1 = t.getTokenArray();
+ assertEquals(2, tokens1.length);
+ assertEquals("a", tokens1[0]);
+ assertEquals("b", tokens1[1]);
+ assertEquals(2, t.size());
+
+ b.append("c d ");
+ final String[] tokens2 = t.getTokenArray();
+ assertEquals(2, tokens2.length);
+ assertEquals("a", tokens2[0]);
+ assertEquals("b", tokens2[1]);
+ assertEquals(2, t.size());
+ assertEquals("a", t.next());
+ assertEquals("b", t.next());
+
+ t.reset();
+ final String[] tokens3 = t.getTokenArray();
+ assertEquals(4, tokens3.length);
+ assertEquals("a", tokens3[0]);
+ assertEquals("b", tokens3[1]);
+ assertEquals("c", tokens3[2]);
+ assertEquals("d", tokens3[3]);
+ assertEquals(4, t.size());
+ assertEquals("a", t.next());
+ assertEquals("b", t.next());
+ assertEquals("c", t.next());
+ assertEquals("d", t.next());
+
+ assertEquals("a b c d ", t.getContent());
+ }
+
+ @Test
+ public void testAsReader() throws Exception {
+ final StrBuilder sb = new StrBuilder("some text");
+ Reader reader = sb.asReader();
+ assertTrue(reader.ready());
+ final char[] buf = new char[40];
+ assertEquals(9, reader.read(buf));
+ assertEquals("some text", new String(buf, 0, 9));
+
+ assertEquals(-1, reader.read());
+ assertFalse(reader.ready());
+ assertEquals(0, reader.skip(2));
+ assertEquals(0, reader.skip(-1));
+
+ assertTrue(reader.markSupported());
+ reader = sb.asReader();
+ assertEquals('s', reader.read());
+ reader.mark(-1);
+ char[] array = new char[3];
+ assertEquals(3, reader.read(array, 0, 3));
+ assertEquals('o', array[0]);
+ assertEquals('m', array[1]);
+ assertEquals('e', array[2]);
+ reader.reset();
+ assertEquals(1, reader.read(array, 1, 1));
+ assertEquals('o', array[0]);
+ assertEquals('o', array[1]);
+ assertEquals('e', array[2]);
+ assertEquals(2, reader.skip(2));
+ assertEquals(' ', reader.read());
+
+ assertTrue(reader.ready());
+ reader.close();
+ assertTrue(reader.ready());
+
+ try (Reader r = sb.asReader()) {
+ final char[] arr = new char[3];
+ assertThrows(IndexOutOfBoundsException.class, () -> r.read(arr, -1, 0));
+ assertThrows(IndexOutOfBoundsException.class, () -> r.read(arr, 0, -1));
+ assertThrows(IndexOutOfBoundsException.class, () -> r.read(arr, 100, 1));
+ assertThrows(IndexOutOfBoundsException.class, () -> r.read(arr, 0, 100));
+ assertThrows(IndexOutOfBoundsException.class, () -> r.read(arr, Integer.MAX_VALUE, Integer.MAX_VALUE));
+
+ assertEquals(0, r.read(arr, 0, 0));
+ assertEquals(0, arr[0]);
+ assertEquals(0, arr[1]);
+ assertEquals(0, arr[2]);
+
+ r.skip(9);
+ assertEquals(-1, r.read(arr, 0, 1));
+
+ r.reset();
+ array = new char[30];
+ assertEquals(9, r.read(array, 0, 30));
+ }
+ }
+
+ @Test
+ public void testAsWriter() throws Exception {
+ final StrBuilder sb = new StrBuilder("base");
+ try (Writer writer = sb.asWriter()) {
+
+ writer.write('l');
+ assertEquals("basel", sb.toString());
+
+ writer.write(new char[] { 'i', 'n' });
+ assertEquals("baselin", sb.toString());
+
+ writer.write(new char[] { 'n', 'e', 'r' }, 1, 2);
+ assertEquals("baseliner", sb.toString());
+
+ writer.write(" rout");
+ assertEquals("baseliner rout", sb.toString());
+
+ writer.write("ping that server", 1, 3);
+ assertEquals("baseliner routing", sb.toString());
+
+ writer.flush(); // no effect
+ assertEquals("baseliner routing", sb.toString());
+
+ writer.close(); // no effect
+ assertEquals("baseliner routing", sb.toString());
+
+ writer.write(" hi"); // works after close
+ assertEquals("baseliner routing hi", sb.toString());
+
+ sb.setLength(4); // mix and match
+ writer.write('d');
+ assertEquals("based", sb.toString());
+ }
+ }
+
+ @Test
+ public void testEqualsIgnoreCase() {
+ final StrBuilder sb1 = new StrBuilder();
+ final StrBuilder sb2 = new StrBuilder();
+ assertTrue(sb1.equalsIgnoreCase(sb1));
+ assertTrue(sb1.equalsIgnoreCase(sb2));
+ assertTrue(sb2.equalsIgnoreCase(sb2));
+
+ sb1.append("abc");
+ assertFalse(sb1.equalsIgnoreCase(sb2));
+
+ sb2.append("ABC");
+ assertTrue(sb1.equalsIgnoreCase(sb2));
+
+ sb2.clear().append("abc");
+ assertTrue(sb1.equalsIgnoreCase(sb2));
+ assertTrue(sb1.equalsIgnoreCase(sb1));
+ assertTrue(sb2.equalsIgnoreCase(sb2));
+
+ sb2.clear().append("aBc");
+ assertTrue(sb1.equalsIgnoreCase(sb2));
+ }
+
+ @Test
+ public void testEquals() {
+ final StrBuilder sb1 = new StrBuilder();
+ final StrBuilder sb2 = new StrBuilder();
+ assertTrue(sb1.equals(sb2));
+ assertTrue(sb1.equals(sb1));
+ assertTrue(sb2.equals(sb2));
+ assertEquals(sb1, (Object) sb2);
+
+ sb1.append("abc");
+ assertFalse(sb1.equals(sb2));
+ assertNotEquals(sb1, (Object) sb2);
+
+ sb2.append("ABC");
+ assertFalse(sb1.equals(sb2));
+ assertNotEquals(sb1, (Object) sb2);
+
+ sb2.clear().append("abc");
+ assertTrue(sb1.equals(sb2));
+ assertEquals(sb1, (Object) sb2);
+
+ assertNotEquals(sb1, Integer.valueOf(1));
+ assertNotEquals("abc", sb1);
+ }
+
+ @Test
+ public void test_LANG_1131_EqualsWithNullStrBuilder() {
+ final StrBuilder sb = new StrBuilder();
+ final StrBuilder other = null;
+ assertFalse(sb.equals(other));
+ }
+
+ @Test
+ public void testHashCode() {
+ final StrBuilder sb = new StrBuilder();
+ final int hc1a = sb.hashCode();
+ final int hc1b = sb.hashCode();
+ assertEquals(0, hc1a);
+ assertEquals(hc1a, hc1b);
+
+ sb.append("abc");
+ final int hc2a = sb.hashCode();
+ final int hc2b = sb.hashCode();
+ assertTrue(hc2a != 0);
+ assertEquals(hc2a, hc2b);
+ }
+
+ @Test
+ public void testToString() {
+ final StrBuilder sb = new StrBuilder("abc");
+ assertEquals("abc", sb.toString());
+ }
+
+ @Test
+ public void testToStringBuffer() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(new StringBuffer().toString(), sb.toStringBuffer().toString());
+
+ sb.append("junit");
+ assertEquals(new StringBuffer("junit").toString(), sb.toStringBuffer().toString());
+ }
+
+ @Test
+ public void testToStringBuilder() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(new StringBuilder().toString(), sb.toStringBuilder().toString());
+
+ sb.append("junit");
+ assertEquals(new StringBuilder("junit").toString(), sb.toStringBuilder().toString());
+ }
+
+ @Test
+ public void testLang294() {
+ final StrBuilder sb = new StrBuilder("\n%BLAH%\nDo more stuff\neven more stuff\n%BLAH%\n");
+ sb.deleteAll("\n%BLAH%");
+ assertEquals("\nDo more stuff\neven more stuff\n", sb.toString());
+ }
+
+ @Test
+ public void testIndexOfLang294() {
+ final StrBuilder sb = new StrBuilder("onetwothree");
+ sb.deleteFirst("three");
+ assertEquals(-1, sb.indexOf("three"));
+ }
+
+ @Test
+ public void testLang295() {
+ final StrBuilder sb = new StrBuilder("onetwothree");
+ sb.deleteFirst("three");
+ assertFalse(sb.contains('h'), "The contains(char) method is looking beyond the end of the string");
+ assertEquals(-1, sb.indexOf('h'), "The indexOf(char) method is looking beyond the end of the string");
+ }
+
+ @Test
+ public void testLang412Right() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadRight(null, 10, '*');
+ assertEquals("**********", sb.toString(), "Failed to invoke appendFixedWidthPadRight correctly");
+ }
+
+ @Test
+ public void testLang412Left() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadLeft(null, 10, '*');
+ assertEquals("**********", sb.toString(), "Failed to invoke appendFixedWidthPadLeft correctly");
+ }
+
+ @Test
+ public void testAsBuilder() {
+ final StrBuilder sb = new StrBuilder().appendAll("Lorem", " ", "ipsum", " ", "dolor");
+ assertEquals(sb.toString(), sb.build());
+ }
+
+ @Test
+ public void testAppendCharBuffer() {
+ final StrBuilder sb1 = new StrBuilder();
+ final CharBuffer buf = CharBuffer.allocate(10);
+ buf.append("0123456789");
+ buf.flip();
+ sb1.append(buf);
+ assertEquals("0123456789", sb1.toString());
+
+ final StrBuilder sb2 = new StrBuilder();
+ sb2.append(buf, 1, 8);
+ assertEquals("12345678", sb2.toString());
+ }
+
+ @Test
+ public void testAppendToWriter() throws Exception {
+ final StrBuilder sb = new StrBuilder("1234567890");
+ final StringWriter writer = new StringWriter();
+ writer.append("Test ");
+
+ sb.appendTo(writer);
+
+ assertEquals("Test 1234567890", writer.toString());
+ }
+
+ @Test
+ public void testAppendToStringBuilder() throws Exception {
+ final StrBuilder sb = new StrBuilder("1234567890");
+ final StringBuilder builder = new StringBuilder("Test ");
+
+ sb.appendTo(builder);
+
+ assertEquals("Test 1234567890", builder.toString());
+ }
+
+ @Test
+ public void testAppendToStringBuffer() throws Exception {
+ final StrBuilder sb = new StrBuilder("1234567890");
+ final StringBuffer buffer = new StringBuffer("Test ");
+
+ sb.appendTo(buffer);
+
+ assertEquals("Test 1234567890", buffer.toString());
+ }
+
+ @Test
+ public void testAppendToCharBuffer() throws Exception {
+ final StrBuilder sb = new StrBuilder("1234567890");
+ final String text = "Test ";
+ final CharBuffer buffer = CharBuffer.allocate(sb.size() + text.length());
+ buffer.put(text);
+
+ sb.appendTo(buffer);
+
+ buffer.flip();
+ assertEquals("Test 1234567890", buffer.toString());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/StrLookupTest.java b/src/test/java/org/apache/commons/lang3/text/StrLookupTest.java
new file mode 100644
index 000000000..acc42dac9
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/StrLookupTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for StrLookup.
+ */
+@Deprecated
+public class StrLookupTest extends AbstractLangTest {
+
+ @Test
+ public void testNoneLookup() {
+ assertNull(StrLookup.noneLookup().lookup(null));
+ assertNull(StrLookup.noneLookup().lookup(""));
+ assertNull(StrLookup.noneLookup().lookup("any"));
+ }
+
+ @Test
+ public void testSystemPropertiesLookup() {
+ assertEquals(System.getProperty("os.name"), StrLookup.systemPropertiesLookup().lookup("os.name"));
+ assertNull(StrLookup.systemPropertiesLookup().lookup(""));
+ assertNull(StrLookup.systemPropertiesLookup().lookup("other"));
+ assertNull(StrLookup.systemPropertiesLookup().lookup(null));
+ }
+
+ /**
+ * Tests that a lookup object for system properties can deal with a full
+ * replacement of the system properties object. This test is related to
+ * LANG-1055.
+ */
+ @Test
+ public void testSystemPropertiesLookupReplacedProperties() {
+ final Properties oldProperties = System.getProperties();
+ final String osName = "os.name";
+ final String newOsName = oldProperties.getProperty(osName) + "_changed";
+
+ final StrLookup<String> sysLookup = StrLookup.systemPropertiesLookup();
+ final Properties newProps = new Properties();
+ newProps.setProperty(osName, newOsName);
+ System.setProperties(newProps);
+ try {
+ assertEquals(newOsName, sysLookup.lookup(osName), "Changed properties not detected");
+ } finally {
+ System.setProperties(oldProperties);
+ }
+ }
+
+ /**
+ * Tests that a lookup object for system properties sees changes on system
+ * properties. This test is related to LANG-1141.
+ */
+ @Test
+ public void testSystemPropertiesLookupUpdatedProperty() {
+ final String osName = "os.name";
+ final String oldOs = System.getProperty(osName);
+ final String newOsName = oldOs + "_changed";
+
+ final StrLookup<String> sysLookup = StrLookup.systemPropertiesLookup();
+ System.setProperty(osName, newOsName);
+ try {
+ assertEquals(newOsName, sysLookup.lookup(osName), "Changed properties not detected");
+ } finally {
+ System.setProperty(osName, oldOs);
+ }
+ }
+
+ @Test
+ public void testMapLookup() {
+ final Map<String, Object> map = new HashMap<>();
+ map.put("key", "value");
+ map.put("number", Integer.valueOf(2));
+ assertEquals("value", StrLookup.mapLookup(map).lookup("key"));
+ assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
+ assertNull(StrLookup.mapLookup(map).lookup(null));
+ assertNull(StrLookup.mapLookup(map).lookup(""));
+ assertNull(StrLookup.mapLookup(map).lookup("other"));
+ }
+
+ @Test
+ public void testMapLookup_nullMap() {
+ final Map<String, ?> map = null;
+ assertNull(StrLookup.mapLookup(map).lookup(null));
+ assertNull(StrLookup.mapLookup(map).lookup(""));
+ assertNull(StrLookup.mapLookup(map).lookup("any"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/StrMatcherTest.java b/src/test/java/org/apache/commons/lang3/text/StrMatcherTest.java
new file mode 100644
index 000000000..84062502e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/StrMatcherTest.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.StrMatcher}.
+ */
+@Deprecated
+public class StrMatcherTest extends AbstractLangTest {
+
+ private static final char[] BUFFER1 = "0,1\t2 3\n\r\f\u0000'\"".toCharArray();
+
+ private static final char[] BUFFER2 = "abcdef".toCharArray();
+
+
+ @Test
+ public void testCommaMatcher() {
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ assertSame(matcher, StrMatcher.commaMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 0));
+ assertEquals(1, matcher.isMatch(BUFFER1, 1));
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ }
+
+ @Test
+ public void testTabMatcher() {
+ final StrMatcher matcher = StrMatcher.tabMatcher();
+ assertSame(matcher, StrMatcher.tabMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ assertEquals(1, matcher.isMatch(BUFFER1, 3));
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ }
+
+ @Test
+ public void testSpaceMatcher() {
+ final StrMatcher matcher = StrMatcher.spaceMatcher();
+ assertSame(matcher, StrMatcher.spaceMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ assertEquals(1, matcher.isMatch(BUFFER1, 5));
+ assertEquals(0, matcher.isMatch(BUFFER1, 6));
+ }
+
+ @Test
+ public void testSplitMatcher() {
+ final StrMatcher matcher = StrMatcher.splitMatcher();
+ assertSame(matcher, StrMatcher.splitMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ assertEquals(1, matcher.isMatch(BUFFER1, 3));
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ assertEquals(1, matcher.isMatch(BUFFER1, 5));
+ assertEquals(0, matcher.isMatch(BUFFER1, 6));
+ assertEquals(1, matcher.isMatch(BUFFER1, 7));
+ assertEquals(1, matcher.isMatch(BUFFER1, 8));
+ assertEquals(1, matcher.isMatch(BUFFER1, 9));
+ assertEquals(0, matcher.isMatch(BUFFER1, 10));
+ }
+
+ @Test
+ public void testTrimMatcher() {
+ final StrMatcher matcher = StrMatcher.trimMatcher();
+ assertSame(matcher, StrMatcher.trimMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ assertEquals(1, matcher.isMatch(BUFFER1, 3));
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ assertEquals(1, matcher.isMatch(BUFFER1, 5));
+ assertEquals(0, matcher.isMatch(BUFFER1, 6));
+ assertEquals(1, matcher.isMatch(BUFFER1, 7));
+ assertEquals(1, matcher.isMatch(BUFFER1, 8));
+ assertEquals(1, matcher.isMatch(BUFFER1, 9));
+ assertEquals(1, matcher.isMatch(BUFFER1, 10));
+ }
+
+ @Test
+ public void testSingleQuoteMatcher() {
+ final StrMatcher matcher = StrMatcher.singleQuoteMatcher();
+ assertSame(matcher, StrMatcher.singleQuoteMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 10));
+ assertEquals(1, matcher.isMatch(BUFFER1, 11));
+ assertEquals(0, matcher.isMatch(BUFFER1, 12));
+ }
+
+ @Test
+ public void testDoubleQuoteMatcher() {
+ final StrMatcher matcher = StrMatcher.doubleQuoteMatcher();
+ assertSame(matcher, StrMatcher.doubleQuoteMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 11));
+ assertEquals(1, matcher.isMatch(BUFFER1, 12));
+ }
+
+ @Test
+ public void testQuoteMatcher() {
+ final StrMatcher matcher = StrMatcher.quoteMatcher();
+ assertSame(matcher, StrMatcher.quoteMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 10));
+ assertEquals(1, matcher.isMatch(BUFFER1, 11));
+ assertEquals(1, matcher.isMatch(BUFFER1, 12));
+ }
+
+ @Test
+ public void testNoneMatcher() {
+ final StrMatcher matcher = StrMatcher.noneMatcher();
+ assertSame(matcher, StrMatcher.noneMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 0));
+ assertEquals(0, matcher.isMatch(BUFFER1, 1));
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ assertEquals(0, matcher.isMatch(BUFFER1, 3));
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ assertEquals(0, matcher.isMatch(BUFFER1, 5));
+ assertEquals(0, matcher.isMatch(BUFFER1, 6));
+ assertEquals(0, matcher.isMatch(BUFFER1, 7));
+ assertEquals(0, matcher.isMatch(BUFFER1, 8));
+ assertEquals(0, matcher.isMatch(BUFFER1, 9));
+ assertEquals(0, matcher.isMatch(BUFFER1, 10));
+ assertEquals(0, matcher.isMatch(BUFFER1, 11));
+ assertEquals(0, matcher.isMatch(BUFFER1, 12));
+ }
+
+ @Test
+ public void testCharMatcher_char() {
+ final StrMatcher matcher = StrMatcher.charMatcher('c');
+ assertEquals(0, matcher.isMatch(BUFFER2, 0));
+ assertEquals(0, matcher.isMatch(BUFFER2, 1));
+ assertEquals(1, matcher.isMatch(BUFFER2, 2));
+ assertEquals(0, matcher.isMatch(BUFFER2, 3));
+ assertEquals(0, matcher.isMatch(BUFFER2, 4));
+ assertEquals(0, matcher.isMatch(BUFFER2, 5));
+ }
+
+ @Test
+ public void testCharSetMatcher_String() {
+ final StrMatcher matcher = StrMatcher.charSetMatcher("ace");
+ assertEquals(1, matcher.isMatch(BUFFER2, 0));
+ assertEquals(0, matcher.isMatch(BUFFER2, 1));
+ assertEquals(1, matcher.isMatch(BUFFER2, 2));
+ assertEquals(0, matcher.isMatch(BUFFER2, 3));
+ assertEquals(1, matcher.isMatch(BUFFER2, 4));
+ assertEquals(0, matcher.isMatch(BUFFER2, 5));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.charSetMatcher(""));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.charSetMatcher((String) null));
+ assertTrue(StrMatcher.charSetMatcher("a") instanceof StrMatcher.CharMatcher);
+ }
+
+ @Test
+ public void testCharSetMatcher_charArray() {
+ final StrMatcher matcher = StrMatcher.charSetMatcher("ace".toCharArray());
+ assertEquals(1, matcher.isMatch(BUFFER2, 0));
+ assertEquals(0, matcher.isMatch(BUFFER2, 1));
+ assertEquals(1, matcher.isMatch(BUFFER2, 2));
+ assertEquals(0, matcher.isMatch(BUFFER2, 3));
+ assertEquals(1, matcher.isMatch(BUFFER2, 4));
+ assertEquals(0, matcher.isMatch(BUFFER2, 5));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.charSetMatcher());
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.charSetMatcher((char[]) null));
+ assertTrue(StrMatcher.charSetMatcher("a".toCharArray()) instanceof StrMatcher.CharMatcher);
+ }
+
+ @Test
+ public void testStringMatcher_String() {
+ final StrMatcher matcher = StrMatcher.stringMatcher("bc");
+ assertEquals(0, matcher.isMatch(BUFFER2, 0));
+ assertEquals(2, matcher.isMatch(BUFFER2, 1));
+ assertEquals(0, matcher.isMatch(BUFFER2, 2));
+ assertEquals(0, matcher.isMatch(BUFFER2, 3));
+ assertEquals(0, matcher.isMatch(BUFFER2, 4));
+ assertEquals(0, matcher.isMatch(BUFFER2, 5));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.stringMatcher(""));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.stringMatcher(null));
+ }
+
+ @Test
+ public void testMatcherIndices() {
+ // remember that the API contract is tight for the isMatch() method
+ // all the onus is on the caller, so invalid inputs are not
+ // the concern of StrMatcher, and are not bugs
+ final StrMatcher matcher = StrMatcher.stringMatcher("bc");
+ assertEquals(2, matcher.isMatch(BUFFER2, 1, 1, BUFFER2.length));
+ assertEquals(2, matcher.isMatch(BUFFER2, 1, 0, 3));
+ assertEquals(0, matcher.isMatch(BUFFER2, 1, 0, 2));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java b/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
new file mode 100644
index 000000000..9b3d9f2ae
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
@@ -0,0 +1,712 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for StrSubstitutor.
+ */
+@Deprecated
+public class StrSubstitutorTest extends AbstractLangTest {
+
+ private Map<String, String> values;
+
+ @BeforeEach
+ public void setUp() {
+ values = new HashMap<>();
+ values.put("animal", "quick brown fox");
+ values.put("target", "lazy dog");
+ }
+
+ @AfterEach
+ public void tearDown() {
+ values = null;
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplaceSimple() {
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplaceSolo() {
+ doTestReplace("quick brown fox", "${animal}", false);
+ }
+
+ /**
+ * Tests replace with no variables.
+ */
+ @Test
+ public void testReplaceNoVariables() {
+ doTestNoReplace("The balloon arrived.");
+ }
+
+ /**
+ * Tests replace with null.
+ */
+ @Test
+ public void testReplaceNull() {
+ doTestNoReplace(null);
+ }
+
+ /**
+ * Tests replace with null.
+ */
+ @Test
+ public void testReplaceEmpty() {
+ doTestNoReplace("");
+ }
+
+ /**
+ * Tests key replace changing map after initialization (not recommended).
+ */
+ @Test
+ public void testReplaceChangedMap() {
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ values.put("target", "moon");
+ assertEquals("The quick brown fox jumps over the moon.", sub.replace("The ${animal} jumps over the ${target}."));
+ }
+
+ /**
+ * Tests unknown key replace.
+ */
+ @Test
+ public void testReplaceUnknownKey() {
+ doTestReplace("The ${person} jumps over the lazy dog.", "The ${person} jumps over the ${target}.", true);
+ doTestReplace("The ${person} jumps over the lazy dog. 1234567890.", "The ${person} jumps over the ${target}. ${undefined.number:-1234567890}.", true);
+ }
+
+ /**
+ * Tests adjacent keys.
+ */
+ @Test
+ public void testReplaceAdjacentAtStart() {
+ values.put("code", "GBP");
+ values.put("amount", "12.50");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals("GBP12.50 charged", sub.replace("${code}${amount} charged"));
+ }
+
+ /**
+ * Tests adjacent keys.
+ */
+ @Test
+ public void testReplaceAdjacentAtEnd() {
+ values.put("code", "GBP");
+ values.put("amount", "12.50");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals("Amount is GBP12.50", sub.replace("Amount is ${code}${amount}"));
+ }
+
+ /**
+ * Tests simple recursive replace.
+ */
+ @Test
+ public void testReplaceRecursive() {
+ values.put("animal", "${critter}");
+ values.put("target", "${pet}");
+ values.put("pet", "${petCharacteristic} dog");
+ values.put("petCharacteristic", "lazy");
+ values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
+ values.put("critterSpeed", "quick");
+ values.put("critterColor", "brown");
+ values.put("critterType", "fox");
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+
+ values.put("pet", "${petCharacteristicUnknown:-lazy} dog");
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests escaping.
+ */
+ @Test
+ public void testReplaceEscaping() {
+ doTestReplace("The ${animal} jumps over the lazy dog.", "The $${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests escaping.
+ */
+ @Test
+ public void testReplaceSoloEscaping() {
+ doTestReplace("${animal}", "$${animal}", false);
+ }
+
+ /**
+ * Tests complex escaping.
+ */
+ @Test
+ public void testReplaceComplexEscaping() {
+ doTestReplace("The ${quick brown fox} jumps over the lazy dog.", "The $${${animal}} jumps over the ${target}.", true);
+ doTestReplace("The ${quick brown fox} jumps over the lazy dog. ${1234567890}.", "The $${${animal}} jumps over the ${target}. $${${undefined.number:-1234567890}}.", true);
+ }
+
+ /**
+ * Tests when no prefix or suffix.
+ */
+ @Test
+ public void testReplaceNoPrefixNoSuffix() {
+ doTestReplace("The animal jumps over the lazy dog.", "The animal jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when no incomplete prefix.
+ */
+ @Test
+ public void testReplaceIncompletePrefix() {
+ doTestReplace("The {animal} jumps over the lazy dog.", "The {animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when prefix but no suffix.
+ */
+ @Test
+ public void testReplacePrefixNoSuffix() {
+ doTestReplace("The ${animal jumps over the ${target} lazy dog.", "The ${animal jumps over the ${target} ${target}.", true);
+ }
+
+ /**
+ * Tests when suffix but no prefix.
+ */
+ @Test
+ public void testReplaceNoPrefixSuffix() {
+ doTestReplace("The animal} jumps over the lazy dog.", "The animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when no variable name.
+ */
+ @Test
+ public void testReplaceEmptyKeys() {
+ doTestReplace("The ${} jumps over the lazy dog.", "The ${} jumps over the ${target}.", true);
+ doTestReplace("The animal jumps over the lazy dog.", "The ${:-animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests replace creates output same as input.
+ */
+ @Test
+ public void testReplaceToIdentical() {
+ values.put("animal", "$${${thing}}");
+ values.put("thing", "animal");
+ doTestReplace("The ${animal} jumps.", "The ${animal} jumps.", true);
+ }
+
+ /**
+ * Tests a cyclic replace operation.
+ * The cycle should be detected and cause an exception to be thrown.
+ */
+ @Test
+ public void testCyclicReplacement() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("animal", "${critter}");
+ map.put("target", "${pet}");
+ map.put("pet", "${petCharacteristic} dog");
+ map.put("petCharacteristic", "lazy");
+ map.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
+ map.put("critterSpeed", "quick");
+ map.put("critterColor", "brown");
+ map.put("critterType", "${animal}");
+ final StrSubstitutor sub = new StrSubstitutor(map);
+ assertThrows(
+ IllegalStateException.class,
+ () -> sub.replace("The ${animal} jumps over the ${target}."),
+ "Cyclic replacement was not detected!");
+
+ // also check even when default value is set.
+ map.put("critterType", "${animal:-fox}");
+ final StrSubstitutor sub2 = new StrSubstitutor(map);
+ assertThrows(
+ IllegalStateException.class,
+ () -> sub2.replace("The ${animal} jumps over the ${target}."),
+ "Cyclic replacement was not detected!");
+ }
+
+ /**
+ * Tests interpolation with weird boundary patterns.
+ */
+ @Test
+ public void testReplaceWeirdPattens() {
+ doTestNoReplace("");
+ doTestNoReplace("${}");
+ doTestNoReplace("${ }");
+ doTestNoReplace("${\t}");
+ doTestNoReplace("${\n}");
+ doTestNoReplace("${\b}");
+ doTestNoReplace("${");
+ doTestNoReplace("$}");
+ doTestNoReplace("}");
+ doTestNoReplace("${}$");
+ doTestNoReplace("${${");
+ doTestNoReplace("${${}}");
+ doTestNoReplace("${$${}}");
+ doTestNoReplace("${$$${}}");
+ doTestNoReplace("${$$${$}}");
+ doTestNoReplace("${${}}");
+ doTestNoReplace("${${ }}");
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplacePartialString_noReplace() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15));
+ }
+
+ /**
+ * Tests whether a variable can be replaced in a variable name.
+ */
+ @Test
+ public void testReplaceInVariable() {
+ values.put("animal.1", "fox");
+ values.put("animal.2", "mouse");
+ values.put("species", "2");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ sub.setEnableSubstitutionInVariables(true);
+ assertEquals(
+ "The mouse jumps over the lazy dog.",
+ sub.replace("The ${animal.${species}} jumps over the ${target}."),
+ "Wrong result (1)");
+ values.put("species", "1");
+ assertEquals(
+ "The fox jumps over the lazy dog.",
+ sub.replace("The ${animal.${species}} jumps over the ${target}."),
+ "Wrong result (2)");
+ assertEquals(
+ "The fox jumps over the lazy dog.",
+ sub.replace("The ${unknown.animal.${unknown.species:-1}:-fox} jumps over the ${unknown.target:-lazy dog}."),
+ "Wrong result (3)");
+ }
+
+ /**
+ * Tests whether substitution in variable names is disabled per default.
+ */
+ @Test
+ public void testReplaceInVariableDisabled() {
+ values.put("animal.1", "fox");
+ values.put("animal.2", "mouse");
+ values.put("species", "2");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals(
+ "The ${animal.${species}} jumps over the lazy dog.",
+ sub.replace("The ${animal.${species}} jumps over the ${target}."),
+ "Wrong result (1)");
+ assertEquals(
+ "The ${animal.${species:-1}} jumps over the lazy dog.",
+ sub.replace("The ${animal.${species:-1}} jumps over the ${target}."),
+ "Wrong result (2)");
+ }
+
+ /**
+ * Tests complex and recursive substitution in variable names.
+ */
+ @Test
+ public void testReplaceInVariableRecursive() {
+ values.put("animal.2", "brown fox");
+ values.put("animal.1", "white mouse");
+ values.put("color", "white");
+ values.put("species.white", "1");
+ values.put("species.brown", "2");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ sub.setEnableSubstitutionInVariables(true);
+ assertEquals(
+ "The white mouse jumps over the lazy dog.",
+ sub.replace("The ${animal.${species.${color}}} jumps over the ${target}."),
+ "Wrong result (1)");
+ assertEquals(
+ "The brown fox jumps over the lazy dog.",
+ sub.replace("The ${animal.${species.${unknownColor:-brown}}} jumps over the ${target}."),
+ "Wrong result (2)");
+ }
+
+ @Test
+ public void testDefaultValueDelimiters() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("animal", "fox");
+ map.put("target", "dog");
+
+ StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number:-1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "?:");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number?:1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "||");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number||1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "!");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "");
+ sub.setValueDelimiterMatcher(null);
+ assertEquals("The fox jumps over the lazy dog. ${undefined.number!1234567890}.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$');
+ sub.setValueDelimiterMatcher(null);
+ assertEquals("The fox jumps over the lazy dog. ${undefined.number!1234567890}.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
+ }
+
+ /**
+ * Tests protected.
+ */
+ @Test
+ public void testResolveVariable() {
+ final StrBuilder builder = new StrBuilder("Hi ${name}!");
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ final StrSubstitutor sub = new StrSubstitutor(map) {
+ @Override
+ protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) {
+ assertEquals("name", variableName);
+ assertSame(builder, buf);
+ assertEquals(3, startPos);
+ assertEquals(10, endPos);
+ return "jakarta";
+ }
+ };
+ sub.replaceIn(builder);
+ assertEquals("Hi jakarta!", builder.toString());
+ }
+
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorNoArgs() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals("Hi ${name}", sub.replace("Hi ${name}"));
+ }
+
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorMapPrefixSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ final StrSubstitutor sub = new StrSubstitutor(map, "<", ">");
+ assertEquals("Hi < commons", sub.replace("Hi $< <name>"));
+ }
+
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorMapFull() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ StrSubstitutor sub = new StrSubstitutor(map, "<", ">", '!');
+ assertEquals("Hi < commons", sub.replace("Hi !< <name>"));
+ sub = new StrSubstitutor(map, "<", ">", '!', "||");
+ assertEquals("Hi < commons", sub.replace("Hi !< <name2||commons>"));
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetEscape() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals('$', sub.getEscapeChar());
+ sub.setEscapeChar('<');
+ assertEquals('<', sub.getEscapeChar());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetPrefix() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setVariablePrefix('<');
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setVariablePrefix("<<");
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+ assertThrows(NullPointerException.class, () -> sub.setVariablePrefix(null));
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setVariablePrefixMatcher(matcher);
+ assertSame(matcher, sub.getVariablePrefixMatcher());
+ assertThrows(NullPointerException.class, () -> sub.setVariablePrefixMatcher(null));
+ assertSame(matcher, sub.getVariablePrefixMatcher());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetSuffix() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setVariableSuffix('<');
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setVariableSuffix("<<");
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+ assertThrows(NullPointerException.class, () -> sub.setVariableSuffix(null));
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setVariableSuffixMatcher(matcher);
+ assertSame(matcher, sub.getVariableSuffixMatcher());
+ assertThrows(NullPointerException.class, () -> sub.setVariableSuffixMatcher(null));
+ assertSame(matcher, sub.getVariableSuffixMatcher());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetValueDelimiter() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setValueDelimiter(':');
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setValueDelimiter("||");
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setValueDelimiter(null);
+ assertNull(sub.getValueDelimiterMatcher());
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setValueDelimiterMatcher(matcher);
+ assertSame(matcher, sub.getValueDelimiterMatcher());
+ sub.setValueDelimiterMatcher(null);
+ assertNull(sub.getValueDelimiterMatcher());
+ }
+
+ /**
+ * Tests static.
+ */
+ @Test
+ public void testStaticReplace() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi ${name}!", map));
+ }
+
+ /**
+ * Tests static.
+ */
+ @Test
+ public void testStaticReplacePrefixSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi <name>!", map, "<", ">"));
+ }
+
+ /**
+ * Tests interpolation with system properties.
+ */
+ @Test
+ public void testStaticReplaceSystemProperties() {
+ final StrBuilder buf = new StrBuilder();
+ buf.append("Hi ").append(System.getProperty("user.name"));
+ buf.append(", you are working with ");
+ buf.append(System.getProperty("os.name"));
+ buf.append(", your home directory is ");
+ buf.append(System.getProperty("user.home")).append('.');
+ assertEquals(buf.toString(), StrSubstitutor.replaceSystemProperties("Hi ${user.name}, you are "
+ + "working with ${os.name}, your home "
+ + "directory is ${user.home}."));
+ }
+
+ /**
+ * Test for LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently
+ */
+ @Test
+ public void testLANG1055() {
+ System.setProperty("test_key", "test_value");
+
+ final String expected = StrSubstitutor.replace("test_key=${test_key}", System.getProperties());
+ final String actual = StrSubstitutor.replaceSystemProperties("test_key=${test_key}");
+ assertEquals(expected, actual);
+ }
+
+ /**
+ * Test the replace of a properties object
+ */
+ @Test
+ public void testSubstituteDefaultProperties() {
+ final String org = "${doesnotwork}";
+ System.setProperty("doesnotwork", "It works!");
+
+ // create a new Properties object with the System.getProperties as default
+ final Properties props = new Properties(System.getProperties());
+
+ assertEquals("It works!", StrSubstitutor.replace(org, props));
+ }
+
+ @Test
+ public void testSamePrefixAndSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("greeting", "Hello");
+ map.put(" there ", "XXX");
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi @name@!", map, "@", "@"));
+ assertEquals("Hello there commons!", StrSubstitutor.replace("@greeting@ there @name@!", map, "@", "@"));
+ }
+
+ @Test
+ public void testSubstitutePreserveEscape() {
+ final String org = "${not-escaped} $${escaped}";
+ final Map<String, String> map = new HashMap<>();
+ map.put("not-escaped", "value");
+
+ final StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
+ assertFalse(sub.isPreserveEscapes());
+ assertEquals("value ${escaped}", sub.replace(org));
+
+ sub.setPreserveEscapes(true);
+ assertTrue(sub.isPreserveEscapes());
+ assertEquals("value $${escaped}", sub.replace(org));
+ }
+
+ private void doTestReplace(final String expectedResult, final String replaceTemplate, final boolean substring) {
+ final String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
+ final StrSubstitutor sub = new StrSubstitutor(values);
+
+ // replace using String
+ assertEquals(expectedResult, sub.replace(replaceTemplate));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
+ }
+
+ // replace using char[]
+ final char[] chars = replaceTemplate.toCharArray();
+ assertEquals(expectedResult, sub.replace(chars));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2));
+ }
+
+ // replace using StringBuffer
+ StringBuffer buf = new StringBuffer(replaceTemplate);
+ assertEquals(expectedResult, sub.replace(buf));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(buf, 1, buf.length() - 2));
+ }
+
+ // replace using StringBuilder
+ StringBuilder builder = new StringBuilder(replaceTemplate);
+ assertEquals(expectedResult, sub.replace(builder));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(builder, 1, builder.length() - 2));
+ }
+
+ // replace using StrBuilder
+ StrBuilder bld = new StrBuilder(replaceTemplate);
+ assertEquals(expectedResult, sub.replace(bld));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(bld, 1, bld.length() - 2));
+ }
+
+ // replace using object
+ final MutableObject<String> obj = new MutableObject<>(replaceTemplate); // toString returns template
+ assertEquals(expectedResult, sub.replace(obj));
+
+ // replace in StringBuffer
+ buf = new StringBuffer(replaceTemplate);
+ assertTrue(sub.replaceIn(buf));
+ assertEquals(expectedResult, buf.toString());
+ if (substring) {
+ buf = new StringBuffer(replaceTemplate);
+ assertTrue(sub.replaceIn(buf, 1, buf.length() - 2));
+ assertEquals(expectedResult, buf.toString()); // expect full result as remainder is untouched
+ }
+
+ // replace in StringBuilder
+ builder = new StringBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(builder));
+ assertEquals(expectedResult, builder.toString());
+ if (substring) {
+ builder = new StringBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(builder, 1, builder.length() - 2));
+ assertEquals(expectedResult, builder.toString()); // expect full result as remainder is untouched
+ }
+
+ // replace in StrBuilder
+ bld = new StrBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(bld));
+ assertEquals(expectedResult, bld.toString());
+ if (substring) {
+ bld = new StrBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(bld, 1, bld.length() - 2));
+ assertEquals(expectedResult, bld.toString()); // expect full result as remainder is untouched
+ }
+ }
+
+ private void doTestNoReplace(final String replaceTemplate) {
+ final StrSubstitutor sub = new StrSubstitutor(values);
+
+ if (replaceTemplate == null) {
+ assertNull(sub.replace((String) null));
+ assertNull(sub.replace((String) null, 0, 100));
+ assertNull(sub.replace((char[]) null));
+ assertNull(sub.replace((char[]) null, 0, 100));
+ assertNull(sub.replace((StringBuffer) null));
+ assertNull(sub.replace((StringBuffer) null, 0, 100));
+ assertNull(sub.replace((StrBuilder) null));
+ assertNull(sub.replace((StrBuilder) null, 0, 100));
+ assertNull(sub.replace((Object) null));
+ assertFalse(sub.replaceIn((StringBuffer) null));
+ assertFalse(sub.replaceIn((StringBuffer) null, 0, 100));
+ assertFalse(sub.replaceIn((StrBuilder) null));
+ assertFalse(sub.replaceIn((StrBuilder) null, 0, 100));
+ } else {
+ assertEquals(replaceTemplate, sub.replace(replaceTemplate));
+ final StrBuilder bld = new StrBuilder(replaceTemplate);
+ assertFalse(sub.replaceIn(bld));
+ assertEquals(replaceTemplate, bld.toString());
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/StrTokenizerTest.java b/src/test/java/org/apache/commons/lang3/text/StrTokenizerTest.java
new file mode 100644
index 000000000..18f4091e9
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/StrTokenizerTest.java
@@ -0,0 +1,848 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit test for Tokenizer.
+ */
+@Deprecated
+public class StrTokenizerTest extends AbstractLangTest {
+
+ private static final String CSV_SIMPLE_FIXTURE = "A,b,c";
+
+ private static final String TSV_SIMPLE_FIXTURE = "A\tb\tc";
+
+ private void checkClone(final StrTokenizer tokenizer) {
+ assertNotSame(StrTokenizer.getCSVInstance(), tokenizer);
+ assertNotSame(StrTokenizer.getTSVInstance(), tokenizer);
+ }
+
+ @Test
+ public void test1() {
+
+ final String input = "a;b;c;\"d;\"\"e\";f; ; ; ";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ final String[] tokens = tok.getTokenArray();
+
+ final String[] expected = {"a", "b", "c", "d;\"e", "f", "", "", ""};
+
+ assertEquals(expected.length, tokens.length, ArrayUtils.toString(tokens));
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], tokens[i],
+ "token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'");
+ }
+
+ }
+
+ @Test
+ public void test2() {
+
+ final String input = "a;b;c ;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.noneMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ final String[] tokens = tok.getTokenArray();
+
+ final String[] expected = {"a", "b", "c ", "d;\"e", "f", " ", " ", ""};
+
+ assertEquals(expected.length, tokens.length, ArrayUtils.toString(tokens));
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], tokens[i],
+ "token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'");
+ }
+
+ }
+
+ @Test
+ public void test3() {
+
+ final String input = "a;b; c;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.noneMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ final String[] tokens = tok.getTokenArray();
+
+ final String[] expected = {"a", "b", " c", "d;\"e", "f", " ", " ", ""};
+
+ assertEquals(expected.length, tokens.length, ArrayUtils.toString(tokens));
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], tokens[i],
+ "token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'");
+ }
+
+ }
+
+ @Test
+ public void test4() {
+
+ final String input = "a;b; c;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(true);
+ final String[] tokens = tok.getTokenArray();
+
+ final String[] expected = {"a", "b", "c", "d;\"e", "f"};
+
+ assertEquals(expected.length, tokens.length, ArrayUtils.toString(tokens));
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], tokens[i],
+ "token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'");
+ }
+
+ }
+
+ @Test
+ public void test5() {
+
+ final String input = "a;b; c;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ final String[] tokens = tok.getTokenArray();
+
+ final String[] expected = {"a", "b", "c", "d;\"e", "f", null, null, null};
+
+ assertEquals(expected.length, tokens.length, ArrayUtils.toString(tokens));
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], tokens[i],
+ "token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'");
+ }
+
+ }
+
+ @Test
+ public void test6() {
+
+ final String input = "a;b; c;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ // tok.setTreatingEmptyAsNull(true);
+ final String[] tokens = tok.getTokenArray();
+
+ final String[] expected = {"a", "b", " c", "d;\"e", "f", null, null, null};
+
+ int nextCount = 0;
+ while (tok.hasNext()) {
+ tok.next();
+ nextCount++;
+ }
+
+ int prevCount = 0;
+ while (tok.hasPrevious()) {
+ tok.previous();
+ prevCount++;
+ }
+
+ assertEquals(expected.length, tokens.length, ArrayUtils.toString(tokens));
+
+ assertEquals(nextCount, expected.length, "could not cycle through entire token list" + " using the 'hasNext' and 'next' methods");
+
+ assertEquals(prevCount, expected.length, "could not cycle through entire token list" + " using the 'hasPrevious' and 'previous' methods");
+
+ }
+
+ @Test
+ public void test7() {
+
+ final String input = "a b c \"d e\" f ";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterMatcher(StrMatcher.spaceMatcher());
+ tok.setQuoteMatcher(StrMatcher.doubleQuoteMatcher());
+ tok.setIgnoredMatcher(StrMatcher.noneMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ final String[] tokens = tok.getTokenArray();
+
+ final String[] expected = {"a", "", "", "b", "c", "d e", "f", ""};
+
+ assertEquals(expected.length, tokens.length, ArrayUtils.toString(tokens));
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], tokens[i],
+ "token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'");
+ }
+
+ }
+
+ @Test
+ public void test8() {
+
+ final String input = "a b c \"d e\" f ";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterMatcher(StrMatcher.spaceMatcher());
+ tok.setQuoteMatcher(StrMatcher.doubleQuoteMatcher());
+ tok.setIgnoredMatcher(StrMatcher.noneMatcher());
+ tok.setIgnoreEmptyTokens(true);
+ final String[] tokens = tok.getTokenArray();
+
+ final String[] expected = {"a", "b", "c", "d e", "f"};
+
+ assertEquals(expected.length, tokens.length, ArrayUtils.toString(tokens));
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], tokens[i],
+ "token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'");
+ }
+
+ }
+
+ @Test
+ public void testBasic1() {
+ final String input = "a b c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasic2() {
+ final String input = "a \nb\fc";
+ final StrTokenizer tok = new StrTokenizer(input);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasic3() {
+ final String input = "a \nb\u0001\fc";
+ final StrTokenizer tok = new StrTokenizer(input);
+ assertEquals("a", tok.next());
+ assertEquals("b\u0001", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasic4() {
+ final String input = "a \"b\" c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ assertEquals("a", tok.next());
+ assertEquals("\"b\"", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasic5() {
+ final String input = "a:b':c";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ assertEquals("a", tok.next());
+ assertEquals("b'", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicDelim1() {
+ final String input = "a:b:c";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicDelim2() {
+ final String input = "a:b:c";
+ final StrTokenizer tok = new StrTokenizer(input, ',');
+ assertEquals("a:b:c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicEmpty1() {
+ final String input = "a b c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setIgnoreEmptyTokens(false);
+ assertEquals("a", tok.next());
+ assertEquals("", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicEmpty2() {
+ final String input = "a b c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertNull(tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted1() {
+ final String input = "a 'b' c";
+ final StrTokenizer tok = new StrTokenizer(input, ' ', '\'');
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted2() {
+ final String input = "a:'b':";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertNull(tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted3() {
+ final String input = "a:'b''c'";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b'c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted4() {
+ final String input = "a: 'b' 'c' :d";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b c", tok.next());
+ assertEquals("d", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted5() {
+ final String input = "a: 'b'x'c' :d";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("bxc", tok.next());
+ assertEquals("d", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted6() {
+ final String input = "a:'b'\"c':d";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setQuoteMatcher(StrMatcher.quoteMatcher());
+ assertEquals("a", tok.next());
+ assertEquals("b\"c:d", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted7() {
+ final String input = "a:\"There's a reason here\":b";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setQuoteMatcher(StrMatcher.quoteMatcher());
+ assertEquals("a", tok.next());
+ assertEquals("There's a reason here", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuotedTrimmed1() {
+ final String input = "a: 'b' :";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertNull(tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicTrimmed1() {
+ final String input = "a: b : ";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertNull(tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicTrimmed2() {
+ final String input = "a: b :";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setTrimmerMatcher(StrMatcher.stringMatcher(" "));
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertNull(tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicIgnoreTrimmed1() {
+ final String input = "a: bIGNOREc : ";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setIgnoredMatcher(StrMatcher.stringMatcher("IGNORE"));
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("bc", tok.next());
+ assertNull(tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicIgnoreTrimmed2() {
+ final String input = "IGNOREaIGNORE: IGNORE bIGNOREc IGNORE : IGNORE ";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setIgnoredMatcher(StrMatcher.stringMatcher("IGNORE"));
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("bc", tok.next());
+ assertNull(tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicIgnoreTrimmed3() {
+ final String input = "IGNOREaIGNORE: IGNORE bIGNOREc IGNORE : IGNORE ";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setIgnoredMatcher(StrMatcher.stringMatcher("IGNORE"));
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals(" bc ", tok.next());
+ assertEquals(" ", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicIgnoreTrimmed4() {
+ final String input = "IGNOREaIGNORE: IGNORE 'bIGNOREc'IGNORE'd' IGNORE : IGNORE ";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setIgnoredMatcher(StrMatcher.stringMatcher("IGNORE"));
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("bIGNOREcd", tok.next());
+ assertNull(tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testListArray() {
+ final String input = "a b c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ final String[] array = tok.getTokenArray();
+ final List<?> list = tok.getTokenList();
+
+ assertEquals(Arrays.asList(array), list);
+ assertEquals(3, list.size());
+ }
+
+ private void testCSV(final String data) {
+ this.testXSVAbc(StrTokenizer.getCSVInstance(data));
+ this.testXSVAbc(StrTokenizer.getCSVInstance(data.toCharArray()));
+ }
+
+ @Test
+ public void testCSVEmpty() {
+ this.testEmpty(StrTokenizer.getCSVInstance());
+ this.testEmpty(StrTokenizer.getCSVInstance(""));
+ }
+
+ @Test
+ public void testCSVSimple() {
+ this.testCSV(CSV_SIMPLE_FIXTURE);
+ }
+
+ @Test
+ public void testCSVSimpleNeedsTrim() {
+ this.testCSV(" " + CSV_SIMPLE_FIXTURE);
+ this.testCSV(" \n\t " + CSV_SIMPLE_FIXTURE);
+ this.testCSV(" \n " + CSV_SIMPLE_FIXTURE + "\n\n\r");
+ }
+
+ void testEmpty(final StrTokenizer tokenizer) {
+ this.checkClone(tokenizer);
+ assertFalse(tokenizer.hasNext());
+ assertFalse(tokenizer.hasPrevious());
+ assertNull(tokenizer.nextToken());
+ assertEquals(0, tokenizer.size());
+ assertThrows(NoSuchElementException.class, tokenizer::next);
+ }
+
+ @Test
+ public void testGetContent() {
+ final String input = "a b c \"d e\" f ";
+ StrTokenizer tok = new StrTokenizer(input);
+ assertEquals(input, tok.getContent());
+
+ tok = new StrTokenizer(input.toCharArray());
+ assertEquals(input, tok.getContent());
+
+ tok = new StrTokenizer();
+ assertNull(tok.getContent());
+ }
+
+ @Test
+ public void testChaining() {
+ final StrTokenizer tok = new StrTokenizer();
+ assertEquals(tok, tok.reset());
+ assertEquals(tok, tok.reset(""));
+ assertEquals(tok, tok.reset(new char[0]));
+ assertEquals(tok, tok.setDelimiterChar(' '));
+ assertEquals(tok, tok.setDelimiterString(" "));
+ assertEquals(tok, tok.setDelimiterMatcher(null));
+ assertEquals(tok, tok.setQuoteChar(' '));
+ assertEquals(tok, tok.setQuoteMatcher(null));
+ assertEquals(tok, tok.setIgnoredChar(' '));
+ assertEquals(tok, tok.setIgnoredMatcher(null));
+ assertEquals(tok, tok.setTrimmerMatcher(null));
+ assertEquals(tok, tok.setEmptyTokenAsNull(false));
+ assertEquals(tok, tok.setIgnoreEmptyTokens(false));
+ }
+
+ /**
+ * Tests that the {@link StrTokenizer#clone()} clone method catches {@link CloneNotSupportedException} and returns
+ * {@code null}.
+ */
+ @Test
+ public void testCloneNotSupportedException() {
+ final Object notCloned = new StrTokenizer() {
+ @Override
+ Object cloneReset() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException("test");
+ }
+ }.clone();
+ assertNull(notCloned);
+ }
+
+ @Test
+ public void testCloneNull() {
+ final StrTokenizer tokenizer = new StrTokenizer((char[]) null);
+ // Start sanity check
+ assertNull(tokenizer.nextToken());
+ tokenizer.reset();
+ assertNull(tokenizer.nextToken());
+ // End sanity check
+ final StrTokenizer clonedTokenizer = (StrTokenizer) tokenizer.clone();
+ tokenizer.reset();
+ assertNull(tokenizer.nextToken());
+ assertNull(clonedTokenizer.nextToken());
+ }
+
+ @Test
+ public void testCloneReset() {
+ final char[] input = {'a'};
+ final StrTokenizer tokenizer = new StrTokenizer(input);
+ // Start sanity check
+ assertEquals("a", tokenizer.nextToken());
+ tokenizer.reset(input);
+ assertEquals("a", tokenizer.nextToken());
+ // End sanity check
+ final StrTokenizer clonedTokenizer = (StrTokenizer) tokenizer.clone();
+ input[0] = 'b';
+ tokenizer.reset(input);
+ assertEquals("b", tokenizer.nextToken());
+ assertEquals("a", clonedTokenizer.nextToken());
+ }
+
+ @Test
+ public void testConstructor_String() {
+ StrTokenizer tok = new StrTokenizer("a b");
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer("");
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((String) null);
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testConstructor_String_char() {
+ StrTokenizer tok = new StrTokenizer("a b", ' ');
+ assertEquals(1, tok.getDelimiterMatcher().isMatch(" ".toCharArray(), 0, 0, 1));
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer("", ' ');
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((String) null, ' ');
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testConstructor_String_char_char() {
+ StrTokenizer tok = new StrTokenizer("a b", ' ', '"');
+ assertEquals(1, tok.getDelimiterMatcher().isMatch(" ".toCharArray(), 0, 0, 1));
+ assertEquals(1, tok.getQuoteMatcher().isMatch("\"".toCharArray(), 0, 0, 1));
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer("", ' ', '"');
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((String) null, ' ', '"');
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testConstructor_charArray() {
+ StrTokenizer tok = new StrTokenizer("a b".toCharArray());
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer(new char[0]);
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((char[]) null);
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testConstructor_charArray_char() {
+ StrTokenizer tok = new StrTokenizer("a b".toCharArray(), ' ');
+ assertEquals(1, tok.getDelimiterMatcher().isMatch(" ".toCharArray(), 0, 0, 1));
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer(new char[0], ' ');
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((char[]) null, ' ');
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testConstructor_charArray_char_char() {
+ StrTokenizer tok = new StrTokenizer("a b".toCharArray(), ' ', '"');
+ assertEquals(1, tok.getDelimiterMatcher().isMatch(" ".toCharArray(), 0, 0, 1));
+ assertEquals(1, tok.getQuoteMatcher().isMatch("\"".toCharArray(), 0, 0, 1));
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer(new char[0], ' ', '"');
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((char[]) null, ' ', '"');
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testReset() {
+ final StrTokenizer tok = new StrTokenizer("a b c");
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok.reset();
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testReset_String() {
+ final StrTokenizer tok = new StrTokenizer("x x x");
+ tok.reset("d e");
+ assertEquals("d", tok.next());
+ assertEquals("e", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok.reset((String) null);
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testReset_charArray() {
+ final StrTokenizer tok = new StrTokenizer("x x x");
+
+ final char[] array = {'a', 'b', 'c'};
+ tok.reset(array);
+ assertEquals("abc", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok.reset((char[]) null);
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testTSV() {
+ this.testXSVAbc(StrTokenizer.getTSVInstance(TSV_SIMPLE_FIXTURE));
+ this.testXSVAbc(StrTokenizer.getTSVInstance(TSV_SIMPLE_FIXTURE.toCharArray()));
+ }
+
+ @Test
+ public void testTSVEmpty() {
+ this.testEmpty(StrTokenizer.getTSVInstance());
+ this.testEmpty(StrTokenizer.getTSVInstance(""));
+ }
+
+ void testXSVAbc(final StrTokenizer tokenizer) {
+ this.checkClone(tokenizer);
+ assertEquals(-1, tokenizer.previousIndex());
+ assertEquals(0, tokenizer.nextIndex());
+ assertNull(tokenizer.previousToken());
+ assertEquals("A", tokenizer.nextToken());
+ assertEquals(1, tokenizer.nextIndex());
+ assertEquals("b", tokenizer.nextToken());
+ assertEquals(2, tokenizer.nextIndex());
+ assertEquals("c", tokenizer.nextToken());
+ assertEquals(3, tokenizer.nextIndex());
+ assertNull(tokenizer.nextToken());
+ assertEquals(3, tokenizer.nextIndex());
+ assertEquals("c", tokenizer.previousToken());
+ assertEquals(2, tokenizer.nextIndex());
+ assertEquals("b", tokenizer.previousToken());
+ assertEquals(1, tokenizer.nextIndex());
+ assertEquals("A", tokenizer.previousToken());
+ assertEquals(0, tokenizer.nextIndex());
+ assertNull(tokenizer.previousToken());
+ assertEquals(0, tokenizer.nextIndex());
+ assertEquals(-1, tokenizer.previousIndex());
+ assertEquals(3, tokenizer.size());
+ }
+
+ @Test
+ public void testIteration() {
+ final StrTokenizer tkn = new StrTokenizer("a b c");
+ assertFalse(tkn.hasPrevious());
+ assertThrows(NoSuchElementException.class, tkn::previous);
+ assertTrue(tkn.hasNext());
+
+ assertEquals("a", tkn.next());
+ assertThrows(UnsupportedOperationException.class, tkn::remove);
+ assertThrows(UnsupportedOperationException.class, () -> tkn.set("x"));
+ assertThrows(UnsupportedOperationException.class, () -> tkn.add("y"));
+ assertTrue(tkn.hasPrevious());
+ assertTrue(tkn.hasNext());
+
+ assertEquals("b", tkn.next());
+ assertTrue(tkn.hasPrevious());
+ assertTrue(tkn.hasNext());
+
+ assertEquals("c", tkn.next());
+ assertTrue(tkn.hasPrevious());
+ assertFalse(tkn.hasNext());
+
+ assertThrows(NoSuchElementException.class, tkn::next);
+ assertTrue(tkn.hasPrevious());
+ assertFalse(tkn.hasNext());
+ }
+
+ @Test
+ public void testTokenizeSubclassInputChange() {
+ final StrTokenizer tkn = new StrTokenizer("a b c d e") {
+ @Override
+ protected List<String> tokenize(final char[] chars, final int offset, final int count) {
+ return super.tokenize("w x y z".toCharArray(), 2, 5);
+ }
+ };
+ assertEquals("x", tkn.next());
+ assertEquals("y", tkn.next());
+ }
+
+ @Test
+ public void testTokenizeSubclassOutputChange() {
+ final StrTokenizer tkn = new StrTokenizer("a b c") {
+ @Override
+ protected List<String> tokenize(final char[] chars, final int offset, final int count) {
+ final List<String> list = super.tokenize(chars, offset, count);
+ Collections.reverse(list);
+ return list;
+ }
+ };
+ assertEquals("c", tkn.next());
+ assertEquals("b", tkn.next());
+ assertEquals("a", tkn.next());
+ }
+
+ @Test
+ public void testToString() {
+ final StrTokenizer tkn = new StrTokenizer("a b c d e");
+ assertEquals("StrTokenizer[not tokenized yet]", tkn.toString());
+ tkn.next();
+ assertEquals("StrTokenizer[a, b, c, d, e]", tkn.toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java b/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java
new file mode 100644
index 000000000..3839575b8
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java
@@ -0,0 +1,432 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for WordUtils class.
+ */
+@Deprecated
+public class WordUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new WordUtils());
+ final Constructor<?>[] cons = WordUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(WordUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(WordUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testWrap_StringInt() {
+ assertNull(WordUtils.wrap(null, 20));
+ assertNull(WordUtils.wrap(null, -1));
+
+ assertEquals("", WordUtils.wrap("", 20));
+ assertEquals("", WordUtils.wrap("", -1));
+
+ // normal
+ final String systemNewLine = System.lineSeparator();
+ String input = "Here is one line of text that is going to be wrapped after 20 columns.";
+ String expected = "Here is one line of" + systemNewLine + "text that is going"
+ + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20));
+
+ // long word at end
+ input = "Click here to jump to the commons website - https://commons.apache.org";
+ expected = "Click here to jump" + systemNewLine + "to the commons" + systemNewLine
+ + "website -" + systemNewLine + "https://commons.apache.org";
+ assertEquals(expected, WordUtils.wrap(input, 20));
+
+ // long word in middle
+ input = "Click here, https://commons.apache.org, to jump to the commons website";
+ expected = "Click here," + systemNewLine + "https://commons.apache.org," + systemNewLine
+ + "to jump to the" + systemNewLine + "commons website";
+ assertEquals(expected, WordUtils.wrap(input, 20));
+
+ // leading spaces on a new line are stripped
+ // trailing spaces are not stripped
+ input = "word1 word2 word3";
+ expected = "word1 " + systemNewLine + "word2 " + systemNewLine + "word3";
+ assertEquals(expected, WordUtils.wrap(input, 7));
+ }
+
+ @Test
+ public void testWrap_StringIntStringBoolean() {
+ assertNull(WordUtils.wrap(null, 20, "\n", false));
+ assertNull(WordUtils.wrap(null, 20, "\n", true));
+ assertNull(WordUtils.wrap(null, 20, null, true));
+ assertNull(WordUtils.wrap(null, 20, null, false));
+ assertNull(WordUtils.wrap(null, -1, null, true));
+ assertNull(WordUtils.wrap(null, -1, null, false));
+
+ assertEquals("", WordUtils.wrap("", 20, "\n", false));
+ assertEquals("", WordUtils.wrap("", 20, "\n", true));
+ assertEquals("", WordUtils.wrap("", 20, null, false));
+ assertEquals("", WordUtils.wrap("", 20, null, true));
+ assertEquals("", WordUtils.wrap("", -1, null, false));
+ assertEquals("", WordUtils.wrap("", -1, null, true));
+
+ // normal
+ String input = "Here is one line of text that is going to be wrapped after 20 columns.";
+ String expected = "Here is one line of\ntext that is going\nto be wrapped after\n20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // unusual newline char
+ input = "Here is one line of text that is going to be wrapped after 20 columns.";
+ expected = "Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "<br />", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "<br />", true));
+
+ // short line length
+ input = "Here is one line";
+ expected = "Here\nis one\nline";
+ assertEquals(expected, WordUtils.wrap(input, 6, "\n", false));
+ expected = "Here\nis\none\nline";
+ assertEquals(expected, WordUtils.wrap(input, 2, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, -1, "\n", false));
+
+ // system newline char
+ final String systemNewLine = System.lineSeparator();
+ input = "Here is one line of text that is going to be wrapped after 20 columns.";
+ expected = "Here is one line of" + systemNewLine + "text that is going" + systemNewLine
+ + "to be wrapped after" + systemNewLine + "20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, null, false));
+ assertEquals(expected, WordUtils.wrap(input, 20, null, true));
+
+ // with extra spaces
+ input = " Here: is one line of text that is going to be wrapped after 20 columns.";
+ expected = "Here: is one line\nof text that is \ngoing to be \nwrapped after 20 \ncolumns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // with tab
+ input = "Here is\tone line of text that is going to be wrapped after 20 columns.";
+ expected = "Here is\tone line of\ntext that is going\nto be wrapped after\n20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // with tab at wrapColumn
+ input = "Here is one line of\ttext that is going to be wrapped after 20 columns.";
+ expected = "Here is one line\nof\ttext that is\ngoing to be wrapped\nafter 20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // difference because of long word
+ input = "Click here to jump to the commons website - https://commons.apache.org";
+ expected = "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ expected = "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apac\nhe.org";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // difference because of long word in middle
+ input = "Click here, https://commons.apache.org, to jump to the commons website";
+ expected = "Click here,\nhttps://commons.apache.org,\nto jump to the\ncommons website";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ expected = "Click here,\nhttps://commons.apac\nhe.org, to jump to\nthe commons website";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+ }
+
+ @Test
+ public void testWrap_StringIntStringBooleanString() {
+
+ //no changes test
+ String input = "flammable/inflammable";
+ String expected = "flammable/inflammable";
+ assertEquals(expected, WordUtils.wrap(input, 30, "\n", false, "/"));
+
+ // wrap on / and small width
+ expected = "flammable\ninflammable";
+ assertEquals(expected, WordUtils.wrap(input, 2, "\n", false, "/"));
+
+ // wrap long words on / 1
+ expected = "flammable\ninflammab\nle";
+ assertEquals(expected, WordUtils.wrap(input, 9, "\n", true, "/"));
+
+ // wrap long words on / 2
+ expected = "flammable\ninflammable";
+ assertEquals(expected, WordUtils.wrap(input, 15, "\n", true, "/"));
+
+ // wrap long words on / 3
+ input = "flammableinflammable";
+ expected = "flammableinflam\nmable";
+ assertEquals(expected, WordUtils.wrap(input, 15, "\n", true, "/"));
+ }
+
+ @Test
+ public void testCapitalize_String() {
+ assertNull(WordUtils.capitalize(null));
+ assertEquals("", WordUtils.capitalize(""));
+ assertEquals(" ", WordUtils.capitalize(" "));
+
+ assertEquals("I", WordUtils.capitalize("I") );
+ assertEquals("I", WordUtils.capitalize("i") );
+ assertEquals("I Am Here 123", WordUtils.capitalize("i am here 123") );
+ assertEquals("I Am Here 123", WordUtils.capitalize("I Am Here 123") );
+ assertEquals("I Am HERE 123", WordUtils.capitalize("i am HERE 123") );
+ assertEquals("I AM HERE 123", WordUtils.capitalize("I AM HERE 123") );
+ }
+
+ @Test
+ public void testCapitalizeWithDelimiters_String() {
+ assertNull(WordUtils.capitalize(null, null));
+ assertEquals("", WordUtils.capitalize(""));
+ assertEquals(" ", WordUtils.capitalize(" "));
+
+ char[] chars = { '-', '+', ' ', '@' };
+ assertEquals("I", WordUtils.capitalize("I", chars) );
+ assertEquals("I", WordUtils.capitalize("i", chars) );
+ assertEquals("I-Am Here+123", WordUtils.capitalize("i-am here+123", chars) );
+ assertEquals("I Am+Here-123", WordUtils.capitalize("I Am+Here-123", chars) );
+ assertEquals("I+Am-HERE 123", WordUtils.capitalize("i+am-HERE 123", chars) );
+ assertEquals("I-AM HERE+123", WordUtils.capitalize("I-AM HERE+123", chars) );
+ chars = new char[] {'.'};
+ assertEquals("I aM.Fine", WordUtils.capitalize("i aM.fine", chars) );
+ assertEquals("I Am.fine", WordUtils.capitalize("i am.fine", null) );
+ }
+
+ @Test
+ public void testCapitalizeFully_String() {
+ assertNull(WordUtils.capitalizeFully(null));
+ assertEquals("", WordUtils.capitalizeFully(""));
+ assertEquals(" ", WordUtils.capitalizeFully(" "));
+
+ assertEquals("I", WordUtils.capitalizeFully("I") );
+ assertEquals("I", WordUtils.capitalizeFully("i") );
+ assertEquals("I Am Here 123", WordUtils.capitalizeFully("i am here 123") );
+ assertEquals("I Am Here 123", WordUtils.capitalizeFully("I Am Here 123") );
+ assertEquals("I Am Here 123", WordUtils.capitalizeFully("i am HERE 123") );
+ assertEquals("I Am Here 123", WordUtils.capitalizeFully("I AM HERE 123") );
+ }
+
+ @Test
+ public void testCapitalizeFullyWithDelimiters_String() {
+ assertNull(WordUtils.capitalizeFully(null, null));
+ assertEquals("", WordUtils.capitalizeFully(""));
+ assertEquals(" ", WordUtils.capitalizeFully(" "));
+
+ char[] chars = { '-', '+', ' ', '@' };
+ assertEquals("I", WordUtils.capitalizeFully("I", chars) );
+ assertEquals("I", WordUtils.capitalizeFully("i", chars) );
+ assertEquals("I-Am Here+123", WordUtils.capitalizeFully("i-am here+123", chars) );
+ assertEquals("I Am+Here-123", WordUtils.capitalizeFully("I Am+Here-123", chars) );
+ assertEquals("I+Am-Here 123", WordUtils.capitalizeFully("i+am-HERE 123", chars) );
+ assertEquals("I-Am Here+123", WordUtils.capitalizeFully("I-AM HERE+123", chars) );
+ chars = new char[] {'.'};
+ assertEquals("I am.Fine", WordUtils.capitalizeFully("i aM.fine", chars) );
+ assertEquals("I Am.fine", WordUtils.capitalizeFully("i am.fine", null) );
+ }
+
+ @Test
+ public void testContainsAllWords_StringString() {
+ assertFalse(WordUtils.containsAllWords(null, (String) null));
+ assertFalse(WordUtils.containsAllWords(null, ""));
+ assertFalse(WordUtils.containsAllWords(null, "ab"));
+
+ assertFalse(WordUtils.containsAllWords("", (String) null));
+ assertFalse(WordUtils.containsAllWords("", ""));
+ assertFalse(WordUtils.containsAllWords("", "ab"));
+
+ assertFalse(WordUtils.containsAllWords("foo", (String) null));
+ assertFalse(WordUtils.containsAllWords("bar", ""));
+ assertFalse(WordUtils.containsAllWords("zzabyycdxx", "by"));
+ assertTrue(WordUtils.containsAllWords("lorem ipsum dolor sit amet", "ipsum", "lorem", "dolor"));
+ assertFalse(WordUtils.containsAllWords("lorem ipsum dolor sit amet", "ipsum", null, "lorem", "dolor"));
+ assertFalse(WordUtils.containsAllWords("lorem ipsum null dolor sit amet", "ipsum", null, "lorem", "dolor"));
+ assertFalse(WordUtils.containsAllWords("ab", "b"));
+ assertFalse(WordUtils.containsAllWords("ab", "z"));
+ }
+
+ @Test
+ public void testUncapitalize_String() {
+ assertNull(WordUtils.uncapitalize(null));
+ assertEquals("", WordUtils.uncapitalize(""));
+ assertEquals(" ", WordUtils.uncapitalize(" "));
+
+ assertEquals("i", WordUtils.uncapitalize("I") );
+ assertEquals("i", WordUtils.uncapitalize("i") );
+ assertEquals("i am here 123", WordUtils.uncapitalize("i am here 123") );
+ assertEquals("i am here 123", WordUtils.uncapitalize("I Am Here 123") );
+ assertEquals("i am hERE 123", WordUtils.uncapitalize("i am HERE 123") );
+ assertEquals("i aM hERE 123", WordUtils.uncapitalize("I AM HERE 123") );
+ }
+
+ @Test
+ public void testUncapitalizeWithDelimiters_String() {
+ assertNull(WordUtils.uncapitalize(null, null));
+ assertEquals("", WordUtils.uncapitalize(""));
+ assertEquals(" ", WordUtils.uncapitalize(" "));
+
+ char[] chars = { '-', '+', ' ', '@' };
+ assertEquals("i", WordUtils.uncapitalize("I", chars) );
+ assertEquals("i", WordUtils.uncapitalize("i", chars) );
+ assertEquals("i am-here+123", WordUtils.uncapitalize("i am-here+123", chars) );
+ assertEquals("i+am here-123", WordUtils.uncapitalize("I+Am Here-123", chars) );
+ assertEquals("i-am+hERE 123", WordUtils.uncapitalize("i-am+HERE 123", chars) );
+ assertEquals("i aM-hERE+123", WordUtils.uncapitalize("I AM-HERE+123", chars) );
+ chars = new char[] {'.'};
+ assertEquals("i AM.fINE", WordUtils.uncapitalize("I AM.FINE", chars) );
+ assertEquals("i aM.FINE", WordUtils.uncapitalize("I AM.FINE", null) );
+ }
+
+ @Test
+ public void testInitials_String() {
+ assertNull(WordUtils.initials(null));
+ assertEquals("", WordUtils.initials(""));
+ assertEquals("", WordUtils.initials(" "));
+
+ assertEquals("I", WordUtils.initials("I"));
+ assertEquals("i", WordUtils.initials("i"));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee"));
+ assertEquals("BJL", WordUtils.initials(" Ben \n John\tLee\t"));
+ assertEquals("BJ", WordUtils.initials("Ben J.Lee"));
+ assertEquals("BJ.L", WordUtils.initials(" Ben John . Lee"));
+ assertEquals("iah1", WordUtils.initials("i am here 123"));
+ }
+
+ @Test
+ public void testInitials_String_charArray() {
+ char[] array = null;
+ assertNull(WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("I", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("S", WordUtils.initials("SJC", array));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee", array));
+ assertEquals("BJL", WordUtils.initials(" Ben \n John\tLee\t", array));
+ assertEquals("BJ", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("BJ.L", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("KO", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("iah1", WordUtils.initials("i am here 123", array));
+
+ array = new char[0];
+ assertNull(WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("", WordUtils.initials("I", array));
+ assertEquals("", WordUtils.initials("i", array));
+ assertEquals("", WordUtils.initials("SJC", array));
+ assertEquals("", WordUtils.initials("Ben John Lee", array));
+ assertEquals("", WordUtils.initials(" Ben \n John\tLee\t", array));
+ assertEquals("", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("", WordUtils.initials("i am here 123", array));
+
+ array = " ".toCharArray();
+ assertNull(WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("I", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("S", WordUtils.initials("SJC", array));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee", array));
+ assertEquals("BJ", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("B\nJ", WordUtils.initials(" Ben \n John\tLee\t", array));
+ assertEquals("BJ.L", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("KO", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("iah1", WordUtils.initials("i am here 123", array));
+
+ array = " .".toCharArray();
+ assertNull(WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("I", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("S", WordUtils.initials("SJC", array));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee", array));
+ assertEquals("BJL", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("BJL", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("KO", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("iah1", WordUtils.initials("i am here 123", array));
+
+ array = " .'".toCharArray();
+ assertNull(WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("I", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("S", WordUtils.initials("SJC", array));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee", array));
+ assertEquals("BJL", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("BJL", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("KOM", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("iah1", WordUtils.initials("i am here 123", array));
+
+ array = "SIJo1".toCharArray();
+ assertNull(WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals(" ", WordUtils.initials(" ", array));
+ assertEquals("", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("C", WordUtils.initials("SJC", array));
+ assertEquals("Bh", WordUtils.initials("Ben John Lee", array));
+ assertEquals("B.", WordUtils.initials("Ben J.Lee", array));
+ assertEquals(" h", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("K", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("i2", WordUtils.initials("i am here 123", array));
+ }
+
+ @Test
+ public void testSwapCase_String() {
+ assertNull(WordUtils.swapCase(null));
+ assertEquals("", WordUtils.swapCase(""));
+ assertEquals(" ", WordUtils.swapCase(" "));
+
+ assertEquals("i", WordUtils.swapCase("I") );
+ assertEquals("I", WordUtils.swapCase("i") );
+ assertEquals("I AM HERE 123", WordUtils.swapCase("i am here 123") );
+ assertEquals("i aM hERE 123", WordUtils.swapCase("I Am Here 123") );
+ assertEquals("I AM here 123", WordUtils.swapCase("i am HERE 123") );
+ assertEquals("i am here 123", WordUtils.swapCase("I AM HERE 123") );
+
+ final String test = "This String contains a TitleCase character: \u01C8";
+ final String expect = "tHIS sTRING CONTAINS A tITLEcASE CHARACTER: \u01C9";
+ assertEquals(expect, WordUtils.swapCase(test));
+ }
+
+ @Test
+ public void testLANG1292() {
+ // Prior to fix, this was throwing StringIndexOutOfBoundsException
+ WordUtils.wrap("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 70);
+ }
+
+ @Test
+ public void testLANG1397() {
+ // Prior to fix, this was throwing StringIndexOutOfBoundsException
+ WordUtils.wrap("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Integer.MAX_VALUE);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/translate/EntityArraysTest.java b/src/test/java/org/apache/commons/lang3/text/translate/EntityArraysTest.java
new file mode 100644
index 000000000..58f832170
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/translate/EntityArraysTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.text.translate;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.translate.EntityArrays}.
+ */
+@Deprecated
+public class EntityArraysTest extends AbstractLangTest {
+
+ @Test
+ public void testConstructorExists() {
+ new EntityArrays();
+ }
+
+ // LANG-659 - check arrays for duplicate entries
+ @Test
+ public void testHTML40_EXTENDED_ESCAPE() {
+ final Set<String> col0 = new HashSet<>();
+ final Set<String> col1 = new HashSet<>();
+ final String [][] sa = EntityArrays.HTML40_EXTENDED_ESCAPE();
+ for (int i =0; i <sa.length; i++) {
+ assertTrue(col0.add(sa[i][0]), "Already added entry 0: "+i+" "+sa[i][0]);
+ assertTrue(col1.add(sa[i][1]), "Already added entry 1: "+i+" "+sa[i][1]);
+ }
+ }
+
+ // LANG-658 - check arrays for duplicate entries
+ @Test
+ public void testISO8859_1_ESCAPE() {
+ final Set<String> col0 = new HashSet<>();
+ final Set<String> col1 = new HashSet<>();
+ final String [][] sa = EntityArrays.ISO8859_1_ESCAPE();
+ boolean success = true;
+ for (int i =0; i <sa.length; i++) {
+ final boolean add0 = col0.add(sa[i][0]);
+ final boolean add1 = col1.add(sa[i][1]);
+ if (!add0) {
+ success = false;
+ System.out.println("Already added entry 0: "+i+" "+sa[i][0]+" "+sa[i][1]);
+ }
+ if (!add1) {
+ success = false;
+ System.out.println("Already added entry 1: "+i+" "+sa[i][0]+" "+sa[i][1]);
+ }
+ }
+ assertTrue(success, "One or more errors detected");
+ }
+
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/translate/LookupTranslatorTest.java b/src/test/java/org/apache/commons/lang3/text/translate/LookupTranslatorTest.java
new file mode 100644
index 000000000..6549f335f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/translate/LookupTranslatorTest.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.text.translate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.translate.LookupTranslator}.
+ */
+@Deprecated
+public class LookupTranslatorTest extends AbstractLangTest {
+
+ @Test
+ public void testBasicLookup() throws IOException {
+ final LookupTranslator lt = new LookupTranslator(new CharSequence[][] { { "one", "two" } });
+ final StringWriter out = new StringWriter();
+ final int result = lt.translate("one", 0, out);
+ assertEquals(3, result, "Incorrect code point consumption");
+ assertEquals("two", out.toString(), "Incorrect value");
+ }
+
+ // Tests: https://issues.apache.org/jira/browse/LANG-882
+ @Test
+ public void testLang882() throws IOException {
+ final LookupTranslator lt = new LookupTranslator(new CharSequence[][] { { new StringBuffer("one"), new StringBuffer("two") } });
+ final StringWriter out = new StringWriter();
+ final int result = lt.translate(new StringBuffer("one"), 0, out);
+ assertEquals(3, result, "Incorrect code point consumption");
+ assertEquals("two", out.toString(), "Incorrect value");
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/translate/NumericEntityEscaperTest.java b/src/test/java/org/apache/commons/lang3/text/translate/NumericEntityEscaperTest.java
new file mode 100644
index 000000000..3b92c4640
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/translate/NumericEntityEscaperTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.translate.NumericEntityEscaper}.
+ */
+@Deprecated
+public class NumericEntityEscaperTest extends AbstractLangTest {
+
+ @Test
+ public void testBelow() {
+ final NumericEntityEscaper nee = NumericEntityEscaper.below('F');
+
+ final String input = "ADFGZ";
+ final String result = nee.translate(input);
+ assertEquals("&#65;&#68;FGZ", result, "Failed to escape numeric entities via the below method");
+ }
+
+ @Test
+ public void testBetween() {
+ final NumericEntityEscaper nee = NumericEntityEscaper.between('F', 'L');
+
+ final String input = "ADFGZ";
+ final String result = nee.translate(input);
+ assertEquals("AD&#70;&#71;Z", result, "Failed to escape numeric entities via the between method");
+ }
+
+ @Test
+ public void testAbove() {
+ final NumericEntityEscaper nee = NumericEntityEscaper.above('F');
+
+ final String input = "ADFGZ";
+ final String result = nee.translate(input);
+ assertEquals("ADF&#71;&#90;", result, "Failed to escape numeric entities via the above method");
+ }
+
+ // See LANG-617
+ @Test
+ public void testSupplementary() {
+ final NumericEntityEscaper nee = new NumericEntityEscaper();
+ final String input = "\uD803\uDC22";
+ final String expected = "&#68642;";
+
+ final String result = nee.translate(input);
+ assertEquals(expected, result, "Failed to escape numeric entities supplementary characters");
+
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaperTest.java b/src/test/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaperTest.java
new file mode 100644
index 000000000..a1c00d371
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaperTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.translate.NumericEntityUnescaper}.
+ */
+@Deprecated
+public class NumericEntityUnescaperTest extends AbstractLangTest {
+
+ @Test
+ public void testSupplementaryUnescaping() {
+ final NumericEntityUnescaper neu = new NumericEntityUnescaper();
+ final String input = "&#68642;";
+ final String expected = "\uD803\uDC22";
+
+ final String result = neu.translate(input);
+ assertEquals(expected, result, "Failed to unescape numeric entities supplementary characters");
+ }
+
+ @Test
+ public void testOutOfBounds() {
+ final NumericEntityUnescaper neu = new NumericEntityUnescaper();
+
+ assertEquals("Test &", neu.translate("Test &"), "Failed to ignore when last character is &");
+ assertEquals("Test &#", neu.translate("Test &#"), "Failed to ignore when last character is &");
+ assertEquals("Test &#x", neu.translate("Test &#x"), "Failed to ignore when last character is &");
+ assertEquals("Test &#X", neu.translate("Test &#X"), "Failed to ignore when last character is &");
+ }
+
+ @Test
+ public void testUnfinishedEntity() {
+ // parse it
+ NumericEntityUnescaper neu = new NumericEntityUnescaper(NumericEntityUnescaper.OPTION.semiColonOptional);
+ String input = "Test &#x30 not test";
+ String expected = "Test \u0030 not test";
+
+ String result = neu.translate(input);
+ assertEquals(expected, result, "Failed to support unfinished entities (i.e. missing semicolon)");
+
+ // ignore it
+ neu = new NumericEntityUnescaper();
+ input = "Test &#x30 not test";
+ expected = input;
+
+ result = neu.translate(input);
+ assertEquals(expected, result, "Failed to ignore unfinished entities (i.e. missing semicolon)");
+
+ // fail it
+ final NumericEntityUnescaper failingNeu =
+ new NumericEntityUnescaper(NumericEntityUnescaper.OPTION.errorIfNoSemiColon);
+ final String failingInput = "Test &#x30 not test";
+ assertThrows(IllegalArgumentException.class, () -> failingNeu.translate(failingInput));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/translate/OctalUnescaperTest.java b/src/test/java/org/apache/commons/lang3/text/translate/OctalUnescaperTest.java
new file mode 100644
index 000000000..8fa2d4948
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/translate/OctalUnescaperTest.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.text.translate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.translate.OctalUnescaper}.
+ */
+@Deprecated
+public class OctalUnescaperTest extends AbstractLangTest {
+
+ @Test
+ public void testBetween() {
+ final OctalUnescaper oue = new OctalUnescaper(); //.between("1", "377");
+
+ String input = "\\45";
+ String result = oue.translate(input);
+ assertEquals("\45", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\377";
+ result = oue.translate(input);
+ assertEquals("\377", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\377 and";
+ result = oue.translate(input);
+ assertEquals("\377 and", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\378 and";
+ result = oue.translate(input);
+ assertEquals("\37" + "8 and", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\378";
+ result = oue.translate(input);
+ assertEquals("\37" + "8", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\1";
+ result = oue.translate(input);
+ assertEquals("\1", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\036";
+ result = oue.translate(input);
+ assertEquals("\036", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\0365";
+ result = oue.translate(input);
+ assertEquals("\036" + "5", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\003";
+ result = oue.translate(input);
+ assertEquals("\003", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\0003";
+ result = oue.translate(input);
+ assertEquals("\000" + "3", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\279";
+ result = oue.translate(input);
+ assertEquals("\279", result, "Failed to unescape octal characters via the between method");
+
+ input = "\\999";
+ result = oue.translate(input);
+ assertEquals("\\999", result, "Failed to ignore an out of range octal character via the between method");
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/translate/UnicodeEscaperTest.java b/src/test/java/org/apache/commons/lang3/text/translate/UnicodeEscaperTest.java
new file mode 100644
index 000000000..6e66bd84e
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/translate/UnicodeEscaperTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.translate.UnicodeEscaper}.
+ */
+@Deprecated
+public class UnicodeEscaperTest extends AbstractLangTest {
+
+ @Test
+ public void testBelow() {
+ final UnicodeEscaper ue = UnicodeEscaper.below('F');
+
+ final String input = "ADFGZ";
+ final String result = ue.translate(input);
+ assertEquals("\\u0041\\u0044FGZ", result, "Failed to escape Unicode characters via the below method");
+ }
+
+ @Test
+ public void testBetween() {
+ final UnicodeEscaper ue = UnicodeEscaper.between('F', 'L');
+
+ final String input = "ADFGZ";
+ final String result = ue.translate(input);
+ assertEquals("AD\\u0046\\u0047Z", result, "Failed to escape Unicode characters via the between method");
+ }
+
+ @Test
+ public void testAbove() {
+ final UnicodeEscaper ue = UnicodeEscaper.above('F');
+
+ final String input = "ADFGZ";
+ final String result = ue.translate(input);
+ assertEquals("ADF\\u0047\\u005A", result, "Failed to escape Unicode characters via the above method");
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/translate/UnicodeUnescaperTest.java b/src/test/java/org/apache/commons/lang3/text/translate/UnicodeUnescaperTest.java
new file mode 100644
index 000000000..54d1235ce
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/translate/UnicodeUnescaperTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.translate.UnicodeEscaper}.
+ */
+@Deprecated
+public class UnicodeUnescaperTest extends AbstractLangTest {
+
+ // Requested in LANG-507
+ @Test
+ public void testUPlus() {
+ final UnicodeUnescaper uu = new UnicodeUnescaper();
+
+ final String input = "\\u+0047";
+ assertEquals("G", uu.translate(input), "Failed to unescape Unicode characters with 'u+' notation");
+ }
+
+ @Test
+ public void testUuuuu() {
+ final UnicodeUnescaper uu = new UnicodeUnescaper();
+
+ final String input = "\\uuuuuuuu0047";
+ final String result = uu.translate(input);
+ assertEquals("G", result, "Failed to unescape Unicode characters with many 'u' characters");
+ }
+
+ @Test
+ public void testLessThanFour() {
+ final UnicodeUnescaper uu = new UnicodeUnescaper();
+
+ final String input = "\\0047\\u006";
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> uu.translate(input),
+ "A lack of digits in a Unicode escape sequence failed to throw an exception");
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemoverTest.java b/src/test/java/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemoverTest.java
new file mode 100644
index 000000000..8ff0e2f50
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemoverTest.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.text.translate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link org.apache.commons.lang3.text.translate.UnicodeUnpairedSurrogateRemover}.
+ */
+@Deprecated
+public class UnicodeUnpairedSurrogateRemoverTest extends AbstractLangTest {
+ final UnicodeUnpairedSurrogateRemover subject = new UnicodeUnpairedSurrogateRemover();
+ final CharArrayWriter writer = new CharArrayWriter(); // nothing is ever written to it
+
+ @Test
+ public void testValidCharacters() throws IOException {
+ assertFalse(subject.translate(0xd7ff, writer));
+ assertFalse(subject.translate(0xe000, writer));
+ assertEquals(0, writer.size());
+ }
+
+ @Test
+ public void testInvalidCharacters() throws IOException {
+ assertTrue(subject.translate(0xd800, writer));
+ assertTrue(subject.translate(0xdfff, writer));
+ assertEquals(0, writer.size());
+ }
+}
+
diff --git a/src/test/java/org/apache/commons/lang3/time/CalendarUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/CalendarUtilsTest.java
new file mode 100644
index 000000000..79685f7a3
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/CalendarUtilsTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+public class CalendarUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testGetDayOfMonth() {
+ assertEquals(Calendar.getInstance().get(Calendar.DAY_OF_MONTH), CalendarUtils.INSTANCE.getDayOfMonth());
+ }
+
+ @Test
+ public void testGetDayOfYear() {
+ assertEquals(Calendar.getInstance().get(Calendar.DAY_OF_YEAR), CalendarUtils.INSTANCE.getDayOfYear());
+ }
+
+ @Test
+ public void testGetMonth() {
+ assertEquals(Calendar.getInstance().get(Calendar.MONTH), CalendarUtils.INSTANCE.getMonth());
+ }
+
+ @Test
+ public void testGetStandaloneLongMonthNames() {
+ final String[] monthNames = CalendarUtils.getInstance(Locale.GERMAN).getStandaloneLongMonthNames();
+ assertEquals(12, monthNames.length);
+ assertEquals("Januar", monthNames[0]);
+ assertEquals("Februar", monthNames[1]);
+ assertEquals("M\u00e4rz", monthNames[2]);
+ assertEquals("April", monthNames[3]);
+ assertEquals("Mai", monthNames[4]);
+ assertEquals("Juni", monthNames[5]);
+ assertEquals("Juli", monthNames[6]);
+ assertEquals("August", monthNames[7]);
+ assertEquals("September", monthNames[8]);
+ assertEquals("Oktober", monthNames[9]);
+ assertEquals("November", monthNames[10]);
+ assertEquals("Dezember", monthNames[11]);
+ }
+
+ @Test
+ public void testGetStandaloneShortMonthNames() {
+ final String[] monthNames = CalendarUtils.getInstance(Locale.GERMAN).getStandaloneShortMonthNames();
+ assertEquals(12, monthNames.length);
+ assertEquals("Jan", monthNames[0]);
+ assertEquals("Feb", monthNames[1]);
+ assertEquals("M\u00e4r", monthNames[2]);
+ assertEquals("Apr", monthNames[3]);
+ assertEquals("Mai", monthNames[4]);
+ assertEquals("Jun", monthNames[5]);
+ assertEquals("Jul", monthNames[6]);
+ assertEquals("Aug", monthNames[7]);
+ assertEquals("Sep", monthNames[8]);
+ assertEquals("Okt", monthNames[9]);
+ assertEquals("Nov", monthNames[10]);
+ assertEquals("Dez", monthNames[11]);
+ }
+
+ @Test
+ public void testGetYear() {
+ assertEquals(Calendar.getInstance().get(Calendar.YEAR), CalendarUtils.INSTANCE.getYear());
+ }
+
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/DateFormatUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/DateFormatUtilsTest.java
new file mode 100644
index 000000000..3e8fd08fd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/DateFormatUtilsTest.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.DefaultLocale;
+import org.junitpioneer.jupiter.DefaultTimeZone;
+
+/**
+ * TestCase for DateFormatUtils.
+ */
+@SuppressWarnings("deprecation") // tests lots of deprecated items
+public class DateFormatUtilsTest extends AbstractLangTest {
+ private void assertFormats(final String expectedValue, final String pattern, final TimeZone timeZone, final Calendar cal) {
+ assertEquals(expectedValue, DateFormatUtils.format(cal.getTime(), pattern, timeZone));
+ assertEquals(expectedValue, DateFormatUtils.format(cal.getTime().getTime(), pattern, timeZone));
+ assertEquals(expectedValue, DateFormatUtils.format(cal, pattern, timeZone));
+ }
+
+ private Calendar createFebruaryTestDate(final TimeZone timeZone) {
+ final Calendar cal = Calendar.getInstance(timeZone);
+ cal.set(2002, Calendar.FEBRUARY, 23, 9, 11, 12);
+ return cal;
+ }
+
+ private Calendar createJuneTestDate(final TimeZone timeZone) {
+ final Calendar cal = Calendar.getInstance(timeZone);
+ cal.set(2003, Calendar.JUNE, 8, 10, 11, 12);
+ return cal;
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new DateFormatUtils());
+ final Constructor<?>[] cons = DateFormatUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(DateFormatUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(DateFormatUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testDateISO() {
+ testGmtMinus3("2002-02-23", DateFormatUtils.ISO_DATE_FORMAT.getPattern());
+ testGmtMinus3("2002-02-23-03:00", DateFormatUtils.ISO_DATE_TIME_ZONE_FORMAT.getPattern());
+ testUTC("2002-02-23Z", DateFormatUtils.ISO_DATE_TIME_ZONE_FORMAT.getPattern());
+ }
+
+ @Test
+ public void testDateTimeISO() {
+ testGmtMinus3("2002-02-23T09:11:12", DateFormatUtils.ISO_DATETIME_FORMAT.getPattern());
+ testGmtMinus3("2002-02-23T09:11:12-03:00", DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern());
+ testUTC("2002-02-23T09:11:12Z", DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern());
+ }
+
+ @Test
+ public void testFormat() {
+ final Calendar c = Calendar.getInstance(FastTimeZone.getGmtTimeZone());
+ c.set(2005, Calendar.JANUARY, 1, 12, 0, 0);
+ c.setTimeZone(TimeZone.getDefault());
+ final StringBuilder buffer = new StringBuilder ();
+ final int year = c.get(Calendar.YEAR);
+ final int month = c.get(Calendar.MONTH) + 1;
+ final int day = c.get(Calendar.DAY_OF_MONTH);
+ final int hour = c.get(Calendar.HOUR_OF_DAY);
+ buffer.append (year);
+ buffer.append(month);
+ buffer.append(day);
+ buffer.append(hour);
+ assertEquals(buffer.toString(), DateFormatUtils.format(c.getTime(), "yyyyMdH"));
+
+ assertEquals(buffer.toString(), DateFormatUtils.format(c.getTime().getTime(), "yyyyMdH"));
+
+ assertEquals(buffer.toString(), DateFormatUtils.format(c.getTime(), "yyyyMdH", Locale.US));
+
+ assertEquals(buffer.toString(), DateFormatUtils.format(c.getTime().getTime(), "yyyyMdH", Locale.US));
+ }
+
+ @Test
+ public void testFormatCalendar() {
+ final Calendar c = Calendar.getInstance(FastTimeZone.getGmtTimeZone());
+ c.set(2005, Calendar.JANUARY, 1, 12, 0, 0);
+ c.setTimeZone(TimeZone.getDefault());
+ final StringBuilder buffer = new StringBuilder ();
+ final int year = c.get(Calendar.YEAR);
+ final int month = c.get(Calendar.MONTH) + 1;
+ final int day = c.get(Calendar.DAY_OF_MONTH);
+ final int hour = c.get(Calendar.HOUR_OF_DAY);
+ buffer.append (year);
+ buffer.append(month);
+ buffer.append(day);
+ buffer.append(hour);
+ assertEquals(buffer.toString(), DateFormatUtils.format(c, "yyyyMdH"));
+
+ assertEquals(buffer.toString(), DateFormatUtils.format(c.getTime(), "yyyyMdH"));
+
+ assertEquals(buffer.toString(), DateFormatUtils.format(c, "yyyyMdH", Locale.US));
+
+ assertEquals(buffer.toString(), DateFormatUtils.format(c.getTime(), "yyyyMdH", Locale.US));
+ }
+
+ @Test
+ public void testFormatUTC() {
+ final Calendar c = Calendar.getInstance(FastTimeZone.getGmtTimeZone());
+ c.set(2005, Calendar.JANUARY, 1, 12, 0, 0);
+ assertEquals ("2005-01-01T12:00:00", DateFormatUtils.formatUTC(c.getTime(), DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()));
+
+ assertEquals ("2005-01-01T12:00:00", DateFormatUtils.formatUTC(c.getTime().getTime(), DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()));
+
+ assertEquals ("2005-01-01T12:00:00", DateFormatUtils.formatUTC(c.getTime(), DateFormatUtils.ISO_DATETIME_FORMAT.getPattern(), Locale.US));
+
+ assertEquals ("2005-01-01T12:00:00", DateFormatUtils.formatUTC(c.getTime().getTime(), DateFormatUtils.ISO_DATETIME_FORMAT.getPattern(), Locale.US));
+ }
+
+ private void testGmtMinus3(final String expectedValue, final String pattern) {
+ final TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
+ assertFormats(expectedValue, pattern, timeZone, createFebruaryTestDate(timeZone));
+ }
+
+ @Test
+ public void testLANG1000() throws Exception {
+ final String date = "2013-11-18T12:48:05Z";
+ DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.parse(date);
+ }
+
+ @Test
+ public void testLANG1462() {
+ final TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
+ final Calendar calendar = createJuneTestDate(timeZone);
+ assertEquals("20030608101112", DateFormatUtils.format(calendar, "yyyyMMddHHmmss"));
+ calendar.setTimeZone(TimeZone.getTimeZone("JST"));
+ assertEquals("20030608221112", DateFormatUtils.format(calendar, "yyyyMMddHHmmss"));
+ }
+
+ @DefaultTimeZone("UTC")
+ @Test
+ public void testLang530() throws ParseException {
+ final Date d = new Date();
+ final String isoDateStr = DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(d);
+ final Date d2 = DateUtils.parseDate(isoDateStr, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern());
+ // the format loses milliseconds so have to reintroduce them
+ assertEquals(d.getTime(), d2.getTime() + d.getTime() % 1000, "Date not equal to itself ISO formatted and parsed");
+ }
+
+ /**
+ * According to LANG-916 (https://issues.apache.org/jira/browse/LANG-916),
+ * the format method did contain a bug: it did not use the TimeZone data.
+ *
+ * This method test that the bug is fixed.
+ */
+ @Test
+ public void testLang916() {
+
+ final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris"));
+ cal.clear();
+ cal.set(2009, 9, 16, 8, 42, 16);
+
+ // Long.
+ {
+ final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Europe/Paris"));
+ assertEquals("2009-10-16T08:42:16+02:00", value, "long");
+ }
+ {
+ final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Asia/Kolkata"));
+ assertEquals("2009-10-16T12:12:16+05:30", value, "long");
+ }
+ {
+ final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Europe/London"));
+ assertEquals("2009-10-16T07:42:16+01:00", value, "long");
+ }
+
+ // Calendar.
+ {
+ final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Europe/Paris"));
+ assertEquals("2009-10-16T08:42:16+02:00", value, "calendar");
+ }
+ {
+ final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Asia/Kolkata"));
+ assertEquals("2009-10-16T12:12:16+05:30", value, "calendar");
+ }
+ {
+ final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Europe/London"));
+ assertEquals("2009-10-16T07:42:16+01:00", value, "calendar");
+ }
+ }
+
+ @DefaultLocale(language = "en")
+ @Test
+ public void testSMTP() {
+ TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
+ Calendar june = createJuneTestDate(timeZone);
+
+ assertFormats("Sun, 08 Jun 2003 10:11:12 -0300", DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(),
+ timeZone, june);
+
+ timeZone = FastTimeZone.getGmtTimeZone();
+ june = createJuneTestDate(timeZone);
+ assertFormats("Sun, 08 Jun 2003 10:11:12 +0000", DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(),
+ timeZone, june);
+ }
+
+ @Test
+ public void testTimeISO() {
+ testGmtMinus3("T09:11:12", DateFormatUtils.ISO_TIME_FORMAT.getPattern());
+ testGmtMinus3("T09:11:12-03:00", DateFormatUtils.ISO_TIME_TIME_ZONE_FORMAT.getPattern());
+ testUTC("T09:11:12Z", DateFormatUtils.ISO_TIME_TIME_ZONE_FORMAT.getPattern());
+ }
+
+ @Test
+ public void testTimeNoTISO() {
+ testGmtMinus3("09:11:12", DateFormatUtils.ISO_TIME_NO_T_FORMAT.getPattern());
+ testGmtMinus3("09:11:12-03:00", DateFormatUtils.ISO_TIME_NO_T_TIME_ZONE_FORMAT.getPattern());
+ testUTC("09:11:12Z", DateFormatUtils.ISO_TIME_NO_T_TIME_ZONE_FORMAT.getPattern());
+ }
+
+ private void testUTC(final String expectedValue, final String pattern) {
+ final TimeZone timeZone = FastTimeZone.getGmtTimeZone();
+ assertFormats(expectedValue, pattern, timeZone, createFebruaryTestDate(timeZone));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/DateUtilsFragmentTest.java b/src/test/java/org/apache/commons/lang3/time/DateUtilsFragmentTest.java
new file mode 100644
index 000000000..960067938
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/DateUtilsFragmentTest.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.time;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class DateUtilsFragmentTest extends AbstractLangTest {
+
+ private static final int months = 7; // second final prime before 12
+ private static final int days = 23; // second final prime before 31 (and valid)
+ private static final int hours = 19; // second final prime before 24
+ private static final int minutes = 53; // second final prime before 60
+ private static final int seconds = 47; // third final prime before 60
+ private static final int millis = 991; // second final prime before 1000
+
+ private Date aDate;
+ private Calendar aCalendar;
+
+
+ @BeforeEach
+ public void setUp() {
+ aCalendar = Calendar.getInstance();
+ aCalendar.set(2005, months, days, hours, minutes, seconds);
+ aCalendar.set(Calendar.MILLISECOND, millis);
+ aDate = aCalendar.getTime();
+ }
+
+ @Test
+ public void testDateFragmentInLargerUnitWithCalendar() {
+ assertEquals(0, DateUtils.getFragmentInDays(aCalendar, Calendar.DATE));
+ }
+
+ @Test
+ public void testDateFragmentInLargerUnitWithDate() {
+ assertEquals(0, DateUtils.getFragmentInDays(aDate, Calendar.DATE));
+ }
+
+ @Test
+ public void testDayOfYearFragmentInLargerUnitWithCalendar() {
+ assertEquals(0, DateUtils.getFragmentInDays(aCalendar, Calendar.DAY_OF_YEAR));
+ }
+
+ @Test
+ public void testDayOfYearFragmentInLargerUnitWithDate() {
+ assertEquals(0, DateUtils.getFragmentInDays(aDate, Calendar.DAY_OF_YEAR));
+ }
+
+ @Test
+ public void testDaysOfMonthWithCalendar() {
+ final long testResult = DateUtils.getFragmentInDays(aCalendar, Calendar.MONTH);
+ assertEquals(days, testResult);
+ }
+
+ @Test
+ public void testDaysOfMonthWithDate() {
+ final long testResult = DateUtils.getFragmentInDays(aDate, Calendar.MONTH);
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(aDate);
+ assertEquals(cal.get(Calendar.DAY_OF_MONTH), testResult);
+ }
+
+ @Test
+ public void testDaysOfYearWithCalendar() {
+ final long testResult = DateUtils.getFragmentInDays(aCalendar, Calendar.YEAR);
+ assertEquals(aCalendar.get(Calendar.DAY_OF_YEAR), testResult);
+ }
+
+ @Test
+ public void testDaysOfYearWithDate() {
+ final long testResult = DateUtils.getFragmentInDays(aDate, Calendar.YEAR);
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(aDate);
+ assertEquals(cal.get(Calendar.DAY_OF_YEAR), testResult);
+ }
+
+ @Test
+ public void testHourOfDayFragmentInLargerUnitWithCalendar() {
+ assertEquals(0, DateUtils.getFragmentInHours(aCalendar, Calendar.HOUR_OF_DAY));
+ assertEquals(0, DateUtils.getFragmentInDays(aCalendar, Calendar.HOUR_OF_DAY));
+ }
+
+ @Test
+ public void testHourOfDayFragmentInLargerUnitWithDate() {
+ assertEquals(0, DateUtils.getFragmentInHours(aDate, Calendar.HOUR_OF_DAY));
+ assertEquals(0, DateUtils.getFragmentInDays(aDate, Calendar.HOUR_OF_DAY));
+ }
+
+ @Test
+ public void testHoursOfDayWithCalendar() {
+ long testResult = DateUtils.getFragmentInHours(aCalendar, Calendar.DATE);
+ final long expectedValue = hours;
+ assertEquals(expectedValue, testResult);
+ testResult = DateUtils.getFragmentInHours(aCalendar, Calendar.DAY_OF_YEAR);
+ assertEquals(expectedValue, testResult);
+ }
+
+ @Test
+ public void testHoursOfDayWithDate() {
+ long testResult = DateUtils.getFragmentInHours(aDate, Calendar.DATE);
+ final long expectedValue = hours;
+ assertEquals(expectedValue, testResult);
+ testResult = DateUtils.getFragmentInHours(aDate, Calendar.DAY_OF_YEAR);
+ assertEquals(expectedValue, testResult);
+ }
+
+ @Test
+ public void testHoursOfMonthWithCalendar() {
+ final long testResult = DateUtils.getFragmentInHours(aCalendar, Calendar.MONTH);
+ assertEquals( hours +(((days - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_HOUR,
+ testResult);
+ }
+
+ @Test
+ public void testHoursOfMonthWithDate() {
+ final long testResult = DateUtils.getFragmentInHours(aDate, Calendar.MONTH);
+ assertEquals(hours + (((days - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_HOUR,
+ testResult);
+ }
+
+ @Test
+ public void testHoursOfYearWithCalendar() {
+ final long testResult = DateUtils.getFragmentInHours(aCalendar, Calendar.YEAR);
+ assertEquals( hours +(((aCalendar.get(Calendar.DAY_OF_YEAR) - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_HOUR,
+ testResult);
+ }
+
+ @Test
+ public void testHoursOfYearWithDate() {
+ final long testResult = DateUtils.getFragmentInHours(aDate, Calendar.YEAR);
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(aDate);
+ assertEquals(hours + (((cal.get(Calendar.DAY_OF_YEAR) - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_HOUR,
+ testResult);
+ }
+
+ //Calendar.SECOND as useful fragment
+
+ @Test
+ public void testInvalidFragmentWithCalendar() {
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInMilliseconds(aCalendar, 0));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInSeconds(aCalendar, 0));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInMinutes(aCalendar, 0));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInHours(aCalendar, 0));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInDays(aCalendar, 0));
+ }
+
+ @Test
+ public void testInvalidFragmentWithDate() {
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInMilliseconds(aDate, 0));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInSeconds(aDate, 0));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInMinutes(aDate, 0));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInHours(aDate, 0));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.getFragmentInDays(aDate, 0));
+ }
+
+ //Calendar.MINUTE as useful fragment
+
+ @Test
+ public void testMillisecondFragmentInLargerUnitWithCalendar() {
+ assertEquals(0, DateUtils.getFragmentInMilliseconds(aCalendar, Calendar.MILLISECOND));
+ assertEquals(0, DateUtils.getFragmentInSeconds(aCalendar, Calendar.MILLISECOND));
+ assertEquals(0, DateUtils.getFragmentInMinutes(aCalendar, Calendar.MILLISECOND));
+ assertEquals(0, DateUtils.getFragmentInHours(aCalendar, Calendar.MILLISECOND));
+ assertEquals(0, DateUtils.getFragmentInDays(aCalendar, Calendar.MILLISECOND));
+ }
+
+ @Test
+ public void testMillisecondFragmentInLargerUnitWithDate() {
+ assertEquals(0, DateUtils.getFragmentInMilliseconds(aDate, Calendar.MILLISECOND));
+ assertEquals(0, DateUtils.getFragmentInSeconds(aDate, Calendar.MILLISECOND));
+ assertEquals(0, DateUtils.getFragmentInMinutes(aDate, Calendar.MILLISECOND));
+ assertEquals(0, DateUtils.getFragmentInHours(aDate, Calendar.MILLISECOND));
+ assertEquals(0, DateUtils.getFragmentInDays(aDate, Calendar.MILLISECOND));
+ }
+
+ @Test
+ public void testMillisecondsOfDayWithCalendar() {
+ long testresult = DateUtils.getFragmentInMilliseconds(aCalendar, Calendar.DATE);
+ final long expectedValue = millis + (seconds * DateUtils.MILLIS_PER_SECOND) + (minutes * DateUtils.MILLIS_PER_MINUTE) + (hours * DateUtils.MILLIS_PER_HOUR);
+ assertEquals(expectedValue, testresult);
+ testresult = DateUtils.getFragmentInMilliseconds(aCalendar, Calendar.DAY_OF_YEAR);
+ assertEquals(expectedValue, testresult);
+ }
+
+ //Calendar.DATE and Calendar.DAY_OF_YEAR as useful fragment
+ @Test
+ public void testMillisecondsOfDayWithDate() {
+ long testresult = DateUtils.getFragmentInMilliseconds(aDate, Calendar.DATE);
+ final long expectedValue = millis + (seconds * DateUtils.MILLIS_PER_SECOND) + (minutes * DateUtils.MILLIS_PER_MINUTE) + (hours * DateUtils.MILLIS_PER_HOUR);
+ assertEquals(expectedValue, testresult);
+ testresult = DateUtils.getFragmentInMilliseconds(aDate, Calendar.DAY_OF_YEAR);
+ assertEquals(expectedValue, testresult);
+ }
+
+ //Calendar.HOUR_OF_DAY as useful fragment
+
+ @Test
+ public void testMillisecondsOfHourWithCalendar() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aCalendar, Calendar.HOUR_OF_DAY);
+ assertEquals(millis + (seconds * DateUtils.MILLIS_PER_SECOND) + (minutes * DateUtils.MILLIS_PER_MINUTE), testResult);
+ }
+
+ @Test
+ public void testMillisecondsOfHourWithDate() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aDate, Calendar.HOUR_OF_DAY);
+ assertEquals(millis + (seconds * DateUtils.MILLIS_PER_SECOND) + (minutes * DateUtils.MILLIS_PER_MINUTE), testResult);
+ }
+
+ @Test
+ public void testMillisecondsOfMinuteWithCalender() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aCalendar, Calendar.MINUTE);
+ assertEquals(millis + (seconds * DateUtils.MILLIS_PER_SECOND), testResult);
+ }
+
+ @Test
+ public void testMillisecondsOfMinuteWithDate() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aDate, Calendar.MINUTE);
+ assertEquals(millis + (seconds * DateUtils.MILLIS_PER_SECOND), testResult);
+ }
+
+ @Test
+ public void testMillisecondsOfMonthWithCalendar() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aCalendar, Calendar.MONTH);
+ assertEquals(millis + (seconds * DateUtils.MILLIS_PER_SECOND) + (minutes * DateUtils.MILLIS_PER_MINUTE)
+ + (hours * DateUtils.MILLIS_PER_HOUR) + ((days - 1) * DateUtils.MILLIS_PER_DAY),
+testResult);
+ }
+
+ //Calendar.MONTH as useful fragment
+ @Test
+ public void testMillisecondsOfMonthWithDate() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aDate, Calendar.MONTH);
+ assertEquals(millis + (seconds * DateUtils.MILLIS_PER_SECOND) + (minutes * DateUtils.MILLIS_PER_MINUTE)
+ + (hours * DateUtils.MILLIS_PER_HOUR) + ((days - 1) * DateUtils.MILLIS_PER_DAY),
+ testResult);
+ }
+
+ @Test
+ public void testMillisecondsOfSecondWithCalendar() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aCalendar, Calendar.SECOND);
+ assertEquals(millis, testResult);
+ assertEquals(aCalendar.get(Calendar.MILLISECOND), testResult);
+ }
+
+ @Test
+ public void testMillisecondsOfSecondWithDate() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aDate, Calendar.SECOND);
+ assertEquals(millis, testResult);
+ }
+
+ @Test
+ public void testMillisecondsOfYearWithCalendar() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aCalendar, Calendar.YEAR);
+ assertEquals(millis + (seconds * DateUtils.MILLIS_PER_SECOND) + (minutes * DateUtils.MILLIS_PER_MINUTE)
+ + (hours * DateUtils.MILLIS_PER_HOUR) + ((aCalendar.get(Calendar.DAY_OF_YEAR) - 1) * DateUtils.MILLIS_PER_DAY),
+testResult);
+ }
+
+ //Calendar.YEAR as useful fragment
+ @Test
+ public void testMillisecondsOfYearWithDate() {
+ final long testResult = DateUtils.getFragmentInMilliseconds(aDate, Calendar.YEAR);
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(aDate);
+ assertEquals(millis + (seconds * DateUtils.MILLIS_PER_SECOND) + (minutes * DateUtils.MILLIS_PER_MINUTE)
+ + (hours * DateUtils.MILLIS_PER_HOUR) + ((cal.get(Calendar.DAY_OF_YEAR) - 1)* DateUtils.MILLIS_PER_DAY),
+ testResult);
+ }
+
+ @Test
+ public void testMinuteFragmentInLargerUnitWithCalendar() {
+ assertEquals(0, DateUtils.getFragmentInMinutes(aCalendar, Calendar.MINUTE));
+ assertEquals(0, DateUtils.getFragmentInHours(aCalendar, Calendar.MINUTE));
+ assertEquals(0, DateUtils.getFragmentInDays(aCalendar, Calendar.MINUTE));
+ }
+
+ @Test
+ public void testMinuteFragmentInLargerUnitWithDate() {
+ assertEquals(0, DateUtils.getFragmentInMinutes(aDate, Calendar.MINUTE));
+ assertEquals(0, DateUtils.getFragmentInHours(aDate, Calendar.MINUTE));
+ assertEquals(0, DateUtils.getFragmentInDays(aDate, Calendar.MINUTE));
+ }
+
+ @Test
+ public void testMinutesOfDayWithCalendar() {
+ long testResult = DateUtils.getFragmentInMinutes(aCalendar, Calendar.DATE);
+ final long expectedValue = minutes + ((hours * DateUtils.MILLIS_PER_HOUR))/ DateUtils.MILLIS_PER_MINUTE;
+ assertEquals(expectedValue, testResult);
+ testResult = DateUtils.getFragmentInMinutes(aCalendar, Calendar.DAY_OF_YEAR);
+ assertEquals(expectedValue, testResult);
+ }
+
+ @Test
+ public void testMinutesOfDayWithDate() {
+ long testResult = DateUtils.getFragmentInMinutes(aDate, Calendar.DATE);
+ final long expectedValue = minutes + ((hours * DateUtils.MILLIS_PER_HOUR))/ DateUtils.MILLIS_PER_MINUTE;
+ assertEquals(expectedValue, testResult);
+ testResult = DateUtils.getFragmentInMinutes(aDate, Calendar.DAY_OF_YEAR);
+ assertEquals(expectedValue, testResult);
+ }
+
+
+ @Test
+ public void testMinutesOfHourWithCalendar() {
+ final long testResult = DateUtils.getFragmentInMinutes(aCalendar, Calendar.HOUR_OF_DAY);
+ assertEquals(minutes, testResult);
+ }
+
+ @Test
+ public void testMinutesOfHourWithDate() {
+ final long testResult = DateUtils.getFragmentInMinutes(aDate, Calendar.HOUR_OF_DAY);
+ assertEquals(minutes, testResult);
+ }
+
+ @Test
+ public void testMinutesOfMonthWithCalendar() {
+ final long testResult = DateUtils.getFragmentInMinutes(aCalendar, Calendar.MONTH);
+ assertEquals( minutes +((hours * DateUtils.MILLIS_PER_HOUR) + ((days - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_MINUTE,
+ testResult);
+ }
+
+ @Test
+ public void testMinutesOfMonthWithDate() {
+ final long testResult = DateUtils.getFragmentInMinutes(aDate, Calendar.MONTH);
+ assertEquals(minutes
+ + ((hours * DateUtils.MILLIS_PER_HOUR) + ((days - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_MINUTE,
+ testResult);
+ }
+
+ @Test
+ public void testMinutesOfYearWithCalendar() {
+ final long testResult = DateUtils.getFragmentInMinutes(aCalendar, Calendar.YEAR);
+ assertEquals( minutes +((hours * DateUtils.MILLIS_PER_HOUR) + ((aCalendar.get(Calendar.DAY_OF_YEAR) - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_MINUTE,
+ testResult);
+ }
+
+ @Test
+ public void testMinutesOfYearWithDate() {
+ final long testResult = DateUtils.getFragmentInMinutes(aDate, Calendar.YEAR);
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(aDate);
+ assertEquals(minutes
+ + ((hours * DateUtils.MILLIS_PER_HOUR) + ((cal.get(Calendar.DAY_OF_YEAR) - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_MINUTE,
+ testResult);
+ }
+
+ @Test
+ public void testMinutesOfYearWithWrongOffsetBugWithCalendar() {
+ final Calendar c = Calendar.getInstance();
+ c.set(Calendar.MONTH, Calendar.JANUARY);
+ c.set(Calendar.DAY_OF_YEAR, 1);
+ c.set(Calendar.HOUR_OF_DAY, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ final long testResult = DateUtils.getFragmentInMinutes(c, Calendar.YEAR);
+ assertEquals( 0, testResult);
+ }
+
+ @Test
+ public void testNullCalendar() {
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInMilliseconds((Calendar) null, Calendar.MILLISECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInSeconds((Calendar) null, Calendar.MILLISECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInMinutes((Calendar) null, Calendar.MILLISECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInHours((Calendar) null, Calendar.MILLISECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInDays((Calendar) null, Calendar.MILLISECOND));
+ }
+
+ @Test
+ public void testNullDate() {
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInMilliseconds((Date) null, Calendar.MILLISECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInSeconds((Date) null, Calendar.MILLISECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInMinutes((Date) null, Calendar.MILLISECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInHours((Date) null, Calendar.MILLISECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.getFragmentInDays((Date) null, Calendar.MILLISECOND));
+ }
+
+ @Test
+ public void testSecondFragmentInLargerUnitWithCalendar() {
+ assertEquals(0, DateUtils.getFragmentInSeconds(aCalendar, Calendar.SECOND));
+ assertEquals(0, DateUtils.getFragmentInMinutes(aCalendar, Calendar.SECOND));
+ assertEquals(0, DateUtils.getFragmentInHours(aCalendar, Calendar.SECOND));
+ assertEquals(0, DateUtils.getFragmentInDays(aCalendar, Calendar.SECOND));
+ }
+
+ @Test
+ public void testSecondFragmentInLargerUnitWithDate() {
+ assertEquals(0, DateUtils.getFragmentInSeconds(aDate, Calendar.SECOND));
+ assertEquals(0, DateUtils.getFragmentInMinutes(aDate, Calendar.SECOND));
+ assertEquals(0, DateUtils.getFragmentInHours(aDate, Calendar.SECOND));
+ assertEquals(0, DateUtils.getFragmentInDays(aDate, Calendar.SECOND));
+ }
+
+ @Test
+ public void testSecondsOfDayWithCalendar() {
+ long testresult = DateUtils.getFragmentInSeconds(aCalendar, Calendar.DATE);
+ final long expectedValue = seconds + ((minutes * DateUtils.MILLIS_PER_MINUTE) + (hours * DateUtils.MILLIS_PER_HOUR))/ DateUtils.MILLIS_PER_SECOND;
+ assertEquals(expectedValue, testresult);
+ testresult = DateUtils.getFragmentInSeconds(aCalendar, Calendar.DAY_OF_YEAR);
+ assertEquals(expectedValue, testresult);
+ }
+
+ @Test
+ public void testSecondsOfDayWithDate() {
+ long testresult = DateUtils.getFragmentInSeconds(aDate, Calendar.DATE);
+ final long expectedValue = seconds + ((minutes * DateUtils.MILLIS_PER_MINUTE) + (hours * DateUtils.MILLIS_PER_HOUR))/ DateUtils.MILLIS_PER_SECOND;
+ assertEquals(expectedValue, testresult);
+ testresult = DateUtils.getFragmentInSeconds(aDate, Calendar.DAY_OF_YEAR);
+ assertEquals(expectedValue, testresult);
+ }
+
+ @Test
+ public void testSecondsofHourWithCalendar() {
+ final long testResult = DateUtils.getFragmentInSeconds(aCalendar, Calendar.HOUR_OF_DAY);
+ assertEquals(
+ seconds
+ + (minutes
+ * DateUtils.MILLIS_PER_MINUTE / DateUtils.MILLIS_PER_SECOND),
+ testResult);
+ }
+
+ @Test
+ public void testSecondsofHourWithDate() {
+ final long testResult = DateUtils.getFragmentInSeconds(aDate, Calendar.HOUR_OF_DAY);
+ assertEquals(
+ seconds
+ + (minutes
+ * DateUtils.MILLIS_PER_MINUTE / DateUtils.MILLIS_PER_SECOND),
+ testResult);
+ }
+
+ @Test
+ public void testSecondsofMinuteWithCalendar() {
+ final long testResult = DateUtils.getFragmentInSeconds(aCalendar, Calendar.MINUTE);
+ assertEquals(seconds, testResult);
+ assertEquals(aCalendar.get(Calendar.SECOND), testResult);
+ }
+
+ @Test
+ public void testSecondsofMinuteWithDate() {
+ final long testResult = DateUtils.getFragmentInSeconds(aDate, Calendar.MINUTE);
+ assertEquals(seconds, testResult);
+ }
+
+ @Test
+ public void testSecondsOfMonthWithCalendar() {
+ final long testResult = DateUtils.getFragmentInSeconds(aCalendar, Calendar.MONTH);
+ assertEquals(
+ seconds
+ + ((minutes * DateUtils.MILLIS_PER_MINUTE)
+ + (hours * DateUtils.MILLIS_PER_HOUR) + ((days - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_SECOND,
+ testResult);
+ }
+
+ @Test
+ public void testSecondsOfMonthWithDate() {
+ final long testResult = DateUtils.getFragmentInSeconds(aDate, Calendar.MONTH);
+ assertEquals(
+ seconds
+ + ((minutes * DateUtils.MILLIS_PER_MINUTE)
+ + (hours * DateUtils.MILLIS_PER_HOUR) + ((days - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_SECOND,
+ testResult);
+ }
+
+ @Test
+ public void testSecondsOfYearWithCalendar() {
+ final long testResult = DateUtils.getFragmentInSeconds(aCalendar, Calendar.YEAR);
+ assertEquals(
+ seconds
+ + ((minutes * DateUtils.MILLIS_PER_MINUTE)
+ + (hours * DateUtils.MILLIS_PER_HOUR) + ((aCalendar.get(Calendar.DAY_OF_YEAR) - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_SECOND,
+ testResult);
+ }
+
+ @Test
+ public void testSecondsOfYearWithDate() {
+ final long testResult = DateUtils.getFragmentInSeconds(aDate, Calendar.YEAR);
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(aDate);
+ assertEquals(
+ seconds
+ + ((minutes * DateUtils.MILLIS_PER_MINUTE)
+ + (hours * DateUtils.MILLIS_PER_HOUR) + ((cal.get(Calendar.DAY_OF_YEAR) - 1) * DateUtils.MILLIS_PER_DAY))
+ / DateUtils.MILLIS_PER_SECOND,
+ testResult);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/DateUtilsRoundingTest.java b/src/test/java/org/apache/commons/lang3/time/DateUtilsRoundingTest.java
new file mode 100644
index 000000000..255b25fd6
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/DateUtilsRoundingTest.java
@@ -0,0 +1,759 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * These Unit-tests will check all possible extremes when using some rounding-methods of DateUtils.
+ * The extremes are tested at the switch-point in milliseconds
+ *
+ * According to the implementation SEMI_MONTH will either round/truncate to the 1st or 16th
+ * When rounding Calendar.MONTH it depends on the number of days within that month.
+ * A month with 28 days will be rounded up from the 15th
+ * A month with 29 or 30 days will be rounded up from the 16th
+ * A month with 31 days will be rounded up from the 17th
+ *
+ * @since 3.0
+ */
+public class DateUtilsRoundingTest extends AbstractLangTest {
+
+ DateFormat dateTimeParser;
+
+ Date januaryOneDate;
+ Date targetYearDate;
+ //No targetMonths, these must be tested for every type of month(28-31 days)
+ Date targetDateDate, targetDayOfMonthDate, targetAmDate, targetPmDate;
+ Date targetHourOfDayDate, targetHourDate;
+ Date targetMinuteDate;
+ Date targetSecondDate;
+ Date targetMilliSecondDate;
+
+ Calendar januaryOneCalendar;
+ @SuppressWarnings("deprecation")
+ FastDateFormat fdf = DateFormatUtils.ISO_DATETIME_FORMAT;
+
+
+ /**
+ * When using this basetest all extremes are tested.<br>
+ * It will test the Date, Calendar and Object-implementation<br>
+ * lastRoundDownDate should round down to roundedDownDate<br>
+ * lastRoundDownDate + 1 millisecond should round up to roundedUpDate
+ *
+ * @param roundedUpDate the next rounded date after <strong>roundedDownDate</strong> when using <strong>calendarField</strong>
+ * @param roundedDownDate the result if <strong>lastRoundDownDate</strong> was rounded with <strong>calendarField</strong>
+ * @param lastRoundDownDate rounding this value with <strong>calendarField</strong> will result in <strong>roundedDownDate</strong>
+ * @param calendarField a Calendar.field value
+ * @since 3.0
+ */
+ protected void baseRoundTest(final Date roundedUpDate, final Date roundedDownDate, final Date lastRoundDownDate, final int calendarField) {
+ final Date firstRoundUpDate = DateUtils.addMilliseconds(lastRoundDownDate, 1);
+
+ //Date-comparison
+ assertEquals(roundedDownDate, DateUtils.round(roundedDownDate, calendarField));
+ assertEquals(roundedUpDate, DateUtils.round(roundedUpDate, calendarField));
+ assertEquals(roundedDownDate, DateUtils.round(lastRoundDownDate, calendarField));
+ assertEquals(roundedUpDate, DateUtils.round(firstRoundUpDate, calendarField));
+
+ //Calendar-initiations
+ final Calendar roundedUpCalendar;
+ Calendar roundedDownCalendar;
+ Calendar lastRoundDownCalendar;
+ final Calendar firstRoundUpCalendar;
+ roundedDownCalendar = Calendar.getInstance();
+ roundedUpCalendar = Calendar.getInstance();
+ lastRoundDownCalendar = Calendar.getInstance();
+ firstRoundUpCalendar = Calendar.getInstance();
+ roundedDownCalendar.setTime(roundedDownDate);
+ roundedUpCalendar.setTime(roundedUpDate);
+ lastRoundDownCalendar.setTime(lastRoundDownDate);
+ firstRoundUpCalendar.setTime(firstRoundUpDate);
+
+ //Calendar-comparison
+ assertEquals(roundedDownCalendar, DateUtils.round(roundedDownCalendar, calendarField));
+ assertEquals(roundedUpCalendar, DateUtils.round(roundedUpCalendar, calendarField));
+ assertEquals(roundedDownCalendar, DateUtils.round(lastRoundDownCalendar, calendarField));
+ assertEquals(roundedUpCalendar, DateUtils.round(firstRoundUpCalendar, calendarField));
+
+ //Object-comparison
+ assertEquals(roundedDownDate, DateUtils.round((Object) roundedDownDate, calendarField));
+ assertEquals(roundedUpDate, DateUtils.round((Object) roundedUpDate, calendarField));
+ assertEquals(roundedDownDate, DateUtils.round((Object) lastRoundDownDate, calendarField));
+ assertEquals(roundedUpDate, DateUtils.round((Object) firstRoundUpDate, calendarField));
+ assertEquals(roundedDownDate, DateUtils.round((Object) roundedDownCalendar, calendarField));
+ assertEquals(roundedUpDate, DateUtils.round((Object) roundedUpCalendar, calendarField));
+ assertEquals(roundedDownDate, DateUtils.round((Object) lastRoundDownDate, calendarField));
+ assertEquals(roundedUpDate, DateUtils.round((Object) firstRoundUpDate, calendarField));
+ }
+
+ /**
+ * When using this basetest all extremes are tested.<br>
+ * It will test the Date, Calendar and Object-implementation<br>
+ * lastTruncateDate should round down to truncatedDate<br>
+ * lastTruncateDate + 1 millisecond should never round down to truncatedDate
+ *
+ * @param truncatedDate expected Date when <strong>lastTruncateDate</strong> is truncated with <strong>calendarField</strong>
+ * @param lastTruncateDate the last possible Date which will truncate to <strong>truncatedDate</strong> with <strong>calendarField</strong>
+ * @param calendarField a Calendar.field value
+ * @since 3.0
+ */
+ protected void baseTruncateTest(final Date truncatedDate, final Date lastTruncateDate, final int calendarField) {
+ final Date nextTruncateDate = DateUtils.addMilliseconds(lastTruncateDate, 1);
+
+ //Date-comparison
+ assertEquals(truncatedDate, DateUtils.truncate(truncatedDate, calendarField), "Truncating "+ fdf.format(truncatedDate) +" as Date with CalendarField-value "+ calendarField +" must return itself");
+ assertEquals(truncatedDate, DateUtils.truncate(lastTruncateDate, calendarField));
+ assertNotEquals(truncatedDate, DateUtils.truncate(nextTruncateDate, calendarField), fdf.format(lastTruncateDate) + " is not an extreme when truncating as Date with CalendarField-value " + calendarField);
+
+ //Calendar-initiations
+ final Calendar truncatedCalendar;
+ Calendar lastTruncateCalendar;
+ final Calendar nextTruncateCalendar;
+ truncatedCalendar = Calendar.getInstance();
+ lastTruncateCalendar = Calendar.getInstance();
+ nextTruncateCalendar = Calendar.getInstance();
+ truncatedCalendar.setTime(truncatedDate);
+ lastTruncateCalendar.setTime(lastTruncateDate);
+ nextTruncateCalendar.setTime(nextTruncateDate);
+
+ //Calendar-comparison
+ assertEquals(truncatedCalendar, DateUtils.truncate(truncatedCalendar, calendarField), "Truncating "+ fdf.format(truncatedCalendar) +" as Calendar with CalendarField-value "+ calendarField +" must return itself");
+ assertEquals(truncatedCalendar, DateUtils.truncate(lastTruncateCalendar, calendarField));
+ assertNotEquals(truncatedCalendar, DateUtils.truncate(nextTruncateCalendar, calendarField), fdf.format(lastTruncateCalendar) + " is not an extreme when truncating as Calendar with CalendarField-value " + calendarField);
+
+ //Object-comparison
+ assertEquals(truncatedDate, DateUtils.truncate((Object) truncatedDate, calendarField), "Truncating "+ fdf.format(truncatedDate) +" as Date cast to Object with CalendarField-value "+ calendarField +" must return itself as Date");
+ assertEquals(truncatedDate, DateUtils.truncate((Object) lastTruncateDate, calendarField));
+ assertNotEquals(truncatedDate, DateUtils.truncate((Object) nextTruncateDate, calendarField), fdf.format(lastTruncateDate) + " is not an extreme when truncating as Date cast to Object with CalendarField-value " + calendarField);
+ assertEquals(truncatedDate, DateUtils.truncate((Object) truncatedCalendar, calendarField), "Truncating "+ fdf.format(truncatedCalendar) +" as Calendar cast to Object with CalendarField-value "+ calendarField +" must return itself as Date");
+ assertEquals(truncatedDate, DateUtils.truncate((Object) lastTruncateCalendar, calendarField));
+ assertNotEquals(truncatedDate, DateUtils.truncate((Object) nextTruncateCalendar, calendarField), fdf.format(lastTruncateCalendar) + " is not an extreme when truncating as Calendar cast to Object with CalendarField-value " + calendarField);
+ }
+
+ /**
+ *
+ * Any January 1 could be considered as the ultimate extreme.
+ * Instead of comparing the results if the input has a difference of 1 millisecond we check the output to be exactly January first.
+ *
+ * @param minDate the lower bound
+ * @param maxDate the upper bound
+ * @param calendarField a Calendar.field value
+ * @since 3.0
+ */
+ protected void roundToJanuaryFirst(final Date minDate, final Date maxDate, final int calendarField) {
+ assertEquals(januaryOneDate, DateUtils.round(januaryOneDate, calendarField), "Rounding "+ fdf.format(januaryOneDate) +" as Date with CalendarField-value "+ calendarField +" must return itself");
+ assertEquals(januaryOneDate, DateUtils.round(minDate, calendarField));
+ assertEquals(januaryOneDate, DateUtils.round(maxDate, calendarField));
+
+ final Calendar minCalendar = Calendar.getInstance();
+ minCalendar.setTime(minDate);
+ final Calendar maxCalendar = Calendar.getInstance();
+ maxCalendar.setTime(maxDate);
+ assertEquals(januaryOneCalendar, DateUtils.round(januaryOneCalendar, calendarField), "Rounding "+ fdf.format(januaryOneCalendar) +" as Date with CalendarField-value "+ calendarField +" must return itself");
+ assertEquals(januaryOneCalendar, DateUtils.round(minCalendar, calendarField));
+ assertEquals(januaryOneCalendar, DateUtils.round(maxCalendar, calendarField));
+
+ final Date toPrevRoundDate = DateUtils.addMilliseconds(minDate, -1);
+ final Date toNextRoundDate = DateUtils.addMilliseconds(maxDate, 1);
+ assertNotEquals(januaryOneDate, DateUtils.round(toPrevRoundDate, calendarField), fdf.format(minDate) + " is not an lower-extreme when rounding as Date with CalendarField-value " + calendarField);
+ assertNotEquals(januaryOneDate, DateUtils.round(toNextRoundDate, calendarField), fdf.format(maxDate) + " is not an upper-extreme when rounding as Date with CalendarField-value " + calendarField);
+
+ final Calendar toPrevRoundCalendar = Calendar.getInstance();
+ toPrevRoundCalendar.setTime(toPrevRoundDate);
+ final Calendar toNextRoundCalendar = Calendar.getInstance();
+ toNextRoundCalendar.setTime(toNextRoundDate);
+ assertNotEquals(januaryOneDate, DateUtils.round(toPrevRoundDate, calendarField), fdf.format(minCalendar) + " is not an lower-extreme when rounding as Date with CalendarField-value " + calendarField);
+ assertNotEquals(januaryOneDate, DateUtils.round(toNextRoundDate, calendarField), fdf.format(maxCalendar) + " is not an upper-extreme when rounding as Date with CalendarField-value " + calendarField);
+ }
+
+ @BeforeEach
+ public void setUp() throws Exception {
+
+ dateTimeParser = new SimpleDateFormat("MMM dd, yyyy H:mm:ss.SSS", Locale.ENGLISH);
+
+ targetYearDate = dateTimeParser.parse("January 1, 2007 0:00:00.000");
+ targetDateDate = targetDayOfMonthDate = dateTimeParser.parse("June 1, 2008 0:00:00.000");
+ targetAmDate = dateTimeParser.parse("June 1, 2008 0:00:00.000");
+ targetPmDate = dateTimeParser.parse("June 1, 2008 12:00:00.000");
+ targetHourDate = dateTimeParser.parse("June 1, 2008 8:00:00.000");
+ targetHourOfDayDate = dateTimeParser.parse("June 1, 2008 8:00:00.000");
+ targetMinuteDate = dateTimeParser.parse("June 1, 2008 8:15:00.000");
+ targetSecondDate = dateTimeParser.parse("June 1, 2008 8:15:14.000");
+ targetMilliSecondDate = dateTimeParser.parse("June 1, 2008 8:15:14.231");
+
+ januaryOneDate = dateTimeParser.parse("January 1, 2008 0:00:00.000");
+ januaryOneCalendar = Calendar.getInstance();
+ januaryOneCalendar.setTime(januaryOneDate);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.AM_PM
+ * Includes rounding the extremes of both AM and PM of one day
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundAmPm() throws Exception {
+ final int calendarField = Calendar.AM_PM;
+ Date roundedUpDate, roundedDownDate, lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ //AM
+ roundedUpDate = dateTimeParser.parse("June 1, 2008 12:00:00.000");
+ roundedDownDate = targetAmDate;
+ lastRoundedDownDate = dateTimeParser.parse("June 1, 2008 5:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //PM
+ roundedUpDate = dateTimeParser.parse("June 2, 2008 0:00:00.000");
+ roundedDownDate = targetPmDate;
+ lastRoundedDownDate = dateTimeParser.parse("June 1, 2008 17:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 31, 2007 18:00:00.000");
+ maxDate = dateTimeParser.parse("January 1, 2008 5:59:59.999");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.DATE
+ * Includes rounding the extremes of one day
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundDate() throws Exception {
+ final int calendarField = Calendar.DATE;
+ final Date roundedUpDate;
+ Date roundedDownDate;
+ final Date lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ roundedUpDate = dateTimeParser.parse("June 2, 2008 0:00:00.000");
+ roundedDownDate = targetDateDate;
+ lastRoundedDownDate = dateTimeParser.parse("June 1, 2008 11:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 31, 2007 12:00:00.000");
+ maxDate = dateTimeParser.parse("January 1, 2008 11:59:59.999");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.DAY_OF_MONTH
+ * Includes rounding the extremes of one day
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundDayOfMonth() throws Exception {
+ final int calendarField = Calendar.DAY_OF_MONTH;
+ final Date roundedUpDate;
+ final Date roundedDownDate;
+ final Date lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ roundedUpDate = dateTimeParser.parse("June 2, 2008 0:00:00.000");
+ roundedDownDate = targetDayOfMonthDate;
+ lastRoundedDownDate = dateTimeParser.parse("June 1, 2008 11:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 31, 2007 12:00:00.000");
+ maxDate = dateTimeParser.parse("January 1, 2008 11:59:59.999");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.HOUR
+ * Includes rounding the extremes of one hour
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundHour() throws Exception {
+ final int calendarField = Calendar.HOUR;
+ final Date roundedUpDate;
+ final Date roundedDownDate;
+ final Date lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ roundedUpDate = dateTimeParser.parse("June 1, 2008 9:00:00.000");
+ roundedDownDate = targetHourDate;
+ lastRoundedDownDate = dateTimeParser.parse("June 1, 2008 8:29:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 31, 2007 23:30:00.000");
+ maxDate = dateTimeParser.parse("January 1, 2008 0:29:59.999");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.HOUR_OF_DAY
+ * Includes rounding the extremes of one hour
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundHourOfDay() throws Exception {
+ final int calendarField = Calendar.HOUR_OF_DAY;
+ final Date roundedUpDate;
+ final Date roundedDownDate;
+ final Date lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ roundedUpDate = dateTimeParser.parse("June 1, 2008 9:00:00.000");
+ roundedDownDate = targetHourOfDayDate;
+ lastRoundedDownDate = dateTimeParser.parse("June 1, 2008 8:29:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 31, 2007 23:30:00.000");
+ maxDate = dateTimeParser.parse("January 1, 2008 0:29:59.999");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.MILLISECOND
+ * Includes rounding the extremes of one second
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundMilliSecond() throws Exception {
+ final int calendarField = Calendar.MILLISECOND;
+ final Date roundedUpDate;
+ final Date roundedDownDate;
+ final Date lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ roundedDownDate = lastRoundedDownDate = targetMilliSecondDate;
+ roundedUpDate = dateTimeParser.parse("June 1, 2008 8:15:14.232");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = maxDate = januaryOneDate;
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.MINUTE
+ * Includes rounding the extremes of one minute
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundMinute() throws Exception {
+ final int calendarField = Calendar.MINUTE;
+ final Date roundedUpDate;
+ final Date roundedDownDate;
+ final Date lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ roundedUpDate = dateTimeParser.parse("June 1, 2008 8:16:00.000");
+ roundedDownDate = targetMinuteDate;
+ lastRoundedDownDate = dateTimeParser.parse("June 1, 2008 8:15:29.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 31, 2007 23:59:30.000");
+ maxDate = dateTimeParser.parse("January 1, 2008 0:00:29.999");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.MONTH
+ * Includes rounding months with 28, 29, 30 and 31 days
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundMonth() throws Exception {
+ final int calendarField = Calendar.MONTH;
+ Date roundedUpDate, roundedDownDate, lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ //month with 28 days
+ roundedUpDate = dateTimeParser.parse("March 1, 2007 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("February 1, 2007 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("February 14, 2007 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 29 days
+ roundedUpDate = dateTimeParser.parse("March 1, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("February 1, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("February 15, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 30 days
+ roundedUpDate = dateTimeParser.parse("May 1, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("April 1, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("April 15, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 31 days
+ roundedUpDate = dateTimeParser.parse("June 1, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("May 1, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("May 16, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 17, 2007 00:00:00.000");
+ maxDate = dateTimeParser.parse("January 16, 2008 23:59:59.999");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.SECOND
+ * Includes rounding the extremes of one second
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundSecond() throws Exception {
+ final int calendarField = Calendar.SECOND;
+ final Date roundedUpDate;
+ final Date roundedDownDate;
+ final Date lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ roundedUpDate = dateTimeParser.parse("June 1, 2008 8:15:15.000");
+ roundedDownDate = targetSecondDate;
+ lastRoundedDownDate = dateTimeParser.parse("June 1, 2008 8:15:14.499");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 31, 2007 23:59:59.500");
+ maxDate = dateTimeParser.parse("January 1, 2008 0:00:00.499");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with DateUtils.SEMI_MONTH
+ * Includes rounding months with 28, 29, 30 and 31 days, each with first and second half
+ * Includes rounding to January 1
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundSemiMonth() throws Exception {
+ final int calendarField = DateUtils.SEMI_MONTH;
+ Date roundedUpDate, roundedDownDate, lastRoundedDownDate;
+ final Date minDate;
+ final Date maxDate;
+
+ //month with 28 days (1)
+ roundedUpDate = dateTimeParser.parse("February 16, 2007 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("February 1, 2007 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("February 8, 2007 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 28 days (2)
+ roundedUpDate = dateTimeParser.parse("March 1, 2007 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("February 16, 2007 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("February 23, 2007 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 29 days (1)
+ roundedUpDate = dateTimeParser.parse("February 16, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("February 1, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("February 8, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 29 days (2)
+ roundedUpDate = dateTimeParser.parse("March 1, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("February 16, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("February 23, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 30 days (1)
+ roundedUpDate = dateTimeParser.parse("April 16, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("April 1, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("April 8, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 30 days (2)
+ roundedUpDate = dateTimeParser.parse("May 1, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("April 16, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("April 23, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 31 days (1)
+ roundedUpDate = dateTimeParser.parse("May 16, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("May 1, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("May 8, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //month with 31 days (2)
+ roundedUpDate = dateTimeParser.parse("June 1, 2008 0:00:00.000");
+ roundedDownDate = dateTimeParser.parse("May 16, 2008 0:00:00.000");
+ lastRoundedDownDate = dateTimeParser.parse("May 23, 2008 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+
+ //round to January 1
+ minDate = dateTimeParser.parse("December 24, 2007 00:00:00.000");
+ maxDate = dateTimeParser.parse("January 8, 2008 23:59:59.999");
+ roundToJanuaryFirst(minDate, maxDate, calendarField);
+ }
+
+ /**
+ * Tests DateUtils.round()-method with Calendar.Year
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testRoundYear() throws Exception {
+ final int calendarField = Calendar.YEAR;
+ final Date roundedUpDate = dateTimeParser.parse("January 1, 2008 0:00:00.000");
+ final Date roundedDownDate = targetYearDate;
+ final Date lastRoundedDownDate = dateTimeParser.parse("June 30, 2007 23:59:59.999");
+ baseRoundTest(roundedUpDate, roundedDownDate, lastRoundedDownDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.AM_PM
+ * Includes truncating the extremes of both AM and PM of one day
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateAmPm() throws Exception {
+ final int calendarField = Calendar.AM_PM;
+
+ //AM
+ Date lastTruncateDate = dateTimeParser.parse("June 1, 2008 11:59:59.999");
+ baseTruncateTest(targetAmDate, lastTruncateDate, calendarField);
+
+ //PM
+ lastTruncateDate = dateTimeParser.parse("June 1, 2008 23:59:59.999");
+ baseTruncateTest(targetPmDate, lastTruncateDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.DATE
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateDate() throws Exception {
+ final int calendarField = Calendar.DATE;
+ final Date lastTruncateDate = dateTimeParser.parse("June 1, 2008 23:59:59.999");
+ baseTruncateTest(targetDateDate, lastTruncateDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.DAY_OF_MONTH
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateDayOfMonth() throws Exception {
+ final int calendarField = Calendar.DAY_OF_MONTH;
+ final Date lastTruncateDate = dateTimeParser.parse("June 1, 2008 23:59:59.999");
+ baseTruncateTest(targetDayOfMonthDate, lastTruncateDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.HOUR
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateHour() throws Exception {
+ final int calendarField = Calendar.HOUR;
+ final Date lastTruncateDate = dateTimeParser.parse("June 1, 2008 8:59:59.999");
+ baseTruncateTest(targetHourDate, lastTruncateDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.HOUR_OF_DAY
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateHourOfDay() throws Exception {
+ final int calendarField = Calendar.HOUR_OF_DAY;
+ final Date lastTruncateDate = dateTimeParser.parse("June 1, 2008 8:59:59.999");
+ baseTruncateTest(targetHourOfDayDate, lastTruncateDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.SECOND
+ *
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateMilliSecond() {
+ final int calendarField = Calendar.MILLISECOND;
+ baseTruncateTest(targetMilliSecondDate, targetMilliSecondDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.MINUTE
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateMinute() throws Exception {
+ final int calendarField = Calendar.MINUTE;
+ final Date lastTruncateDate = dateTimeParser.parse("June 1, 2008 8:15:59.999");
+ baseTruncateTest(targetMinuteDate, lastTruncateDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.MONTH
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateMonth() throws Exception {
+ final int calendarField = Calendar.MONTH;
+ final Date truncatedDate = dateTimeParser.parse("March 1, 2008 0:00:00.000");
+ final Date lastTruncateDate = dateTimeParser.parse("March 31, 2008 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.SECOND
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateSecond() throws Exception {
+ final int calendarField = Calendar.SECOND;
+ final Date lastTruncateDate = dateTimeParser.parse("June 1, 2008 8:15:14.999");
+ baseTruncateTest(targetSecondDate, lastTruncateDate, calendarField);
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with DateUtils.SEMI_MONTH
+ * Includes truncating months with 28, 29, 30 and 31 days, each with first and second half
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateSemiMonth() throws Exception {
+ final int calendarField = DateUtils.SEMI_MONTH;
+ Date truncatedDate, lastTruncateDate;
+
+ //month with 28 days (1)
+ truncatedDate = dateTimeParser.parse("February 1, 2007 0:00:00.000");
+ lastTruncateDate = dateTimeParser.parse("February 15, 2007 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+
+ //month with 28 days (2)
+ truncatedDate = dateTimeParser.parse("February 16, 2007 0:00:00.000");
+ lastTruncateDate = dateTimeParser.parse("February 28, 2007 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+
+ //month with 29 days (1)
+ truncatedDate = dateTimeParser.parse("February 1, 2008 0:00:00.000");
+ lastTruncateDate = dateTimeParser.parse("February 15, 2008 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+
+ //month with 29 days (2)
+ truncatedDate = dateTimeParser.parse("February 16, 2008 0:00:00.000");
+ lastTruncateDate = dateTimeParser.parse("February 29, 2008 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+
+ //month with 30 days (1)
+ truncatedDate = dateTimeParser.parse("April 1, 2008 0:00:00.000");
+ lastTruncateDate = dateTimeParser.parse("April 15, 2008 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+
+ //month with 30 days (2)
+ truncatedDate = dateTimeParser.parse("April 16, 2008 0:00:00.000");
+ lastTruncateDate = dateTimeParser.parse("April 30, 2008 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+
+ //month with 31 days (1)
+ truncatedDate = dateTimeParser.parse("March 1, 2008 0:00:00.000");
+ lastTruncateDate = dateTimeParser.parse("March 15, 2008 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+
+ //month with 31 days (2)
+ truncatedDate = dateTimeParser.parse("March 16, 2008 0:00:00.000");
+ lastTruncateDate = dateTimeParser.parse("March 31, 2008 23:59:59.999");
+ baseTruncateTest(truncatedDate, lastTruncateDate, calendarField);
+
+ }
+
+ /**
+ * Test DateUtils.truncate()-method with Calendar.YEAR
+ *
+ * @throws Exception so we don't have to catch it
+ * @since 3.0
+ */
+ @Test
+ public void testTruncateYear() throws Exception {
+ final int calendarField = Calendar.YEAR;
+ final Date lastTruncateDate = dateTimeParser.parse("December 31, 2007 23:59:59.999");
+ baseTruncateTest(targetYearDate, lastTruncateDate, calendarField);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java
new file mode 100644
index 000000000..d3e7abbac
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java
@@ -0,0 +1,1702 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.DefaultLocale;
+
+/**
+ * Tests {@link org.apache.commons.lang3.time.DateUtils}.
+ * <p>
+ * NOT THREAD-SAFE.
+ * </p>
+ */
+public class DateUtilsTest extends AbstractLangTest {
+
+ private static Date BASE_DATE;
+ private static TimeZone DEFAULT_ZONE;
+
+ /**
+ * Used to check that Calendar objects are close enough
+ * delta is in milliseconds
+ */
+ private static void assertCalendarsEquals(final String message, final Calendar cal1, final Calendar cal2, final long delta) {
+ assertFalse(Math.abs(cal1.getTime().getTime() - cal2.getTime().getTime()) > delta,
+ message + " expected " + cal1.getTime() + " but got " + cal2.getTime());
+ }
+
+ /**
+ * This checks that this is a 7 element iterator of Calendar objects
+ * that are dates (no time), and exactly 1 day spaced after each other.
+ */
+ private static void assertWeekIterator(final Iterator<?> it, final Calendar start) {
+ final Calendar end = (Calendar) start.clone();
+ end.add(Calendar.DATE, 6);
+
+ assertWeekIterator(it, start, end);
+ }
+
+ /**
+ * This checks that this is a 7 divisible iterator of Calendar objects
+ * that are dates (no time), and exactly 1 day spaced after each other
+ * (in addition to the proper start and stop dates)
+ */
+ private static void assertWeekIterator(final Iterator<?> it, final Calendar start, final Calendar end) {
+ Calendar cal = (Calendar) it.next();
+ assertCalendarsEquals("", start, cal, 0);
+ Calendar last = null;
+ int count = 1;
+ while (it.hasNext()) {
+ //Check this is just a date (no time component)
+ assertCalendarsEquals("", cal, DateUtils.truncate(cal, Calendar.DATE), 0);
+
+ last = cal;
+ cal = (Calendar) it.next();
+ count++;
+
+ //Check that this is one day more than the last date
+ last.add(Calendar.DATE, 1);
+ assertCalendarsEquals("", last, cal, 0);
+ }
+
+ assertFalse(count % 7 != 0, "There were " + count + " days in this iterator");
+ assertCalendarsEquals("", end, cal, 0);
+ }
+
+ /**
+ * Convenience method for when working with Date objects
+ */
+ private static void assertWeekIterator(final Iterator<?> it, final Date start, final Date end) {
+ final Calendar calStart = Calendar.getInstance();
+ calStart.setTime(start);
+ final Calendar calEnd = Calendar.getInstance();
+ calEnd.setTime(end);
+
+ assertWeekIterator(it, calStart, calEnd);
+ }
+
+ @BeforeAll
+ public static void classSetup() {
+ final GregorianCalendar cal = new GregorianCalendar(2000, 6, 5, 4, 3, 2);
+ cal.set(Calendar.MILLISECOND, 1);
+ BASE_DATE = cal.getTime();
+ }
+
+ private DateFormat dateParser;
+ private DateFormat dateTimeParser;
+ private Date dateAmPm1;
+ private Date dateAmPm2;
+ private Date dateAmPm3;
+ private Date dateAmPm4;
+ private Date date0;
+ private Date date1;
+ private Date date2;
+ private Date date3;
+ private Date date4;
+ private Date date5;
+ private Date date6;
+ private Date date7;
+ private Date date8;
+ private Calendar calAmPm1;
+ private Calendar calAmPm2;
+ private Calendar calAmPm3;
+ private Calendar calAmPm4;
+ private Calendar cal1;
+ private Calendar cal2;
+ private Calendar cal3;
+ private Calendar cal4;
+ private Calendar cal5;
+ private Calendar cal6;
+
+ private Calendar cal7;
+
+ private Calendar cal8;
+
+ private TimeZone zone;
+
+ @AfterEach
+ public void afterEachResetTimeZone() {
+ TimeZone.setDefault(DEFAULT_ZONE);
+ }
+
+ private void assertDate(final Date date, final int year, final int month, final int day, final int hour, final int min, final int sec, final int mil) {
+ final GregorianCalendar cal = new GregorianCalendar();
+ cal.setTime(date);
+ assertEquals(year, cal.get(Calendar.YEAR));
+ assertEquals(month, cal.get(Calendar.MONTH));
+ assertEquals(day, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(hour, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(min, cal.get(Calendar.MINUTE));
+ assertEquals(sec, cal.get(Calendar.SECOND));
+ assertEquals(mil, cal.get(Calendar.MILLISECOND));
+ }
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ dateParser = new SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH);
+ dateTimeParser = new SimpleDateFormat("MMM dd, yyyy H:mm:ss.SSS", Locale.ENGLISH);
+
+ dateAmPm1 = dateTimeParser.parse("February 3, 2002 01:10:00.000");
+ dateAmPm2 = dateTimeParser.parse("February 3, 2002 11:10:00.000");
+ dateAmPm3 = dateTimeParser.parse("February 3, 2002 13:10:00.000");
+ dateAmPm4 = dateTimeParser.parse("February 3, 2002 19:10:00.000");
+ date0 = dateTimeParser.parse("February 3, 2002 12:34:56.789");
+ date1 = dateTimeParser.parse("February 12, 2002 12:34:56.789");
+ date2 = dateTimeParser.parse("November 18, 2001 1:23:11.321");
+ DEFAULT_ZONE = TimeZone.getDefault();
+ zone = TimeZone.getTimeZone("MET");
+ try {
+ TimeZone.setDefault(zone);
+ dateTimeParser.setTimeZone(zone);
+ date3 = dateTimeParser.parse("March 30, 2003 05:30:45.000");
+ date4 = dateTimeParser.parse("March 30, 2003 01:10:00.000");
+ date5 = dateTimeParser.parse("March 30, 2003 01:40:00.000");
+ date6 = dateTimeParser.parse("March 30, 2003 02:10:00.000");
+ date7 = dateTimeParser.parse("March 30, 2003 02:40:00.000");
+ date8 = dateTimeParser.parse("October 26, 2003 05:30:45.000");
+ } finally {
+ dateTimeParser.setTimeZone(DEFAULT_ZONE);
+ TimeZone.setDefault(DEFAULT_ZONE);
+ }
+ calAmPm1 = Calendar.getInstance();
+ calAmPm1.setTime(dateAmPm1);
+ calAmPm2 = Calendar.getInstance();
+ calAmPm2.setTime(dateAmPm2);
+ calAmPm3 = Calendar.getInstance();
+ calAmPm3.setTime(dateAmPm3);
+ calAmPm4 = Calendar.getInstance();
+ calAmPm4.setTime(dateAmPm4);
+ cal1 = Calendar.getInstance();
+ cal1.setTime(date1);
+ cal2 = Calendar.getInstance();
+ cal2.setTime(date2);
+ try {
+ TimeZone.setDefault(zone);
+ cal3 = Calendar.getInstance();
+ cal3.setTime(date3);
+ cal4 = Calendar.getInstance();
+ cal4.setTime(date4);
+ cal5 = Calendar.getInstance();
+ cal5.setTime(date5);
+ cal6 = Calendar.getInstance();
+ cal6.setTime(date6);
+ cal7 = Calendar.getInstance();
+ cal7.setTime(date7);
+ cal8 = Calendar.getInstance();
+ cal8.setTime(date8);
+ } finally {
+ TimeZone.setDefault(DEFAULT_ZONE);
+ }
+ }
+
+ @Test
+ public void testAddDays() throws Exception {
+ Date result = DateUtils.addDays(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addDays(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 6, 4, 3, 2, 1);
+
+ result = DateUtils.addDays(BASE_DATE, -1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 4, 4, 3, 2, 1);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.addDays(null, 0));
+ }
+
+ @Test
+ public void testAddHours() throws Exception {
+ Date result = DateUtils.addHours(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addHours(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 5, 3, 2, 1);
+
+ result = DateUtils.addHours(BASE_DATE, -1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 3, 3, 2, 1);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.addHours(null, 0));
+ }
+
+ @Test
+ public void testAddMilliseconds() throws Exception {
+ Date result = DateUtils.addMilliseconds(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addMilliseconds(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 2);
+
+ result = DateUtils.addMilliseconds(BASE_DATE, -1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 0);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.addMilliseconds(null, 0));
+ }
+
+ @Test
+ public void testAddMinutes() throws Exception {
+ Date result = DateUtils.addMinutes(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addMinutes(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 4, 2, 1);
+
+ result = DateUtils.addMinutes(BASE_DATE, -1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 2, 2, 1);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.addMinutes(null, 0));
+ }
+
+ @Test
+ public void testAddMonths() throws Exception {
+ Date result = DateUtils.addMonths(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addMonths(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 7, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addMonths(BASE_DATE, -1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 5, 5, 4, 3, 2, 1);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.addMonths(null, 0));
+ }
+
+ @Test
+ public void testAddSeconds() throws Exception {
+ Date result = DateUtils.addSeconds(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addSeconds(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 3, 1);
+
+ result = DateUtils.addSeconds(BASE_DATE, -1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 1, 1);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.addSeconds(null, 0));
+ }
+
+ @Test
+ public void testAddWeeks() throws Exception {
+ Date result = DateUtils.addWeeks(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addWeeks(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 12, 4, 3, 2, 1);
+
+ result = DateUtils.addWeeks(BASE_DATE, -1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1); // july
+ assertDate(result, 2000, 5, 28, 4, 3, 2, 1); // june
+
+ assertThrows(NullPointerException.class, () -> DateUtils.addMonths(null, 0));
+ }
+
+ @Test
+ public void testAddYears() throws Exception {
+ Date result = DateUtils.addYears(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addYears(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2001, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.addYears(BASE_DATE, -1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 1999, 6, 5, 4, 3, 2, 1);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.addYears(null, 0));
+ }
+
+ /**
+ * Tests various values with the ceiling method
+ *
+ * @throws Exception so we don't have to catch it
+ */
+ @Test
+ public void testCeil() throws Exception {
+ // test javadoc
+ assertEquals(dateTimeParser.parse("March 28, 2002 14:00:00.000"),
+ DateUtils.ceiling(
+ dateTimeParser.parse("March 28, 2002 13:45:01.231"),
+ Calendar.HOUR),
+ "ceiling javadoc-1 failed");
+ assertEquals(dateTimeParser.parse("April 1, 2002 00:00:00.000"),
+ DateUtils.ceiling(
+ dateTimeParser.parse("March 28, 2002 13:45:01.231"),
+ Calendar.MONTH),
+ "ceiling javadoc-2 failed");
+
+ // tests public static Date ceiling(Date date, int field)
+ assertEquals(dateParser.parse("January 1, 2003"),
+ DateUtils.ceiling(date1, Calendar.YEAR),
+ "ceiling year-1 failed");
+ assertEquals(dateParser.parse("January 1, 2002"),
+ DateUtils.ceiling(date2, Calendar.YEAR),
+ "ceiling year-2 failed");
+ assertEquals(dateParser.parse("March 1, 2002"),
+ DateUtils.ceiling(date1, Calendar.MONTH),
+ "ceiling month-1 failed");
+ assertEquals(dateParser.parse("December 1, 2001"),
+ DateUtils.ceiling(date2, Calendar.MONTH),
+ "ceiling month-2 failed");
+ assertEquals(dateParser.parse("February 16, 2002"),
+ DateUtils.ceiling(date1, DateUtils.SEMI_MONTH),
+ "ceiling semimonth-1 failed");
+ assertEquals(dateParser.parse("December 1, 2001"),
+ DateUtils.ceiling(date2, DateUtils.SEMI_MONTH),
+ "ceiling semimonth-2 failed");
+ assertEquals(dateParser.parse("February 13, 2002"),
+ DateUtils.ceiling(date1, Calendar.DATE),
+ "ceiling date-1 failed");
+ assertEquals(dateParser.parse("November 19, 2001"),
+ DateUtils.ceiling(date2, Calendar.DATE),
+ "ceiling date-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 13:00:00.000"),
+ DateUtils.ceiling(date1, Calendar.HOUR),
+ "ceiling hour-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 2:00:00.000"),
+ DateUtils.ceiling(date2, Calendar.HOUR),
+ "ceiling hour-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:35:00.000"),
+ DateUtils.ceiling(date1, Calendar.MINUTE),
+ "ceiling minute-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:24:00.000"),
+ DateUtils.ceiling(date2, Calendar.MINUTE),
+ "ceiling minute-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:57.000"),
+ DateUtils.ceiling(date1, Calendar.SECOND),
+ "ceiling second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:12.000"),
+ DateUtils.ceiling(date2, Calendar.SECOND),
+ "ceiling second-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.ceiling(dateAmPm1, Calendar.AM_PM),
+ "ceiling ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.ceiling(dateAmPm2, Calendar.AM_PM),
+ "ceiling ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.ceiling(dateAmPm3, Calendar.AM_PM),
+ "ceiling ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.ceiling(dateAmPm4, Calendar.AM_PM),
+ "ceiling ampm-4 failed");
+
+ // tests public static Date ceiling(Object date, int field)
+ assertEquals(dateParser.parse("January 1, 2003"),
+ DateUtils.ceiling((Object) date1, Calendar.YEAR),
+ "ceiling year-1 failed");
+ assertEquals(dateParser.parse("January 1, 2002"),
+ DateUtils.ceiling((Object) date2, Calendar.YEAR),
+ "ceiling year-2 failed");
+ assertEquals(dateParser.parse("March 1, 2002"),
+ DateUtils.ceiling((Object) date1, Calendar.MONTH),
+ "ceiling month-1 failed");
+ assertEquals(dateParser.parse("December 1, 2001"),
+ DateUtils.ceiling((Object) date2, Calendar.MONTH),
+ "ceiling month-2 failed");
+ assertEquals(dateParser.parse("February 16, 2002"),
+ DateUtils.ceiling((Object) date1, DateUtils.SEMI_MONTH),
+ "ceiling semimonth-1 failed");
+ assertEquals(dateParser.parse("December 1, 2001"),
+ DateUtils.ceiling((Object) date2, DateUtils.SEMI_MONTH),
+ "ceiling semimonth-2 failed");
+ assertEquals(dateParser.parse("February 13, 2002"),
+ DateUtils.ceiling((Object) date1, Calendar.DATE),
+ "ceiling date-1 failed");
+ assertEquals(dateParser.parse("November 19, 2001"),
+ DateUtils.ceiling((Object) date2, Calendar.DATE),
+ "ceiling date-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 13:00:00.000"),
+ DateUtils.ceiling((Object) date1, Calendar.HOUR),
+ "ceiling hour-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 2:00:00.000"),
+ DateUtils.ceiling((Object) date2, Calendar.HOUR),
+ "ceiling hour-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:35:00.000"),
+ DateUtils.ceiling((Object) date1, Calendar.MINUTE),
+ "ceiling minute-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:24:00.000"),
+ DateUtils.ceiling((Object) date2, Calendar.MINUTE),
+ "ceiling minute-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:57.000"),
+ DateUtils.ceiling((Object) date1, Calendar.SECOND),
+ "ceiling second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:12.000"),
+ DateUtils.ceiling((Object) date2, Calendar.SECOND),
+ "ceiling second-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.ceiling((Object) dateAmPm1, Calendar.AM_PM),
+ "ceiling ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.ceiling((Object) dateAmPm2, Calendar.AM_PM),
+ "ceiling ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.ceiling((Object) dateAmPm3, Calendar.AM_PM),
+ "ceiling ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.ceiling((Object) dateAmPm4, Calendar.AM_PM),
+ "ceiling ampm-4 failed");
+
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:57.000"),
+ DateUtils.ceiling((Object) cal1, Calendar.SECOND),
+ "ceiling calendar second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:12.000"),
+ DateUtils.ceiling((Object) cal2, Calendar.SECOND),
+ "ceiling calendar second-2 failed");
+
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.ceiling((Object) calAmPm1, Calendar.AM_PM),
+ "ceiling ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.ceiling((Object) calAmPm2, Calendar.AM_PM),
+ "ceiling ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.ceiling((Object) calAmPm3, Calendar.AM_PM),
+ "ceiling ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.ceiling((Object) calAmPm4, Calendar.AM_PM),
+ "ceiling ampm-4 failed");
+
+ assertThrows(NullPointerException.class, () -> DateUtils.ceiling((Date) null, Calendar.SECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.ceiling((Calendar) null, Calendar.SECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.ceiling((Object) null, Calendar.SECOND));
+ assertThrows(ClassCastException.class, () -> DateUtils.ceiling("", Calendar.SECOND));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.ceiling(date1, -9999));
+
+ // Fix for https://issues.apache.org/bugzilla/show_bug.cgi?id=25560
+ // Test ceiling across the beginning of daylight saving time
+ try {
+ TimeZone.setDefault(zone);
+ dateTimeParser.setTimeZone(zone);
+
+ assertEquals(dateTimeParser.parse("March 31, 2003 00:00:00.000"),
+ DateUtils.ceiling(date4, Calendar.DATE),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 31, 2003 00:00:00.000"),
+ DateUtils.ceiling((Object) cal4, Calendar.DATE),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 31, 2003 00:00:00.000"),
+ DateUtils.ceiling(date5, Calendar.DATE),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 31, 2003 00:00:00.000"),
+ DateUtils.ceiling((Object) cal5, Calendar.DATE),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 31, 2003 00:00:00.000"),
+ DateUtils.ceiling(date6, Calendar.DATE),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 31, 2003 00:00:00.000"),
+ DateUtils.ceiling((Object) cal6, Calendar.DATE),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 31, 2003 00:00:00.000"),
+ DateUtils.ceiling(date7, Calendar.DATE),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 31, 2003 00:00:00.000"),
+ DateUtils.ceiling((Object) cal7, Calendar.DATE),
+ "ceiling MET date across DST change-over");
+
+ assertEquals(dateTimeParser.parse("March 30, 2003 03:00:00.000"),
+ DateUtils.ceiling(date4, Calendar.HOUR_OF_DAY),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 03:00:00.000"),
+ DateUtils.ceiling((Object) cal4, Calendar.HOUR_OF_DAY),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 03:00:00.000"),
+ DateUtils.ceiling(date5, Calendar.HOUR_OF_DAY),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 03:00:00.000"),
+ DateUtils.ceiling((Object) cal5, Calendar.HOUR_OF_DAY),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 04:00:00.000"),
+ DateUtils.ceiling(date6, Calendar.HOUR_OF_DAY),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 04:00:00.000"),
+ DateUtils.ceiling((Object) cal6, Calendar.HOUR_OF_DAY),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 04:00:00.000"),
+ DateUtils.ceiling(date7, Calendar.HOUR_OF_DAY),
+ "ceiling MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 04:00:00.000"),
+ DateUtils.ceiling((Object) cal7, Calendar.HOUR_OF_DAY),
+ "ceiling MET date across DST change-over");
+
+ } finally {
+ TimeZone.setDefault(DEFAULT_ZONE);
+ dateTimeParser.setTimeZone(DEFAULT_ZONE);
+ }
+
+ // Bug 31395, large dates
+ final Date endOfTime = new Date(Long.MAX_VALUE); // fyi: Sun Aug 17 07:12:55 CET 292278994 -- 807 millis
+ final GregorianCalendar endCal = new GregorianCalendar();
+ endCal.setTime(endOfTime);
+ assertThrows(ArithmeticException.class, () -> DateUtils.ceiling(endCal, Calendar.DATE));
+ endCal.set(Calendar.YEAR, 280000001);
+ assertThrows(ArithmeticException.class, () -> DateUtils.ceiling(endCal, Calendar.DATE));
+ endCal.set(Calendar.YEAR, 280000000);
+ final Calendar cal = DateUtils.ceiling(endCal, Calendar.DATE);
+ assertEquals(0, cal.get(Calendar.HOUR));
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new DateUtils());
+ final Constructor<?>[] cons = DateUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(DateUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(DateUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testIsSameDay_Cal() {
+ final GregorianCalendar cala = new GregorianCalendar(2004, 6, 9, 13, 45);
+ final GregorianCalendar calb = new GregorianCalendar(2004, 6, 9, 13, 45);
+ assertTrue(DateUtils.isSameDay(cala, calb));
+ calb.add(Calendar.DAY_OF_YEAR, 1);
+ assertFalse(DateUtils.isSameDay(cala, calb));
+ cala.add(Calendar.DAY_OF_YEAR, 1);
+ assertTrue(DateUtils.isSameDay(cala, calb));
+ calb.add(Calendar.YEAR, 1);
+ assertFalse(DateUtils.isSameDay(cala, calb));
+ }
+
+ @Test
+ public void testIsSameDay_CalNotNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameDay(Calendar.getInstance(), null));
+ }
+
+ @Test
+ public void testIsSameDay_CalNullNotNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameDay(null, Calendar.getInstance()));
+ }
+
+ @Test
+ public void testIsSameDay_CalNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameDay((Calendar) null, null));
+ }
+
+ @Test
+ public void testIsSameDay_Date() {
+ Date datea = new GregorianCalendar(2004, 6, 9, 13, 45).getTime();
+ Date dateb = new GregorianCalendar(2004, 6, 9, 13, 45).getTime();
+ assertTrue(DateUtils.isSameDay(datea, dateb));
+ dateb = new GregorianCalendar(2004, 6, 10, 13, 45).getTime();
+ assertFalse(DateUtils.isSameDay(datea, dateb));
+ datea = new GregorianCalendar(2004, 6, 10, 13, 45).getTime();
+ assertTrue(DateUtils.isSameDay(datea, dateb));
+ dateb = new GregorianCalendar(2005, 6, 10, 13, 45).getTime();
+ assertFalse(DateUtils.isSameDay(datea, dateb));
+ }
+
+ @Test
+ public void testIsSameDay_DateNotNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameDay(new Date(), null));
+ }
+
+ @Test
+ public void testIsSameDay_DateNullNotNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameDay(null, new Date()));
+ }
+
+ @Test
+ public void testIsSameDay_DateNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameDay((Date) null, null));
+ }
+
+ @Test
+ public void testIsSameInstant_Cal() {
+ final GregorianCalendar cala = new GregorianCalendar(TimeZone.getTimeZone("GMT+1"));
+ final GregorianCalendar calb = new GregorianCalendar(TimeZone.getTimeZone("GMT-1"));
+ cala.set(2004, Calendar.JULY, 9, 13, 45, 0);
+ cala.set(Calendar.MILLISECOND, 0);
+ calb.set(2004, Calendar.JULY, 9, 13, 45, 0);
+ calb.set(Calendar.MILLISECOND, 0);
+ assertFalse(DateUtils.isSameInstant(cala, calb));
+
+ calb.set(2004, Calendar.JULY, 9, 11, 45, 0);
+ assertTrue(DateUtils.isSameInstant(cala, calb));
+ }
+
+ @Test
+ public void testIsSameInstant_CalNotNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameInstant(Calendar.getInstance(), null));
+ }
+
+ @Test
+ public void testIsSameInstant_CalNullNotNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameInstant(null, Calendar.getInstance()));
+ }
+
+ @Test
+ public void testIsSameInstant_CalNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameInstant((Calendar) null, null));
+ }
+
+ @Test
+ public void testIsSameInstant_Date() {
+ Date datea = new GregorianCalendar(2004, 6, 9, 13, 45).getTime();
+ Date dateb = new GregorianCalendar(2004, 6, 9, 13, 45).getTime();
+ assertTrue(DateUtils.isSameInstant(datea, dateb));
+ dateb = new GregorianCalendar(2004, 6, 10, 13, 45).getTime();
+ assertFalse(DateUtils.isSameInstant(datea, dateb));
+ datea = new GregorianCalendar(2004, 6, 10, 13, 45).getTime();
+ assertTrue(DateUtils.isSameInstant(datea, dateb));
+ dateb = new GregorianCalendar(2005, 6, 10, 13, 45).getTime();
+ assertFalse(DateUtils.isSameInstant(datea, dateb));
+ }
+
+ @Test
+ public void testIsSameInstant_DateNotNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameInstant(new Date(), null));
+ }
+
+ @Test
+ public void testIsSameInstant_DateNullNotNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameInstant(null, new Date()));
+ }
+
+ @Test
+ public void testIsSameInstant_DateNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameInstant((Date) null, null));
+ }
+
+ @Test
+ public void testIsSameLocalTime_Cal() {
+ final GregorianCalendar cala = new GregorianCalendar(TimeZone.getTimeZone("GMT+1"));
+ final GregorianCalendar calb = new GregorianCalendar(TimeZone.getTimeZone("GMT-1"));
+ cala.set(2004, Calendar.JULY, 9, 13, 45, 0);
+ cala.set(Calendar.MILLISECOND, 0);
+ calb.set(2004, Calendar.JULY, 9, 13, 45, 0);
+ calb.set(Calendar.MILLISECOND, 0);
+ assertTrue(DateUtils.isSameLocalTime(cala, calb));
+
+ final Calendar calc = Calendar.getInstance();
+ final Calendar cald = Calendar.getInstance();
+ calc.set(2004, Calendar.JULY, 9, 4, 0, 0);
+ cald.set(2004, Calendar.JULY, 9, 16, 0, 0);
+ calc.set(Calendar.MILLISECOND, 0);
+ cald.set(Calendar.MILLISECOND, 0);
+ assertFalse(DateUtils.isSameLocalTime(calc, cald), "LANG-677");
+
+ calb.set(2004, Calendar.JULY, 9, 11, 45, 0);
+ assertFalse(DateUtils.isSameLocalTime(cala, calb));
+ }
+
+ @Test
+ public void testIsSameLocalTime_CalNotNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameLocalTime(Calendar.getInstance(), null));
+ }
+
+ @Test
+ public void testIsSameLocalTime_CalNullNotNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameLocalTime(null, Calendar.getInstance()));
+ }
+
+ @Test
+ public void testIsSameLocalTime_CalNullNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.isSameLocalTime(null, null));
+ }
+
+ /**
+ * Tests the iterator exceptions
+ */
+ @Test
+ public void testIteratorEx() {
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.iterator(Calendar.getInstance(), -9999));
+ assertThrows(NullPointerException.class, () -> DateUtils.iterator((Date) null, DateUtils.RANGE_WEEK_CENTER));
+ assertThrows(NullPointerException.class, () -> DateUtils.iterator((Calendar) null, DateUtils.RANGE_WEEK_CENTER));
+ assertThrows(NullPointerException.class, () -> DateUtils.iterator((Object) null, DateUtils.RANGE_WEEK_CENTER));
+ assertThrows(ClassCastException.class, () -> DateUtils.iterator("", DateUtils.RANGE_WEEK_CENTER));
+ }
+
+ /** https://issues.apache.org/jira/browse/LANG-530 */
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testLang530() throws ParseException {
+ final Date d = new Date();
+ final String isoDateStr = DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(d);
+ final Date d2 = DateUtils.parseDate(isoDateStr, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern());
+ // the format loses milliseconds so have to reintroduce them
+ assertEquals(d.getTime(), d2.getTime() + d.getTime() % 1000, "Date not equal to itself ISO formatted and parsed");
+ }
+
+ @Test
+ public void testLANG799() throws ParseException {
+ DateUtils.parseDateStrictly("09 abril 2008 23:55:38 GMT", new Locale("es"), "dd MMM yyyy HH:mm:ss zzz");
+ }
+
+ /** Parse English date with German Locale. */
+ @DefaultLocale(language = "de")
+ @Test
+ public void testLANG799_DE_FAIL() {
+ assertThrows(ParseException.class, () -> DateUtils.parseDate("Wed, 09 Apr 2008 23:55:38 GMT", "EEE, dd MMM yyyy HH:mm:ss zzz"));
+ }
+
+ @DefaultLocale(language = "de")
+ @Test
+ public void testLANG799_DE_OK() throws ParseException {
+ DateUtils.parseDate("Mi, 09 Apr 2008 23:55:38 GMT", "EEE, dd MMM yyyy HH:mm:ss zzz");
+ DateUtils.parseDateStrictly("Mi, 09 Apr 2008 23:55:38 GMT", "EEE, dd MMM yyyy HH:mm:ss zzz");
+ }
+
+ // Parse German date with English Locale
+ @DefaultLocale(language = "en")
+ @Test
+ public void testLANG799_EN_FAIL() {
+ assertThrows(ParseException.class, () -> DateUtils.parseDate("Mi, 09 Apr 2008 23:55:38 GMT", "EEE, dd MMM yyyy HH:mm:ss zzz"));
+ }
+
+ @DefaultLocale(language = "en")
+ @Test
+ public void testLANG799_EN_OK() throws ParseException {
+ DateUtils.parseDate("Wed, 09 Apr 2008 23:55:38 GMT", "EEE, dd MMM yyyy HH:mm:ss zzz");
+ DateUtils.parseDateStrictly("Wed, 09 Apr 2008 23:55:38 GMT", "EEE, dd MMM yyyy HH:mm:ss zzz");
+ }
+
+ /** Parse German date with English Locale, specifying German Locale override. */
+ @DefaultLocale(language = "en")
+ @Test
+ public void testLANG799_EN_WITH_DE_LOCALE() throws ParseException {
+ DateUtils.parseDate("Mi, 09 Apr 2008 23:55:38 GMT", Locale.GERMAN, "EEE, dd MMM yyyy HH:mm:ss zzz");
+ }
+
+ /**
+ * Tests the calendar iterator for month-based ranges
+ *
+ * @throws Exception so we don't have to catch it
+ */
+ @Test
+ public void testMonthIterator() throws Exception {
+ Iterator<?> it = DateUtils.iterator(date1, DateUtils.RANGE_MONTH_SUNDAY);
+ assertWeekIterator(it,
+ dateParser.parse("January 27, 2002"),
+ dateParser.parse("March 2, 2002"));
+
+ it = DateUtils.iterator(date1, DateUtils.RANGE_MONTH_MONDAY);
+ assertWeekIterator(it,
+ dateParser.parse("January 28, 2002"),
+ dateParser.parse("March 3, 2002"));
+
+ it = DateUtils.iterator(date2, DateUtils.RANGE_MONTH_SUNDAY);
+ assertWeekIterator(it,
+ dateParser.parse("October 28, 2001"),
+ dateParser.parse("December 1, 2001"));
+
+ it = DateUtils.iterator(date2, DateUtils.RANGE_MONTH_MONDAY);
+ assertWeekIterator(it,
+ dateParser.parse("October 29, 2001"),
+ dateParser.parse("December 2, 2001"));
+ }
+
+ @Test
+ public void testParse_EmptyParsers() {
+ assertThrows(ParseException.class, () -> DateUtils.parseDate("19721203"));
+ }
+
+ @Test
+ public void testParse_NullParsers() {
+ assertThrows(NullPointerException.class, () -> DateUtils.parseDate("19721203", (String[]) null));
+ }
+
+ @Test
+ public void testParseDate() throws Exception {
+ final GregorianCalendar cal = new GregorianCalendar(1972, 11, 3);
+ String dateStr = "1972-12-03";
+ final String[] parsers = {"yyyy'-'DDD", "yyyy'-'MM'-'dd", "yyyyMMdd"};
+ Date date = DateUtils.parseDate(dateStr, parsers);
+ assertEquals(cal.getTime(), date);
+
+ dateStr = "1972-338";
+ date = DateUtils.parseDate(dateStr, parsers);
+ assertEquals(cal.getTime(), date);
+
+ dateStr = "19721203";
+ date = DateUtils.parseDate(dateStr, parsers);
+ assertEquals(cal.getTime(), date);
+ }
+
+ @Test
+ public void testParseDate_InvalidDateString() {
+ final String[] parsers = {"yyyy'-'DDD", "yyyy'-'MM'-'dd", "yyyyMMdd"};
+ assertThrows(ParseException.class, () -> DateUtils.parseDate("197212AB", parsers));
+ }
+
+ @Test
+ public void testParseDate_NoDateString() {
+ final String[] parsers = {"yyyy'-'DDD", "yyyy'-'MM'-'dd", "yyyyMMdd"};
+ assertThrows(ParseException.class, () -> DateUtils.parseDate("PURPLE", parsers));
+ }
+
+ @Test
+ public void testParseDate_Null() {
+ final String[] parsers = {"yyyy'-'DDD", "yyyy'-'MM'-'dd", "yyyyMMdd"};
+ assertThrows(NullPointerException.class, () -> DateUtils.parseDate(null, parsers));
+ }
+
+ /** LANG-486 */
+ @Test
+ public void testParseDateWithLeniency() throws ParseException {
+ final GregorianCalendar cal = new GregorianCalendar(1998, 6, 30);
+ final String dateStr = "02 942, 1996";
+ final String[] parsers = {"MM DDD, yyyy"};
+
+ final Date date = DateUtils.parseDate(dateStr, parsers);
+ assertEquals(cal.getTime(), date);
+
+ assertThrows(ParseException.class, () -> DateUtils.parseDateStrictly(dateStr, parsers));
+ }
+
+ /**
+ * Tests various values with the round method
+ *
+ * @throws Exception so we don't have to catch it
+ */
+ @Test
+ public void testRound() throws Exception {
+ // tests for public static Date round(Date date, int field)
+ assertEquals(dateParser.parse("January 1, 2002"),
+ DateUtils.round(date1, Calendar.YEAR),
+ "round year-1 failed");
+ assertEquals(dateParser.parse("January 1, 2002"),
+ DateUtils.round(date2, Calendar.YEAR),
+ "round year-2 failed");
+ assertEquals(dateParser.parse("February 1, 2002"),
+ DateUtils.round(date1, Calendar.MONTH),
+ "round month-1 failed");
+ assertEquals(dateParser.parse("December 1, 2001"),
+ DateUtils.round(date2, Calendar.MONTH),
+ "round month-2 failed");
+ assertEquals(dateParser.parse("February 1, 2002"),
+ DateUtils.round(date0, DateUtils.SEMI_MONTH),
+ "round semimonth-0 failed");
+ assertEquals(dateParser.parse("February 16, 2002"),
+ DateUtils.round(date1, DateUtils.SEMI_MONTH),
+ "round semimonth-1 failed");
+ assertEquals(dateParser.parse("November 16, 2001"),
+ DateUtils.round(date2, DateUtils.SEMI_MONTH),
+ "round semimonth-2 failed");
+
+
+ assertEquals(dateParser.parse("February 13, 2002"),
+ DateUtils.round(date1, Calendar.DATE),
+ "round date-1 failed");
+ assertEquals(dateParser.parse("November 18, 2001"),
+ DateUtils.round(date2, Calendar.DATE),
+ "round date-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 13:00:00.000"),
+ DateUtils.round(date1, Calendar.HOUR),
+ "round hour-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:00:00.000"),
+ DateUtils.round(date2, Calendar.HOUR),
+ "round hour-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:35:00.000"),
+ DateUtils.round(date1, Calendar.MINUTE),
+ "round minute-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:00.000"),
+ DateUtils.round(date2, Calendar.MINUTE),
+ "round minute-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:57.000"),
+ DateUtils.round(date1, Calendar.SECOND),
+ "round second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:11.000"),
+ DateUtils.round(date2, Calendar.SECOND),
+ "round second-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.round(dateAmPm1, Calendar.AM_PM),
+ "round ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.round(dateAmPm2, Calendar.AM_PM),
+ "round ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.round(dateAmPm3, Calendar.AM_PM),
+ "round ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.round(dateAmPm4, Calendar.AM_PM),
+ "round ampm-4 failed");
+
+ // tests for public static Date round(Object date, int field)
+ assertEquals(dateParser.parse("January 1, 2002"),
+ DateUtils.round((Object) date1, Calendar.YEAR),
+ "round year-1 failed");
+ assertEquals(dateParser.parse("January 1, 2002"),
+ DateUtils.round((Object) date2, Calendar.YEAR),
+ "round year-2 failed");
+ assertEquals(dateParser.parse("February 1, 2002"),
+ DateUtils.round((Object) date1, Calendar.MONTH),
+ "round month-1 failed");
+ assertEquals(dateParser.parse("December 1, 2001"),
+ DateUtils.round((Object) date2, Calendar.MONTH),
+ "round month-2 failed");
+ assertEquals(dateParser.parse("February 16, 2002"),
+ DateUtils.round((Object) date1, DateUtils.SEMI_MONTH),
+ "round semimonth-1 failed");
+ assertEquals(dateParser.parse("November 16, 2001"),
+ DateUtils.round((Object) date2, DateUtils.SEMI_MONTH),
+ "round semimonth-2 failed");
+ assertEquals(dateParser.parse("February 13, 2002"),
+ DateUtils.round((Object) date1, Calendar.DATE),
+ "round date-1 failed");
+ assertEquals(dateParser.parse("November 18, 2001"),
+ DateUtils.round((Object) date2, Calendar.DATE),
+ "round date-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 13:00:00.000"),
+ DateUtils.round((Object) date1, Calendar.HOUR),
+ "round hour-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:00:00.000"),
+ DateUtils.round((Object) date2, Calendar.HOUR),
+ "round hour-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:35:00.000"),
+ DateUtils.round((Object) date1, Calendar.MINUTE),
+ "round minute-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:00.000"),
+ DateUtils.round((Object) date2, Calendar.MINUTE),
+ "round minute-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:57.000"),
+ DateUtils.round((Object) date1, Calendar.SECOND),
+ "round second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:11.000"),
+ DateUtils.round((Object) date2, Calendar.SECOND),
+ "round second-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:57.000"),
+ DateUtils.round((Object) cal1, Calendar.SECOND),
+ "round calendar second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:11.000"),
+ DateUtils.round((Object) cal2, Calendar.SECOND),
+ "round calendar second-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.round((Object) dateAmPm1, Calendar.AM_PM),
+ "round ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.round((Object) dateAmPm2, Calendar.AM_PM),
+ "round ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.round((Object) dateAmPm3, Calendar.AM_PM),
+ "round ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.round((Object) dateAmPm4, Calendar.AM_PM),
+ "round ampm-4 failed");
+
+ assertThrows(NullPointerException.class, () -> DateUtils.round((Date) null, Calendar.SECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.round((Calendar) null, Calendar.SECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.round((Object) null, Calendar.SECOND));
+ assertThrows(ClassCastException.class, () -> DateUtils.round("", Calendar.SECOND));
+ assertThrows(IllegalArgumentException.class, () -> DateUtils.round(date1, -9999));
+
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.round((Object) calAmPm1, Calendar.AM_PM),
+ "round ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.round((Object) calAmPm2, Calendar.AM_PM),
+ "round ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.round((Object) calAmPm3, Calendar.AM_PM),
+ "round ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 4, 2002 00:00:00.000"),
+ DateUtils.round((Object) calAmPm4, Calendar.AM_PM),
+ "round ampm-4 failed");
+
+ // Fix for https://issues.apache.org/bugzilla/show_bug.cgi?id=25560 / LANG-13
+ // Test rounding across the beginning of daylight saving time
+ try {
+ TimeZone.setDefault(zone);
+ dateTimeParser.setTimeZone(zone);
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.round(date4, Calendar.DATE),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.round((Object) cal4, Calendar.DATE),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.round(date5, Calendar.DATE),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.round((Object) cal5, Calendar.DATE),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.round(date6, Calendar.DATE),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.round((Object) cal6, Calendar.DATE),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.round(date7, Calendar.DATE),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.round((Object) cal7, Calendar.DATE),
+ "round MET date across DST change-over");
+
+ assertEquals(dateTimeParser.parse("March 30, 2003 01:00:00.000"),
+ DateUtils.round(date4, Calendar.HOUR_OF_DAY),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 01:00:00.000"),
+ DateUtils.round((Object) cal4, Calendar.HOUR_OF_DAY),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 03:00:00.000"),
+ DateUtils.round(date5, Calendar.HOUR_OF_DAY),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 03:00:00.000"),
+ DateUtils.round((Object) cal5, Calendar.HOUR_OF_DAY),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 03:00:00.000"),
+ DateUtils.round(date6, Calendar.HOUR_OF_DAY),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 03:00:00.000"),
+ DateUtils.round((Object) cal6, Calendar.HOUR_OF_DAY),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 04:00:00.000"),
+ DateUtils.round(date7, Calendar.HOUR_OF_DAY),
+ "round MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 04:00:00.000"),
+ DateUtils.round((Object) cal7, Calendar.HOUR_OF_DAY),
+ "round MET date across DST change-over");
+ } finally {
+ TimeZone.setDefault(DEFAULT_ZONE);
+ dateTimeParser.setTimeZone(DEFAULT_ZONE);
+ }
+ }
+
+ /**
+ * Tests the Changes Made by LANG-346 to the DateUtils.modify() private method invoked
+ * by DateUtils.round().
+ *
+ * @throws Exception so we don't have to catch it
+ */
+ @Test
+ public void testRoundLang346() throws Exception {
+ final Calendar testCalendar = Calendar.getInstance();
+ testCalendar.set(2007, Calendar.JULY, 2, 8, 8, 50);
+ Date date = testCalendar.getTime();
+ assertEquals(dateTimeParser.parse("July 2, 2007 08:09:00.000"),
+ DateUtils.round(date, Calendar.MINUTE),
+ "Minute Round Up Failed");
+
+ testCalendar.set(2007, Calendar.JULY, 2, 8, 8, 20);
+ date = testCalendar.getTime();
+ assertEquals(dateTimeParser.parse("July 2, 2007 08:08:00.000"),
+ DateUtils.round(date, Calendar.MINUTE),
+ "Minute No Round Failed");
+
+ testCalendar.set(2007, Calendar.JULY, 2, 8, 8, 50);
+ testCalendar.set(Calendar.MILLISECOND, 600);
+ date = testCalendar.getTime();
+
+ assertEquals(dateTimeParser.parse("July 2, 2007 08:08:51.000"),
+ DateUtils.round(date, Calendar.SECOND),
+ "Second Round Up with 600 Milli Seconds Failed");
+
+ testCalendar.set(2007, Calendar.JULY, 2, 8, 8, 50);
+ testCalendar.set(Calendar.MILLISECOND, 200);
+ date = testCalendar.getTime();
+ assertEquals(dateTimeParser.parse("July 2, 2007 08:08:50.000"),
+ DateUtils.round(date, Calendar.SECOND),
+ "Second Round Down with 200 Milli Seconds Failed");
+
+ testCalendar.set(2007, Calendar.JULY, 2, 8, 8, 20);
+ testCalendar.set(Calendar.MILLISECOND, 600);
+ date = testCalendar.getTime();
+ assertEquals(dateTimeParser.parse("July 2, 2007 08:08:21.000"),
+ DateUtils.round(date, Calendar.SECOND),
+ "Second Round Up with 200 Milli Seconds Failed");
+
+ testCalendar.set(2007, Calendar.JULY, 2, 8, 8, 20);
+ testCalendar.set(Calendar.MILLISECOND, 200);
+ date = testCalendar.getTime();
+ assertEquals(dateTimeParser.parse("July 2, 2007 08:08:20.000"),
+ DateUtils.round(date, Calendar.SECOND),
+ "Second Round Down with 200 Milli Seconds Failed");
+
+ testCalendar.set(2007, Calendar.JULY, 2, 8, 8, 50);
+ date = testCalendar.getTime();
+ assertEquals(dateTimeParser.parse("July 2, 2007 08:00:00.000"),
+ DateUtils.round(date, Calendar.HOUR),
+ "Hour Round Down Failed");
+
+ testCalendar.set(2007, Calendar.JULY, 2, 8, 31, 50);
+ date = testCalendar.getTime();
+ assertEquals(dateTimeParser.parse("July 2, 2007 09:00:00.000"),
+ DateUtils.round(date, Calendar.HOUR),
+ "Hour Round Up Failed");
+ }
+
+ @Test
+ public void testSetDays() throws Exception {
+ Date result = DateUtils.setDays(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 1, 4, 3, 2, 1);
+
+ result = DateUtils.setDays(BASE_DATE, 29);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 29, 4, 3, 2, 1);
+
+ final String outsideOfRangeAssertionMessage = "DateUtils.setDays did not throw an expected IllegalArgumentException for amount outside of range 1 to 31.";
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setDays(BASE_DATE, 32),
+ outsideOfRangeAssertionMessage);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setDays(BASE_DATE, 0),
+ outsideOfRangeAssertionMessage);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.setDays(null, 1));
+ }
+
+ @Test
+ public void testSetHours() throws Exception {
+ Date result = DateUtils.setHours(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 0, 3, 2, 1);
+
+ result = DateUtils.setHours(BASE_DATE, 23);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 23, 3, 2, 1);
+
+ final String outsideOfRangeAssertionMessage = "DateUtils.setHours did not throw an expected IllegalArgumentException for amount outside of range 0 to 23.";
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setHours(BASE_DATE, 24),
+ outsideOfRangeAssertionMessage);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setHours(BASE_DATE, -1),
+ outsideOfRangeAssertionMessage);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.setHours(null, 0));
+ }
+
+ @Test
+ public void testSetMilliseconds() throws Exception {
+ Date result = DateUtils.setMilliseconds(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 0);
+
+ result = DateUtils.setMilliseconds(BASE_DATE, 999);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 999);
+
+ final String outsideOfRangeAssertionMessage = "DateUtils.setMilliseconds did not throw an expected IllegalArgumentException for range outside of 0 to 999.";
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setMilliseconds(BASE_DATE, 1000),
+ outsideOfRangeAssertionMessage);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setMilliseconds(BASE_DATE, -1),
+ outsideOfRangeAssertionMessage);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.setMilliseconds(null, 0));
+ }
+
+ @Test
+ public void testSetMinutes() throws Exception {
+ Date result = DateUtils.setMinutes(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 0, 2, 1);
+
+ result = DateUtils.setMinutes(BASE_DATE, 59);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 59, 2, 1);
+
+ final String outsideOfRangeAssertionMessage = "DateUtils.setMinutes did not throw an expected IllegalArgumentException for amount outside of range 0 to 59.";
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setMinutes(BASE_DATE, 60),
+ outsideOfRangeAssertionMessage);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setMinutes(BASE_DATE, -1),
+ outsideOfRangeAssertionMessage);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.setMinutes(null, 0));
+ }
+
+ @Test
+ public void testSetMonths() throws Exception {
+ Date result = DateUtils.setMonths(BASE_DATE, 5);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 5, 5, 4, 3, 2, 1);
+
+ result = DateUtils.setMonths(BASE_DATE, 1);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 1, 5, 4, 3, 2, 1);
+
+ result = DateUtils.setMonths(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 0, 5, 4, 3, 2, 1);
+
+ final String outsideOfRangeAssertionMessage = "DateUtils.setMonths did not throw an expected IllegalArgumentException for amount outside of range 0 to 11.";
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setMonths(BASE_DATE, 12),
+ outsideOfRangeAssertionMessage);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setMonths(BASE_DATE, -1),
+ outsideOfRangeAssertionMessage);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.setMonths(null, 0));
+ }
+
+ @Test
+ public void testSetSeconds() throws Exception {
+ Date result = DateUtils.setSeconds(BASE_DATE, 0);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 0, 1);
+
+ result = DateUtils.setSeconds(BASE_DATE, 59);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 59, 1);
+
+ final String outsideOfRangeAssertionMessage = "DateUtils.setSeconds did not throw an expected IllegalArgumentException for amount outside of range 0 to 59.";
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setSeconds(BASE_DATE, 60),
+ outsideOfRangeAssertionMessage);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DateUtils.setSeconds(BASE_DATE, -1),
+ outsideOfRangeAssertionMessage);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.setSeconds(null, 0));
+ }
+
+ @Test
+ public void testSetYears() throws Exception {
+ Date result = DateUtils.setYears(BASE_DATE, 2000);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2000, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.setYears(BASE_DATE, 2008);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2008, 6, 5, 4, 3, 2, 1);
+
+ result = DateUtils.setYears(BASE_DATE, 2005);
+ assertNotSame(BASE_DATE, result);
+ assertDate(BASE_DATE, 2000, 6, 5, 4, 3, 2, 1);
+ assertDate(result, 2005, 6, 5, 4, 3, 2, 1);
+
+ assertThrows(NullPointerException.class, () -> DateUtils.setYears(null, 0));
+ }
+
+ @Test
+ public void testToCalendar() {
+ assertEquals(date1, DateUtils.toCalendar(date1).getTime(), "Failed to convert to a Calendar and back");
+ assertThrows(NullPointerException.class, () -> DateUtils.toCalendar(null));
+ }
+
+ @Test
+ public void testToCalendarWithDateAndTimeZoneNotNull() {
+ final Calendar c = DateUtils.toCalendar(date2, DEFAULT_ZONE);
+ assertEquals(date2, c.getTime(), "Convert Date and TimeZone to a Calendar, but failed to get the Date back");
+ assertEquals(DEFAULT_ZONE, c.getTimeZone(), "Convert Date and TimeZone to a Calendar, but failed to get the TimeZone back");
+ }
+
+ @Test
+ public void testToCalendarWithDateAndTimeZoneNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.toCalendar(null, null));
+ }
+
+ @Test
+ public void testToCalendarWithDateNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.toCalendar(null, zone));
+ }
+
+ @Test
+ public void testToCalendarWithTimeZoneNull() {
+ assertThrows(NullPointerException.class, () -> DateUtils.toCalendar(date1, null));
+ }
+
+ /**
+ * Tests various values with the trunc method
+ *
+ * @throws Exception so we don't have to catch it
+ */
+ @Test
+ public void testTruncate() throws Exception {
+ // tests public static Date truncate(Date date, int field)
+ assertEquals(dateParser.parse("January 1, 2002"),
+ DateUtils.truncate(date1, Calendar.YEAR),
+ "truncate year-1 failed");
+ assertEquals(dateParser.parse("January 1, 2001"),
+ DateUtils.truncate(date2, Calendar.YEAR),
+ "truncate year-2 failed");
+ assertEquals(dateParser.parse("February 1, 2002"),
+ DateUtils.truncate(date1, Calendar.MONTH),
+ "truncate month-1 failed");
+ assertEquals(dateParser.parse("November 1, 2001"),
+ DateUtils.truncate(date2, Calendar.MONTH),
+ "truncate month-2 failed");
+ assertEquals(dateParser.parse("February 1, 2002"),
+ DateUtils.truncate(date1, DateUtils.SEMI_MONTH),
+ "truncate semimonth-1 failed");
+ assertEquals(dateParser.parse("November 16, 2001"),
+ DateUtils.truncate(date2, DateUtils.SEMI_MONTH),
+ "truncate semimonth-2 failed");
+ assertEquals(dateParser.parse("February 12, 2002"),
+ DateUtils.truncate(date1, Calendar.DATE),
+ "truncate date-1 failed");
+ assertEquals(dateParser.parse("November 18, 2001"),
+ DateUtils.truncate(date2, Calendar.DATE),
+ "truncate date-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:00:00.000"),
+ DateUtils.truncate(date1, Calendar.HOUR),
+ "truncate hour-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:00:00.000"),
+ DateUtils.truncate(date2, Calendar.HOUR),
+ "truncate hour-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:00.000"),
+ DateUtils.truncate(date1, Calendar.MINUTE),
+ "truncate minute-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:00.000"),
+ DateUtils.truncate(date2, Calendar.MINUTE),
+ "truncate minute-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:56.000"),
+ DateUtils.truncate(date1, Calendar.SECOND),
+ "truncate second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:11.000"),
+ DateUtils.truncate(date2, Calendar.SECOND),
+ "truncate second-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.truncate(dateAmPm1, Calendar.AM_PM),
+ "truncate ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.truncate(dateAmPm2, Calendar.AM_PM),
+ "truncate ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.truncate(dateAmPm3, Calendar.AM_PM),
+ "truncate ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.truncate(dateAmPm4, Calendar.AM_PM),
+ "truncate ampm-4 failed");
+
+ // tests public static Date truncate(Object date, int field)
+ assertEquals(dateParser.parse("January 1, 2002"),
+ DateUtils.truncate((Object) date1, Calendar.YEAR),
+ "truncate year-1 failed");
+ assertEquals(dateParser.parse("January 1, 2001"),
+ DateUtils.truncate((Object) date2, Calendar.YEAR),
+ "truncate year-2 failed");
+ assertEquals(dateParser.parse("February 1, 2002"),
+ DateUtils.truncate((Object) date1, Calendar.MONTH),
+ "truncate month-1 failed");
+ assertEquals(dateParser.parse("November 1, 2001"),
+ DateUtils.truncate((Object) date2, Calendar.MONTH),
+ "truncate month-2 failed");
+ assertEquals(dateParser.parse("February 1, 2002"),
+ DateUtils.truncate((Object) date1, DateUtils.SEMI_MONTH),
+ "truncate semimonth-1 failed");
+ assertEquals(dateParser.parse("November 16, 2001"),
+ DateUtils.truncate((Object) date2, DateUtils.SEMI_MONTH),
+ "truncate semimonth-2 failed");
+ assertEquals(dateParser.parse("February 12, 2002"),
+ DateUtils.truncate((Object) date1, Calendar.DATE),
+ "truncate date-1 failed");
+ assertEquals(dateParser.parse("November 18, 2001"),
+ DateUtils.truncate((Object) date2, Calendar.DATE),
+ "truncate date-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:00:00.000"),
+ DateUtils.truncate((Object) date1, Calendar.HOUR),
+ "truncate hour-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:00:00.000"),
+ DateUtils.truncate((Object) date2, Calendar.HOUR),
+ "truncate hour-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:00.000"),
+ DateUtils.truncate((Object) date1, Calendar.MINUTE),
+ "truncate minute-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:00.000"),
+ DateUtils.truncate((Object) date2, Calendar.MINUTE),
+ "truncate minute-2 failed");
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:56.000"),
+ DateUtils.truncate((Object) date1, Calendar.SECOND),
+ "truncate second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:11.000"),
+ DateUtils.truncate((Object) date2, Calendar.SECOND),
+ "truncate second-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.truncate((Object) dateAmPm1, Calendar.AM_PM),
+ "truncate ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.truncate((Object) dateAmPm2, Calendar.AM_PM),
+ "truncate ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.truncate((Object) dateAmPm3, Calendar.AM_PM),
+ "truncate ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.truncate((Object) dateAmPm4, Calendar.AM_PM),
+ "truncate ampm-4 failed");
+
+ assertEquals(dateTimeParser.parse("February 12, 2002 12:34:56.000"),
+ DateUtils.truncate((Object) cal1, Calendar.SECOND),
+ "truncate calendar second-1 failed");
+ assertEquals(dateTimeParser.parse("November 18, 2001 1:23:11.000"),
+ DateUtils.truncate((Object) cal2, Calendar.SECOND),
+ "truncate calendar second-2 failed");
+
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.truncate((Object) calAmPm1, Calendar.AM_PM),
+ "truncate ampm-1 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 00:00:00.000"),
+ DateUtils.truncate((Object) calAmPm2, Calendar.AM_PM),
+ "truncate ampm-2 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.truncate((Object) calAmPm3, Calendar.AM_PM),
+ "truncate ampm-3 failed");
+ assertEquals(dateTimeParser.parse("February 3, 2002 12:00:00.000"),
+ DateUtils.truncate((Object) calAmPm4, Calendar.AM_PM),
+ "truncate ampm-4 failed");
+
+ assertThrows(NullPointerException.class, () -> DateUtils.truncate((Date) null, Calendar.SECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.truncate((Calendar) null, Calendar.SECOND));
+ assertThrows(NullPointerException.class, () -> DateUtils.truncate((Object) null, Calendar.SECOND));
+ assertThrows(ClassCastException.class, () -> DateUtils.truncate("", Calendar.SECOND));
+
+ // Fix for https://issues.apache.org/bugzilla/show_bug.cgi?id=25560
+ // Test truncate across beginning of daylight saving time
+ try {
+ TimeZone.setDefault(zone);
+ dateTimeParser.setTimeZone(zone);
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.truncate(date3, Calendar.DATE),
+ "truncate MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("March 30, 2003 00:00:00.000"),
+ DateUtils.truncate((Object) cal3, Calendar.DATE),
+ "truncate MET date across DST change-over");
+ // Test truncate across end of daylight saving time
+ assertEquals(dateTimeParser.parse("October 26, 2003 00:00:00.000"),
+ DateUtils.truncate(date8, Calendar.DATE),
+ "truncate MET date across DST change-over");
+ assertEquals(dateTimeParser.parse("October 26, 2003 00:00:00.000"),
+ DateUtils.truncate((Object) cal8, Calendar.DATE),
+ "truncate MET date across DST change-over");
+ } finally {
+ TimeZone.setDefault(DEFAULT_ZONE);
+ dateTimeParser.setTimeZone(DEFAULT_ZONE);
+ }
+
+ // Bug 31395, large dates
+ final Date endOfTime = new Date(Long.MAX_VALUE); // fyi: Sun Aug 17 07:12:55 CET 292278994 -- 807 millis
+ final GregorianCalendar endCal = new GregorianCalendar();
+ endCal.setTime(endOfTime);
+ assertThrows(ArithmeticException.class, () -> DateUtils.truncate(endCal, Calendar.DATE));
+ endCal.set(Calendar.YEAR, 280000001);
+ assertThrows(ArithmeticException.class, () -> DateUtils.truncate(endCal, Calendar.DATE));
+ endCal.set(Calendar.YEAR, 280000000);
+ final Calendar cal = DateUtils.truncate(endCal, Calendar.DATE);
+ assertEquals(0, cal.get(Calendar.HOUR));
+ }
+
+ /**
+ * Tests for LANG-59
+ *
+ * see https://issues.apache.org/jira/browse/LANG-59
+ */
+ @Test
+ public void testTruncateLang59() {
+ try {
+ // Set TimeZone to Mountain Time
+ final TimeZone denverZone = TimeZone.getTimeZone("America/Denver");
+ TimeZone.setDefault(denverZone);
+ final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS XXX");
+ format.setTimeZone(denverZone);
+
+ final Date oct31_01MDT = new Date(1099206000000L);
+
+ final Date oct31MDT = new Date(oct31_01MDT.getTime() - 3600000L); // - 1 hour
+ final Date oct31_01_02MDT = new Date(oct31_01MDT.getTime() + 120000L); // + 2 minutes
+ final Date oct31_01_02_03MDT = new Date(oct31_01_02MDT.getTime() + 3000L); // + 3 seconds
+ final Date oct31_01_02_03_04MDT = new Date(oct31_01_02_03MDT.getTime() + 4L); // + 4 milliseconds
+
+ assertEquals("2004-10-31 00:00:00.000 -06:00", format.format(oct31MDT), "Check 00:00:00.000");
+ assertEquals("2004-10-31 01:00:00.000 -06:00", format.format(oct31_01MDT), "Check 01:00:00.000");
+ assertEquals("2004-10-31 01:02:00.000 -06:00", format.format(oct31_01_02MDT), "Check 01:02:00.000");
+ assertEquals("2004-10-31 01:02:03.000 -06:00", format.format(oct31_01_02_03MDT), "Check 01:02:03.000");
+ assertEquals("2004-10-31 01:02:03.004 -06:00", format.format(oct31_01_02_03_04MDT), "Check 01:02:03.004");
+
+ // ------- Demonstrate Problem -------
+ final Calendar gval = Calendar.getInstance();
+ gval.setTime(new Date(oct31_01MDT.getTime()));
+ gval.set(Calendar.MINUTE, gval.get(Calendar.MINUTE)); // set minutes to the same value
+ assertEquals(gval.getTime().getTime(), oct31_01MDT.getTime() + 3600000L, "Demonstrate Problem");
+
+ // ---------- Test Truncate ----------
+ assertEquals(oct31_01_02_03_04MDT, DateUtils.truncate(oct31_01_02_03_04MDT, Calendar.MILLISECOND),
+ "Truncate Calendar.MILLISECOND");
+
+ assertEquals(oct31_01_02_03MDT, DateUtils.truncate(oct31_01_02_03_04MDT, Calendar.SECOND),
+ "Truncate Calendar.SECOND");
+
+ assertEquals(oct31_01_02MDT, DateUtils.truncate(oct31_01_02_03_04MDT, Calendar.MINUTE),
+ "Truncate Calendar.MINUTE");
+
+ assertEquals(oct31_01MDT, DateUtils.truncate(oct31_01_02_03_04MDT, Calendar.HOUR_OF_DAY),
+ "Truncate Calendar.HOUR_OF_DAY");
+
+ assertEquals(oct31_01MDT, DateUtils.truncate(oct31_01_02_03_04MDT, Calendar.HOUR),
+ "Truncate Calendar.HOUR");
+
+ assertEquals(oct31MDT, DateUtils.truncate(oct31_01_02_03_04MDT, Calendar.DATE),
+ "Truncate Calendar.DATE");
+
+ // ---------- Test Round (down) ----------
+ assertEquals(oct31_01_02_03_04MDT, DateUtils.round(oct31_01_02_03_04MDT, Calendar.MILLISECOND),
+ "Round Calendar.MILLISECOND");
+
+ assertEquals(oct31_01_02_03MDT, DateUtils.round(oct31_01_02_03_04MDT, Calendar.SECOND),
+ "Round Calendar.SECOND");
+
+ assertEquals(oct31_01_02MDT, DateUtils.round(oct31_01_02_03_04MDT, Calendar.MINUTE),
+ "Round Calendar.MINUTE");
+
+ assertEquals(oct31_01MDT, DateUtils.round(oct31_01_02_03_04MDT, Calendar.HOUR_OF_DAY),
+ "Round Calendar.HOUR_OF_DAY");
+
+ assertEquals(oct31_01MDT, DateUtils.round(oct31_01_02_03_04MDT, Calendar.HOUR),
+ "Round Calendar.HOUR");
+
+ assertEquals(oct31MDT, DateUtils.round(oct31_01_02_03_04MDT, Calendar.DATE),
+ "Round Calendar.DATE");
+ } finally {
+ // restore default time zone
+ TimeZone.setDefault(DEFAULT_ZONE);
+ }
+ }
+
+ /**
+ * Tests the calendar iterator for week ranges
+ */
+ @Test
+ public void testWeekIterator() {
+ final Calendar now = Calendar.getInstance();
+ for (int i = 0; i< 7; i++) {
+ final Calendar today = DateUtils.truncate(now, Calendar.DATE);
+ final Calendar sunday = DateUtils.truncate(now, Calendar.DATE);
+ sunday.add(Calendar.DATE, 1 - sunday.get(Calendar.DAY_OF_WEEK));
+ final Calendar monday = DateUtils.truncate(now, Calendar.DATE);
+ if (monday.get(Calendar.DAY_OF_WEEK) == 1) {
+ //This is sunday... roll back 6 days
+ monday.add(Calendar.DATE, -6);
+ } else {
+ monday.add(Calendar.DATE, 2 - monday.get(Calendar.DAY_OF_WEEK));
+ }
+ final Calendar centered = DateUtils.truncate(now, Calendar.DATE);
+ centered.add(Calendar.DATE, -3);
+
+ Iterator<?> it = DateUtils.iterator(now, DateUtils.RANGE_WEEK_SUNDAY);
+ assertWeekIterator(it, sunday);
+ it = DateUtils.iterator(now, DateUtils.RANGE_WEEK_MONDAY);
+ assertWeekIterator(it, monday);
+ it = DateUtils.iterator(now, DateUtils.RANGE_WEEK_RELATIVE);
+ assertWeekIterator(it, today);
+ it = DateUtils.iterator(now, DateUtils.RANGE_WEEK_CENTER);
+ assertWeekIterator(it, centered);
+
+ it = DateUtils.iterator((Object) now, DateUtils.RANGE_WEEK_CENTER);
+ assertWeekIterator(it, centered);
+ final Iterator<?> it2 = DateUtils.iterator((Object) now.getTime(), DateUtils.RANGE_WEEK_CENTER);
+ assertWeekIterator(it2, centered);
+ assertThrows(NoSuchElementException.class, it2::next);
+ final Iterator<?> it3 = DateUtils.iterator(now, DateUtils.RANGE_WEEK_CENTER);
+ it3.next();
+ assertThrows(UnsupportedOperationException.class, it3::remove);
+
+ now.add(Calendar.DATE, 1);
+ }
+ }
+
+}
+
diff --git a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
new file mode 100644
index 000000000..2266abb4a
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
@@ -0,0 +1,635 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.DefaultTimeZone;
+
+/**
+ * TestCase for DurationFormatUtils.
+ * <p>
+ * NOT THREAD-SAFE.
+ * </p>
+ */
+public class DurationFormatUtilsTest extends AbstractLangTest {
+
+ private static final int FOUR_YEARS = 365 * 3 + 366;
+
+ private void assertEqualDuration(final String expected, final int[] start, final int[] end, final String format) {
+ assertEqualDuration(null, expected, start, end, format);
+ }
+
+ private void assertEqualDuration(final String message, final String expected, final int[] start, final int[] end, final String format) {
+ final Calendar cal1 = Calendar.getInstance();
+ cal1.set(start[0], start[1], start[2], start[3], start[4], start[5]);
+ cal1.set(Calendar.MILLISECOND, 0);
+ final Calendar cal2 = Calendar.getInstance();
+ cal2.set(end[0], end[1], end[2], end[3], end[4], end[5]);
+ cal2.set(Calendar.MILLISECOND, 0);
+ final long milli1 = cal1.getTime().getTime();
+ final long milli2 = cal2.getTime().getTime();
+ final String result = DurationFormatUtils.formatPeriod(milli1, milli2, format);
+ if (message == null) {
+ assertEquals(expected, result);
+ } else {
+ assertEquals(expected, result, message);
+ }
+ }
+
+ private void bruteForce(final int year, final int month, final int day, final String format, final int calendarType) {
+ final String msg = year + "-" + month + "-" + day + " to ";
+ final Calendar c = Calendar.getInstance();
+ c.set(year, month, day, 0, 0, 0);
+ final int[] array1 = { year, month, day, 0, 0, 0 };
+ final int[] array2 = { year, month, day, 0, 0, 0 };
+ for (int i=0; i < FOUR_YEARS; i++) {
+ array2[0] = c.get(Calendar.YEAR);
+ array2[1] = c.get(Calendar.MONTH);
+ array2[2] = c.get(Calendar.DAY_OF_MONTH);
+ final String tmpMsg = msg + array2[0] + "-" + array2[1] + "-" + array2[2] + " at ";
+ assertEqualDuration(tmpMsg + i, Integer.toString(i), array1, array2, format );
+ c.add(calendarType, 1);
+ }
+ }
+
+ /** https://issues.apache.org/bugzilla/show_bug.cgi?id=38401 */
+ @Test
+ public void testBugzilla38401() {
+ assertEqualDuration("0000/00/30 16:00:00 000", new int[] { 2006, 0, 26, 18, 47, 34 },
+ new int[] { 2006, 1, 26, 10, 47, 34 }, "yyyy/MM/dd HH:mm:ss SSS");
+ }
+
+ @Test
+ public void testConstructor() {
+ assertNotNull(new DurationFormatUtils());
+ final Constructor<?>[] cons = DurationFormatUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(DurationFormatUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(DurationFormatUtils.class.getModifiers()));
+ }
+
+ @Test
+ public void testDurationsByBruteForce() {
+ bruteForce(2006, 0, 1, "d", Calendar.DAY_OF_MONTH);
+ bruteForce(2006, 0, 2, "d", Calendar.DAY_OF_MONTH);
+ bruteForce(2007, 1, 2, "d", Calendar.DAY_OF_MONTH);
+ bruteForce(2004, 1, 29, "d", Calendar.DAY_OF_MONTH);
+ bruteForce(1996, 1, 29, "d", Calendar.DAY_OF_MONTH);
+
+ bruteForce(1969, 1, 28, "M", Calendar.MONTH); // tests for 48 years
+ //bruteForce(1996, 1, 29, "M", Calendar.MONTH); // this will fail
+ }
+
+ /** Attempting to test edge cases in DurationFormatUtils.formatPeriod. */
+ @Test
+ @DefaultTimeZone(TimeZones.GMT_ID)
+ public void testEdgeDurations() {
+ // This test case must use a time zone without DST
+ TimeZone.setDefault(FastTimeZone.getGmtTimeZone());
+ assertEqualDuration("01", new int[] { 2006, 0, 15, 0, 0, 0 },
+ new int[] { 2006, 2, 10, 0, 0, 0 }, "MM");
+ assertEqualDuration("12", new int[] { 2005, 0, 15, 0, 0, 0 },
+ new int[] { 2006, 0, 15, 0, 0, 0 }, "MM");
+ assertEqualDuration("12", new int[] { 2005, 0, 15, 0, 0, 0 },
+ new int[] { 2006, 0, 16, 0, 0, 0 }, "MM");
+ assertEqualDuration("11", new int[] { 2005, 0, 15, 0, 0, 0 },
+ new int[] { 2006, 0, 14, 0, 0, 0 }, "MM");
+
+ assertEqualDuration("01 26", new int[] { 2006, 0, 15, 0, 0, 0 },
+ new int[] { 2006, 2, 10, 0, 0, 0 }, "MM dd");
+ assertEqualDuration("54", new int[] { 2006, 0, 15, 0, 0, 0 },
+ new int[] { 2006, 2, 10, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("09 12", new int[] { 2006, 1, 20, 0, 0, 0 },
+ new int[] { 2006, 11, 4, 0, 0, 0 }, "MM dd");
+ assertEqualDuration("287", new int[] { 2006, 1, 20, 0, 0, 0 },
+ new int[] { 2006, 11, 4, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("11 30", new int[] { 2006, 0, 2, 0, 0, 0 },
+ new int[] { 2007, 0, 1, 0, 0, 0 }, "MM dd");
+ assertEqualDuration("364", new int[] { 2006, 0, 2, 0, 0, 0 },
+ new int[] { 2007, 0, 1, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("12 00", new int[] { 2006, 0, 1, 0, 0, 0 },
+ new int[] { 2007, 0, 1, 0, 0, 0 }, "MM dd");
+ assertEqualDuration("365", new int[] { 2006, 0, 1, 0, 0, 0 },
+ new int[] { 2007, 0, 1, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("31", new int[] { 2006, 0, 1, 0, 0, 0 },
+ new int[] { 2006, 1, 1, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("92", new int[] { 2005, 9, 1, 0, 0, 0 },
+ new int[] { 2006, 0, 1, 0, 0, 0 }, "dd");
+ assertEqualDuration("77", new int[] { 2005, 9, 16, 0, 0, 0 },
+ new int[] { 2006, 0, 1, 0, 0, 0 }, "dd");
+
+ // test month larger in start than end
+ assertEqualDuration("136", new int[] { 2005, 9, 16, 0, 0, 0 },
+ new int[] { 2006, 2, 1, 0, 0, 0 }, "dd");
+ // test when start in leap year
+ assertEqualDuration("136", new int[] { 2004, 9, 16, 0, 0, 0 },
+ new int[] { 2005, 2, 1, 0, 0, 0 }, "dd");
+ // test when end in leap year
+ assertEqualDuration("137", new int[] { 2003, 9, 16, 0, 0, 0 },
+ new int[] { 2004, 2, 1, 0, 0, 0 }, "dd");
+ // test when end in leap year but less than end of feb
+ assertEqualDuration("135", new int[] { 2003, 9, 16, 0, 0, 0 },
+ new int[] { 2004, 1, 28, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("364", new int[] { 2007, 0, 2, 0, 0, 0 },
+ new int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
+ assertEqualDuration("729", new int[] { 2006, 0, 2, 0, 0, 0 },
+ new int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("365", new int[] { 2007, 2, 2, 0, 0, 0 },
+ new int[] { 2008, 2, 1, 0, 0, 0 }, "dd");
+ assertEqualDuration("333", new int[] { 2007, 1, 2, 0, 0, 0 },
+ new int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("28", new int[] { 2008, 1, 2, 0, 0, 0 },
+ new int[] { 2008, 2, 1, 0, 0, 0 }, "dd");
+ assertEqualDuration("393", new int[] { 2007, 1, 2, 0, 0, 0 },
+ new int[] { 2008, 2, 1, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("369", new int[] { 2004, 0, 29, 0, 0, 0 },
+ new int[] { 2005, 1, 1, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("338", new int[] { 2004, 1, 29, 0, 0, 0 },
+ new int[] { 2005, 1, 1, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("28", new int[] { 2004, 2, 8, 0, 0, 0 },
+ new int[] { 2004, 3, 5, 0, 0, 0 }, "dd");
+
+ assertEqualDuration("48", new int[] { 1992, 1, 29, 0, 0, 0 },
+ new int[] { 1996, 1, 29, 0, 0, 0 }, "M");
+
+
+ // this seems odd - and will fail if I throw it in as a brute force
+ // below as it expects the answer to be 12. It's a tricky edge case
+ assertEqualDuration("11", new int[] { 1996, 1, 29, 0, 0, 0 },
+ new int[] { 1997, 1, 28, 0, 0, 0 }, "M");
+ // again - this seems odd
+ assertEqualDuration("11 28", new int[] { 1996, 1, 29, 0, 0, 0 },
+ new int[] { 1997, 1, 28, 0, 0, 0 }, "M d");
+
+ }
+
+ @Test
+ public void testFormatDuration() {
+ long duration = 0;
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "y"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "M"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "d"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "H"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "m"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "s"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "S"));
+ assertEquals("0000", DurationFormatUtils.formatDuration(duration, "SSSS"));
+ assertEquals("0000", DurationFormatUtils.formatDuration(duration, "yyyy"));
+ assertEquals("0000", DurationFormatUtils.formatDuration(duration, "yyMM"));
+
+ duration = 60 * 1000;
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "y"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "M"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "d"));
+ assertEquals("0", DurationFormatUtils.formatDuration(duration, "H"));
+ assertEquals("1", DurationFormatUtils.formatDuration(duration, "m"));
+ assertEquals("60", DurationFormatUtils.formatDuration(duration, "s"));
+ assertEquals("60000", DurationFormatUtils.formatDuration(duration, "S"));
+ assertEquals("01:00", DurationFormatUtils.formatDuration(duration, "mm:ss"));
+
+ final Calendar base = Calendar.getInstance();
+ base.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
+ base.set(Calendar.MILLISECOND, 0);
+
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2003, Calendar.FEBRUARY, 1, 0, 0, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ duration = cal.getTime().getTime() - base.getTime().getTime(); // duration from 2000-01-01 to cal
+ // don't use 1970 in test as time zones were less reliable in 1970 than now
+ // remember that duration formatting ignores time zones, working on strict hour lengths
+ final int days = 366 + 365 + 365 + 31;
+ assertEquals("0 0 " + days, DurationFormatUtils.formatDuration(duration, "y M d"));
+ }
+
+ @Test
+ public void testFormatDurationHMS() {
+ long time = 0;
+ assertEquals("00:00:00.000", DurationFormatUtils.formatDurationHMS(time));
+
+ time = 1;
+ assertEquals("00:00:00.001", DurationFormatUtils.formatDurationHMS(time));
+
+ time = 15;
+ assertEquals("00:00:00.015", DurationFormatUtils.formatDurationHMS(time));
+
+ time = 165;
+ assertEquals("00:00:00.165", DurationFormatUtils.formatDurationHMS(time));
+
+ time = 1675;
+ assertEquals("00:00:01.675", DurationFormatUtils.formatDurationHMS(time));
+
+ time = 13465;
+ assertEquals("00:00:13.465", DurationFormatUtils.formatDurationHMS(time));
+
+ time = 72789;
+ assertEquals("00:01:12.789", DurationFormatUtils.formatDurationHMS(time));
+
+ time = 12789 + 32 * 60000;
+ assertEquals("00:32:12.789", DurationFormatUtils.formatDurationHMS(time));
+
+ time = 12789 + 62 * 60000;
+ assertEquals("01:02:12.789", DurationFormatUtils.formatDurationHMS(time));
+ }
+
+ @Test
+ public void testFormatDurationISO() {
+ assertEquals("P0Y0M0DT0H0M0.000S", DurationFormatUtils.formatDurationISO(0L));
+ assertEquals("P0Y0M0DT0H0M0.001S", DurationFormatUtils.formatDurationISO(1L));
+ assertEquals("P0Y0M0DT0H0M0.010S", DurationFormatUtils.formatDurationISO(10L));
+ assertEquals("P0Y0M0DT0H0M0.100S", DurationFormatUtils.formatDurationISO(100L));
+ assertEquals("P0Y0M0DT0H1M15.321S", DurationFormatUtils.formatDurationISO(75321L));
+ }
+
+ /**
+ * Tests that "1 &lt;unit&gt;s" gets converted to "1 &lt;unit&gt;" but that "11 &lt;unit&gt;s" is left alone.
+ */
+ @Test
+ public void testFormatDurationPluralWords() {
+ final long oneSecond = 1000;
+ final long oneMinute = oneSecond * 60;
+ final long oneHour = oneMinute * 60;
+ final long oneDay = oneHour * 24;
+ String text;
+
+ text = DurationFormatUtils.formatDurationWords(oneSecond, false, false);
+ assertEquals("0 days 0 hours 0 minutes 1 second", text);
+ text = DurationFormatUtils.formatDurationWords(oneSecond * 2, false, false);
+ assertEquals("0 days 0 hours 0 minutes 2 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneSecond * 11, false, false);
+ assertEquals("0 days 0 hours 0 minutes 11 seconds", text);
+
+ text = DurationFormatUtils.formatDurationWords(oneMinute, false, false);
+ assertEquals("0 days 0 hours 1 minute 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneMinute * 2, false, false);
+ assertEquals("0 days 0 hours 2 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneMinute * 11, false, false);
+ assertEquals("0 days 0 hours 11 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneMinute + oneSecond, false, false);
+ assertEquals("0 days 0 hours 1 minute 1 second", text);
+
+ text = DurationFormatUtils.formatDurationWords(oneHour, false, false);
+ assertEquals("0 days 1 hour 0 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneHour * 2, false, false);
+ assertEquals("0 days 2 hours 0 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneHour * 11, false, false);
+ assertEquals("0 days 11 hours 0 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneHour + oneMinute + oneSecond, false, false);
+ assertEquals("0 days 1 hour 1 minute 1 second", text);
+
+ text = DurationFormatUtils.formatDurationWords(oneDay, false, false);
+ assertEquals("1 day 0 hours 0 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneDay * 2, false, false);
+ assertEquals("2 days 0 hours 0 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneDay * 11, false, false);
+ assertEquals("11 days 0 hours 0 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(oneDay + oneHour + oneMinute + oneSecond, false, false);
+ assertEquals("1 day 1 hour 1 minute 1 second", text);
+ }
+
+ @Test
+ public void testFormatDurationWords() {
+ String text;
+
+ text = DurationFormatUtils.formatDurationWords(50 * 1000, true, false);
+ assertEquals("50 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(65 * 1000, true, false);
+ assertEquals("1 minute 5 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(120 * 1000, true, false);
+ assertEquals("2 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(121 * 1000, true, false);
+ assertEquals("2 minutes 1 second", text);
+ text = DurationFormatUtils.formatDurationWords(72 * 60 * 1000, true, false);
+ assertEquals("1 hour 12 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(24 * 60 * 60 * 1000, true, false);
+ assertEquals("1 day 0 hours 0 minutes 0 seconds", text);
+
+ text = DurationFormatUtils.formatDurationWords(50 * 1000, true, true);
+ assertEquals("50 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(65 * 1000, true, true);
+ assertEquals("1 minute 5 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(120 * 1000, true, true);
+ assertEquals("2 minutes", text);
+ text = DurationFormatUtils.formatDurationWords(121 * 1000, true, true);
+ assertEquals("2 minutes 1 second", text);
+ text = DurationFormatUtils.formatDurationWords(72 * 60 * 1000, true, true);
+ assertEquals("1 hour 12 minutes", text);
+ text = DurationFormatUtils.formatDurationWords(24 * 60 * 60 * 1000, true, true);
+ assertEquals("1 day", text);
+
+ text = DurationFormatUtils.formatDurationWords(50 * 1000, false, true);
+ assertEquals("0 days 0 hours 0 minutes 50 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(65 * 1000, false, true);
+ assertEquals("0 days 0 hours 1 minute 5 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(120 * 1000, false, true);
+ assertEquals("0 days 0 hours 2 minutes", text);
+ text = DurationFormatUtils.formatDurationWords(121 * 1000, false, true);
+ assertEquals("0 days 0 hours 2 minutes 1 second", text);
+ text = DurationFormatUtils.formatDurationWords(72 * 60 * 1000, false, true);
+ assertEquals("0 days 1 hour 12 minutes", text);
+ text = DurationFormatUtils.formatDurationWords(24 * 60 * 60 * 1000, false, true);
+ assertEquals("1 day", text);
+
+ text = DurationFormatUtils.formatDurationWords(50 * 1000, false, false);
+ assertEquals("0 days 0 hours 0 minutes 50 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(65 * 1000, false, false);
+ assertEquals("0 days 0 hours 1 minute 5 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(120 * 1000, false, false);
+ assertEquals("0 days 0 hours 2 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(121 * 1000, false, false);
+ assertEquals("0 days 0 hours 2 minutes 1 second", text);
+ text = DurationFormatUtils.formatDurationWords(72 * 60 * 1000, false, false);
+ assertEquals("0 days 1 hour 12 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(24 * 60 * 60 * 1000 + 72 * 60 * 1000, false, false);
+ assertEquals("1 day 1 hour 12 minutes 0 seconds", text);
+ text = DurationFormatUtils.formatDurationWords(2 * 24 * 60 * 60 * 1000 + 72 * 60 * 1000, false, false);
+ assertEquals("2 days 1 hour 12 minutes 0 seconds", text);
+ for (int i = 2; i < 31; i++) {
+ text = DurationFormatUtils.formatDurationWords(i * 24 * 60 * 60 * 1000L, false, false);
+ assertEquals(i + " days 0 hours 0 minutes 0 seconds", text);
+ }
+ }
+
+ @Test
+ public void testFormatNegativeDuration() {
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(-5000, "S", true));
+ }
+
+ @Test
+ public void testFormatNegativeDurationHMS() {
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationHMS(-5000));
+ }
+
+ @Test
+ public void testFormatNegativeDurationISO() {
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationISO(-5000));
+ }
+
+
+ @Test
+ public void testFormatNegativeDurationWords() {
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationWords(-5000, true, true));
+ }
+
+ @Test
+ public void testFormatPeriod() {
+ final Calendar cal1970 = Calendar.getInstance();
+ cal1970.set(1970, Calendar.JANUARY, 1, 0, 0, 0);
+ cal1970.set(Calendar.MILLISECOND, 0);
+ final long time1970 = cal1970.getTime().getTime();
+
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "y"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "M"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "d"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "H"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "m"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "s"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "S"));
+ assertEquals("0000", DurationFormatUtils.formatPeriod(time1970, time1970, "SSSS"));
+ assertEquals("0000", DurationFormatUtils.formatPeriod(time1970, time1970, "yyyy"));
+ assertEquals("0000", DurationFormatUtils.formatPeriod(time1970, time1970, "yyMM"));
+
+ long time = time1970 + 60 * 1000;
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time, "y"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time, "M"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time, "d"));
+ assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time, "H"));
+ assertEquals("1", DurationFormatUtils.formatPeriod(time1970, time, "m"));
+ assertEquals("60", DurationFormatUtils.formatPeriod(time1970, time, "s"));
+ assertEquals("60000", DurationFormatUtils.formatPeriod(time1970, time, "S"));
+ assertEquals("01:00", DurationFormatUtils.formatPeriod(time1970, time, "mm:ss"));
+
+ final Calendar cal = Calendar.getInstance();
+ cal.set(1973, Calendar.JULY, 1, 0, 0, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ time = cal.getTime().getTime();
+ assertEquals("36", DurationFormatUtils.formatPeriod(time1970, time, "yM"));
+ assertEquals("3 years 6 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'"));
+ assertEquals("03/06", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM"));
+
+ cal.set(1973, Calendar.NOVEMBER, 1, 0, 0, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ time = cal.getTime().getTime();
+ assertEquals("310", DurationFormatUtils.formatPeriod(time1970, time, "yM"));
+ assertEquals("3 years 10 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'"));
+ assertEquals("03/10", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM"));
+
+ cal.set(1974, Calendar.JANUARY, 1, 0, 0, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ time = cal.getTime().getTime();
+ assertEquals("40", DurationFormatUtils.formatPeriod(time1970, time, "yM"));
+ assertEquals("4 years 0 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'"));
+ assertEquals("04/00", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM"));
+ assertEquals("48", DurationFormatUtils.formatPeriod(time1970, time, "M"));
+ assertEquals("48", DurationFormatUtils.formatPeriod(time1970, time, "MM"));
+ assertEquals("048", DurationFormatUtils.formatPeriod(time1970, time, "MMM"));
+ }
+
+ @Test
+ public void testFormatPeriodeStartGreaterEnd() {
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatPeriod(5000, 2500, "yy/MM"));
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testFormatPeriodISO() {
+ final TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
+ final Calendar base = Calendar.getInstance(timeZone);
+ base.set(1970, Calendar.JANUARY, 1, 0, 0, 0);
+ base.set(Calendar.MILLISECOND, 0);
+
+ final Calendar cal = Calendar.getInstance(timeZone);
+ cal.set(2002, Calendar.FEBRUARY, 23, 9, 11, 12);
+ cal.set(Calendar.MILLISECOND, 1);
+ String text;
+ // repeat a test from testDateTimeISO to compare extended and not extended.
+ text = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), timeZone);
+ assertEquals("2002-02-23T09:11:12-03:00", text);
+ // test fixture is the same as above, but now with extended format.
+ text = DurationFormatUtils.formatPeriod(base.getTime().getTime(), cal.getTime().getTime(),
+ DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN, false, timeZone);
+ assertEquals("P32Y1M22DT9H11M12.001S", text);
+ // test fixture from example in https://www.w3.org/TR/xmlschema-2/#duration
+ cal.set(1971, Calendar.FEBRUARY, 3, 10, 30, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ text = DurationFormatUtils.formatPeriod(base.getTime().getTime(), cal.getTime().getTime(),
+ DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN, false, timeZone);
+ assertEquals("P1Y1M2DT10H30M0.000S", text);
+ // want a way to say 'don't print the seconds in format()' or other fields for that matter:
+ // assertEquals("P1Y2M3DT10H30M", text);
+ }
+
+ @Test
+ public void testFormatPeriodISOMethod() {
+ assertEquals("P0Y0M0DT0H0M0.000S", DurationFormatUtils.formatPeriodISO(0L, 0L));
+ assertEquals("P0Y0M0DT0H0M1.000S", DurationFormatUtils.formatPeriodISO(0L, 1000L));
+ assertEquals("P0Y0M0DT0H1M1.000S", DurationFormatUtils.formatPeriodISO(0L, 61000L));
+ }
+
+ @Test
+ public void testFormatPeriodISOStartGreaterEnd() {
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatPeriodISO(5000, 2000));
+ }
+
+ // https://issues.apache.org/jira/browse/LANG-281
+ @Test
+ public void testJiraLang281() {
+ assertEqualDuration("09", new int[] { 2005, 11, 31, 0, 0, 0 },
+ new int[] { 2006, 9, 6, 0, 0, 0 }, "MM");
+ }
+
+ @Test
+ public void testLANG815() {
+ final Calendar calendar = Calendar.getInstance();
+ calendar.set(2012, Calendar.JULY, 30, 0, 0, 0);
+ final long startMillis = calendar.getTimeInMillis();
+
+ calendar.set(2012, Calendar.SEPTEMBER, 8);
+ final long endMillis = calendar.getTimeInMillis();
+
+ assertEquals("1 9", DurationFormatUtils.formatPeriod(startMillis, endMillis, "M d"));
+ }
+
+ @Test
+ public void testLANG981() { // unmatched quote char in lexx
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.lexx("'yMdHms''S"));
+ }
+
+ @Test
+ public void testLANG982() { // More than 3 millisecond digits following a second
+ assertEquals("61.999", DurationFormatUtils.formatDuration(61999, "s.S"));
+ assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m S"));
+ assertEquals("61.999", DurationFormatUtils.formatDuration(61999, "s.SSS"));
+ assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m SSS"));
+ assertEquals("61.0999", DurationFormatUtils.formatDuration(61999, "s.SSSS"));
+ assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m SSSS"));
+ assertEquals("61.00999", DurationFormatUtils.formatDuration(61999, "s.SSSSS"));
+ assertEquals("1 01999", DurationFormatUtils.formatDuration(61999, "m SSSSS"));
+ }
+
+ // Takes a minute to run, so generally turned off
+// public void testBrutally() {
+// Calendar c = Calendar.getInstance();
+// c.set(2004, 0, 1, 0, 0, 0);
+// for (int i=0; i < FOUR_YEARS; i++) {
+// bruteForce(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH), "d", Calendar.DAY_OF_MONTH );
+// c.add(Calendar.DAY_OF_MONTH, 1);
+// }
+// }
+
+ @Test
+ public void testLANG984() { // Long durations
+ assertEquals("0", DurationFormatUtils.formatDuration(0, "S"));
+ assertEquals(Integer.toString(Integer.MAX_VALUE), DurationFormatUtils.formatDuration(Integer.MAX_VALUE, "S"));
+ long maxIntPlus=Integer.MAX_VALUE;
+ maxIntPlus++;
+ assertEquals(Long.toString(maxIntPlus), DurationFormatUtils.formatDuration(maxIntPlus, "S"));
+ assertEquals(Long.toString(Long.MAX_VALUE), DurationFormatUtils.formatDuration(Long.MAX_VALUE, "S"));
+ }
+
+ @Test
+ public void testLexx() {
+ // tests each constant
+ assertArrayEquals(new DurationFormatUtils.Token[]{
+ new DurationFormatUtils.Token(DurationFormatUtils.y, 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.M, 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.d, 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.H, 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.m, 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.s, 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.S, 1)}, DurationFormatUtils.lexx("yMdHmsS"));
+
+ // tests the ISO 8601-like
+ assertArrayEquals(new DurationFormatUtils.Token[]{
+ new DurationFormatUtils.Token(DurationFormatUtils.H, 2),
+ new DurationFormatUtils.Token(new StringBuilder(":"), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.m, 2),
+ new DurationFormatUtils.Token(new StringBuilder(":"), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.s, 2),
+ new DurationFormatUtils.Token(new StringBuilder("."), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.S, 3)}, DurationFormatUtils.lexx("HH:mm:ss.SSS"));
+
+ // test the iso extended format
+ assertArrayEquals(new DurationFormatUtils.Token[]{
+ new DurationFormatUtils.Token(new StringBuilder("P"), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.y, 4),
+ new DurationFormatUtils.Token(new StringBuilder("Y"), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.M, 1),
+ new DurationFormatUtils.Token(new StringBuilder("M"), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.d, 1),
+ new DurationFormatUtils.Token(new StringBuilder("DT"), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.H, 1),
+ new DurationFormatUtils.Token(new StringBuilder("H"), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.m, 1),
+ new DurationFormatUtils.Token(new StringBuilder("M"), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.s, 1),
+ new DurationFormatUtils.Token(new StringBuilder("."), 1),
+ new DurationFormatUtils.Token(DurationFormatUtils.S, 3),
+ new DurationFormatUtils.Token(new StringBuilder("S"), 1)}, DurationFormatUtils
+ .lexx(DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN));
+
+ // test failures in equals
+ final DurationFormatUtils.Token token = new DurationFormatUtils.Token(DurationFormatUtils.y, 4);
+ assertNotEquals(token, new Object(), "Token equal to non-Token class. ");
+ assertNotEquals(token, new DurationFormatUtils.Token(new Object()), "Token equal to Token with wrong value class. ");
+ assertNotEquals(token, new DurationFormatUtils.Token(DurationFormatUtils.y, 1), "Token equal to Token with different count. ");
+ final DurationFormatUtils.Token numToken = new DurationFormatUtils.Token(Integer.valueOf(1), 4);
+ assertEquals(numToken, numToken, "Token with Number value not equal to itself. ");
+ }
+ // Testing the under a day range in DurationFormatUtils.formatPeriod
+ @Test
+ public void testLowDurations() {
+ for (int hr=0; hr < 24; hr++) {
+ for (int min=0; min < 60; min++) {
+ for (int sec=0; sec < 60; sec++) {
+ assertEqualDuration(hr + ":" + min + ":" + sec,
+ new int[] { 2000, 0, 1, 0, 0, 0, 0 },
+ new int[] { 2000, 0, 1, hr, min, sec },
+ "H:m:s"
+ );
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/DurationUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/DurationUtilsTest.java
new file mode 100644
index 000000000..af258c79a
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/DurationUtilsTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link DurationUtils}.
+ */
+public class DurationUtilsTest extends AbstractLangTest {
+
+ @Test
+ public void testGetNanosOfMilli() {
+ assertEquals(0, DurationUtils.getNanosOfMilli(null));
+ assertEquals(0, DurationUtils.getNanosOfMilli(Duration.ZERO));
+ assertEquals(1, DurationUtils.getNanosOfMilli(Duration.ofNanos(1)));
+ assertEquals(10, DurationUtils.getNanosOfMilli(Duration.ofNanos(10)));
+ assertEquals(100, DurationUtils.getNanosOfMilli(Duration.ofNanos(100)));
+ assertEquals(1_000, DurationUtils.getNanosOfMilli(Duration.ofNanos(1_000)));
+ assertEquals(10_000, DurationUtils.getNanosOfMilli(Duration.ofNanos(10_000)));
+ assertEquals(100_000, DurationUtils.getNanosOfMilli(Duration.ofNanos(100_000)));
+ assertEquals(0, DurationUtils.getNanosOfMilli(Duration.ofNanos(1_000_000)));
+ assertEquals(1, DurationUtils.getNanosOfMilli(Duration.ofNanos(1_000_001)));
+ }
+
+ @Test
+ public void testGetNanosOfMiili() {
+ assertEquals(0, DurationUtils.getNanosOfMiili(null));
+ assertEquals(0, DurationUtils.getNanosOfMiili(Duration.ZERO));
+ assertEquals(1, DurationUtils.getNanosOfMiili(Duration.ofNanos(1)));
+ assertEquals(10, DurationUtils.getNanosOfMiili(Duration.ofNanos(10)));
+ assertEquals(100, DurationUtils.getNanosOfMiili(Duration.ofNanos(100)));
+ assertEquals(1_000, DurationUtils.getNanosOfMiili(Duration.ofNanos(1_000)));
+ assertEquals(10_000, DurationUtils.getNanosOfMiili(Duration.ofNanos(10_000)));
+ assertEquals(100_000, DurationUtils.getNanosOfMiili(Duration.ofNanos(100_000)));
+ assertEquals(0, DurationUtils.getNanosOfMiili(Duration.ofNanos(1_000_000)));
+ assertEquals(1, DurationUtils.getNanosOfMiili(Duration.ofNanos(1_000_001)));
+ }
+
+ @Test
+ public void testIsPositive() {
+ assertFalse(DurationUtils.isPositive(Duration.ZERO));
+ assertFalse(DurationUtils.isPositive(Duration.ofMillis(-1)));
+ assertTrue(DurationUtils.isPositive(Duration.ofMillis(1)));
+ }
+
+ @Test
+ public void testLongToIntRangeFit() {
+ assertEquals(0, DurationUtils.LONG_TO_INT_RANGE.fit(0L));
+ //
+ assertEquals(Integer.MIN_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit(NumberUtils.LONG_INT_MIN_VALUE));
+ assertEquals(Integer.MIN_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit(NumberUtils.LONG_INT_MIN_VALUE - 1));
+ assertEquals(Integer.MIN_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit(NumberUtils.LONG_INT_MIN_VALUE - 2));
+ assertEquals(Integer.MAX_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit(NumberUtils.LONG_INT_MAX_VALUE));
+ assertEquals(Integer.MAX_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit(NumberUtils.LONG_INT_MAX_VALUE + 1));
+ assertEquals(Integer.MAX_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit(NumberUtils.LONG_INT_MAX_VALUE + 2));
+ //
+ assertEquals(Integer.MIN_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit(Long.MIN_VALUE));
+ assertEquals(Integer.MAX_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit(Long.MAX_VALUE));
+ //
+ assertEquals(Short.MIN_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit((long) Short.MIN_VALUE));
+ assertEquals(Short.MAX_VALUE, DurationUtils.LONG_TO_INT_RANGE.fit((long) Short.MAX_VALUE));
+ }
+
+ @Test
+ public void testOfRunnble() {
+ assertTrue(DurationUtils.of(this::testSince).compareTo(Duration.ZERO) >= 0);
+ }
+
+ @Test
+ public void testOfConsumer() {
+ assertTrue(DurationUtils.of(start -> assertTrue(start.compareTo(Instant.now()) <= 0)).compareTo(Duration.ZERO) >= 0);
+ }
+
+ @Test
+ public void testOfRunnbleThrowing() {
+ assertThrows(IOException.class, () -> DurationUtils.of(() -> {
+ throw new IOException();
+ }));
+ }
+
+ @Test
+ public void testSince() {
+ assertTrue(DurationUtils.since(Instant.EPOCH).compareTo(Duration.ZERO) >= 0);
+ assertTrue(DurationUtils.since(Instant.MIN).compareTo(Duration.ZERO) >= 0);
+ assertTrue(DurationUtils.since(Instant.MAX).compareTo(Duration.ZERO) <= 0);
+ }
+
+ @Test
+ public void testToDuration() {
+ assertEquals(Duration.ofDays(1), DurationUtils.toDuration(1, TimeUnit.DAYS));
+ assertEquals(Duration.ofHours(1), DurationUtils.toDuration(1, TimeUnit.HOURS));
+ assertEquals(Duration.ofMillis(1), DurationUtils.toDuration(1_000, TimeUnit.MICROSECONDS));
+ assertEquals(Duration.ofMillis(1), DurationUtils.toDuration(1, TimeUnit.MILLISECONDS));
+ assertEquals(Duration.ofMinutes(1), DurationUtils.toDuration(1, TimeUnit.MINUTES));
+ assertEquals(Duration.ofNanos(1), DurationUtils.toDuration(1, TimeUnit.NANOSECONDS));
+ assertEquals(Duration.ofSeconds(1), DurationUtils.toDuration(1, TimeUnit.SECONDS));
+ assertEquals(1, DurationUtils.toDuration(1, TimeUnit.MILLISECONDS).toMillis());
+ assertEquals(-1, DurationUtils.toDuration(-1, TimeUnit.MILLISECONDS).toMillis());
+ assertEquals(0, DurationUtils.toDuration(0, TimeUnit.SECONDS).toMillis());
+ }
+
+ @Test
+ public void testToMillisInt() {
+ assertEquals(0, DurationUtils.toMillisInt(Duration.ZERO));
+ assertEquals(1, DurationUtils.toMillisInt(Duration.ofMillis(1)));
+ //
+ assertEquals(Integer.MIN_VALUE, DurationUtils.toMillisInt(Duration.ofMillis(Integer.MIN_VALUE)));
+ assertEquals(Integer.MAX_VALUE, DurationUtils.toMillisInt(Duration.ofMillis(Integer.MAX_VALUE)));
+ assertEquals(Integer.MAX_VALUE, DurationUtils.toMillisInt(Duration.ofMillis(NumberUtils.LONG_INT_MAX_VALUE + 1)));
+ assertEquals(Integer.MAX_VALUE, DurationUtils.toMillisInt(Duration.ofMillis(NumberUtils.LONG_INT_MAX_VALUE + 2)));
+ assertEquals(Integer.MIN_VALUE, DurationUtils.toMillisInt(Duration.ofMillis(NumberUtils.LONG_INT_MIN_VALUE - 1)));
+ assertEquals(Integer.MIN_VALUE, DurationUtils.toMillisInt(Duration.ofMillis(NumberUtils.LONG_INT_MIN_VALUE - 2)));
+ //
+ assertEquals(Integer.MIN_VALUE, DurationUtils.toMillisInt(Duration.ofNanos(Long.MIN_VALUE)));
+ assertEquals(Integer.MAX_VALUE, DurationUtils.toMillisInt(Duration.ofNanos(Long.MAX_VALUE)));
+ }
+
+ @Test
+ public void testToMillisIntNullDuration() {
+ assertThrows(NullPointerException.class, () -> DurationUtils.toMillisInt(null));
+ }
+
+ @Test
+ public void testZeroIfNull() {
+ assertEquals(Duration.ZERO, DurationUtils.zeroIfNull(null));
+ assertEquals(Duration.ofDays(1), DurationUtils.zeroIfNull(Duration.ofDays(1)));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
new file mode 100644
index 000000000..dda25cb47
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
@@ -0,0 +1,361 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLongArray;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.DefaultLocale;
+import org.junitpioneer.jupiter.DefaultTimeZone;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.time.FastDateFormat}.
+ *
+ * @since 2.0
+ */
+public class FastDateFormatTest extends AbstractLangTest {
+ private static final int NTHREADS = 10;
+
+ private static final int NROUNDS = 10000;
+
+ final Locale FINNISH = Locale.forLanguageTag("fi");
+ final Locale HUNGARIAN = Locale.forLanguageTag("hu");
+
+ private AtomicLongArray measureTime(final Format printer, final Format parser) throws InterruptedException {
+ final ExecutorService pool = Executors.newFixedThreadPool(NTHREADS);
+ final AtomicInteger failures = new AtomicInteger(0);
+ final AtomicLongArray totalElapsed = new AtomicLongArray(2);
+ try {
+ for (int i = 0; i < NTHREADS; ++i) {
+ pool.submit(() -> {
+ for (int j = 0; j < NROUNDS; ++j) {
+ try {
+ final Date date = new Date();
+
+ final long t0Millis = System.currentTimeMillis();
+ final String formattedDate = printer.format(date);
+ totalElapsed.addAndGet(0, System.currentTimeMillis() - t0Millis);
+
+ final long t1Millis = System.currentTimeMillis();
+ final Object pd = parser.parseObject(formattedDate);
+ totalElapsed.addAndGet(1, System.currentTimeMillis() - t1Millis);
+
+ if (!date.equals(pd)) {
+ failures.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ failures.incrementAndGet();
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ } finally {
+ pool.shutdown();
+ // depending on the performance of the machine used to run the parsing,
+ // the tests can run for a while. It should however complete within
+ // 30 seconds. Might need increase on very slow machines.
+ if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
+ pool.shutdownNow();
+ fail("did not complete tasks");
+ }
+ }
+ assertEquals(0, failures.get());
+ return totalElapsed;
+ }
+
+ @DefaultLocale(language = "en", country = "US")
+ @Test
+ public void test_changeDefault_Locale_DateInstance() {
+ final FastDateFormat format1 = FastDateFormat.getDateInstance(FastDateFormat.FULL, Locale.GERMANY);
+ final FastDateFormat format2 = FastDateFormat.getDateInstance(FastDateFormat.FULL);
+ Locale.setDefault(Locale.GERMANY);
+ final FastDateFormat format3 = FastDateFormat.getDateInstance(FastDateFormat.FULL);
+
+ assertSame(Locale.GERMANY, format1.getLocale());
+ assertEquals(Locale.US, format2.getLocale());
+ assertSame(Locale.GERMANY, format3.getLocale());
+ assertNotSame(format1, format2);
+ assertNotSame(format2, format3);
+ }
+
+ @DefaultLocale(language = "en", country = "US")
+ @Test
+ public void test_changeDefault_Locale_DateTimeInstance() {
+ final FastDateFormat format1 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL, Locale.GERMANY);
+ final FastDateFormat format2 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL);
+ Locale.setDefault(Locale.GERMANY);
+ final FastDateFormat format3 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL);
+
+ assertSame(Locale.GERMANY, format1.getLocale());
+ assertEquals(Locale.US, format2.getLocale());
+ assertSame(Locale.GERMANY, format3.getLocale());
+ assertNotSame(format1, format2);
+ assertNotSame(format2, format3);
+ }
+
+ /*
+ * Only the cache methods need to be tested here.
+ * The print methods are tested by {@link FastDateFormat_PrinterTest}
+ * and the parse methods are tested by {@link FastDateFormat_ParserTest}
+ */
+ @Test
+ public void test_getInstance() {
+ final FastDateFormat format1 = FastDateFormat.getInstance();
+ final FastDateFormat format2 = FastDateFormat.getInstance();
+ assertSame(format1, format2);
+ }
+
+ @Test
+ public void test_getInstance_String() {
+ final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy");
+ final FastDateFormat format2 = FastDateFormat.getInstance("MM-DD-yyyy");
+ final FastDateFormat format3 = FastDateFormat.getInstance("MM-DD-yyyy");
+
+ assertNotSame(format1, format2);
+ assertSame(format2, format3);
+ assertEquals("MM/DD/yyyy", format1.getPattern());
+ assertEquals(TimeZone.getDefault(), format1.getTimeZone());
+ assertEquals(TimeZone.getDefault(), format2.getTimeZone());
+ }
+
+ @DefaultLocale(language = "en", country = "US")
+ @Test
+ public void test_getInstance_String_Locale() {
+ final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
+ final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy");
+ final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
+
+ assertNotSame(format1, format2);
+ assertSame(format1, format3);
+ assertEquals(Locale.GERMANY, format1.getLocale());
+ }
+
+ @DefaultLocale(language = "en", country = "US")
+ @DefaultTimeZone("America/New_York")
+ @Test
+ public void test_getInstance_String_TimeZone() {
+
+ final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy",
+ TimeZone.getTimeZone("Atlantic/Reykjavik"));
+ final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy");
+ final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault());
+ final FastDateFormat format4 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault());
+ final FastDateFormat format5 = FastDateFormat.getInstance("MM-DD-yyyy", TimeZone.getDefault());
+ final FastDateFormat format6 = FastDateFormat.getInstance("MM-DD-yyyy");
+
+ assertNotSame(format1, format2);
+ assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
+ assertEquals(TimeZone.getDefault(), format2.getTimeZone());
+ assertSame(format3, format4);
+ assertNotSame(format3, format5);
+ assertNotSame(format4, format6);
+ }
+
+ @DefaultLocale(language = "en", country = "US")
+ @DefaultTimeZone("America/New_York")
+ @Test
+ public void test_getInstance_String_TimeZone_Locale() {
+ final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy",
+ TimeZone.getTimeZone("Atlantic/Reykjavik"), Locale.GERMANY);
+ final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
+ final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy",
+ TimeZone.getDefault(), Locale.GERMANY);
+
+ assertNotSame(format1, format2);
+ assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
+ assertEquals(TimeZone.getDefault(), format2.getTimeZone());
+ assertEquals(TimeZone.getDefault(), format3.getTimeZone());
+ assertEquals(Locale.GERMANY, format1.getLocale());
+ assertEquals(Locale.GERMANY, format2.getLocale());
+ assertEquals(Locale.GERMANY, format3.getLocale());
+ }
+
+ @Test
+ public void testCheckDefaults() {
+ final FastDateFormat format = FastDateFormat.getInstance();
+ final FastDateFormat medium = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT);
+ assertEquals(medium, format);
+
+ final SimpleDateFormat sdf = new SimpleDateFormat();
+ assertEquals(sdf.toPattern(), format.getPattern());
+
+ assertEquals(Locale.getDefault(), format.getLocale());
+ assertEquals(TimeZone.getDefault(), format.getTimeZone());
+ }
+
+ @Test
+ public void testCheckDifferingStyles() {
+ final FastDateFormat shortShort = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT, Locale.US);
+ final FastDateFormat shortLong = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.LONG, Locale.US);
+ final FastDateFormat longShort = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT, Locale.US);
+ final FastDateFormat longLong = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.LONG, Locale.US);
+
+ assertNotEquals(shortShort, shortLong);
+ assertNotEquals(shortShort, longShort);
+ assertNotEquals(shortShort, longLong);
+ assertNotEquals(shortLong, longShort);
+ assertNotEquals(shortLong, longLong);
+ assertNotEquals(longShort, longLong);
+ }
+
+ @Test
+ public void testDateDefaults() {
+ assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, Locale.CANADA),
+ FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
+
+ assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York")),
+ FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
+
+ assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG),
+ FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
+ }
+
+ @Test
+ public void testLANG_1152() {
+ final TimeZone utc = FastTimeZone.getGmtTimeZone();
+ final Date date = new Date(Long.MAX_VALUE);
+
+ String dateAsString = FastDateFormat.getInstance("yyyy-MM-dd", utc, Locale.US).format(date);
+ assertEquals("292278994-08-17", dateAsString);
+
+ dateAsString = FastDateFormat.getInstance("dd/MM/yyyy", utc, Locale.US).format(date);
+ assertEquals("17/08/292278994", dateAsString);
+ }
+ @Test
+ public void testLANG_1267() {
+ FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+ }
+
+ /**
+ * According to LANG-954 (https://issues.apache.org/jira/browse/LANG-954) this is broken in Android 2.1.
+ */
+ @Test
+ public void testLANG_954() {
+ final String pattern = "yyyy-MM-dd'T'";
+ FastDateFormat.getInstance(pattern);
+ }
+
+ @Test
+ public void testParseSync() throws InterruptedException {
+ final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS";
+ final SimpleDateFormat inner = new SimpleDateFormat(pattern);
+ final Format sdf= new Format() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj,
+ final StringBuffer toAppendTo,
+ final FieldPosition fieldPosition) {
+ synchronized(this) {
+ return inner.format(obj, toAppendTo, fieldPosition);
+ }
+ }
+
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ synchronized(this) {
+ return inner.parseObject(source, pos);
+ }
+ }
+ };
+ final AtomicLongArray sdfTime= measureTime(sdf, sdf);
+
+ final Format fdf = FastDateFormat.getInstance(pattern);
+ final AtomicLongArray fdfTime= measureTime(fdf, fdf);
+
+ //System.out.println(">>FastDateFormatTest: FastDatePrinter:"+fdfTime.get(0)+" SimpleDateFormat:"+sdfTime.get(0));
+ //System.out.println(">>FastDateFormatTest: FastDateParser:"+fdfTime.get(1)+" SimpleDateFormat:"+sdfTime.get(1));
+ }
+
+ @Test
+ public void testTimeDateDefaults() {
+ assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, Locale.CANADA),
+ FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.CANADA));
+
+ assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getTimeZone("America/New_York")),
+ FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
+
+ assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM),
+ FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.getDefault()));
+ }
+
+ @Test
+ public void testTimeDefaults() {
+ assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, Locale.CANADA),
+ FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
+
+ assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York")),
+ FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
+
+ assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG),
+ FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
+ }
+
+ @Test
+ public void testStandaloneShortMonthForm() {
+ final TimeZone utc = FastTimeZone.getGmtTimeZone();
+ final Instant testInstant = LocalDate.of(1970, 9, 15).atStartOfDay(ZoneId.of("UTC")).toInstant();
+ final Date date = Date.from(testInstant);
+
+ String dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, Locale.GERMAN).format(date);
+ assertEquals("1970-Sep-15", dateAsString);
+
+ dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, FINNISH).format(date);
+ assertEquals("1970-syys-15", dateAsString);
+
+ dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, HUNGARIAN).format(date);
+ assertEquals("1970-szept.-15", dateAsString);
+ }
+
+ @Test
+ public void testStandaloneLongMonthForm() {
+ final TimeZone utc = FastTimeZone.getGmtTimeZone();
+ final Instant testInstant = LocalDate.of(1970, 9, 15).atStartOfDay(ZoneId.of("UTC")).toInstant();
+ final Date date = Date.from(testInstant);
+
+ String dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, Locale.GERMAN).format(date);
+ assertEquals("1970-September-15", dateAsString);
+
+ dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, FINNISH).format(date);
+ assertEquals("1970-syyskuu-15", dateAsString);
+
+ dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, HUNGARIAN).format(date);
+ assertEquals("1970-szeptember-15", dateAsString);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.java
new file mode 100644
index 000000000..6ab4e22cc
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.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.
+ */
+package org.apache.commons.lang3.time;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Unit tests for the print methods of FastDateFormat
+ *
+ * @since 3.2
+ */
+public class FastDateFormat_PrinterTest extends FastDatePrinterTest {
+
+ @Override
+ protected DatePrinter getInstance(final String format, final TimeZone timeZone, final Locale locale) {
+ return FastDateFormat.getInstance(format, timeZone, locale);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java
new file mode 100644
index 000000000..7ce2b4720
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Compare FastDateParser with SimpleDateFormat
+ */
+public class FastDateParserSDFTest extends AbstractLangTest {
+
+ private static final TimeZone timeZone = TimeZone.getDefault();
+
+ public static Stream<Arguments> data() {
+ return Stream.of(
+ // General Time zone tests
+ Arguments.of("z yyyy", "GMT 2010", Locale.UK, true), // no offset specified, but this is allowed as a TimeZone name
+ Arguments.of("z yyyy", "GMT-123 2010", Locale.UK, false),
+ Arguments.of("z yyyy", "GMT-1234 2010", Locale.UK, false),
+ Arguments.of("z yyyy", "GMT-12:34 2010", Locale.UK, true),
+ Arguments.of("z yyyy", "GMT-1:23 2010", Locale.UK, true),
+ // RFC 822 tests
+ Arguments.of("z yyyy", "-1234 2010", Locale.UK, true),
+ Arguments.of("z yyyy", "-12:34 2010", Locale.UK, false),
+ Arguments.of("z yyyy", "-123 2010", Locale.UK, false),
+ // year tests
+ Arguments.of( "MM/dd/yyyy", "01/11/12", Locale.UK, true),
+ Arguments.of( "MM/dd/yy", "01/11/12", Locale.UK, true),
+
+ // LANG-1089
+ Arguments.of( "HH", "00", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "00", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "00", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "00", Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
+
+ Arguments.of( "HH", "01", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "01", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "01", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "01", Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
+
+ Arguments.of( "HH", "11", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "11", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "11", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "11", Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
+
+ Arguments.of( "HH", "12", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "12", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "12", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "12", Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
+
+ Arguments.of( "HH", "13", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "13", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "13", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "13", Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
+
+ Arguments.of( "HH", "23", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "23", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "23", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "23", Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
+
+ Arguments.of( "HH", "24", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "24", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "24", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "24", Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
+
+ Arguments.of( "HH", "25", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "25", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "25", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "25", Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
+
+ Arguments.of( "HH", "48", Locale.UK, true), // Hour in day (0-23)
+ Arguments.of( "KK", "48", Locale.UK, true), // Hour in am/pm (0-11)
+ Arguments.of( "hh", "48", Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ Arguments.of( "kk", "48", Locale.UK, true) // Hour in day (1-24), i.e. midnight is 24, not 0
+ );
+ }
+
+ private void checkParse(final String formattedDate, final String format, final Locale locale, final boolean valid) {
+ final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+ sdf.setTimeZone(timeZone);
+ final DateParser fdf = new FastDateParser(format, timeZone, locale);
+ Date expectedTime=null;
+ Class<?> sdfE = null;
+ try {
+ expectedTime = sdf.parse(formattedDate);
+ if (!valid) {
+ // Error in test data
+ throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime);
+ }
+ } catch (final ParseException e) {
+ if (valid) {
+ // Error in test data
+ throw new RuntimeException("Test data error: expected SDF parse to succeed, but got " + e);
+ }
+ sdfE = e.getClass();
+ }
+ Date actualTime = null;
+ Class<?> fdfE = null;
+ try {
+ actualTime = fdf.parse(formattedDate);
+ // failure in test
+ assertTrue(valid, "Expected FDP parse to fail, but got " + actualTime);
+ } catch (final ParseException e) {
+ // failure in test
+ assertFalse(valid, "Expected FDP parse to succeed, but got " + e);
+ fdfE = e.getClass();
+ }
+ if (valid) {
+ assertEquals(expectedTime, actualTime, locale.toString()+" "+formattedDate +"\n");
+ } else {
+ assertEquals(sdfE, fdfE, locale.toString()+" "+formattedDate + " expected same Exception ");
+ }
+ }
+
+ private void checkParsePosition(final String formattedDate, final String format, final Locale locale, final boolean valid) {
+ final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+ sdf.setTimeZone(timeZone);
+ final DateParser fdf = new FastDateParser(format, timeZone, locale);
+
+ final ParsePosition sdfP = new ParsePosition(0);
+ final Date expectedTime = sdf.parse(formattedDate, sdfP);
+ final int sdferrorIndex = sdfP.getErrorIndex();
+ if (valid) {
+ assertEquals(-1, sdferrorIndex, "Expected SDF error index -1 ");
+ final int endIndex = sdfP.getIndex();
+ final int length = formattedDate.length();
+ if (endIndex != length) {
+ // Error in test data
+ throw new RuntimeException("Test data error: expected SDF parse to consume entire string; endindex " + endIndex + " != " + length);
+ }
+ } else {
+ final int errorIndex = sdfP.getErrorIndex();
+ if (errorIndex == -1) {
+ throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime);
+ }
+ }
+
+ final ParsePosition fdfP = new ParsePosition(0);
+ final Date actualTime = fdf.parse(formattedDate, fdfP);
+ final int fdferrorIndex = fdfP.getErrorIndex();
+ if (valid) {
+ assertEquals(-1, fdferrorIndex, "Expected FDF error index -1 ");
+ final int endIndex = fdfP.getIndex();
+ final int length = formattedDate.length();
+ assertEquals(length, endIndex, "Expected FDF to parse full string " + fdfP);
+ assertEquals(expectedTime, actualTime, locale.toString()+" "+formattedDate +"\n");
+ } else {
+ assertNotEquals(-1, fdferrorIndex, "Test data error: expected FDF parse to fail, but got " + actualTime);
+ assertTrue(sdferrorIndex - fdferrorIndex <= 4,
+ "FDF error index ("+ fdferrorIndex + ") should approximate SDF index (" + sdferrorIndex + ")");
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testLowerCase(final String format, final String input, final Locale locale, final boolean valid) {
+ checkParse(input.toLowerCase(locale), format, locale, valid);
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testLowerCasePP(final String format, final String input, final Locale locale, final boolean valid) {
+ checkParsePosition(input.toLowerCase(locale), format, locale, valid);
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testOriginal(final String format, final String input, final Locale locale, final boolean valid) {
+ checkParse(input, format, locale, valid);
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testOriginalPP(final String format, final String input, final Locale locale, final boolean valid) {
+ checkParsePosition(input, format, locale, valid);
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testUpperCase(final String format, final String input, final Locale locale, final boolean valid) {
+ checkParse(input.toUpperCase(locale), format, locale, valid);
+ }
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testUpperCasePP(final String format, final String input, final Locale locale, final boolean valid) {
+ checkParsePosition(input.toUpperCase(locale), format, locale, valid);
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
new file mode 100644
index 000000000..dea55a89b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
@@ -0,0 +1,730 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.Serializable;
+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.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.LocaleUtils;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.commons.lang3.function.TriFunction;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.time.FastDateParser}.
+ *
+ * @since 3.2
+ */
+public class FastDateParserTest extends AbstractLangTest {
+
+ private enum Expected1806 {
+ India(INDIA, "+05", "+0530", "+05:30", true), Greenwich(TimeZones.GMT, "Z", "Z", "Z", false),
+ NewYork(NEW_YORK, "-05", "-0500", "-05:00", false);
+
+ final TimeZone zone;
+
+ final String one;
+ final String two;
+ final String three;
+ final long offset;
+
+ Expected1806(final TimeZone zone, final String one, final String two, final String three,
+ final boolean hasHalfHourOffset) {
+ this.zone = zone;
+ this.one = one;
+ this.two = two;
+ this.three = three;
+ this.offset = hasHalfHourOffset ? 30 * 60 * 1000 : 0;
+ }
+ }
+
+ static final String DATE_PARSER_PARAMETERS = "dateParserParameters";
+
+ static final String SHORT_FORMAT_NOERA = "y/M/d/h/a/m/s/E";
+
+ static final String LONG_FORMAT_NOERA = "yyyy/MMMM/dddd/hhhh/mmmm/ss/aaaa/EEEE";
+ static final String SHORT_FORMAT = "G/" + SHORT_FORMAT_NOERA;
+ static final String LONG_FORMAT = "GGGG/" + LONG_FORMAT_NOERA;
+
+ private static final String yMdHmsSZ = "yyyy-MM-dd'T'HH:mm:ss.SSS Z";
+ private static final String DMY_DOT = "dd.MM.yyyy";
+ private static final String YMD_SLASH = "yyyy/MM/dd";
+ private static final String MDY_DASH = "MM-DD-yyyy";
+ private static final String MDY_SLASH = "MM/DD/yyyy";
+
+ private static final TimeZone REYKJAVIK = TimeZone.getTimeZone("Atlantic/Reykjavik");
+ private static final TimeZone NEW_YORK = TimeZone.getTimeZone("America/New_York");
+ private static final TimeZone INDIA = TimeZone.getTimeZone("Asia/Calcutta");
+
+ private static final Locale SWEDEN = new Locale("sv", "SE");
+
+ static void checkParse(final Locale locale, final Calendar cal, final SimpleDateFormat simpleDateFormat,
+ final DateParser dateParser) {
+ final String formattedDate = simpleDateFormat.format(cal.getTime());
+ checkParse(locale, simpleDateFormat, dateParser, formattedDate, formattedDate);
+ checkParse(locale, simpleDateFormat, dateParser, formattedDate.toLowerCase(locale), formattedDate);
+ checkParse(locale, simpleDateFormat, dateParser, formattedDate.toUpperCase(locale), formattedDate);
+ }
+
+ static void checkParse(final Locale locale, final SimpleDateFormat simpleDateFormat, final DateParser dateParser,
+ final String formattedDate, final String originalFormattedDate) {
+ try {
+ final Date expectedTime = simpleDateFormat.parse(formattedDate);
+ final Date actualTime = dateParser.parse(formattedDate);
+ assertEquals(expectedTime, actualTime,
+ "locale: " + locale + ", formattedDate: '" + formattedDate + "', originalFormattedDate: '"
+ + originalFormattedDate + ", simpleDateFormat.pattern: '" + simpleDateFormat + "', Java: "
+ + SystemUtils.JAVA_RUNTIME_VERSION + "\n");
+ } catch (final Exception e) {
+ fail("locale: " + locale + ", formattedDate: '" + formattedDate + "', error : " + e + "\n", e);
+ }
+ }
+
+ static Stream<Arguments> dateParserParameters() {
+ return Stream.of(
+ // @formatter:off
+ Arguments.of((TriFunction<String, TimeZone, Locale, DateParser>) (format, timeZone, locale)
+ -> new FastDateParser(format, timeZone, locale, null)),
+ Arguments.of((TriFunction<String, TimeZone, Locale, DateParser>) FastDateFormat::getInstance)
+ // @formatter:on
+ );
+ }
+
+ private static Calendar initializeCalendar(final TimeZone timeZone) {
+ final Calendar cal = Calendar.getInstance(timeZone);
+ cal.set(Calendar.YEAR, 2001);
+ cal.set(Calendar.MONTH, 1); // not daylight savings
+ cal.set(Calendar.DAY_OF_MONTH, 4);
+ cal.set(Calendar.HOUR_OF_DAY, 12);
+ cal.set(Calendar.MINUTE, 8);
+ cal.set(Calendar.SECOND, 56);
+ cal.set(Calendar.MILLISECOND, 235);
+ return cal;
+ }
+
+ private final TriFunction<String, TimeZone, Locale, DateParser> dateParserProvider = (format, timeZone,
+ locale) -> new FastDateParser(format, timeZone, locale, null);
+
+ private DateParser getDateInstance(final int dateStyle, final Locale locale) {
+ return getInstance(null, FormatCache.getPatternForStyle(Integer.valueOf(dateStyle), null, locale),
+ TimeZone.getDefault(), Locale.getDefault());
+ }
+
+ private Calendar getEraStart(int year, final TimeZone zone, final Locale locale) {
+ final Calendar cal = Calendar.getInstance(zone, locale);
+ cal.clear();
+
+ // https://docs.oracle.com/javase/8/docs/technotes/guides/intl/calendar.doc.html
+ if (locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
+ if (year < 1868) {
+ cal.set(Calendar.ERA, 0);
+ cal.set(Calendar.YEAR, 1868 - year);
+ }
+ } else {
+ if (year < 0) {
+ cal.set(Calendar.ERA, GregorianCalendar.BC);
+ year = -year;
+ }
+ cal.set(Calendar.YEAR, year / 100 * 100);
+ }
+ return cal;
+ }
+
+ DateParser getInstance(final String format) {
+ return getInstance(null, format, TimeZone.getDefault(), Locale.getDefault());
+ }
+
+ DateParser getInstance(final String format, final Locale locale) {
+ return getInstance(null, format, TimeZone.getDefault(), locale);
+ }
+
+ private DateParser getInstance(final String format, final TimeZone timeZone) {
+ return getInstance(null, format, timeZone, Locale.getDefault());
+ }
+
+ /**
+ * Override this method in derived tests to change the construction of instances
+ *
+ * @param dpProvider TODO
+ * @param format the format string to use
+ * @param timeZone the time zone to use
+ * @param locale the locale to use
+ *
+ * @return the DateParser instance to use for testing
+ */
+ protected DateParser getInstance(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider,
+ final String format, final TimeZone timeZone, final Locale locale) {
+ return (dpProvider == null ? this.dateParserProvider : dpProvider).apply(format, timeZone, locale);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void test_Equality_Hash(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) {
+ final DateParser[] parsers = {getInstance(dpProvider, yMdHmsSZ, NEW_YORK, Locale.US),
+ getInstance(dpProvider, DMY_DOT, NEW_YORK, Locale.US),
+ getInstance(dpProvider, YMD_SLASH, NEW_YORK, Locale.US),
+ getInstance(dpProvider, MDY_DASH, NEW_YORK, Locale.US),
+ getInstance(dpProvider, MDY_SLASH, NEW_YORK, Locale.US),
+ getInstance(dpProvider, MDY_SLASH, REYKJAVIK, Locale.US),
+ getInstance(dpProvider, MDY_SLASH, REYKJAVIK, SWEDEN)};
+
+ final Map<DateParser, Integer> map = new HashMap<>();
+ int i = 0;
+ for (final DateParser parser : parsers) {
+ map.put(parser, Integer.valueOf(i++));
+ }
+
+ i = 0;
+ for (final DateParser parser : parsers) {
+ assertEquals(i++, map.get(parser).intValue());
+ }
+ }
+
+ @Test
+ public void test1806() throws ParseException {
+ final String formatStub = "yyyy-MM-dd'T'HH:mm:ss.SSS";
+ final String dateStub = "2001-02-04T12:08:56.235";
+
+ for (final Expected1806 trial : Expected1806.values()) {
+ final Calendar cal = initializeCalendar(trial.zone);
+
+ final String message = trial.zone.getDisplayName() + ";";
+
+ DateParser parser = getInstance(formatStub + "X", trial.zone);
+ assertEquals(cal.getTime().getTime(), parser.parse(dateStub + trial.one).getTime() - trial.offset,
+ message + trial.one);
+
+ parser = getInstance(formatStub + "XX", trial.zone);
+ assertEquals(cal.getTime(), parser.parse(dateStub + trial.two), message + trial.two);
+
+ parser = getInstance(formatStub + "XXX", trial.zone);
+ assertEquals(cal.getTime(), parser.parse(dateStub + trial.three), message + trial.three);
+ }
+ }
+
+ @Test
+ public void test1806Argument() {
+ assertThrows(IllegalArgumentException.class, () -> getInstance("XXXX"));
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testAmPm(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
+ final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
+ cal.clear();
+
+ final DateParser h = getInstance(dpProvider, "yyyy-MM-dd hh a mm:ss", NEW_YORK, Locale.US);
+ final DateParser K = getInstance(dpProvider, "yyyy-MM-dd KK a mm:ss", NEW_YORK, Locale.US);
+ final DateParser k = getInstance(dpProvider, "yyyy-MM-dd kk:mm:ss", NEW_YORK, Locale.US);
+ final DateParser H = getInstance(dpProvider, "yyyy-MM-dd HH:mm:ss", NEW_YORK, Locale.US);
+
+ cal.set(2010, Calendar.AUGUST, 1, 0, 33, 20);
+ assertEquals(cal.getTime(), h.parse("2010-08-01 12 AM 33:20"));
+ assertEquals(cal.getTime(), K.parse("2010-08-01 0 AM 33:20"));
+ assertEquals(cal.getTime(), k.parse("2010-08-01 00:33:20"));
+ assertEquals(cal.getTime(), H.parse("2010-08-01 00:33:20"));
+
+ cal.set(2010, Calendar.AUGUST, 1, 3, 33, 20);
+ assertEquals(cal.getTime(), h.parse("2010-08-01 3 AM 33:20"));
+ assertEquals(cal.getTime(), K.parse("2010-08-01 3 AM 33:20"));
+ assertEquals(cal.getTime(), k.parse("2010-08-01 03:33:20"));
+ assertEquals(cal.getTime(), H.parse("2010-08-01 03:33:20"));
+
+ cal.set(2010, Calendar.AUGUST, 1, 15, 33, 20);
+ assertEquals(cal.getTime(), h.parse("2010-08-01 3 PM 33:20"));
+ assertEquals(cal.getTime(), K.parse("2010-08-01 3 PM 33:20"));
+ assertEquals(cal.getTime(), k.parse("2010-08-01 15:33:20"));
+ assertEquals(cal.getTime(), H.parse("2010-08-01 15:33:20"));
+
+ cal.set(2010, Calendar.AUGUST, 1, 12, 33, 20);
+ assertEquals(cal.getTime(), h.parse("2010-08-01 12 PM 33:20"));
+ assertEquals(cal.getTime(), K.parse("2010-08-01 0 PM 33:20"));
+ assertEquals(cal.getTime(), k.parse("2010-08-01 12:33:20"));
+ assertEquals(cal.getTime(), H.parse("2010-08-01 12:33:20"));
+ }
+
+ @Test
+ public void testDayNumberOfWeek() throws ParseException {
+ final DateParser parser = getInstance("u");
+ final Calendar calendar = Calendar.getInstance();
+
+ calendar.setTime(parser.parse("1"));
+ assertEquals(Calendar.MONDAY, calendar.get(Calendar.DAY_OF_WEEK));
+
+ calendar.setTime(parser.parse("6"));
+ assertEquals(Calendar.SATURDAY, calendar.get(Calendar.DAY_OF_WEEK));
+
+ calendar.setTime(parser.parse("7"));
+ assertEquals(Calendar.SUNDAY, calendar.get(Calendar.DAY_OF_WEEK));
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testDayOf(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
+ final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
+ cal.clear();
+ cal.set(2003, Calendar.FEBRUARY, 10);
+
+ final DateParser fdf = getInstance(dpProvider, "W w F D y", NEW_YORK, Locale.US);
+ assertEquals(cal.getTime(), fdf.parse("3 7 2 41 03"));
+ }
+
+ @Test
+ public void testEquals() {
+ final DateParser parser1 = getInstance(YMD_SLASH);
+ final DateParser parser2 = getInstance(YMD_SLASH);
+
+ assertEquals(parser1, parser2);
+ assertEquals(parser1.hashCode(), parser2.hashCode());
+
+ assertNotEquals(parser1, new Object());
+ }
+
+ @Test
+ public void testJpLocales() {
+
+ final Calendar cal = Calendar.getInstance(TimeZones.GMT);
+ cal.clear();
+ cal.set(2003, Calendar.FEBRUARY, 10);
+ cal.set(Calendar.ERA, GregorianCalendar.BC);
+
+ final Locale locale = LocaleUtils.toLocale("zh");
+ // ja_JP_JP cannot handle dates before 1868 properly
+
+ final SimpleDateFormat sdf = new SimpleDateFormat(LONG_FORMAT, locale);
+ final DateParser fdf = getInstance(LONG_FORMAT, locale);
+
+ // If parsing fails, a ParseException will be thrown and the test will fail
+ checkParse(locale, cal, sdf, fdf);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLANG_831(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws Exception {
+ testSdfAndFdp(dpProvider, "M E", "3 Tue", true);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLANG_832(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws Exception {
+ testSdfAndFdp(dpProvider, "'d'd", "d3", false); // OK
+ testSdfAndFdp(dpProvider, "'d'd'", "d3", true); // should fail (unterminated quote)
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLang1121(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
+ final TimeZone kst = TimeZone.getTimeZone("KST");
+ final DateParser fdp = getInstance(dpProvider, "yyyyMMdd", kst, Locale.KOREA);
+
+ assertThrows(ParseException.class, () -> fdp.parse("2015"));
+
+ // Wed Apr 29 00:00:00 KST 2015
+ Date actual = fdp.parse("20150429");
+ final Calendar cal = Calendar.getInstance(kst, Locale.KOREA);
+ cal.clear();
+ cal.set(2015, 3, 29);
+ Date expected = cal.getTime();
+ assertEquals(expected, actual);
+
+ final SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd", Locale.KOREA);
+ df.setTimeZone(kst);
+ expected = df.parse("20150429113100");
+
+ // Thu Mar 16 00:00:00 KST 81724
+ actual = fdp.parse("20150429113100");
+ assertEquals(expected, actual);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLang1380(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
+ final Calendar expected = Calendar.getInstance(TimeZones.GMT, Locale.FRANCE);
+ expected.clear();
+ expected.set(2014, Calendar.APRIL, 14);
+
+ final DateParser fdp = getInstance(dpProvider, "dd MMM yyyy", TimeZones.GMT, Locale.FRANCE);
+ assertEquals(expected.getTime(), fdp.parse("14 avril 2014"));
+ assertEquals(expected.getTime(), fdp.parse("14 avr. 2014"));
+ assertEquals(expected.getTime(), fdp.parse("14 avr 2014"));
+ }
+
+ @Test
+ public void testLang303() throws ParseException {
+ DateParser parser = getInstance(YMD_SLASH);
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2004, Calendar.DECEMBER, 31);
+
+ final Date date = parser.parse("2004/11/31");
+
+ parser = SerializationUtils.deserialize(SerializationUtils.serialize((Serializable) parser));
+ assertEquals(date, parser.parse("2004/11/31"));
+ }
+
+ @Test
+ public void testLang538() throws ParseException {
+ final DateParser parser = getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZones.GMT);
+
+ final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT-8"));
+ cal.clear();
+ cal.set(2009, Calendar.OCTOBER, 16, 8, 42, 16);
+
+ assertEquals(cal.getTime(), parser.parse("2009-10-16T16:42:16.000Z"));
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLang996(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
+ final Calendar expected = Calendar.getInstance(NEW_YORK, Locale.US);
+ expected.clear();
+ expected.set(2014, Calendar.MAY, 14);
+
+ final DateParser fdp = getInstance(dpProvider, "ddMMMyyyy", NEW_YORK, Locale.US);
+ assertEquals(expected.getTime(), fdp.parse("14may2014"));
+ assertEquals(expected.getTime(), fdp.parse("14MAY2014"));
+ assertEquals(expected.getTime(), fdp.parse("14May2014"));
+ }
+
+ @Test
+ public void testLocaleMatches() {
+ final DateParser parser = getInstance(yMdHmsSZ, SWEDEN);
+ assertEquals(SWEDEN, parser.getLocale());
+ }
+
+ /**
+ * Tests that pre-1000AD years get padded with yyyy
+ *
+ * @throws ParseException so we don't have to catch it
+ */
+ @Test
+ public void testLowYearPadding() throws ParseException {
+ final DateParser parser = getInstance(YMD_SLASH);
+ final Calendar cal = Calendar.getInstance();
+ cal.clear();
+
+ cal.set(1, Calendar.JANUARY, 1);
+ assertEquals(cal.getTime(), parser.parse("0001/01/01"));
+ cal.set(10, Calendar.JANUARY, 1);
+ assertEquals(cal.getTime(), parser.parse("0010/01/01"));
+ cal.set(100, Calendar.JANUARY, 1);
+ assertEquals(cal.getTime(), parser.parse("0100/01/01"));
+ cal.set(999, Calendar.JANUARY, 1);
+ assertEquals(cal.getTime(), parser.parse("0999/01/01"));
+ }
+
+ @Test
+ public void testMilleniumBug() throws ParseException {
+ final DateParser parser = getInstance(DMY_DOT);
+ final Calendar cal = Calendar.getInstance();
+ cal.clear();
+
+ cal.set(1000, Calendar.JANUARY, 1);
+ assertEquals(cal.getTime(), parser.parse("01.01.1000"));
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testParseLongShort(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws ParseException {
+ final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
+ cal.clear();
+ cal.set(2003, Calendar.FEBRUARY, 10, 15, 33, 20);
+ cal.set(Calendar.MILLISECOND, 989);
+ cal.setTimeZone(NEW_YORK);
+
+ DateParser fdf = getInstance(dpProvider, "yyyy GGGG MMMM dddd aaaa EEEE HHHH mmmm ssss SSSS ZZZZ", NEW_YORK,
+ Locale.US);
+
+ assertEquals(cal.getTime(), fdf.parse("2003 AD February 0010 PM Monday 0015 0033 0020 0989 GMT-05:00"));
+ cal.set(Calendar.ERA, GregorianCalendar.BC);
+
+ final Date parse = fdf.parse("2003 BC February 0010 PM Saturday 0015 0033 0020 0989 GMT-05:00");
+ assertEquals(cal.getTime(), parse);
+
+ fdf = getInstance(null, "y G M d a E H m s S Z", NEW_YORK, Locale.US);
+ assertEquals(cal.getTime(), fdf.parse("03 BC 2 10 PM Sat 15 33 20 989 -0500"));
+
+ cal.set(Calendar.ERA, GregorianCalendar.AD);
+ assertEquals(cal.getTime(), fdf.parse("03 AD 2 10 PM Saturday 15 33 20 989 -0500"));
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testParseNumerics(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws ParseException {
+ final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
+ cal.clear();
+ cal.set(2003, Calendar.FEBRUARY, 10, 15, 33, 20);
+ cal.set(Calendar.MILLISECOND, 989);
+
+ final DateParser fdf = getInstance(dpProvider, "yyyyMMddHHmmssSSS", NEW_YORK, Locale.US);
+ assertEquals(cal.getTime(), fdf.parse("20030210153320989"));
+ }
+
+ @Test
+ public void testParseOffset() {
+ final DateParser parser = getInstance(YMD_SLASH);
+ final Date date = parser.parse("Today is 2015/07/04", new ParsePosition(9));
+
+ final Calendar cal = Calendar.getInstance();
+ cal.clear();
+ cal.set(2015, Calendar.JULY, 4);
+ assertEquals(cal.getTime(), date);
+ }
+
+ @Test
+ // Check that all Locales can parse the formats we use
+ public void testParses() throws Exception {
+ for (final String format : new String[] {LONG_FORMAT, SHORT_FORMAT}) {
+ for (final Locale locale : Locale.getAvailableLocales()) {
+ for (final TimeZone timeZone : new TimeZone[] {NEW_YORK, REYKJAVIK, TimeZones.GMT}) {
+ for (final int year : new int[] {2003, 1940, 1868, 1867, 1, -1, -1940}) {
+ final Calendar cal = getEraStart(year, timeZone, locale);
+ final Date centuryStart = cal.getTime();
+
+ cal.set(Calendar.MONTH, 1);
+ cal.set(Calendar.DAY_OF_MONTH, 10);
+ final Date in = cal.getTime();
+
+ final FastDateParser fastDateParser = new FastDateParser(format, timeZone, locale,
+ centuryStart);
+ validateSdfFormatFdpParseEquality(format, locale, timeZone, fastDateParser, in, year,
+ centuryStart);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Fails on Java 16 Early Access build 25 and above, last tested with build 36.
+ */
+ @Test
+ public void testParsesKnownJava16Ea25Failure() throws Exception {
+ final String format = LONG_FORMAT;
+ final int year = 2003;
+ final Locale locale = new Locale.Builder().setLanguage("sq").setRegion("MK").build();
+ assertEquals("sq_MK", locale.toString());
+ assertNotNull(locale);
+ final TimeZone timeZone = NEW_YORK;
+ final Calendar cal = getEraStart(year, timeZone, locale);
+ final Date centuryStart = cal.getTime();
+
+ cal.set(Calendar.MONTH, 1);
+ cal.set(Calendar.DAY_OF_MONTH, 10);
+ final Date in = cal.getTime();
+
+ final FastDateParser fastDateParser = new FastDateParser(format, timeZone, locale, centuryStart);
+ validateSdfFormatFdpParseEquality(format, locale, timeZone, fastDateParser, in, year, centuryStart);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testParseZone(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws ParseException {
+ final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
+ cal.clear();
+ cal.set(2003, Calendar.JULY, 10, 16, 33, 20);
+
+ final DateParser fdf = getInstance(dpProvider, yMdHmsSZ, NEW_YORK, Locale.US);
+
+ assertEquals(cal.getTime(), fdf.parse("2003-07-10T15:33:20.000 -0500"));
+ assertEquals(cal.getTime(), fdf.parse("2003-07-10T15:33:20.000 GMT-05:00"));
+ assertEquals(cal.getTime(), fdf.parse("2003-07-10T16:33:20.000 Eastern Daylight Time"));
+ assertEquals(cal.getTime(), fdf.parse("2003-07-10T16:33:20.000 EDT"));
+
+ cal.setTimeZone(TimeZone.getTimeZone("GMT-3"));
+ cal.set(2003, Calendar.FEBRUARY, 10, 9, 0, 0);
+
+ assertEquals(cal.getTime(), fdf.parse("2003-02-10T09:00:00.000 -0300"));
+
+ cal.setTimeZone(TimeZone.getTimeZone("GMT+5"));
+ cal.set(2003, Calendar.FEBRUARY, 10, 15, 5, 6);
+
+ assertEquals(cal.getTime(), fdf.parse("2003-02-10T15:05:06.000 +0500"));
+ }
+
+ @Test
+ public void testPatternMatches() {
+ final DateParser parser = getInstance(yMdHmsSZ);
+ assertEquals(yMdHmsSZ, parser.getPattern());
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testQuotes(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
+ final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
+ cal.clear();
+ cal.set(2003, Calendar.FEBRUARY, 10, 15, 33, 20);
+ cal.set(Calendar.MILLISECOND, 989);
+
+ final DateParser fdf = getInstance(dpProvider, "''yyyyMMdd'A''B'HHmmssSSS''", NEW_YORK, Locale.US);
+ assertEquals(cal.getTime(), fdf.parse("'20030210A'B153320989'"));
+ }
+
+ private void testSdfAndFdp(final TriFunction<String, TimeZone, Locale, DateParser> dbProvider, final String format,
+ final String date, final boolean shouldFail) throws Exception {
+ Date dfdp = null;
+ Date dsdf = null;
+ Throwable f = null;
+ Throwable s = null;
+
+ try {
+ final SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US);
+ sdf.setTimeZone(NEW_YORK);
+ dsdf = sdf.parse(date);
+ assertFalse(shouldFail, "Expected SDF failure, but got " + dsdf + " for [" + format + ", " + date + "]");
+ } catch (final Exception e) {
+ s = e;
+ if (!shouldFail) {
+ throw e;
+ }
+ }
+
+ try {
+ final DateParser fdp = getInstance(dbProvider, format, NEW_YORK, Locale.US);
+ dfdp = fdp.parse(date);
+ assertFalse(shouldFail, "Expected FDF failure, but got " + dfdp + " for [" + format + ", " + date + "]");
+ } catch (final Exception e) {
+ f = e;
+ if (!shouldFail) {
+ throw e;
+ }
+ }
+ // SDF and FDF should produce equivalent results
+ assertEquals((f == null), (s == null), "Should both or neither throw Exceptions");
+ assertEquals(dsdf, dfdp, "Parsed dates should be equal");
+ }
+
+ /**
+ * Test case for {@link FastDateParser#FastDateParser(String, TimeZone, Locale)}.
+ *
+ * @throws ParseException so we don't have to catch it
+ */
+ @Test
+ public void testShortDateStyleWithLocales() throws ParseException {
+ DateParser fdf = getDateInstance(FastDateFormat.SHORT, Locale.US);
+ final Calendar cal = Calendar.getInstance();
+ cal.clear();
+
+ cal.set(2004, Calendar.FEBRUARY, 3);
+ assertEquals(cal.getTime(), fdf.parse("2/3/04"));
+
+ fdf = getDateInstance(FastDateFormat.SHORT, SWEDEN);
+ assertEquals(cal.getTime(), fdf.parse("2004-02-03"));
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testSpecialCharacters(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testSdfAndFdp(dpProvider, "q", "", true); // bad pattern character (at present)
+ testSdfAndFdp(dpProvider, "Q", "", true); // bad pattern character
+ testSdfAndFdp(dpProvider, "$", "$", false); // OK
+ testSdfAndFdp(dpProvider, "?.d", "?.12", false); // OK
+ testSdfAndFdp(dpProvider, "''yyyyMMdd'A''B'HHmmssSSS''", "'20030210A'B153320989'", false); // OK
+ testSdfAndFdp(dpProvider, "''''yyyyMMdd'A''B'HHmmssSSS''", "''20030210A'B153320989'", false); // OK
+ testSdfAndFdp(dpProvider, "'$\\Ed'", "$\\Ed", false); // OK
+
+ // quoted characters are case-sensitive
+ testSdfAndFdp(dpProvider, "'QED'", "QED", false);
+ testSdfAndFdp(dpProvider, "'QED'", "qed", true);
+ // case-sensitive after insensitive Month field
+ testSdfAndFdp(dpProvider, "yyyy-MM-dd 'QED'", "2003-02-10 QED", false);
+ testSdfAndFdp(dpProvider, "yyyy-MM-dd 'QED'", "2003-02-10 qed", true);
+ }
+
+ @Test
+ public void testTimeZoneMatches() {
+ final DateParser parser = getInstance(yMdHmsSZ, REYKJAVIK);
+ assertEquals(REYKJAVIK, parser.getTimeZone());
+ }
+
+ @Test
+ public void testToStringContainsName() {
+ final DateParser parser = getInstance(YMD_SLASH);
+ assertTrue(parser.toString().startsWith("FastDate"));
+ }
+
+ // we cannot use historic dates to test time zone parsing, some time zones have second offsets
+ // as well as hours and minutes which makes the z formats a low fidelity round trip
+ @Test
+ public void testTzParses() throws Exception {
+ // Check that all Locales can parse the time formats we use
+ for (final Locale locale : Locale.getAvailableLocales()) {
+ final FastDateParser fdp = new FastDateParser("yyyy/MM/dd z", TimeZone.getDefault(), locale);
+
+ for (final TimeZone timeZone : new TimeZone[] {NEW_YORK, REYKJAVIK, TimeZones.GMT}) {
+ final Calendar cal = Calendar.getInstance(timeZone, locale);
+ cal.clear();
+ cal.set(Calendar.YEAR, 2000);
+ cal.set(Calendar.MONTH, 1);
+ cal.set(Calendar.DAY_OF_MONTH, 10);
+ final Date expected = cal.getTime();
+
+ final Date actual = fdp.parse("2000/02/10 " + timeZone.getDisplayName(locale));
+ assertEquals(expected, actual, "timeZone:" + timeZone.getID() + " locale:" + locale.getDisplayName());
+ }
+ }
+ }
+
+ private void validateSdfFormatFdpParseEquality(final String formatStr, final Locale locale, final TimeZone timeZone,
+ final FastDateParser dateParser, final Date inDate, final int year, final Date csDate) throws ParseException {
+ final SimpleDateFormat sdf = new SimpleDateFormat(formatStr, locale);
+ sdf.setTimeZone(timeZone);
+ if (formatStr.equals(SHORT_FORMAT)) {
+ sdf.set2DigitYearStart(csDate);
+ }
+ final String fmt = sdf.format(inDate);
+// System.out.printf("[Java %s] Date: '%s' formatted with '%s' -> '%s'%n", SystemUtils.JAVA_RUNTIME_VERSION, inDate,
+// formatStr, fmt);
+ try {
+ final Date out = dateParser.parse(fmt);
+ assertEquals(inDate, out, "format: '" + formatStr + "', locale: '" + locale + "', time zone: '"
+ + timeZone.getID() + "', year: " + year + ", parse: '" + fmt);
+ } catch (final ParseException pe) {
+ if (year >= 1868 || !locale.getCountry().equals("JP")) {
+ // LANG-978
+ throw pe;
+ }
+ }
+ }
+}
+
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateParser_MoreOrLessTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateParser_MoreOrLessTest.java
new file mode 100644
index 000000000..6895de0ed
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParser_MoreOrLessTest.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+public class FastDateParser_MoreOrLessTest extends AbstractLangTest {
+
+ private static final TimeZone NEW_YORK = TimeZone.getTimeZone("America/New_York");
+
+ @Test
+ public void testInputHasLessCharacters() {
+ final FastDateParser parser = new FastDateParser("MM/dd/yyy", TimeZone.getDefault(), Locale.getDefault());
+ final ParsePosition parsePosition = new ParsePosition(0);
+ assertNull(parser.parse("03/23", parsePosition));
+ assertEquals(5, parsePosition.getErrorIndex());
+ }
+
+ @Test
+ public void testInputHasMoreCharacters() {
+ final FastDateParser parser = new FastDateParser("MM/dd", TimeZone.getDefault(), Locale.getDefault());
+ final ParsePosition parsePosition = new ParsePosition(0);
+ final Date date = parser.parse("3/23/61", parsePosition);
+ assertEquals(4, parsePosition.getIndex());
+
+ final Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ assertEquals(2, calendar.get(Calendar.MONTH));
+ assertEquals(23, calendar.get(Calendar.DATE));
+ }
+
+ @Test
+ public void testInputHasPrecedingCharacters() {
+ final FastDateParser parser = new FastDateParser("MM/dd", TimeZone.getDefault(), Locale.getDefault());
+ final ParsePosition parsePosition = new ParsePosition(0);
+ final Date date = parser.parse("A 3/23/61", parsePosition);
+ assertNull(date);
+ assertEquals(0, parsePosition.getIndex());
+ assertEquals(0, parsePosition.getErrorIndex());
+ }
+
+ @Test
+ public void testInputHasWhitespace() {
+ final FastDateParser parser = new FastDateParser("M/d/y", TimeZone.getDefault(), Locale.getDefault());
+ //SimpleDateFormat parser = new SimpleDateFormat("M/d/y");
+ final ParsePosition parsePosition = new ParsePosition(0);
+ final Date date = parser.parse(" 3/ 23/ 1961", parsePosition);
+ assertEquals(12, parsePosition.getIndex());
+
+ final Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ assertEquals(1961, calendar.get(Calendar.YEAR));
+ assertEquals(2, calendar.get(Calendar.MONTH));
+ assertEquals(23, calendar.get(Calendar.DATE));
+ }
+
+ @Test
+ public void testInputHasWrongCharacters() {
+ final FastDateParser parser = new FastDateParser("MM-dd-yyy", TimeZone.getDefault(), Locale.getDefault());
+ final ParsePosition parsePosition = new ParsePosition(0);
+ assertNull(parser.parse("03/23/1961", parsePosition));
+ assertEquals(2, parsePosition.getErrorIndex());
+ }
+
+ @Test
+ public void testInputHasWrongDay() {
+ final FastDateParser parser = new FastDateParser("EEEE, MM/dd/yyy", NEW_YORK, Locale.US);
+ final String input = "Thursday, 03/23/61";
+ final ParsePosition parsePosition = new ParsePosition(0);
+ assertNotNull(parser.parse(input, parsePosition));
+ assertEquals(input.length(), parsePosition.getIndex());
+
+ parsePosition.setIndex(0);
+ assertNull(parser.parse( "Thorsday, 03/23/61", parsePosition));
+ assertEquals(0, parsePosition.getErrorIndex());
+ }
+
+ @Test
+ public void testInputHasWrongTimeZone() {
+ final FastDateParser parser = new FastDateParser("mm:ss z", NEW_YORK, Locale.US);
+
+ final String input = "11:23 Pacific Standard Time";
+ final ParsePosition parsePosition = new ParsePosition(0);
+ assertNotNull(parser.parse(input, parsePosition));
+ assertEquals(input.length(), parsePosition.getIndex());
+
+ parsePosition.setIndex(0);
+ assertNull(parser.parse( "11:23 Pacific Standard ", parsePosition));
+ assertEquals(6, parsePosition.getErrorIndex());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java
new file mode 100644
index 000000000..90c38af8d
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class FastDateParser_TimeZoneStrategyTest extends AbstractLangTest {
+
+ @Test
+ void testLang1219() throws ParseException {
+ final FastDateParser parser = new FastDateParser("dd.MM.yyyy HH:mm:ss z", TimeZone.getDefault(), Locale.GERMAN);
+
+ final Date summer = parser.parse("26.10.2014 02:00:00 MESZ");
+ final Date standard = parser.parse("26.10.2014 02:00:00 MEZ");
+ assertNotEquals(summer.getTime(), standard.getTime());
+ }
+
+ @ParameterizedTest
+ @MethodSource("java.util.Locale#getAvailableLocales")
+ void testTimeZoneStrategyPattern(final Locale locale) throws ParseException {
+ final FastDateParser parser = new FastDateParser("z", TimeZone.getDefault(), locale);
+ final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
+ for (final String[] zone : zones) {
+ for (int t = 1; t < zone.length; ++t) {
+ final String tzDisplay = zone[t];
+ if (tzDisplay == null) {
+ break;
+ }
+ // An exception will be thrown and the test will fail if parsing isn't successful
+ parser.parse(tzDisplay);
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java b/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
new file mode 100644
index 000000000..cf7c2488b
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.Serializable;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.SerializationUtils;
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.DefaultLocale;
+import org.junitpioneer.jupiter.DefaultTimeZone;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.time.FastDatePrinter}.
+ *
+ * @since 3.0
+ */
+public class FastDatePrinterTest extends AbstractLangTest {
+
+ private enum Expected1806 {
+ India(INDIA, "+05", "+0530", "+05:30"), Greenwich(TimeZones.GMT, "Z", "Z", "Z"), NewYork(
+ NEW_YORK, "-05", "-0500", "-05:00");
+
+ final TimeZone zone;
+
+ final String one;
+ final String two;
+ final String three;
+ Expected1806(final TimeZone zone, final String one, final String two, final String three) {
+ this.zone = zone;
+ this.one = one;
+ this.two = two;
+ this.three = three;
+ }
+ }
+ private static final String YYYY_MM_DD = "yyyy/MM/dd";
+ private static final TimeZone NEW_YORK = TimeZone.getTimeZone("America/New_York");
+ private static final TimeZone INDIA = TimeZone.getTimeZone("Asia/Calcutta");
+
+ private static final Locale SWEDEN = new Locale("sv", "SE");
+
+ private static Calendar initializeCalendar(final TimeZone tz) {
+ final Calendar cal = Calendar.getInstance(tz);
+ cal.set(Calendar.YEAR, 2001);
+ cal.set(Calendar.MONTH, 1); // not daylight savings
+ cal.set(Calendar.DAY_OF_MONTH, 4);
+ cal.set(Calendar.HOUR_OF_DAY, 12);
+ cal.set(Calendar.MINUTE, 8);
+ cal.set(Calendar.SECOND, 56);
+ cal.set(Calendar.MILLISECOND, 235);
+ return cal;
+ }
+
+ private DatePrinter getDateInstance(final int dateStyle, final Locale locale) {
+ return getInstance(FormatCache.getPatternForStyle(Integer.valueOf(dateStyle), null, locale), TimeZone.getDefault(), Locale.getDefault());
+ }
+
+ DatePrinter getInstance(final String format) {
+ return getInstance(format, TimeZone.getDefault(), Locale.getDefault());
+ }
+
+ private DatePrinter getInstance(final String format, final Locale locale) {
+ return getInstance(format, TimeZone.getDefault(), locale);
+ }
+
+ private DatePrinter getInstance(final String format, final TimeZone timeZone) {
+ return getInstance(format, timeZone, Locale.getDefault());
+ }
+
+ /**
+ * Override this method in derived tests to change the construction of instances
+ * @param format the format string to use
+ * @param timeZone the time zone to use
+ * @param locale the locale to use
+ * @return the DatePrinter to use for testing
+ */
+ protected DatePrinter getInstance(final String format, final TimeZone timeZone, final Locale locale) {
+ return new FastDatePrinter(format, timeZone, locale);
+ }
+
+ @Test
+ public void test1806() {
+ for (final Expected1806 trial : Expected1806.values()) {
+ final Calendar cal = initializeCalendar(trial.zone);
+
+ DatePrinter printer = getInstance("X", trial.zone);
+ assertEquals(trial.one, printer.format(cal));
+
+ printer = getInstance("XX", trial.zone);
+ assertEquals(trial.two, printer.format(cal));
+
+ printer = getInstance("XXX", trial.zone);
+ assertEquals(trial.three, printer.format(cal));
+ }
+ }
+ @Test
+ public void test1806Argument() {
+ assertThrows(IllegalArgumentException.class, () -> getInstance("XXXX"));
+ }
+
+ @Test
+ public void testAppendableOptions() {
+ final DatePrinter format = getInstance("yyyy-MM-dd HH:mm:ss.SSS Z", TimeZones.GMT);
+ final Calendar calendar = Calendar.getInstance();
+ final StringBuilder sb = new StringBuilder();
+ final String expected = format.format(calendar, sb).toString();
+ sb.setLength(0);
+
+ final Date date = calendar.getTime();
+ assertEquals(expected, format.format(date, sb).toString());
+ sb.setLength(0);
+
+ final long epoch = date.getTime();
+ assertEquals(expected, format.format(epoch, sb).toString());
+ }
+
+ @Test
+ public void testDayNumberOfWeek() {
+ final DatePrinter printer = getInstance("u");
+ final Calendar calendar = Calendar.getInstance();
+
+ calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
+ assertEquals("1", printer.format(calendar.getTime()));
+
+ calendar.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
+ assertEquals("6", printer.format(calendar.getTime()));
+
+ calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
+ assertEquals("7", printer.format(calendar.getTime()));
+ }
+
+ @Test
+ public void testEquals() {
+ final DatePrinter printer1= getInstance(YYYY_MM_DD);
+ final DatePrinter printer2= getInstance(YYYY_MM_DD);
+
+ assertEquals(printer1, printer2);
+ assertEquals(printer1.hashCode(), printer2.hashCode());
+
+ assertNotEquals(printer1, new Object());
+ }
+
+ @DefaultLocale(language = "en", country = "US")
+ @DefaultTimeZone("America/New_York")
+ @Test
+ public void testFormat() {
+ final GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20);
+ final GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 0, 0);
+ final Date date1 = cal1.getTime();
+ final Date date2 = cal2.getTime();
+ final long millis1 = date1.getTime();
+ final long millis2 = date2.getTime();
+
+ DatePrinter fdf = getInstance("yyyy-MM-dd'T'HH:mm:ss");
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+ assertEquals(sdf.format(date1), fdf.format(date1));
+ assertEquals("2003-01-10T15:33:20", fdf.format(date1));
+ assertEquals("2003-01-10T15:33:20", fdf.format(cal1));
+ assertEquals("2003-01-10T15:33:20", fdf.format(millis1));
+ assertEquals("2003-07-10T09:00:00", fdf.format(date2));
+ assertEquals("2003-07-10T09:00:00", fdf.format(cal2));
+ assertEquals("2003-07-10T09:00:00", fdf.format(millis2));
+
+ fdf = getInstance("Z");
+ assertEquals("-0500", fdf.format(date1));
+ assertEquals("-0500", fdf.format(cal1));
+ assertEquals("-0500", fdf.format(millis1));
+
+ assertEquals("-0400", fdf.format(date2));
+ assertEquals("-0400", fdf.format(cal2));
+ assertEquals("-0400", fdf.format(millis2));
+
+ fdf = getInstance("ZZ");
+ assertEquals("-05:00", fdf.format(date1));
+ assertEquals("-05:00", fdf.format(cal1));
+ assertEquals("-05:00", fdf.format(millis1));
+
+ assertEquals("-04:00", fdf.format(date2));
+ assertEquals("-04:00", fdf.format(cal2));
+ assertEquals("-04:00", fdf.format(millis2));
+
+ final String pattern = "GGGG GGG GG G yyyy yyy yy y MMMM MMM MM M LLLL LLL LL L" +
+ " dddd ddd dd d DDDD DDD DD D EEEE EEE EE E aaaa aaa aa a zzzz zzz zz z";
+ fdf = getInstance(pattern);
+ sdf = new SimpleDateFormat(pattern);
+ // SDF bug fix starting with Java 7
+ assertEquals(sdf.format(date1).replace("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date1));
+ assertEquals(sdf.format(date2).replace("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date2));
+ }
+
+ @Test
+ public void testHourFormats() {
+ final Calendar calendar = Calendar.getInstance();
+ calendar.clear();
+ final DatePrinter printer = getInstance("K k H h");
+
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ assertEquals("0 24 0 12", printer.format(calendar));
+
+ calendar.set(Calendar.HOUR_OF_DAY, 12);
+ assertEquals("0 12 12 12", printer.format(calendar));
+
+ calendar.set(Calendar.HOUR_OF_DAY, 23);
+ assertEquals("11 23 23 11", printer.format(calendar));
+ }
+
+ @Test
+ public void testLang1103() {
+ final Calendar cal = Calendar.getInstance(SWEDEN);
+ cal.set(Calendar.DAY_OF_MONTH, 2);
+
+ assertEquals("2", getInstance("d", SWEDEN).format(cal));
+ assertEquals("02", getInstance("dd", SWEDEN).format(cal));
+ assertEquals("002", getInstance("ddd", SWEDEN).format(cal));
+ assertEquals("0002", getInstance("dddd", SWEDEN).format(cal));
+ assertEquals("00002", getInstance("ddddd", SWEDEN).format(cal));
+ }
+
+ @Test
+ public void testLang303() {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2004, Calendar.DECEMBER, 31);
+
+ DatePrinter format = getInstance(YYYY_MM_DD);
+ final String output = format.format(cal);
+
+ format = SerializationUtils.deserialize(SerializationUtils.serialize((Serializable) format));
+ assertEquals(output, format.format(cal));
+ }
+
+ @Test
+ public void testLang538() {
+ // more commonly constructed with: cal = new GregorianCalendar(2009, 9, 16, 8, 42, 16)
+ // for the unit test to work in any time zone, constructing with GMT-8 rather than default locale time zone
+ final GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT-8"));
+ cal.clear();
+ cal.set(2009, Calendar.OCTOBER, 16, 8, 42, 16);
+
+ final DatePrinter format = getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZones.GMT);
+ assertEquals("2009-10-16T16:42:16.000Z", format.format(cal.getTime()), "dateTime");
+ assertEquals("2009-10-16T16:42:16.000Z", format.format(cal), "dateTime");
+ }
+
+ @Test
+ public void testLang645() {
+ final Locale locale = new Locale("sv", "SE");
+
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2010, Calendar.JANUARY, 1, 12, 0, 0);
+ final Date d = cal.getTime();
+
+ final DatePrinter fdf = getInstance("EEEE', week 'ww", locale);
+
+ assertEquals("fredag, week 53", fdf.format(d));
+ }
+
+ /**
+ * According to LANG-916 (https://issues.apache.org/jira/browse/LANG-916),
+ * the format method did contain a bug: it did not use the TimeZone data.
+ *
+ * This method test that the bug is fixed.
+ */
+ @Test
+ public void testLang916() {
+
+ final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris"));
+ cal.clear();
+ cal.set(2009, 9, 16, 8, 42, 16);
+
+ // calendar fast.
+ {
+ final String value = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss Z", TimeZone.getTimeZone("Europe/Paris")).format(cal);
+ assertEquals("2009-10-16T08:42:16 +0200", value, "calendar");
+ }
+ {
+ final String value = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss Z", TimeZone.getTimeZone("Asia/Kolkata")).format(cal);
+ assertEquals("2009-10-16T12:12:16 +0530", value, "calendar");
+ }
+ {
+ final String value = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss Z", TimeZone.getTimeZone("Europe/London")).format(cal);
+ assertEquals("2009-10-16T07:42:16 +0100", value, "calendar");
+ }
+ }
+
+ @Test
+ public void testLocaleMatches() {
+ final DatePrinter printer= getInstance(YYYY_MM_DD, SWEDEN);
+ assertEquals(SWEDEN, printer.getLocale());
+ }
+
+ /**
+ * Tests that pre-1000AD years get padded with yyyy
+ */
+ @Test
+ public void testLowYearPadding() {
+ final Calendar cal = Calendar.getInstance();
+ final DatePrinter format = getInstance(YYYY_MM_DD);
+
+ cal.set(1, Calendar.JANUARY, 1);
+ assertEquals("0001/01/01", format.format(cal));
+ cal.set(10, Calendar.JANUARY, 1);
+ assertEquals("0010/01/01", format.format(cal));
+ cal.set(100, Calendar.JANUARY, 1);
+ assertEquals("0100/01/01", format.format(cal));
+ cal.set(999, Calendar.JANUARY, 1);
+ assertEquals("0999/01/01", format.format(cal));
+ }
+
+ /**
+ * Show Bug #39410 is solved
+ */
+ @Test
+ public void testMilleniumBug() {
+ final Calendar cal = Calendar.getInstance();
+ final DatePrinter format = getInstance("dd.MM.yyyy");
+
+ cal.set(1000, Calendar.JANUARY, 1);
+ assertEquals("01.01.1000", format.format(cal));
+ }
+
+ @Test
+ public void testPatternMatches() {
+ final DatePrinter printer= getInstance(YYYY_MM_DD);
+ assertEquals(YYYY_MM_DD, printer.getPattern());
+ }
+
+ /**
+ * Test case for {@link FastDateParser#FastDateParser(String, TimeZone, Locale)}.
+ */
+ @Test
+ public void testShortDateStyleWithLocales() {
+ final Locale usLocale = Locale.US;
+ final Locale swedishLocale = new Locale("sv", "SE");
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2004, Calendar.FEBRUARY, 3);
+ DatePrinter fdf = getDateInstance(FastDateFormat.SHORT, usLocale);
+ assertEquals("2/3/04", fdf.format(cal));
+
+ fdf = getDateInstance(FastDateFormat.SHORT, swedishLocale);
+ assertEquals("2004-02-03", fdf.format(cal));
+
+ }
+
+ /**
+ * testLowYearPadding showed that the date was buggy
+ * This test confirms it, getting 366 back as a date
+ */
+ @Test
+ public void testSimpleDate() {
+ final Calendar cal = Calendar.getInstance();
+ final DatePrinter format = getInstance(YYYY_MM_DD);
+
+ cal.set(2004, Calendar.DECEMBER, 31);
+ assertEquals("2004/12/31", format.format(cal));
+ cal.set(999, Calendar.DECEMBER, 31);
+ assertEquals("0999/12/31", format.format(cal));
+ cal.set(1, Calendar.MARCH, 2);
+ assertEquals("0001/03/02", format.format(cal));
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testStringBufferOptions() {
+ final DatePrinter format = getInstance("yyyy-MM-dd HH:mm:ss.SSS Z", TimeZones.GMT);
+ final Calendar calendar = Calendar.getInstance();
+ final StringBuffer sb = new StringBuffer();
+ final String expected = format.format(calendar, sb, new FieldPosition(0)).toString();
+ sb.setLength(0);
+ assertEquals(expected, format.format(calendar, sb).toString());
+ sb.setLength(0);
+
+ final Date date = calendar.getTime();
+ assertEquals(expected, format.format(date, sb, new FieldPosition(0)).toString());
+ sb.setLength(0);
+ assertEquals(expected, format.format(date, sb).toString());
+ sb.setLength(0);
+
+ final long epoch = date.getTime();
+ assertEquals(expected, format.format(epoch, sb, new FieldPosition(0)).toString());
+ sb.setLength(0);
+ assertEquals(expected, format.format(epoch, sb).toString());
+ }
+
+ @DefaultTimeZone("UTC")
+ @Test
+ public void testTimeZoneAsZ() {
+ final Calendar c = Calendar.getInstance(FastTimeZone.getGmtTimeZone());
+ final FastDateFormat noColonFormat = FastDateFormat.getInstance("Z");
+ assertEquals("+0000", noColonFormat.format(c));
+
+ final FastDateFormat isoFormat = FastDateFormat.getInstance("ZZ");
+ assertEquals("Z", isoFormat.format(c));
+
+ final FastDateFormat colonFormat = FastDateFormat.getInstance("ZZZ");
+ assertEquals("+00:00", colonFormat.format(c));
+ }
+
+ @Test
+ public void testTimeZoneMatches() {
+ final DatePrinter printer= getInstance(YYYY_MM_DD, NEW_YORK);
+ assertEquals(NEW_YORK, printer.getTimeZone());
+ }
+
+ @Test
+ public void testToStringContainsName() {
+ final DatePrinter printer= getInstance(YYYY_MM_DD);
+ assertTrue(printer.toString().startsWith("FastDate"));
+ }
+
+ @DefaultLocale(language = "en", country = "US")
+ @DefaultTimeZone("America/New_York")
+ @Test
+ public void testWeekYear() {
+ final GregorianCalendar cal = new GregorianCalendar(2020, 12, 31, 0, 0, 0);
+ final DatePrinter printer4Digits = getInstance("YYYY");
+ final DatePrinter printer4DigitsFallback = getInstance("YYY");
+ final DatePrinter printer2Digits = getInstance("YY");
+ final DatePrinter printer4DigitAnotherFallback = getInstance("Y");
+ assertEquals("2021", printer4Digits.format(cal));
+ assertEquals("2021", printer4DigitsFallback.format(cal));
+ assertEquals("2021", printer4DigitAnotherFallback.format(cal));
+ assertEquals("21", printer2Digits.format(cal));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTimeZonesTest.java b/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTimeZonesTest.java
new file mode 100644
index 000000000..0506ffde3
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTimeZonesTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class FastDatePrinterTimeZonesTest extends AbstractLangTest {
+
+ private static final String PATTERN = "h:mma z";
+
+ public static Stream<TimeZone> data() {
+ return Stream.of(TimeZone.getAvailableIDs()).map(TimeZone::getTimeZone);
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testCalendarTimezoneRespected(final TimeZone timeZone) {
+ final Calendar cal = Calendar.getInstance(timeZone);
+
+ final SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
+ sdf.setTimeZone(timeZone);
+ final String expectedValue = sdf.format(cal.getTime());
+ final String actualValue = FastDateFormat.getInstance(PATTERN, timeZone).format(cal);
+ assertEquals(expectedValue, actualValue);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/FastTimeZoneTest.java b/src/test/java/org/apache/commons/lang3/time/FastTimeZoneTest.java
new file mode 100644
index 000000000..e66ed14d0
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastTimeZoneTest.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.time;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for FastTimeZone
+ */
+public class FastTimeZoneTest extends AbstractLangTest {
+
+ private static final int HOURS_23 = 23 * 60 * 60 * 1000;
+ private static final int HOURS_2 = 2 * 60 * 60 * 1000;
+ private static final int MINUTES_59 = 59 * 60 * 1000;
+ private static final int MINUTES_5 = 5 * 60 * 1000;
+
+ @Test
+ public void testBareGmt() {
+ assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone(TimeZones.GMT_ID));
+ }
+
+ @Test
+ public void testGetGmtTimeZone() {
+ assertEquals(0, FastTimeZone.getGmtTimeZone().getRawOffset());
+ }
+
+ @Test
+ public void testGmtPrefix() {
+ assertEquals(HOURS_23, FastTimeZone.getGmtTimeZone("GMT+23:00").getRawOffset());
+ assertEquals(-HOURS_23, FastTimeZone.getGmtTimeZone("GMT-23:00").getRawOffset());
+ }
+
+ @Test
+ public void testHoursColonMinutes() {
+ assertEquals(HOURS_23, FastTimeZone.getGmtTimeZone("23:00").getRawOffset());
+ assertEquals(HOURS_2, FastTimeZone.getGmtTimeZone("2:00").getRawOffset());
+ assertEquals(MINUTES_59, FastTimeZone.getGmtTimeZone("00:59").getRawOffset());
+ assertEquals(MINUTES_5, FastTimeZone.getGmtTimeZone("00:5").getRawOffset());
+ assertEquals(HOURS_23+MINUTES_59, FastTimeZone.getGmtTimeZone("23:59").getRawOffset());
+ assertEquals(HOURS_2+MINUTES_5, FastTimeZone.getGmtTimeZone("2:5").getRawOffset());
+ }
+
+ @Test
+ public void testHoursMinutes() {
+ assertEquals(HOURS_23, FastTimeZone.getGmtTimeZone("2300").getRawOffset());
+ assertEquals(HOURS_2, FastTimeZone.getGmtTimeZone("0200").getRawOffset());
+ assertEquals(MINUTES_59, FastTimeZone.getGmtTimeZone("0059").getRawOffset());
+ assertEquals(MINUTES_5, FastTimeZone.getGmtTimeZone("0005").getRawOffset());
+ assertEquals(HOURS_23+MINUTES_59, FastTimeZone.getGmtTimeZone("2359").getRawOffset());
+ assertEquals(HOURS_2+MINUTES_5, FastTimeZone.getGmtTimeZone("0205").getRawOffset());
+ }
+
+ @Test
+ public void testOlson() {
+ assertEquals(TimeZone.getTimeZone("America/New_York"), FastTimeZone.getTimeZone("America/New_York"));
+ }
+
+ @Test
+ public void testSign() {
+ assertEquals(HOURS_23, FastTimeZone.getGmtTimeZone("+23:00").getRawOffset());
+ assertEquals(HOURS_2, FastTimeZone.getGmtTimeZone("+2:00").getRawOffset());
+ assertEquals(-HOURS_23, FastTimeZone.getGmtTimeZone("-23:00").getRawOffset());
+ assertEquals(-HOURS_2, FastTimeZone.getGmtTimeZone("-2:00").getRawOffset());
+ }
+
+ @Test
+ public void testUTC() {
+ assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("UTC"));
+ }
+
+ @Test
+ public void testZ() {
+ assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("Z"));
+ }
+
+ @Test
+ public void testZeroOffsetsReturnSingleton() {
+ assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("+0"));
+ assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("-0"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/GmtTimeZoneTest.java b/src/test/java/org/apache/commons/lang3/time/GmtTimeZoneTest.java
new file mode 100644
index 000000000..ede51b8f3
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/GmtTimeZoneTest.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.time;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for GmtTimeZone
+ */
+public class GmtTimeZoneTest extends AbstractLangTest {
+
+ @Test
+ public void getID() {
+ assertEquals("GMT+00:00", new GmtTimeZone(false, 0, 0).getID());
+ assertEquals("GMT+01:02", new GmtTimeZone(false, 1, 2).getID());
+ assertEquals("GMT+11:22", new GmtTimeZone(false, 11, 22).getID());
+ assertEquals("GMT-01:02", new GmtTimeZone(true, 1, 2).getID());
+ assertEquals("GMT-11:22", new GmtTimeZone(true, 11, 22).getID());
+ }
+
+ @Test
+ public void getOffset() {
+ assertEquals(0, new GmtTimeZone(false, 0, 0).getOffset(234304));
+ }
+
+ @Test
+ public void getRawOffset() {
+ assertEquals(0, new GmtTimeZone(false, 0, 0).getRawOffset());
+ }
+
+ @Test
+ public void hoursInRange() {
+ assertEquals(23 * 60 * 60 * 1000, new GmtTimeZone(false, 23, 0).getRawOffset());
+ }
+
+ @Test
+ public void hoursOutOfRange() {
+ assertThrows(IllegalArgumentException.class, () -> new GmtTimeZone(false, 24, 0));
+ }
+
+ @Test
+ public void inDaylightTime() {
+ assertFalse(new GmtTimeZone(false, 0, 0).useDaylightTime());
+ }
+
+ @Test
+ public void minutesInRange() {
+ assertEquals(59 * 60 * 1000, new GmtTimeZone(false, 0, 59).getRawOffset());
+ }
+
+ @Test
+ public void minutesOutOfRange() {
+ assertThrows(IllegalArgumentException.class, () -> new GmtTimeZone(false, 0, 60));
+ }
+
+ @Test
+ public void setRawOffset() {
+ assertThrows(UnsupportedOperationException.class, () -> new GmtTimeZone(false, 0, 0).setRawOffset(0));
+ }
+
+ @Test
+ public void testGetOffset() {
+ assertEquals(-(6 * 60 + 30) * 60 * 1000,
+ new GmtTimeZone(true, 6, 30).getOffset(1, 1, 1, 1, 1, 1));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("[GmtTimeZone id=\"GMT-12:00\",offset=-43200000]",
+ new GmtTimeZone(true, 12, 0).toString());
+ }
+
+ @Test
+ public void useDaylightTime() {
+ assertFalse(new GmtTimeZone(false, 0, 0).useDaylightTime());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/Java15BugFastDateParserTest.java b/src/test/java/org/apache/commons/lang3/time/Java15BugFastDateParserTest.java
new file mode 100644
index 000000000..7259f6499
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/Java15BugFastDateParserTest.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.time;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.function.TriFunction;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * These tests fail on Java 15 due to a bug which was only fixed for Java 16.
+ * <ul>
+ * <li>https://bugs.openjdk.java.net/browse/JDK-8248434</li>
+ * <li>https://bugs.openjdk.java.net/browse/JDK-8248655</li>
+ * </ul>
+ */
+public class Java15BugFastDateParserTest extends AbstractLangTest {
+
+ /** @see org.apache.commons.lang3.time.FastDateParserTest#dateParserParameters() */
+ private static final String DATE_PARSER_PARAMETERS = "org.apache.commons.lang3.time.FastDateParserTest#dateParserParameters()";
+
+ @Test
+ public void java15BuggyLocaleTest() throws ParseException {
+ final String buggyLocaleName = "ff_LR_#Adlm";
+ Locale buggyLocale = null;
+ for (final Locale locale : Locale.getAvailableLocales()) {
+ if (buggyLocaleName.equals(locale.toString())) {
+ buggyLocale = locale;
+ break;
+ }
+ }
+ if (buggyLocale == null) {
+ return;
+ }
+ testSingleLocale(buggyLocale);
+ }
+
+ @Test
+ public void java15BuggyLocaleTestAll() throws ParseException {
+ for (final Locale locale : Locale.getAvailableLocales()) {
+ testSingleLocale(locale);
+ }
+ }
+
+ private void testLocales(final TriFunction<String, TimeZone, Locale, DateParser> dbProvider, final String format,
+ final boolean eraBC) throws Exception {
+
+ final Calendar cal = Calendar.getInstance(TimeZones.GMT);
+ cal.clear();
+ cal.set(2003, Calendar.FEBRUARY, 10);
+ if (eraBC) {
+ cal.set(Calendar.ERA, GregorianCalendar.BC);
+ }
+
+ for (final Locale locale : Locale.getAvailableLocales()) {
+ // ja_JP_JP cannot handle dates before 1868 properly
+ if (eraBC && locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
+ continue;
+ }
+ final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+ final DateParser fdf = dbProvider.apply(format, TimeZone.getDefault(), locale);
+
+ // If parsing fails, a ParseException will be thrown and the test will fail
+ FastDateParserTest.checkParse(locale, cal, sdf, fdf);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLocales_Long_AD(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testLocales(dpProvider, FastDateParserTest.LONG_FORMAT, false);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLocales_Long_BC(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testLocales(dpProvider, FastDateParserTest.LONG_FORMAT, true);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLocales_LongNoEra_AD(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testLocales(dpProvider, FastDateParserTest.LONG_FORMAT_NOERA, false);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLocales_LongNoEra_BC(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testLocales(dpProvider, FastDateParserTest.LONG_FORMAT_NOERA, true);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLocales_Short_AD(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testLocales(dpProvider, FastDateParserTest.SHORT_FORMAT, false);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLocales_Short_BC(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testLocales(dpProvider, FastDateParserTest.SHORT_FORMAT, true);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLocales_ShortNoEra_AD(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testLocales(dpProvider, FastDateParserTest.SHORT_FORMAT_NOERA, false);
+ }
+
+ @ParameterizedTest
+ @MethodSource(DATE_PARSER_PARAMETERS)
+ public void testLocales_ShortNoEra_BC(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
+ throws Exception {
+ testLocales(dpProvider, FastDateParserTest.SHORT_FORMAT_NOERA, true);
+ }
+
+ private void testSingleLocale(final Locale locale) throws ParseException {
+ final Calendar cal = Calendar.getInstance(TimeZones.GMT);
+ cal.clear();
+ cal.set(2003, Calendar.FEBRUARY, 10);
+ final SimpleDateFormat sdf = new SimpleDateFormat(FastDateParserTest.LONG_FORMAT, locale);
+ final String formattedDate = sdf.format(cal.getTime());
+ sdf.parse(formattedDate);
+ sdf.parse(formattedDate.toUpperCase(locale));
+ sdf.parse(formattedDate.toLowerCase(locale));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/StopWatchTest.java b/src/test/java/org/apache/commons/lang3/time/StopWatchTest.java
new file mode 100644
index 000000000..69ac71dcd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/StopWatchTest.java
@@ -0,0 +1,367 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ThreadUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link StopWatch}.
+ */
+public class StopWatchTest extends AbstractLangTest {
+
+ private static final Duration MILLIS_200 = Duration.ofMillis(200);
+ private static final Duration MILLIS_550 = Duration.ofMillis(550);
+ private static final String MESSAGE = "Baking cookies";
+ private static final Duration MIN_SLEEP = Duration.ofMillis(20);
+ private static final String ZERO_HOURS_PREFIX = "00:";
+ private static final String ZERO_TIME_ELAPSED = "00:00:00.000";
+
+ /**
+ * <p>
+ * Creates a suspended StopWatch object which appears to have elapsed for the requested amount of time in
+ * nanoseconds.
+ * <p>
+ * <p>
+ *
+ * <pre>
+ * // Create a mock StopWatch with a time of 2:59:01.999
+ * final long nanos = TimeUnit.HOURS.toNanos(2)
+ * + TimeUnit.MINUTES.toNanos(59)
+ * + TimeUnit.SECONDS.toNanos(1)
+ * + TimeUnit.MILLISECONDS.toNanos(999);
+ * final StopWatch watch = createMockStopWatch(nanos);
+ * </pre>
+ *
+ * @param nanos Time in nanoseconds to have elapsed on the stop watch
+ * @return StopWatch in a suspended state with the elapsed time
+ */
+ private StopWatch createMockStopWatch(final long nanos) {
+ final StopWatch watch = StopWatch.createStarted();
+ watch.suspend();
+ try {
+ final long currentNanos = System.nanoTime();
+ FieldUtils.writeField(watch, "startTimeNanos", currentNanos - nanos, true);
+ FieldUtils.writeField(watch, "stopTimeNanos", currentNanos, true);
+ } catch (final IllegalAccessException e) {
+ return null;
+ }
+ return watch;
+ }
+
+ private void sleepQuietly(final Duration duration) throws InterruptedException {
+ ThreadUtils.sleep(duration);
+ }
+
+ // test bad states
+ @Test
+ public void testBadStates() {
+ final StopWatch watch = new StopWatch();
+ assertThrows(IllegalStateException.class, watch::stop,
+ "Calling stop on an unstarted StopWatch should throw an exception. ");
+
+ assertThrows(IllegalStateException.class, watch::suspend,
+ "Calling suspend on an unstarted StopWatch should throw an exception. ");
+
+ assertThrows(IllegalStateException.class, watch::split,
+ "Calling split on a non-running StopWatch should throw an exception. ");
+
+ assertThrows(IllegalStateException.class, watch::unsplit,
+ "Calling unsplit on an unsplit StopWatch should throw an exception. ");
+
+ assertThrows(IllegalStateException.class, watch::resume,
+ "Calling resume on an unsuspended StopWatch should throw an exception. ");
+
+ watch.start();
+
+ assertThrows(IllegalStateException.class, watch::start,
+ "Calling start on a started StopWatch should throw an exception. ");
+
+ assertThrows(IllegalStateException.class, watch::unsplit,
+ "Calling unsplit on an unsplit StopWatch should throw an exception. ");
+
+ assertThrows(IllegalStateException.class, watch::getSplitTime,
+ "Calling getSplitTime on an unsplit StopWatch should throw an exception. ");
+
+ assertThrows(IllegalStateException.class, watch::resume,
+ "Calling resume on an unsuspended StopWatch should throw an exception. ");
+
+ watch.stop();
+
+ assertThrows(IllegalStateException.class, watch::start,
+ "Calling start on a stopped StopWatch should throw an exception as it needs to be reset. ");
+ }
+
+ @Test
+ public void testBooleanStates() {
+ final StopWatch watch = new StopWatch();
+ assertFalse(watch.isStarted());
+ assertFalse(watch.isSuspended());
+ assertTrue(watch.isStopped());
+
+ watch.start();
+ assertTrue(watch.isStarted());
+ assertFalse(watch.isSuspended());
+ assertFalse(watch.isStopped());
+
+ watch.suspend();
+ assertTrue(watch.isStarted());
+ assertTrue(watch.isSuspended());
+ assertFalse(watch.isStopped());
+
+ watch.stop();
+ assertFalse(watch.isStarted());
+ assertFalse(watch.isSuspended());
+ assertTrue(watch.isStopped());
+ }
+
+ @Test
+ public void testFormatSplitTime() {
+ final StopWatch watch = StopWatch.createStarted();
+ ThreadUtils.sleepQuietly(MIN_SLEEP);
+ watch.split();
+ final String formatSplitTime = watch.formatSplitTime();
+ assertNotEquals(ZERO_TIME_ELAPSED, formatSplitTime);
+ assertTrue(formatSplitTime.startsWith(ZERO_HOURS_PREFIX));
+ }
+
+ @Test
+ public void testFormatSplitTimeWithMessage() {
+ final StopWatch watch = new StopWatch(MESSAGE);
+ watch.start();
+ ThreadUtils.sleepQuietly(MIN_SLEEP);
+ watch.split();
+ final String formatSplitTime = watch.formatSplitTime();
+ assertFalse(formatSplitTime.startsWith(MESSAGE), formatSplitTime);
+ assertTrue(formatSplitTime.startsWith(ZERO_HOURS_PREFIX));
+ }
+
+ @Test
+ public void testFormatTime() {
+ final StopWatch watch = StopWatch.create();
+ final String formatTime = watch.formatTime();
+ assertEquals(ZERO_TIME_ELAPSED, formatTime);
+ assertTrue(formatTime.startsWith(ZERO_HOURS_PREFIX));
+ }
+
+ @Test
+ public void testFormatTimeWithMessage() {
+ final StopWatch watch = new StopWatch(MESSAGE);
+ final String formatTime = watch.formatTime();
+ assertFalse(formatTime.startsWith(MESSAGE), formatTime);
+ }
+
+ @Test
+ public void testGetStartTime() {
+ final long beforeStopWatchMillis = System.currentTimeMillis();
+ final StopWatch watch = new StopWatch();
+ assertThrows(IllegalStateException.class, watch::getStartTime,
+ "Calling getStartTime on an unstarted StopWatch should throw an exception");
+ watch.start();
+
+ watch.getStartTime();
+ assertTrue(watch.getStartTime() >= beforeStopWatchMillis);
+
+ watch.reset();
+ assertThrows(IllegalStateException.class, watch::getStartTime,
+ "Calling getStartTime on a reset, but unstarted StopWatch should throw an exception");
+ }
+
+ @Test
+ public void testLang315() throws InterruptedException {
+ final StopWatch watch = StopWatch.createStarted();
+ sleepQuietly(MILLIS_200);
+ watch.suspend();
+ final long suspendTime = watch.getTime();
+ sleepQuietly(MILLIS_200);
+ watch.stop();
+ final long totalTime = watch.getTime();
+ assertEquals(suspendTime, totalTime);
+ }
+
+ @Test
+ public void testMessage() {
+ assertNull(StopWatch.create().getMessage());
+ final StopWatch stopWatch = new StopWatch(MESSAGE);
+ assertEquals(MESSAGE, stopWatch.getMessage());
+ assertTrue(stopWatch.toString().startsWith(MESSAGE));
+ stopWatch.start();
+ stopWatch.split();
+ assertTrue(stopWatch.toSplitString().startsWith(MESSAGE));
+ }
+
+ @Test
+ public void testStopTimeSimple() throws InterruptedException {
+ final StopWatch watch = StopWatch.createStarted();
+ final long testStartMillis = System.currentTimeMillis();
+ sleepQuietly(MILLIS_550);
+ watch.stop();
+ final long testEndMillis = System.currentTimeMillis();
+ final long stopTime = watch.getStopTime();
+ assertEquals(stopTime, watch.getStopTime());
+
+ assertTrue(stopTime >= testStartMillis);
+ assertTrue(stopTime <= testEndMillis);
+ }
+
+ @Test
+ public void testStopWatchGetWithTimeUnit() {
+ // Create a mock StopWatch with a time of 2:59:01.999
+ // @formatter:off
+ final StopWatch watch = createMockStopWatch(
+ TimeUnit.HOURS.toNanos(2)
+ + TimeUnit.MINUTES.toNanos(59)
+ + TimeUnit.SECONDS.toNanos(1)
+ + TimeUnit.MILLISECONDS.toNanos(999));
+ // @formatter:on
+
+ assertEquals(2L, watch.getTime(TimeUnit.HOURS));
+ assertEquals(179L, watch.getTime(TimeUnit.MINUTES));
+ assertEquals(10741L, watch.getTime(TimeUnit.SECONDS));
+ assertEquals(10741999L, watch.getTime(TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testStopWatchSimple() throws InterruptedException {
+ final StopWatch watch = StopWatch.createStarted();
+ sleepQuietly(MILLIS_550);
+ watch.stop();
+ final long time = watch.getTime();
+ assertEquals(time, watch.getTime());
+
+ assertTrue(time >= 500);
+ assertTrue(time < 700);
+
+ watch.reset();
+ assertEquals(0, watch.getTime());
+ }
+
+ @Test
+ public void testStopWatchSimpleGet() throws InterruptedException {
+ final StopWatch watch = new StopWatch();
+ assertEquals(0, watch.getTime());
+ assertEquals(ZERO_TIME_ELAPSED, watch.toString());
+
+ watch.start();
+ sleepQuietly(MILLIS_550);
+ assertTrue(watch.getTime() < 2000);
+ }
+
+ @Test
+ public void testStopWatchSplit() throws InterruptedException {
+ final StopWatch watch = StopWatch.createStarted();
+ sleepQuietly(MILLIS_550);
+ watch.split();
+ final long splitTime = watch.getSplitTime();
+ final String splitStr = watch.toSplitString();
+ sleepQuietly(MILLIS_550);
+ watch.unsplit();
+ sleepQuietly(MILLIS_550);
+ watch.stop();
+ final long totalTime = watch.getTime();
+
+ assertEquals(splitStr.length(), 12, "Formatted split string not the correct length");
+ assertTrue(splitTime >= 500);
+ assertTrue(splitTime < 700);
+ assertTrue(totalTime >= 1500);
+ assertTrue(totalTime < 1900);
+ }
+
+ @Test
+ public void testStopWatchStatic() {
+ final StopWatch watch = StopWatch.createStarted();
+ assertTrue(watch.isStarted());
+ }
+
+ @Test
+ public void testStopWatchSuspend() throws InterruptedException {
+ final StopWatch watch = StopWatch.createStarted();
+ final long testStartMillis = System.currentTimeMillis();
+ sleepQuietly(MILLIS_550);
+ watch.suspend();
+ final long testSuspendMillis = System.currentTimeMillis();
+ final long suspendTime = watch.getTime();
+ final long stopTime = watch.getStopTime();
+
+ assertTrue(testStartMillis <= stopTime);
+ assertTrue(testSuspendMillis <= stopTime);
+
+ sleepQuietly(MILLIS_550);
+ watch.resume();
+ sleepQuietly(MILLIS_550);
+ watch.stop();
+ final long totalTime = watch.getTime();
+
+ assertTrue(suspendTime >= 500);
+ assertTrue(suspendTime < 700);
+ assertTrue(totalTime >= 1000);
+ assertTrue(totalTime < 1300);
+ }
+
+ @Test
+ public void testToSplitString() throws InterruptedException {
+ final StopWatch watch = StopWatch.createStarted();
+ sleepQuietly(MILLIS_550);
+ watch.split();
+ final String splitStr = watch.toSplitString();
+ assertEquals(splitStr.length(), 12, "Formatted split string not the correct length");
+ }
+
+ @Test
+ public void testToSplitStringWithMessage() throws InterruptedException {
+ final StopWatch watch = new StopWatch(MESSAGE);
+ watch.start();
+ sleepQuietly(MILLIS_550);
+ watch.split();
+ final String splitStr = watch.toSplitString();
+ assertEquals(splitStr.length(), 12 + MESSAGE.length() + 1, "Formatted split string not the correct length");
+ }
+
+ @Test
+ public void testToString() throws InterruptedException {
+ //
+ final StopWatch watch = StopWatch.createStarted();
+ sleepQuietly(MILLIS_550);
+ watch.split();
+ final String splitStr = watch.toString();
+ assertEquals(splitStr.length(), 12, "Formatted split string not the correct length");
+ }
+
+ @Test
+ public void testToStringWithMessage() throws InterruptedException {
+ assertTrue(new StopWatch(MESSAGE).toString().startsWith(MESSAGE));
+ //
+ final StopWatch watch = new StopWatch(MESSAGE);
+ watch.start();
+ sleepQuietly(MILLIS_550);
+ watch.split();
+ final String splitStr = watch.toString();
+ assertEquals(splitStr.length(), 12 + MESSAGE.length() + 1, "Formatted split string not the correct length");
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/time/WeekYearTest.java b/src/test/java/org/apache/commons/lang3/time/WeekYearTest.java
new file mode 100644
index 000000000..5f7cabc21
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/WeekYearTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class WeekYearTest extends AbstractLangTest {
+
+ public static Stream<Arguments> data() {
+ return Stream.of(
+ Arguments.of(new GregorianCalendar( 2005, Calendar.JANUARY, 1), "2004-W53-6"),
+ Arguments.of(new GregorianCalendar( 2005, Calendar.JANUARY, 2), "2004-W53-7"),
+ Arguments.of(new GregorianCalendar( 2005, Calendar.DECEMBER, 31), "2005-W52-6"),
+ Arguments.of(new GregorianCalendar( 2007, Calendar.JANUARY, 1), "2007-W01-1"),
+ Arguments.of(new GregorianCalendar( 2007, Calendar.DECEMBER, 30), "2007-W52-7"),
+ Arguments.of(new GregorianCalendar( 2007, Calendar.DECEMBER, 31), "2008-W01-1"),
+ Arguments.of(new GregorianCalendar( 2008, Calendar.JANUARY, 1), "2008-W01-2"),
+ Arguments.of(new GregorianCalendar( 2008, Calendar.DECEMBER, 28), "2008-W52-7"),
+ Arguments.of(new GregorianCalendar( 2008, Calendar.DECEMBER, 29), "2009-W01-1"),
+ Arguments.of(new GregorianCalendar( 2008, Calendar.DECEMBER, 30), "2009-W01-2"),
+ Arguments.of(new GregorianCalendar( 2008, Calendar.DECEMBER, 31), "2009-W01-3"),
+ Arguments.of(new GregorianCalendar( 2009, Calendar.JANUARY, 1), "2009-W01-4"),
+ Arguments.of(new GregorianCalendar( 2009, Calendar.DECEMBER, 31), "2009-W53-4"),
+ Arguments.of(new GregorianCalendar( 2010, Calendar.JANUARY, 1), "2009-W53-5"),
+ Arguments.of(new GregorianCalendar( 2010, Calendar.JANUARY, 2), "2009-W53-6"),
+ Arguments.of(new GregorianCalendar( 2010, Calendar.JANUARY, 3), "2009-W53-7")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testParser(final Calendar vulgar, final String isoForm) {
+ final DateParser parser = new FastDateParser("YYYY-'W'ww-u", TimeZone.getDefault(), Locale.getDefault());
+
+ final Calendar cal = Calendar.getInstance();
+ cal.setMinimalDaysInFirstWeek(4);
+ cal.setFirstDayOfWeek(Calendar.MONDAY);
+ cal.clear();
+
+ parser.parse(isoForm, new ParsePosition(0), cal);
+ assertEquals(vulgar.getTime(), cal.getTime());
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testPrinter(final Calendar vulgar, final String isoForm) {
+ final FastDatePrinter printer = new FastDatePrinter("YYYY-'W'ww-u", TimeZone.getDefault(), Locale.getDefault());
+
+ vulgar.setMinimalDaysInFirstWeek(4);
+ vulgar.setFirstDayOfWeek(Calendar.MONDAY);
+
+ assertEquals(isoForm, printer.format(vulgar));
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/tuple/ImmutablePairTest.java b/src/test/java/org/apache/commons/lang3/tuple/ImmutablePairTest.java
new file mode 100644
index 000000000..18e4ad6d6
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/tuple/ImmutablePairTest.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.SerializationUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test the Pair class.
+ */
+public class ImmutablePairTest extends AbstractLangTest {
+
+ @Test
+ public void testBasic() {
+ ImmutablePair<Integer, String> oldPair = new ImmutablePair<>(0, "foo");
+ ImmutablePair<Integer, String> nowPair;
+ for (int i=0; i<4; i++) {
+ nowPair = ImmutablePair.of(oldPair);
+ assertEquals(0, nowPair.left.intValue());
+ assertEquals(0, nowPair.getLeft().intValue());
+ assertEquals("foo", nowPair.right);
+ assertEquals("foo", nowPair.getRight());
+ assertEquals(oldPair, nowPair);
+ oldPair = nowPair;
+ }
+
+ ImmutablePair<Object, String> oldPair2 = new ImmutablePair<>(null, "bar");
+ ImmutablePair<Object, String> nowPair2;
+ for (int i=0; i<4; i++) {
+ nowPair2 = ImmutablePair.of(oldPair2);
+ assertNull(nowPair2.left);
+ assertNull(nowPair2.getLeft());
+ assertEquals("bar", nowPair2.right);
+ assertEquals("bar", nowPair2.getRight());
+ oldPair2 = nowPair2;
+ }
+ }
+
+ @Test
+ public void testComparableLeftOnly() {
+ final Pair<String, String> pair1 = ImmutablePair.left("A");
+ final Pair<String, String> pair2 = ImmutablePair.left("B");
+ assertEquals("A", pair1.getLeft());
+ assertEquals("B", pair2.getLeft());
+ assertEquals(0, pair1.compareTo(pair1));
+ assertTrue(pair1.compareTo(pair2) < 0);
+ assertEquals(0, pair2.compareTo(pair2));
+ assertTrue(pair2.compareTo(pair1) > 0);
+ }
+
+ @Test
+ public void testComparableRightOnly() {
+ final Pair<String, String> pair1 = ImmutablePair.right("A");
+ final Pair<String, String> pair2 = ImmutablePair.right("B");
+ assertEquals("A", pair1.getRight());
+ assertEquals("B", pair2.getRight());
+ assertEquals(0, pair1.compareTo(pair1));
+ assertTrue(pair1.compareTo(pair2) < 0);
+ assertEquals(0, pair2.compareTo(pair2));
+ assertTrue(pair2.compareTo(pair1) > 0);
+ }
+
+ @Test
+ public void testEmptyArrayGenerics() {
+ final ImmutablePair<Integer, String>[] empty = ImmutablePair.emptyArray();
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEmptyArrayLength() {
+ @SuppressWarnings("unchecked")
+ final ImmutablePair<Integer, String>[] empty = (ImmutablePair<Integer, String>[]) ImmutablePair.EMPTY_ARRAY;
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(ImmutablePair.of(null, "foo"), ImmutablePair.of(null, "foo"));
+ assertNotEquals(ImmutablePair.of("foo", 0), ImmutablePair.of("foo", null));
+ assertNotEquals(ImmutablePair.of("foo", "bar"), ImmutablePair.of("xyz", "bar"));
+
+ final ImmutablePair<String, String> p = ImmutablePair.of("foo", "bar");
+ assertEquals(p, p);
+ assertNotEquals(p, new Object());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(ImmutablePair.of(null, "foo").hashCode(), ImmutablePair.of(null, "foo").hashCode());
+ }
+
+ @Test
+ public void testNullPairEquals() {
+ assertEquals(ImmutablePair.nullPair(), ImmutablePair.nullPair());
+ }
+
+ @Test
+ public void testNullPairKey() {
+ assertNull(ImmutablePair.nullPair().getKey());
+ }
+
+ @Test
+ public void testNullPairLeft() {
+ assertNull(ImmutablePair.nullPair().getLeft());
+ }
+
+ @Test
+ public void testNullPairRight() {
+ assertNull(ImmutablePair.nullPair().getRight());
+ }
+
+ @Test
+ public void testNullPairSame() {
+ assertSame(ImmutablePair.nullPair(), ImmutablePair.nullPair());
+ }
+
+ @Test
+ public void testNullPairTyped() {
+ // No compiler warnings
+ // How do we assert that?
+ final ImmutablePair<String, String> pair = ImmutablePair.nullPair();
+ assertNotNull(pair);
+ }
+
+ @Test
+ public void testNullPairValue() {
+ assertNull(ImmutablePair.nullPair().getValue());
+ }
+
+ @Test
+ public void testOfNonNull() {
+ assertThrows(NullPointerException.class, () -> ImmutablePair.ofNonNull(null, null));
+ assertThrows(NullPointerException.class, () -> ImmutablePair.ofNonNull(null, "x"));
+ assertThrows(NullPointerException.class, () -> ImmutablePair.ofNonNull("x", null));
+ final ImmutablePair<String, String> pair = ImmutablePair.ofNonNull("x", "y");
+ assertEquals("x", pair.left);
+ assertEquals("y", pair.right);
+ }
+
+ @Test
+ public void testPairOfMapEntry() {
+ final HashMap<Integer, String> map = new HashMap<>();
+ map.put(0, "foo");
+ final Entry<Integer, String> entry = map.entrySet().iterator().next();
+ final Pair<Integer, String> pair = ImmutablePair.of(entry);
+ assertEquals(entry.getKey(), pair.getLeft());
+ assertEquals(entry.getValue(), pair.getRight());
+ }
+
+ @Test
+ public void testPairOfObjects() {
+ final ImmutablePair<Integer, String> pair = ImmutablePair.of(0, "foo");
+ assertEquals(0, pair.left.intValue());
+ assertEquals(0, pair.getLeft().intValue());
+ assertEquals("foo", pair.right);
+ assertEquals("foo", pair.getRight());
+ final ImmutablePair<Object, String> pair2 = ImmutablePair.of(null, "bar");
+ assertNull(pair2.left);
+ assertNull(pair2.getLeft());
+ assertEquals("bar", pair2.right);
+ assertEquals("bar", pair2.getRight());
+ final ImmutablePair<?, ?> pair3 = ImmutablePair.of(null, null);
+ assertNull(pair3.left);
+ assertNull(pair3.right);
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ final ImmutablePair<Integer, String> origPair = ImmutablePair.of(0, "foo");
+ final ImmutablePair<Integer, String> deserializedPair = SerializationUtils.roundtrip(origPair);
+ assertEquals(origPair, deserializedPair);
+ assertEquals(origPair.hashCode(), deserializedPair.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("(null,null)", ImmutablePair.of(null, null).toString());
+ assertEquals("(null,two)", ImmutablePair.of(null, "two").toString());
+ assertEquals("(one,null)", ImmutablePair.of("one", null).toString());
+ assertEquals("(one,two)", ImmutablePair.of("one", "two").toString());
+ }
+
+ @Test
+ public void testToStringLeft() {
+ final Pair<String, String> pair = ImmutablePair.left("Key");
+ assertEquals("(Key,null)", pair.toString());
+ }
+
+ @Test
+ public void testToStringRight() {
+ final Pair<String, String> pair = ImmutablePair.right("Value");
+ assertEquals("(null,Value)", pair.toString());
+ }
+
+ @Test
+ public void testUseAsKeyOfHashMap() {
+ final HashMap<ImmutablePair<Object, Object>, String> map = new HashMap<>();
+ final Object o1 = new Object();
+ final Object o2 = new Object();
+ final ImmutablePair<Object, Object> key1 = ImmutablePair.of(o1, o2);
+ final String value1 = "a1";
+ map.put(key1, value1);
+ assertEquals(value1, map.get(key1));
+ assertEquals(value1, map.get(ImmutablePair.of(o1, o2)));
+ }
+
+ @Test
+ public void testUseAsKeyOfTreeMap() {
+ final TreeMap<ImmutablePair<Integer, Integer>, String> map = new TreeMap<>();
+ map.put(ImmutablePair.of(1, 2), "12");
+ map.put(ImmutablePair.of(1, 1), "11");
+ map.put(ImmutablePair.of(0, 1), "01");
+ final ArrayList<ImmutablePair<Integer, Integer>> expected = new ArrayList<>();
+ expected.add(ImmutablePair.of(0, 1));
+ expected.add(ImmutablePair.of(1, 1));
+ expected.add(ImmutablePair.of(1, 2));
+ final Iterator<Entry<ImmutablePair<Integer, Integer>, String>> it = map.entrySet().iterator();
+ for (final ImmutablePair<Integer, Integer> item : expected) {
+ final Entry<ImmutablePair<Integer, Integer>, String> entry = it.next();
+ assertEquals(item, entry.getKey());
+ assertEquals(item.getLeft() + "" + item.getRight(), entry.getValue());
+ }
+ }
+
+ @Test
+ public void testUnsupportedOperation() {
+ final ImmutablePair<Integer, String> pair = new ImmutablePair<>(0, "foo");
+ assertThrows(UnsupportedOperationException.class, () -> pair.setValue("any"));
+
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/tuple/ImmutableTripleTest.java b/src/test/java/org/apache/commons/lang3/tuple/ImmutableTripleTest.java
new file mode 100644
index 000000000..c51d9b9d5
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/tuple/ImmutableTripleTest.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.tuple;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.SerializationUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test the Triple class.
+ */
+public class ImmutableTripleTest extends AbstractLangTest {
+
+ @Test
+ public void testBasic() {
+ final ImmutableTriple<Integer, String, Boolean> triple = new ImmutableTriple<>(0, "foo", Boolean.TRUE);
+ assertEquals(0, triple.left.intValue());
+ assertEquals(0, triple.getLeft().intValue());
+ assertEquals("foo", triple.middle);
+ assertEquals("foo", triple.getMiddle());
+ assertEquals(Boolean.TRUE, triple.right);
+ assertEquals(Boolean.TRUE, triple.getRight());
+ final ImmutableTriple<Object, String, Integer> triple2 = new ImmutableTriple<>(null, "bar", 42);
+ assertNull(triple2.left);
+ assertNull(triple2.getLeft());
+ assertEquals("bar", triple2.middle);
+ assertEquals("bar", triple2.getMiddle());
+ assertEquals(Integer.valueOf(42), triple2.right);
+ assertEquals(Integer.valueOf(42), triple2.getRight());
+ }
+
+ @Test
+ public void testEmptyArrayGenerics() {
+ final ImmutableTriple<Integer, String, Boolean>[] empty = ImmutableTriple.emptyArray();
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEmptyArrayLength() {
+ @SuppressWarnings("unchecked")
+ final ImmutableTriple<Integer, String, Boolean>[] empty = (ImmutableTriple<Integer, String, Boolean>[]) ImmutableTriple.EMPTY_ARRAY;
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(ImmutableTriple.of(null, "foo", 42), ImmutableTriple.of(null, "foo", 42));
+ assertNotEquals(ImmutableTriple.of("foo", 0, Boolean.TRUE), ImmutableTriple.of("foo", null, null));
+ assertNotEquals(ImmutableTriple.of("foo", "bar", "baz"), ImmutableTriple.of("xyz", "bar", "blo"));
+
+ final ImmutableTriple<String, String, String> p = ImmutableTriple.of("foo", "bar", "baz");
+ assertEquals(p, p);
+ assertNotEquals(p, new Object());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(ImmutableTriple.of(null, "foo", Boolean.TRUE).hashCode(), ImmutableTriple.of(null, "foo", Boolean.TRUE).hashCode());
+ }
+
+ @Test
+ public void testNullTripleEquals() {
+ assertEquals(ImmutableTriple.nullTriple(), ImmutableTriple.nullTriple());
+ }
+
+ @Test
+ public void testNullTripleLeft() {
+ assertNull(ImmutableTriple.nullTriple().getLeft());
+ }
+
+ @Test
+ public void testNullTripleMiddle() {
+ assertNull(ImmutableTriple.nullTriple().getMiddle());
+ }
+
+ @Test
+ public void testNullTripleRight() {
+ assertNull(ImmutableTriple.nullTriple().getRight());
+ }
+
+ @Test
+ public void testNullTripleSame() {
+ assertSame(ImmutableTriple.nullTriple(), ImmutableTriple.nullTriple());
+ }
+
+ @Test
+ public void testNullTripleTyped() {
+ // No compiler warnings
+ // How do we assert that?
+ final ImmutableTriple<String, String, String> triple = ImmutableTriple.nullTriple();
+ assertNotNull(triple);
+ }
+
+ @Test
+ public void testOfNonNull() {
+ assertThrows(NullPointerException.class, () -> ImmutableTriple.ofNonNull(null, null, null));
+ assertThrows(NullPointerException.class, () -> ImmutableTriple.ofNonNull(null, null, "z"));
+ assertThrows(NullPointerException.class, () -> ImmutableTriple.ofNonNull(null, "y", "z"));
+ assertThrows(NullPointerException.class, () -> ImmutableTriple.ofNonNull("x", null, null));
+ assertThrows(NullPointerException.class, () -> ImmutableTriple.ofNonNull("x", "y", null));
+ final ImmutableTriple<String, String, String> pair = ImmutableTriple.ofNonNull("x", "y", "z");
+ assertEquals("x", pair.left);
+ assertEquals("y", pair.middle);
+ assertEquals("z", pair.right);
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ final ImmutableTriple<Integer, String, Boolean> origTriple = ImmutableTriple.of(0, "foo", Boolean.TRUE);
+ final ImmutableTriple<Integer, String, Boolean> deserializedTriple = SerializationUtils.roundtrip(origTriple);
+ assertEquals(origTriple, deserializedTriple);
+ assertEquals(origTriple.hashCode(), deserializedTriple.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("(null,null,null)", ImmutableTriple.of(null, null, null).toString());
+ assertEquals("(null,two,null)", ImmutableTriple.of(null, "two", null).toString());
+ assertEquals("(one,null,null)", ImmutableTriple.of("one", null, null).toString());
+ assertEquals("(one,two,null)", ImmutableTriple.of("one", "two", null).toString());
+ assertEquals("(null,two,three)", ImmutableTriple.of(null, "two", "three").toString());
+ assertEquals("(one,null,three)", ImmutableTriple.of("one", null, "three").toString());
+ assertEquals("(one,two,three)", MutableTriple.of("one", "two", "three").toString());
+ }
+
+ @Test
+ public void testTripleOf() {
+ final ImmutableTriple<Integer, String, Boolean> triple = ImmutableTriple.of(0, "foo", Boolean.FALSE);
+ assertEquals(0, triple.left.intValue());
+ assertEquals(0, triple.getLeft().intValue());
+ assertEquals("foo", triple.middle);
+ assertEquals("foo", triple.getMiddle());
+ assertEquals(Boolean.FALSE, triple.right);
+ assertEquals(Boolean.FALSE, triple.getRight());
+ final ImmutableTriple<Object, String, Boolean> triple2 = ImmutableTriple.of(null, "bar", Boolean.TRUE);
+ assertNull(triple2.left);
+ assertNull(triple2.getLeft());
+ assertEquals("bar", triple2.middle);
+ assertEquals("bar", triple2.getMiddle());
+ assertEquals(Boolean.TRUE, triple2.right);
+ assertEquals(Boolean.TRUE, triple2.getRight());
+ }
+
+ @Test
+ public void testUseAsKeyOfHashMap() {
+ final HashMap<ImmutableTriple<Object, Object, Object>, String> map = new HashMap<>();
+ final Object o1 = new Object();
+ final Object o2 = new Object();
+ final Object o3 = new Object();
+ final ImmutableTriple<Object, Object, Object> key1 = ImmutableTriple.of(o1, o2, o3);
+ final String value1 = "a1";
+ map.put(key1, value1);
+ assertEquals(value1, map.get(key1));
+ assertEquals(value1, map.get(ImmutableTriple.of(o1, o2, o3)));
+ }
+
+ @Test
+ public void testUseAsKeyOfTreeMap() {
+ final TreeMap<ImmutableTriple<Integer, Integer, Integer>, String> map = new TreeMap<>();
+ map.put(ImmutableTriple.of(0, 1, 2), "012");
+ map.put(ImmutableTriple.of(0, 1, 1), "011");
+ map.put(ImmutableTriple.of(0, 0, 1), "001");
+ final ArrayList<ImmutableTriple<Integer, Integer, Integer>> expected = new ArrayList<>();
+ expected.add(ImmutableTriple.of(0, 0, 1));
+ expected.add(ImmutableTriple.of(0, 1, 1));
+ expected.add(ImmutableTriple.of(0, 1, 2));
+ final Iterator<Entry<ImmutableTriple<Integer, Integer, Integer>, String>> it = map.entrySet().iterator();
+ for (final ImmutableTriple<Integer, Integer, Integer> item : expected) {
+ final Entry<ImmutableTriple<Integer, Integer, Integer>, String> entry = it.next();
+ assertEquals(item, entry.getKey());
+ assertEquals(item.getLeft() + "" + item.getMiddle() + "" + item.getRight(), entry.getValue());
+ }
+ }
+}
+
diff --git a/src/test/java/org/apache/commons/lang3/tuple/MutablePairTest.java b/src/test/java/org/apache/commons/lang3/tuple/MutablePairTest.java
new file mode 100644
index 000000000..13db4b363
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/tuple/MutablePairTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.SerializationUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test the MutablePair class.
+ */
+public class MutablePairTest extends AbstractLangTest {
+
+ @Test
+ public void testBasic() {
+ MutablePair<Integer, String> oldPair = new MutablePair<>(0, "foo");
+ MutablePair<Integer, String> nowPair;
+ for (int i=0; i<4; i++) {
+ nowPair = MutablePair.of(oldPair);
+ assertEquals(0, nowPair.left.intValue());
+ assertEquals(0, nowPair.getLeft().intValue());
+ assertEquals("foo", nowPair.right);
+ assertEquals("foo", nowPair.getRight());
+ assertEquals(oldPair, nowPair);
+ oldPair = nowPair;
+ }
+
+ MutablePair<Object, String> oldPair2 = new MutablePair<>(null, "bar");
+ MutablePair<Object, String> nowPair2;
+ for (int i=0; i<4; i++) {
+ nowPair2 = MutablePair.of(oldPair2);
+ assertNull(nowPair2.left);
+ assertNull(nowPair2.getLeft());
+ assertEquals("bar", nowPair2.right);
+ assertEquals("bar", nowPair2.getRight());
+ oldPair2 = nowPair2;
+ }
+ }
+
+ @Test
+ public void testDefault() {
+ final MutablePair<Integer, String> pair = new MutablePair<>();
+ assertNull(pair.getLeft());
+ assertNull(pair.getRight());
+ }
+
+ @Test
+ public void testEmptyArrayGenerics() {
+ final MutablePair<Integer, String>[] empty = MutablePair.emptyArray();
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEmptyArrayLength() {
+ @SuppressWarnings("unchecked")
+ final MutablePair<Integer, String>[] empty = (MutablePair<Integer, String>[]) MutablePair.EMPTY_ARRAY;
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(MutablePair.of(null, "foo"), MutablePair.of(null, "foo"));
+ assertNotEquals(MutablePair.of("foo", 0), MutablePair.of("foo", null));
+ assertNotEquals(MutablePair.of("foo", "bar"), MutablePair.of("xyz", "bar"));
+
+ final MutablePair<String, String> p = MutablePair.of("foo", "bar");
+ assertEquals(p, p);
+ assertNotEquals(p, new Object());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(MutablePair.of(null, "foo").hashCode(), MutablePair.of(null, "foo").hashCode());
+ }
+
+ @Test
+ public void testMutate() {
+ final MutablePair<Integer, String> pair = new MutablePair<>(0, "foo");
+ pair.setLeft(42);
+ pair.setRight("bar");
+ assertEquals(42, pair.getLeft().intValue());
+ assertEquals("bar", pair.getRight());
+ }
+
+ @Test
+ public void testOfNonNull() {
+ assertThrows(NullPointerException.class, () -> MutablePair.ofNonNull(null, null));
+ assertThrows(NullPointerException.class, () -> MutablePair.ofNonNull(null, "x"));
+ assertThrows(NullPointerException.class, () -> MutablePair.ofNonNull("x", null));
+ final MutablePair<String, String> pair = MutablePair.ofNonNull("x", "y");
+ assertEquals("x", pair.left);
+ assertEquals("y", pair.right);
+ }
+
+ @Test
+ public void testPairOfMapEntry() {
+ final HashMap<Integer, String> map = new HashMap<>();
+ map.put(0, "foo");
+ final Entry<Integer, String> entry = map.entrySet().iterator().next();
+ final Pair<Integer, String> pair = MutablePair.of(entry);
+ assertEquals(entry.getKey(), pair.getLeft());
+ assertEquals(entry.getValue(), pair.getRight());
+ }
+
+ @Test
+ public void testPairOfObjects() {
+ final MutablePair<Integer, String> pair = MutablePair.of(0, "foo");
+ assertEquals(0, pair.getLeft().intValue());
+ assertEquals("foo", pair.getRight());
+ final MutablePair<Object, String> pair2 = MutablePair.of(null, "bar");
+ assertNull(pair2.getLeft());
+ assertEquals("bar", pair2.getRight());
+ final MutablePair<?, ?> pair3 = MutablePair.of(null, null);
+ assertNull(pair3.left);
+ assertNull(pair3.right);
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ final MutablePair<Integer, String> origPair = MutablePair.of(0, "foo");
+ final MutablePair<Integer, String> deserializedPair = SerializationUtils.roundtrip(origPair);
+ assertEquals(origPair, deserializedPair);
+ assertEquals(origPair.hashCode(), deserializedPair.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("(null,null)", MutablePair.of(null, null).toString());
+ assertEquals("(null,two)", MutablePair.of(null, "two").toString());
+ assertEquals("(one,null)", MutablePair.of("one", null).toString());
+ assertEquals("(one,two)", MutablePair.of("one", "two").toString());
+ }
+}
diff --git a/src/test/java/org/apache/commons/lang3/tuple/MutableTripleTest.java b/src/test/java/org/apache/commons/lang3/tuple/MutableTripleTest.java
new file mode 100644
index 000000000..011bd52de
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/tuple/MutableTripleTest.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.tuple;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.SerializationUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test the MutableTriple class.
+ */
+public class MutableTripleTest extends AbstractLangTest {
+
+ @Test
+ public void testOfNonNull() {
+ assertThrows(NullPointerException.class, () -> MutableTriple.ofNonNull(null, null, null));
+ assertThrows(NullPointerException.class, () -> MutableTriple.ofNonNull(null, null, "z"));
+ assertThrows(NullPointerException.class, () -> MutableTriple.ofNonNull(null, "y", "z"));
+ assertThrows(NullPointerException.class, () -> MutableTriple.ofNonNull("x", null, null));
+ assertThrows(NullPointerException.class, () -> MutableTriple.ofNonNull("x", "y", null));
+ final MutableTriple<String, String, String> pair = MutableTriple.ofNonNull("x", "y", "z");
+ assertEquals("x", pair.left);
+ assertEquals("y", pair.middle);
+ assertEquals("z", pair.right);
+ }
+
+
+ @Test
+ public void testBasic() {
+ final MutableTriple<Integer, String, Boolean> triple = new MutableTriple<>(0, "foo", Boolean.FALSE);
+ assertEquals(0, triple.getLeft().intValue());
+ assertEquals("foo", triple.getMiddle());
+ assertEquals(Boolean.FALSE, triple.getRight());
+ final MutableTriple<Object, String, String> triple2 = new MutableTriple<>(null, "bar", "hello");
+ assertNull(triple2.getLeft());
+ assertEquals("bar", triple2.getMiddle());
+ assertEquals("hello", triple2.getRight());
+ }
+
+ @Test
+ public void testDefault() {
+ final MutableTriple<Integer, String, Boolean> triple = new MutableTriple<>();
+ assertNull(triple.getLeft());
+ assertNull(triple.getMiddle());
+ assertNull(triple.getRight());
+ }
+
+ @Test
+ public void testEmptyArrayGenerics() {
+ final MutableTriple<Integer, String, Boolean>[] empty = MutableTriple.emptyArray();
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEmptyArrayLength() {
+ @SuppressWarnings("unchecked")
+ final MutableTriple<Integer, String, Boolean>[] empty = (MutableTriple<Integer, String, Boolean>[]) MutableTriple.EMPTY_ARRAY;
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(MutableTriple.of(null, "foo", "baz"), MutableTriple.of(null, "foo", "baz"));
+ assertNotEquals(MutableTriple.of("foo", 0, Boolean.TRUE), MutableTriple.of("foo", null, Boolean.TRUE));
+ assertNotEquals(MutableTriple.of("foo", "bar", "baz"), MutableTriple.of("xyz", "bar", "baz"));
+ assertNotEquals(MutableTriple.of("foo", "bar", "baz"), MutableTriple.of("foo", "bar", "blo"));
+
+ final MutableTriple<String, String, String> p = MutableTriple.of("foo", "bar", "baz");
+ assertEquals(p, p);
+ assertNotEquals(p, new Object());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertEquals(MutableTriple.of(null, "foo", "baz").hashCode(), MutableTriple.of(null, "foo", "baz").hashCode());
+ }
+
+ @Test
+ public void testMutate() {
+ final MutableTriple<Integer, String, Boolean> triple = new MutableTriple<>(0, "foo", Boolean.TRUE);
+ triple.setLeft(42);
+ triple.setMiddle("bar");
+ triple.setRight(Boolean.FALSE);
+ assertEquals(42, triple.getLeft().intValue());
+ assertEquals("bar", triple.getMiddle());
+ assertEquals(Boolean.FALSE, triple.getRight());
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ final MutableTriple<Integer, String, Boolean> origTriple = MutableTriple.of(0, "foo", Boolean.TRUE);
+ final MutableTriple<Integer, String, Boolean> deserializedTriple = SerializationUtils.roundtrip(origTriple);
+ assertEquals(origTriple, deserializedTriple);
+ assertEquals(origTriple.hashCode(), deserializedTriple.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("(null,null,null)", MutableTriple.of(null, null, null).toString());
+ assertEquals("(null,two,null)", MutableTriple.of(null, "two", null).toString());
+ assertEquals("(one,null,null)", MutableTriple.of("one", null, null).toString());
+ assertEquals("(one,two,null)", MutableTriple.of("one", "two", null).toString());
+ assertEquals("(null,two,three)", MutableTriple.of(null, "two", "three").toString());
+ assertEquals("(one,null,three)", MutableTriple.of("one", null, "three").toString());
+ assertEquals("(one,two,three)", MutableTriple.of("one", "two", "three").toString());
+ }
+
+ @Test
+ public void testTripleOf() {
+ final MutableTriple<Integer, String, Boolean> triple = MutableTriple.of(0, "foo", Boolean.TRUE);
+ assertEquals(0, triple.getLeft().intValue());
+ assertEquals("foo", triple.getMiddle());
+ assertEquals(Boolean.TRUE, triple.getRight());
+ final MutableTriple<Object, String, String> triple2 = MutableTriple.of(null, "bar", "hello");
+ assertNull(triple2.getLeft());
+ assertEquals("bar", triple2.getMiddle());
+ assertEquals("hello", triple2.getRight());
+ }
+}
+
diff --git a/src/test/java/org/apache/commons/lang3/tuple/PairTest.java b/src/test/java/org/apache/commons/lang3/tuple/PairTest.java
new file mode 100644
index 000000000..235f07a61
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/tuple/PairTest.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test the Pair class.
+ */
+public class PairTest extends AbstractLangTest {
+
+ @Test
+ public void testComparable1() {
+ final Pair<String, String> pair1 = Pair.of("A", "D");
+ final Pair<String, String> pair2 = Pair.of("B", "C");
+ assertEquals(0, pair1.compareTo(pair1));
+ assertTrue(pair1.compareTo(pair2) < 0);
+ assertEquals(0, pair2.compareTo(pair2));
+ assertTrue(pair2.compareTo(pair1) > 0);
+ }
+
+ @Test
+ public void testComparable2() {
+ final Pair<String, String> pair1 = Pair.of("A", "C");
+ final Pair<String, String> pair2 = Pair.of("A", "D");
+ assertEquals(0, pair1.compareTo(pair1));
+ assertTrue(pair1.compareTo(pair2) < 0);
+ assertEquals(0, pair2.compareTo(pair2));
+ assertTrue(pair2.compareTo(pair1) > 0);
+ }
+
+ @Test
+ public void testCompatibilityBetweenPairs() {
+ final Pair<Integer, String> pair = ImmutablePair.of(0, "foo");
+ final Pair<Integer, String> pair2 = MutablePair.of(0, "foo");
+ assertEquals(pair, pair2);
+ assertEquals(pair.hashCode(), pair2.hashCode());
+ final HashSet<Pair<Integer, String>> set = new HashSet<>();
+ set.add(pair);
+ assertTrue(set.contains(pair2));
+
+ pair2.setValue("bar");
+ assertNotEquals(pair, pair2);
+ assertNotEquals(pair.hashCode(), pair2.hashCode());
+ }
+
+ @Test
+ public void testEmptyArrayGenerics() {
+ final Pair<Integer, String>[] empty = Pair.emptyArray();
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEmptyArrayLength() {
+ @SuppressWarnings("unchecked")
+ final Pair<Integer, String>[] empty = (Pair<Integer, String>[]) Pair.EMPTY_ARRAY;
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testFormattable_padded() {
+ final Pair<String, String> pair = Pair.of("Key", "Value");
+ assertEquals(" (Key,Value)", String.format("%1$20s", pair));
+ }
+
+ @Test
+ public void testFormattable_simple() {
+ final Pair<String, String> pair = Pair.of("Key", "Value");
+ assertEquals("(Key,Value)", String.format("%1$s", pair));
+ }
+
+ @Test
+ public void testMapEntry() {
+ final Pair<Integer, String> pair = ImmutablePair.of(0, "foo");
+ final HashMap<Integer, String> map = new HashMap<>();
+ map.put(0, "foo");
+ final Entry<Integer, String> entry = map.entrySet().iterator().next();
+ assertEquals(pair, entry);
+ assertEquals(pair.hashCode(), entry.hashCode());
+ }
+
+ @Test
+ public void testOfNonNull() {
+ assertThrows(NullPointerException.class, () -> Pair.ofNonNull(null, null));
+ assertThrows(NullPointerException.class, () -> Pair.ofNonNull(null, "x"));
+ assertThrows(NullPointerException.class, () -> Pair.ofNonNull("x", null));
+ final Pair<String, String> pair = Pair.ofNonNull("x", "y");
+ assertEquals("x", pair.getLeft());
+ assertEquals("y", pair.getRight());
+ }
+
+ @Test
+ public void testPairOfMapEntry() {
+ final HashMap<Integer, String> map = new HashMap<>();
+ map.put(0, "foo");
+ final Entry<Integer, String> entry = map.entrySet().iterator().next();
+ final Pair<Integer, String> pair = Pair.of(entry);
+ assertEquals(entry.getKey(), pair.getLeft());
+ assertEquals(entry.getValue(), pair.getRight());
+ }
+
+ @Test
+ public void testPairOfObjects() {
+ final Pair<Integer, String> pair = Pair.of(0, "foo");
+ assertTrue(pair instanceof ImmutablePair<?, ?>);
+ assertEquals(0, ((ImmutablePair<Integer, String>) pair).left.intValue());
+ assertEquals("foo", ((ImmutablePair<Integer, String>) pair).right);
+ final Pair<Object, String> pair2 = Pair.of(null, "bar");
+ assertTrue(pair2 instanceof ImmutablePair<?, ?>);
+ assertNull(((ImmutablePair<Object, String>) pair2).left);
+ assertEquals("bar", ((ImmutablePair<Object, String>) pair2).right);
+ final Pair<?, ?> pair3 = Pair.of(null, null);
+ assertNull(pair3.getLeft());
+ assertNull(pair3.getRight());
+ }
+
+ @Test
+ public void testToString() {
+ final Pair<String, String> pair = Pair.of("Key", "Value");
+ assertEquals("(Key,Value)", pair.toString());
+ }
+
+ @Test
+ public void testToStringCustom() {
+ final Calendar date = Calendar.getInstance();
+ date.set(2011, Calendar.APRIL, 25);
+ final Pair<String, Calendar> pair = Pair.of("DOB", date);
+ assertEquals("Test created on " + "04-25-2011", pair.toString("Test created on %2$tm-%2$td-%2$tY"));
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/lang3/tuple/TripleTest.java b/src/test/java/org/apache/commons/lang3/tuple/TripleTest.java
new file mode 100644
index 000000000..4c56535dd
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/tuple/TripleTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Calendar;
+import java.util.HashSet;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test the Triple class.
+ */
+public class TripleTest extends AbstractLangTest {
+
+ @Test
+ public void testOfNonNull() {
+ assertThrows(NullPointerException.class, () -> Triple.ofNonNull(null, null, null));
+ assertThrows(NullPointerException.class, () -> Triple.ofNonNull(null, null, "z"));
+ assertThrows(NullPointerException.class, () -> Triple.ofNonNull(null, "y", "z"));
+ assertThrows(NullPointerException.class, () -> Triple.ofNonNull("x", null, null));
+ assertThrows(NullPointerException.class, () -> Triple.ofNonNull("x", "y", null));
+ final Triple<String, String, String> pair = Triple.ofNonNull("x", "y", "z");
+ assertEquals("x", pair.getLeft());
+ assertEquals("y", pair.getMiddle());
+ assertEquals("z", pair.getRight());
+ }
+
+ @Test
+ public void testComparable1() {
+ final Triple<String, String, String> triple1 = Triple.of("A", "D", "A");
+ final Triple<String, String, String> triple2 = Triple.of("B", "C", "A");
+ assertEquals(0, triple1.compareTo(triple1));
+ assertTrue(triple1.compareTo(triple2) < 0);
+ assertEquals(0, triple2.compareTo(triple2));
+ assertTrue(triple2.compareTo(triple1) > 0);
+ }
+
+ @Test
+ public void testComparable2() {
+ final Triple<String, String, String> triple1 = Triple.of("A", "C", "B");
+ final Triple<String, String, String> triple2 = Triple.of("A", "D", "B");
+ assertEquals(0, triple1.compareTo(triple1));
+ assertTrue(triple1.compareTo(triple2) < 0);
+ assertEquals(0, triple2.compareTo(triple2));
+ assertTrue(triple2.compareTo(triple1) > 0);
+ }
+
+ @Test
+ public void testComparable3() {
+ final Triple<String, String, String> triple1 = Triple.of("A", "A", "D");
+ final Triple<String, String, String> triple2 = Triple.of("A", "B", "C");
+ assertEquals(0, triple1.compareTo(triple1));
+ assertTrue(triple1.compareTo(triple2) < 0);
+ assertEquals(0, triple2.compareTo(triple2));
+ assertTrue(triple2.compareTo(triple1) > 0);
+ }
+
+ @Test
+ public void testComparable4() {
+ final Triple<String, String, String> triple1 = Triple.of("B", "A", "C");
+ final Triple<String, String, String> triple2 = Triple.of("B", "A", "D");
+ assertEquals(0, triple1.compareTo(triple1));
+ assertTrue(triple1.compareTo(triple2) < 0);
+ assertEquals(0, triple2.compareTo(triple2));
+ assertTrue(triple2.compareTo(triple1) > 0);
+ }
+
+ @Test
+ public void testCompatibilityBetweenTriples() {
+ final Triple<Integer, String, Boolean> triple = ImmutableTriple.of(0, "foo", Boolean.TRUE);
+ final Triple<Integer, String, Boolean> triple2 = MutableTriple.of(0, "foo", Boolean.TRUE);
+ assertEquals(triple, triple2);
+ assertEquals(triple.hashCode(), triple2.hashCode());
+ final HashSet<Triple<Integer, String, Boolean>> set = new HashSet<>();
+ set.add(triple);
+ assertTrue(set.contains(triple2));
+ }
+
+ @Test
+ public void testEmptyArrayGenerics() {
+ final Triple<Integer, String, Boolean>[] empty = Triple.emptyArray();
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testEmptyArrayLength() {
+ @SuppressWarnings("unchecked")
+ final Triple<Integer, String, Boolean>[] empty = (Triple<Integer, String, Boolean>[]) Triple.EMPTY_ARRAY;
+ assertEquals(0, empty.length);
+ }
+
+ @Test
+ public void testFormattable_padded() {
+ final Triple<String, String, String> triple = Triple.of("Key", "Something", "Value");
+ assertEquals(" (Key,Something,Value)", String.format("%1$30s", triple));
+ }
+
+ @Test
+ public void testFormattable_simple() {
+ final Triple<String, String, String> triple = Triple.of("Key", "Something", "Value");
+ assertEquals("(Key,Something,Value)", String.format("%1$s", triple));
+ }
+
+ @Test
+ public void testToString() {
+ final Triple<String, String, String> triple = Triple.of("Key", "Something", "Value");
+ assertEquals("(Key,Something,Value)", triple.toString());
+ }
+
+ @Test
+ public void testToStringCustom() {
+ final Calendar date = Calendar.getInstance();
+ date.set(2011, Calendar.APRIL, 25);
+ final Triple<String, String, Calendar> triple = Triple.of("DOB", "string", date);
+ assertEquals("Test created on " + "04-25-2011", triple.toString("Test created on %3$tm-%3$td-%3$tY"));
+ }
+
+ @Test
+ public void testTripleOf() {
+ final Triple<Integer, String, Boolean> triple = Triple.of(0, "foo", Boolean.TRUE);
+ assertTrue(triple instanceof ImmutableTriple<?, ?, ?>);
+ assertEquals(0, ((ImmutableTriple<Integer, String, Boolean>) triple).left.intValue());
+ assertEquals("foo", ((ImmutableTriple<Integer, String, Boolean>) triple).middle);
+ assertEquals(Boolean.TRUE, ((ImmutableTriple<Integer, String, Boolean>) triple).right);
+ final Triple<Object, String, Long> triple2 = Triple.of(null, "bar", Long.valueOf(200L));
+ assertTrue(triple2 instanceof ImmutableTriple<?, ?, ?>);
+ assertNull(((ImmutableTriple<Object, String, Long>) triple2).left);
+ assertEquals("bar", ((ImmutableTriple<Object, String, Long>) triple2).middle);
+ assertEquals(Long.valueOf(200L), ((ImmutableTriple<Object, String, Long>) triple2).right);
+ }
+
+}
+
diff --git a/src/test/java/org/apache/commons/lang3/util/FluentBitSetTest.java b/src/test/java/org/apache/commons/lang3/util/FluentBitSetTest.java
new file mode 100644
index 000000000..3a34424d9
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/util/FluentBitSetTest.java
@@ -0,0 +1,1828 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.BitSet;
+
+import org.apache.commons.lang3.AbstractLangTest;
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link FluentBitSet}.
+ * <p>
+ * Test code originally from Apache Harmony for FluentBitSet and adapted.
+ * </p>
+ */
+public class FluentBitSetTest extends AbstractLangTest {
+
+ private BitSet eightBs;
+ private FluentBitSet eightFbs;
+
+ /**
+ * BeforeEach.
+ */
+ @BeforeEach
+ public void beforeEach() {
+
+ eightFbs = newInstance();
+
+ for (int i = 0; i < 8; i++) {
+ eightFbs.set(i);
+ }
+ eightBs = eightFbs.bitSet();
+ }
+
+ private FluentBitSet newInstance() {
+ return new FluentBitSet();
+ }
+
+ private FluentBitSet newInstance(final int nbits) {
+ return new FluentBitSet(nbits);
+ }
+
+ /**
+ * Tests {@link FluentBitSet#and(FluentBitSet)}.
+ */
+ @Test
+ public void test_and() {
+ // Test for method void java.util.BitSet.and(BitSet)
+ final FluentBitSet bs = newInstance(128);
+ // Initialize the bottom half of the BitSet
+
+ for (int i = 64; i < 128; i++) {
+ bs.set(i);
+ }
+ eightFbs.and(bs);
+ assertFalse(eightFbs.equals(bs), "AND failed to clear bits");
+ eightFbs.set(3);
+ bs.set(3);
+ eightFbs.and(bs);
+ assertTrue(bs.get(3), "AND failed to maintain set bits");
+ bs.and(eightFbs);
+ for (int i = 64; i < 128; i++) {
+ assertFalse(bs.get(i), "Failed to clear extra bits in the receiver BitSet");
+ }
+ }
+
+ /**
+ * Tests {@link FluentBitSet#and(BitSet)}.
+ */
+ @Test
+ public void test_and_BitSet() {
+ // Test for method void java.util.BitSet.and(BitSet)
+ final FluentBitSet bs = newInstance(128);
+ // Initialize the bottom half of the BitSet
+
+ for (int i = 64; i < 128; i++) {
+ bs.set(i);
+ }
+ eightFbs.and(bs.bitSet());
+ assertFalse(eightFbs.equals(bs), "AND failed to clear bits");
+ eightFbs.set(3);
+ bs.set(3);
+ eightFbs.and(bs.bitSet());
+ assertTrue(bs.get(3), "AND failed to maintain set bits");
+ bs.and(eightBs);
+ for (int i = 64; i < 128; i++) {
+ assertFalse(bs.get(i), "Failed to clear extra bits in the receiver BitSet");
+ }
+ }
+
+ /**
+ * Tests {@link FluentBitSet#andNot(BitSet)}.
+ */
+ @Test
+ public void test_andNot() {
+ FluentBitSet bs = (FluentBitSet) eightFbs.clone();
+ bs.clear(5);
+ final FluentBitSet bs2 = newInstance();
+ bs2.set(2);
+ bs2.set(3);
+ bs.andNot(bs2);
+ assertEquals("{0, 1, 4, 6, 7}", bs.toString(), "Incorrect bitset after andNot");
+
+ bs = newInstance(0);
+ bs.andNot(bs2);
+ assertEquals(0, bs.size(), "Incorrect size");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#andNot(BitSet)}.
+ */
+ @Test
+ public void test_andNot_BitSet() {
+ FluentBitSet bs = (FluentBitSet) eightFbs.clone();
+ bs.clear(5);
+ final FluentBitSet bs2 = newInstance();
+ bs2.set(2);
+ bs2.set(3);
+ bs.andNot(bs2.bitSet());
+ assertEquals("{0, 1, 4, 6, 7}", bs.toString(), "Incorrect bitset after andNot");
+
+ bs = newInstance(0);
+ bs.andNot(bs2.bitSet());
+ assertEquals(0, bs.size(), "Incorrect size");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#cardinality()}.
+ */
+ @Test
+ public void test_cardinality() {
+ // test for method int java.util.BitSet.cardinality()
+ final FluentBitSet bs = newInstance(500);
+ bs.set(5);
+ bs.set(32);
+ bs.set(63);
+ bs.set(64);
+ bs.set(71, 110);
+ bs.set(127, 130);
+ bs.set(193);
+ bs.set(450);
+ assertEquals(48, bs.cardinality(), "cardinality() returned wrong value");
+
+ bs.flip(0, 500);
+ assertEquals(452, bs.cardinality(), "cardinality() returned wrong value");
+
+ bs.clear();
+ assertEquals(0, bs.cardinality(), "cardinality() returned wrong value");
+
+ bs.set(0, 500);
+ assertEquals(500, bs.cardinality(), "cardinality() returned wrong value");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#clear()}.
+ */
+ @Test
+ public void test_clear() {
+ eightFbs.clear();
+ for (int i = 0; i < 8; i++) {
+ assertFalse(eightFbs.get(i), "Clear didn't clear bit " + i);
+ }
+ assertEquals(0, eightFbs.length(), "Test1: Wrong length");
+
+ final FluentBitSet bs = newInstance(3400);
+ bs.set(0, bs.size() - 1); // ensure all bits are 1's
+ bs.set(bs.size() - 1);
+ bs.clear();
+ assertEquals(0, bs.length(), "Test2: Wrong length");
+ assertTrue(bs.isEmpty(), "Test2: isEmpty() returned incorrect value");
+ assertEquals(0, bs.cardinality(), "Test2: cardinality() returned incorrect value");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#clear(int)}.
+ */
+ @Test
+ public void test_clearI() {
+ // Test for method void java.util.BitSet.clear(int)
+
+ eightFbs.clear(7);
+ assertFalse(eightFbs.get(7), "Failed to clear bit");
+
+ // Check to see all other bits are still set
+ for (int i = 0; i < 7; i++) {
+ assertTrue(eightFbs.get(i), "Clear cleared incorrect bits");
+ }
+
+ eightFbs.clear(165);
+ assertFalse(eightFbs.get(165), "Failed to clear bit");
+ // Try out of range
+ assertThrows(IndexOutOfBoundsException.class, () -> eightFbs.clear(-1));
+
+ final FluentBitSet bs = newInstance(0);
+ assertEquals(0, bs.length(), "Test1: Wrong length,");
+ assertEquals(0, bs.size(), "Test1: Wrong size,");
+
+ bs.clear(0);
+ assertEquals(0, bs.length(), "Test2: Wrong length,");
+ assertEquals(0, bs.size(), "Test2: Wrong size,");
+
+ bs.clear(60);
+ assertEquals(0, bs.length(), "Test3: Wrong length,");
+ assertEquals(0, bs.size(), "Test3: Wrong size,");
+
+ bs.clear(120);
+ assertEquals(0, bs.size(), "Test4: Wrong size,");
+ assertEquals(0, bs.length(), "Test4: Wrong length,");
+
+ bs.set(25);
+ assertEquals(64, bs.size(), "Test5: Wrong size,");
+ assertEquals(26, bs.length(), "Test5: Wrong length,");
+
+ bs.clear(80);
+ assertEquals(64, bs.size(), "Test6: Wrong size,");
+ assertEquals(26, bs.length(), "Test6: Wrong length,");
+
+ bs.clear(25);
+ assertEquals(64, bs.size(), "Test7: Wrong size,");
+ assertEquals(0, bs.length(), "Test7: Wrong length,");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#clear(int, int)}.
+ */
+ @Test
+ public void test_clearII() {
+ // Regression for HARMONY-98
+ final FluentBitSet bitset = newInstance();
+ for (int i = 0; i < 20; i++) {
+ bitset.set(i);
+ }
+ bitset.clear(10, 10);
+
+ // Test for method void java.util.BitSet.clear(int, int)
+ // pos1 and pos2 are in the same bitset element
+ FluentBitSet bs = newInstance(16);
+ int initialSize = bs.size();
+ bs.set(0, initialSize);
+ bs.clear(5);
+ bs.clear(15);
+ bs.clear(7, 11);
+ for (int i = 0; i < 7; i++) {
+ if (i == 5) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertTrue(bs.get(i), "Shouldn't have cleared bit " + i);
+ }
+ }
+ for (int i = 7; i < 11; i++) {
+ assertFalse(bs.get(i), "Failed to clear bit " + i);
+ }
+
+ for (int i = 11; i < initialSize; i++) {
+ if (i == 15) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertTrue(bs.get(i), "Shouldn't have cleared bit " + i);
+ }
+ }
+
+ for (int i = initialSize; i < bs.size(); i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+
+ // pos1 and pos2 is in the same bitset element, boundry testing
+ bs = newInstance(16);
+ initialSize = bs.size();
+ bs.set(0, initialSize);
+ bs.clear(7, 64);
+ assertEquals(64, bs.size(), "Failed to grow BitSet");
+ for (int i = 0; i < 7; i++) {
+ assertTrue(bs.get(i), "Shouldn't have cleared bit " + i);
+ }
+ for (int i = 7; i < 64; i++) {
+ assertFalse(bs.get(i), "Failed to clear bit " + i);
+ }
+ for (int i = 64; i < bs.size(); i++) {
+ assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+ // more boundary testing
+ bs = newInstance(32);
+ initialSize = bs.size();
+ bs.set(0, initialSize);
+ bs.clear(0, 64);
+ for (int i = 0; i < 64; i++) {
+ assertFalse(bs.get(i), "Failed to clear bit " + i);
+ }
+ for (int i = 64; i < bs.size(); i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+
+ bs = newInstance(32);
+ initialSize = bs.size();
+ bs.set(0, initialSize);
+ bs.clear(0, 65);
+ for (int i = 0; i < 65; i++) {
+ assertFalse(bs.get(i), "Failed to clear bit " + i);
+ }
+ for (int i = 65; i < bs.size(); i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+
+ // pos1 and pos2 are in two sequential bitset elements
+ bs = newInstance(128);
+ initialSize = bs.size();
+ bs.set(0, initialSize);
+ bs.clear(7);
+ bs.clear(110);
+ bs.clear(9, 74);
+ for (int i = 0; i < 9; i++) {
+ if (i == 7) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertTrue(bs.get(i), "Shouldn't have cleared bit " + i);
+ }
+ }
+ for (int i = 9; i < 74; i++) {
+ assertFalse(bs.get(i), "Failed to clear bit " + i);
+ }
+ for (int i = 74; i < initialSize; i++) {
+ if (i == 110) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertTrue(bs.get(i), "Shouldn't have cleared bit " + i);
+ }
+ }
+ for (int i = initialSize; i < bs.size(); i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+
+ // pos1 and pos2 are in two non-sequential bitset elements
+ bs = newInstance(256);
+ bs.set(0, 256);
+ bs.clear(7);
+ bs.clear(255);
+ bs.clear(9, 219);
+ for (int i = 0; i < 9; i++) {
+ if (i == 7) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertTrue(bs.get(i), "Shouldn't have cleared bit " + i);
+ }
+ }
+
+ for (int i = 9; i < 219; i++) {
+ assertFalse(bs.get(i), "failed to clear bit " + i);
+ }
+
+ for (int i = 219; i < 255; i++) {
+ assertTrue(bs.get(i), "Shouldn't have cleared bit " + i);
+ }
+
+ for (int i = 255; i < bs.size(); i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+
+ // test illegal args
+ bs = newInstance(10);
+ assertThrows(IndexOutOfBoundsException.class, () -> newInstance(10).clear(-1, 3),
+ "Test1: Attempt to flip with negative index failed to generate exception");
+
+ assertThrows(IndexOutOfBoundsException.class, () -> newInstance(10).clear(2, -1),
+ "Test2: Attempt to flip with negative index failed to generate exception");
+
+ bs.set(2, 4);
+ bs.clear(2, 2);
+ assertTrue(bs.get(2), "Bit got cleared incorrectly ");
+
+ assertThrows(IndexOutOfBoundsException.class, () -> newInstance(10).clear(4, 2),
+ "Test4: Attempt to flip with illegal args failed to generate exception");
+
+ bs = newInstance(0);
+ assertEquals(0, bs.length(), "Test1: Wrong length,");
+ assertEquals(0, bs.size(), "Test1: Wrong size,");
+
+ bs.clear(0, 2);
+ assertEquals(0, bs.length(), "Test2: Wrong length,");
+ assertEquals(0, bs.size(), "Test2: Wrong size,");
+
+ bs.clear(60, 64);
+ assertEquals(0, bs.length(), "Test3: Wrong length,");
+ assertEquals(0, bs.size(), "Test3: Wrong size,");
+
+ bs.clear(64, 120);
+ assertEquals(0, bs.length(), "Test4: Wrong length,");
+ assertEquals(0, bs.size(), "Test4: Wrong size,");
+
+ bs.set(25);
+ assertEquals(26, bs.length(), "Test5: Wrong length,");
+ assertEquals(64, bs.size(), "Test5: Wrong size,");
+
+ bs.clear(60, 64);
+ assertEquals(26, bs.length(), "Test6: Wrong length,");
+ assertEquals(64, bs.size(), "Test6: Wrong size,");
+
+ bs.clear(64, 120);
+ assertEquals(64, bs.size(), "Test7: Wrong size,");
+ assertEquals(26, bs.length(), "Test7: Wrong length,");
+
+ bs.clear(80);
+ assertEquals(64, bs.size(), "Test8: Wrong size,");
+ assertEquals(26, bs.length(), "Test8: Wrong length,");
+
+ bs.clear(25);
+ assertEquals(64, bs.size(), "Test9: Wrong size,");
+ assertEquals(0, bs.length(), "Test9: Wrong length,");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#clear(int...)}.
+ */
+ @Test
+ public void test_clearIntArray() {
+ // Test for method void java.util.BitSet.clear(int)
+
+ eightFbs.clear(new int[] {7});
+ assertFalse(eightFbs.get(7), "Failed to clear bit");
+
+ // Check to see all other bits are still set
+ for (int i = 0; i < 7; i++) {
+ assertTrue(eightFbs.get(i), "Clear cleared incorrect bits");
+ }
+
+ eightFbs.clear(165);
+ assertFalse(eightFbs.get(165), "Failed to clear bit");
+ // Try out of range
+ assertThrows(IndexOutOfBoundsException.class, () -> eightFbs.clear(-1));
+
+ final FluentBitSet bs = newInstance(0);
+ assertEquals(0, bs.length(), "Test1: Wrong length,");
+ assertEquals(0, bs.size(), "Test1: Wrong size,");
+
+ bs.clear(new int[] {0});
+ assertEquals(0, bs.length(), "Test2: Wrong length,");
+ assertEquals(0, bs.size(), "Test2: Wrong size,");
+
+ bs.clear(new int[] {60});
+ assertEquals(0, bs.length(), "Test3: Wrong length,");
+ assertEquals(0, bs.size(), "Test3: Wrong size,");
+
+ bs.clear(new int[] {120});
+ assertEquals(0, bs.size(), "Test4: Wrong size,");
+ assertEquals(0, bs.length(), "Test4: Wrong length,");
+
+ bs.set(25);
+ assertEquals(64, bs.size(), "Test5: Wrong size,");
+ assertEquals(26, bs.length(), "Test5: Wrong length,");
+
+ bs.clear(new int[] {80});
+ assertEquals(64, bs.size(), "Test6: Wrong size,");
+ assertEquals(26, bs.length(), "Test6: Wrong length,");
+
+ bs.clear(new int[] {25});
+ assertEquals(64, bs.size(), "Test7: Wrong size,");
+ assertEquals(0, bs.length(), "Test7: Wrong length,");
+ }
+
+ /**
+ * Tests FluentBitSet#clone()
+ */
+ @Test
+ public void test_clone() {
+ final FluentBitSet bs = (FluentBitSet) eightFbs.clone();
+ assertEquals(bs, eightFbs, "clone failed to return equal BitSet");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#FluentBitSet()}.
+ */
+ @Test
+ public void test_Constructor() {
+ final FluentBitSet bs = newInstance();
+ assertEquals(64, bs.size(), "Create FluentBitSet of incorrect size");
+ assertEquals("{}", bs.toString(), "New FluentBitSet had invalid string representation");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#FluentBitSet(int)}.
+ */
+ @Test
+ public void test_ConstructorInt() {
+ FluentBitSet bs = newInstance(128);
+ assertEquals(128, bs.size(), "Create FluentBitSet of incorrect size");
+ assertEquals("{}", bs.toString(), "New FluentBitSet had invalid string representation: " + bs.toString());
+ // All BitSets are created with elements of multiples of 64
+ bs = newInstance(89);
+ assertEquals(128, bs.size(), "Failed to round FluentBitSet element size");
+
+ assertThrows(NegativeArraySizeException.class, () -> newInstance(-9));
+ }
+
+ /**
+ * Tests {@link FluentBitSet#equals(java.lang.Object)}.
+ */
+ @Test
+ public void test_equals() {
+ FluentBitSet bs;
+ bs = (FluentBitSet) eightFbs.clone();
+ assertEquals(eightFbs, eightFbs, "Same FluentBitSet returned false");
+ assertEquals(bs, eightFbs, "Identical FluentBitSet returned false");
+ bs.clear(6);
+ assertFalse(eightFbs.equals(bs), "Different BitSets returned true");
+ assertFalse(eightFbs.equals(null), "Different BitSets returned true");
+ assertFalse(eightFbs.equals(new Object()), "Different BitSets returned true");
+
+ bs = (FluentBitSet) eightFbs.clone();
+ bs.set(128);
+ assertFalse(eightFbs.equals(bs), "Different sized FluentBitSet with higher bit set returned true");
+ bs.clear(128);
+ assertTrue(eightFbs.equals(bs), "Different sized FluentBitSet with higher bits not set returned false");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#flip(int)}.
+ */
+ @Test
+ public void test_flipI() {
+ // Test for method void java.util.BitSet.flip(int)
+ FluentBitSet bs = newInstance();
+ bs.clear(8);
+ bs.clear(9);
+ bs.set(10);
+ bs.flip(9);
+ assertFalse(bs.get(8), "Failed to flip bit");
+ assertTrue(bs.get(9), "Failed to flip bit");
+ assertTrue(bs.get(10), "Failed to flip bit");
+
+ bs.set(8);
+ bs.set(9);
+ bs.clear(10);
+ bs.flip(9);
+ assertTrue(bs.get(8), "Failed to flip bit");
+ assertFalse(bs.get(9), "Failed to flip bit");
+ assertFalse(bs.get(10), "Failed to flip bit");
+
+ assertThrows(IndexOutOfBoundsException.class, () -> newInstance().flip(-1), "Attempt to flip at negative index failed to generate exception");
+
+ // Try setting a bit on a 64 boundary
+ bs.flip(128);
+ assertEquals(192, bs.size(), "Failed to grow BitSet");
+ assertTrue(bs.get(128), "Failed to flip bit");
+
+ bs = newInstance(64);
+ for (int i = bs.size(); --i >= 0;) {
+ bs.flip(i);
+ assertTrue(bs.get(i), "Test1: Incorrectly flipped bit" + i);
+ assertEquals(i + 1, bs.length(), "Incorrect length");
+ for (int j = bs.size(); --j > i;) {
+ assertTrue(!bs.get(j), "Test2: Incorrectly flipped bit" + j);
+ }
+ for (int j = i; --j >= 0;) {
+ assertTrue(!bs.get(j), "Test3: Incorrectly flipped bit" + j);
+ }
+ bs.flip(i);
+ }
+
+ final FluentBitSet bs0 = newInstance(0);
+ assertEquals(0, bs0.size(), "Test1: Wrong size");
+ assertEquals(0, bs0.length(), "Test1: Wrong length");
+
+ bs0.flip(0);
+ assertEquals(bs0.size(), 64, "Test2: Wrong size");
+ assertEquals(1, bs0.length(), "Test2: Wrong length");
+
+ bs0.flip(63);
+ assertEquals(64, bs0.size(), "Test3: Wrong size");
+ assertEquals(64, bs0.length(), "Test3: Wrong length");
+
+ eightFbs.flip(7);
+ assertTrue(!eightFbs.get(7), "Failed to flip bit 7");
+
+ // Check to see all other bits are still set
+ for (int i = 0; i < 7; i++) {
+ assertTrue(eightFbs.get(i), "Flip flipped incorrect bits");
+ }
+
+ eightFbs.flip(127);
+ assertTrue(eightFbs.get(127), "Failed to flip bit 127");
+
+ eightFbs.flip(127);
+ assertTrue(!eightFbs.get(127), "Failed to flip bit 127");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#clear(int, int)}.
+ */
+ @Test
+ public void test_flipII() {
+ final FluentBitSet bitset = newInstance();
+ for (int i = 0; i < 20; i++) {
+ bitset.set(i);
+ }
+ bitset.flip(10, 10);
+
+ // Test for method void java.util.BitSet.flip(int, int)
+ // pos1 and pos2 are in the same bitset element
+ FluentBitSet bs = newInstance(16);
+ bs.set(7);
+ bs.set(10);
+ bs.flip(7, 11);
+ for (int i = 0; i < 7; i++) {
+ assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+ assertFalse(bs.get(7), "Failed to flip bit 7");
+ assertTrue(bs.get(8), "Failed to flip bit 8");
+ assertTrue(bs.get(9), "Failed to flip bit 9");
+ assertFalse(bs.get(10), "Failed to flip bit 10");
+ for (int i = 11; i < bs.size(); i++) {
+ assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+
+ // pos1 and pos2 is in the same bitset element, boundry testing
+ bs = newInstance(16);
+ bs.set(7);
+ bs.set(10);
+ bs.flip(7, 64);
+ assertEquals(64, bs.size(), "Failed to grow BitSet");
+ for (int i = 0; i < 7; i++) {
+ assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+ assertFalse(bs.get(7), "Failed to flip bit 7");
+ assertTrue(bs.get(8), "Failed to flip bit 8");
+ assertTrue(bs.get(9), "Failed to flip bit 9");
+ assertFalse(bs.get(10), "Failed to flip bit 10");
+ for (int i = 11; i < 64; i++) {
+ assertTrue(bs.get(i), "failed to flip bit " + i);
+ }
+ assertFalse(bs.get(64), "Shouldn't have flipped bit 64");
+
+ // more boundary testing
+ bs = newInstance(32);
+ bs.flip(0, 64);
+ for (int i = 0; i < 64; i++) {
+ assertTrue(bs.get(i), "Failed to flip bit " + i);
+ }
+ assertFalse(bs.get(64), "Shouldn't have flipped bit 64");
+
+ bs = newInstance(32);
+ bs.flip(0, 65);
+ for (int i = 0; i < 65; i++) {
+ assertTrue(bs.get(i), "Failed to flip bit " + i);
+ }
+ assertFalse(bs.get(65), "Shouldn't have flipped bit 65");
+
+ // pos1 and pos2 are in two sequential bitset elements
+ bs = newInstance(128);
+ bs.set(7);
+ bs.set(10);
+ bs.set(72);
+ bs.set(110);
+ bs.flip(9, 74);
+ for (int i = 0; i < 7; i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+ assertTrue(bs.get(7), "Shouldn't have flipped bit 7");
+ assertFalse(bs.get(8), "Shouldn't have flipped bit 8");
+ assertTrue(bs.get(9), "Failed to flip bit 9");
+ assertFalse(bs.get(10), "Failed to flip bit 10");
+ for (int i = 11; i < 72; i++) {
+ assertTrue(bs.get(i), "failed to flip bit " + i);
+ }
+ assertFalse(bs.get(72), "Failed to flip bit 72");
+ assertTrue(bs.get(73), "Failed to flip bit 73");
+ for (int i = 74; i < 110; i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+ assertTrue(bs.get(110), "Shouldn't have flipped bit 110");
+ for (int i = 111; i < bs.size(); i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+
+ // pos1 and pos2 are in two non-sequential bitset elements
+ bs = newInstance(256);
+ bs.set(7);
+ bs.set(10);
+ bs.set(72);
+ bs.set(110);
+ bs.set(181);
+ bs.set(220);
+ bs.flip(9, 219);
+ for (int i = 0; i < 7; i++) {
+ assertFalse(bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+ assertTrue(bs.get(7), "Shouldn't have flipped bit 7");
+ assertFalse(bs.get(8), "Shouldn't have flipped bit 8");
+ assertTrue(bs.get(9), "Failed to flip bit 9");
+ assertFalse(bs.get(10), "Failed to flip bit 10");
+ for (int i = 11; i < 72; i++) {
+ assertTrue(bs.get(i), "failed to flip bit " + i);
+ }
+ assertFalse(bs.get(72), "Failed to flip bit 72");
+ for (int i = 73; i < 110; i++) {
+ assertTrue(bs.get(i), "failed to flip bit " + i);
+ }
+ assertFalse(bs.get(110), "Failed to flip bit 110");
+ for (int i = 111; i < 181; i++) {
+ assertTrue(bs.get(i), "failed to flip bit " + i);
+ }
+ assertFalse(bs.get(181), "Failed to flip bit 181");
+ for (int i = 182; i < 219; i++) {
+ assertTrue(bs.get(i), "failed to flip bit " + i);
+ }
+ assertFalse(bs.get(219), "Shouldn't have flipped bit 219");
+ assertTrue(bs.get(220), "Shouldn't have flipped bit 220");
+ for (int i = 221; i < bs.size(); i++) {
+ assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i);
+ }
+
+ // test illegal args
+ bs = newInstance(10);
+ try {
+ bs.flip(-1, 3);
+ fail("Test1: Attempt to flip with negative index failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // correct behavior
+ }
+
+ try {
+ bs.flip(2, -1);
+ fail("Test2: Attempt to flip with negative index failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // correct behavior
+ }
+
+ try {
+ bs.flip(4, 2);
+ fail("Test4: Attempt to flip with illegal args failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // correct behavior
+ }
+ }
+
+ /**
+ * Tests {@link FluentBitSet#get(int)}.
+ */
+ @Test
+ public void test_getI() {
+ // Test for method boolean java.util.BitSet.get(int)
+
+ FluentBitSet bs = newInstance();
+ bs.set(8);
+ assertFalse(eightFbs.get(99), "Get returned true for index out of range");
+ assertTrue(eightFbs.get(3), "Get returned false for set value");
+ assertFalse(bs.get(0), "Get returned true for a non set value");
+
+ assertThrows(IndexOutOfBoundsException.class, () -> newInstance().get(-1), "Attempt to get at negative index failed to generate exception");
+
+ bs = newInstance(1);
+ assertFalse(bs.get(64), "Access greater than size");
+
+ bs = newInstance();
+ bs.set(63);
+ assertTrue(bs.get(63), "Test highest bit");
+
+ bs = newInstance(0);
+ assertEquals(0, bs.length(), "Test1: Wrong length,");
+ assertEquals(0, bs.size(), "Test1: Wrong size,");
+
+ bs.get(2);
+ assertEquals(0, bs.length(), "Test2: Wrong length,");
+ assertEquals(0, bs.size(), "Test2: Wrong size,");
+
+ bs.get(70);
+ assertEquals(0, bs.length(), "Test3: Wrong length,");
+ assertEquals(0, bs.size(), "Test3: Wrong size,");
+
+ }
+
+ /**
+ * Tests {@link FluentBitSet#get(int, int)}.
+ */
+ @Test
+ public void test_getII() {
+ final FluentBitSet bitset = newInstance(30);
+ bitset.get(3, 3);
+
+ // Test for method boolean java.util.BitSet.get(int, int)
+ FluentBitSet bs, resultbs, correctbs;
+ bs = newInstance(512);
+ bs.set(3, 9);
+ bs.set(10, 20);
+ bs.set(60, 75);
+ bs.set(121);
+ bs.set(130, 140);
+
+ // pos1 and pos2 are in the same bitset element, at index0
+ resultbs = bs.get(3, 6);
+ correctbs = newInstance(3);
+ correctbs.set(0, 3);
+ assertEquals(correctbs, resultbs, "Test1: Returned incorrect BitSet");
+
+ // pos1 and pos2 are in the same bitset element, at index 1
+ resultbs = bs.get(100, 125);
+ correctbs = newInstance(25);
+ correctbs.set(21);
+ assertEquals(correctbs, resultbs, "Test2: Returned incorrect BitSet");
+
+ // pos1 in bitset element at index 0, and pos2 in bitset element at
+ // index 1
+ resultbs = bs.get(15, 125);
+ correctbs = newInstance(25);
+ correctbs.set(0, 5);
+ correctbs.set(45, 60);
+ correctbs.set(121 - 15);
+ assertEquals(correctbs, resultbs, "Test3: Returned incorrect BitSet");
+
+ // pos1 in bitset element at index 1, and pos2 in bitset element at
+ // index 2
+ resultbs = bs.get(70, 145);
+ correctbs = newInstance(75);
+ correctbs.set(0, 5);
+ correctbs.set(51);
+ correctbs.set(60, 70);
+ assertEquals(correctbs, resultbs, "Test4: Returned incorrect BitSet");
+
+ // pos1 in bitset element at index 0, and pos2 in bitset element at
+ // index 2
+ resultbs = bs.get(5, 145);
+ correctbs = newInstance(140);
+ correctbs.set(0, 4);
+ correctbs.set(5, 15);
+ correctbs.set(55, 70);
+ correctbs.set(116);
+ correctbs.set(125, 135);
+ assertEquals(correctbs, resultbs, "Test5: Returned incorrect BitSet");
+
+ // pos1 in bitset element at index 0, and pos2 in bitset element at
+ // index 3
+ resultbs = bs.get(5, 250);
+ correctbs = newInstance(200);
+ correctbs.set(0, 4);
+ correctbs.set(5, 15);
+ correctbs.set(55, 70);
+ correctbs.set(116);
+ correctbs.set(125, 135);
+ assertEquals(correctbs, resultbs, "Test6: Returned incorrect BitSet");
+
+ assertEquals(bs.get(0, bs.size()), bs, "equality principle 1 ");
+
+ // more tests
+ FluentBitSet bs2 = newInstance(129);
+ bs2.set(0, 20);
+ bs2.set(62, 65);
+ bs2.set(121, 123);
+ resultbs = bs2.get(1, 124);
+ correctbs = newInstance(129);
+ correctbs.set(0, 19);
+ correctbs.set(61, 64);
+ correctbs.set(120, 122);
+ assertEquals(correctbs, resultbs, "Test7: Returned incorrect BitSet");
+
+ // equality principle with some boundary conditions
+ bs2 = newInstance(128);
+ bs2.set(2, 20);
+ bs2.set(62);
+ bs2.set(121, 123);
+ bs2.set(127);
+ resultbs = bs2.get(0, bs2.size());
+ assertEquals(resultbs, bs2, "equality principle 2 ");
+
+ bs2 = newInstance(128);
+ bs2.set(2, 20);
+ bs2.set(62);
+ bs2.set(121, 123);
+ bs2.set(127);
+ bs2.flip(0, 128);
+ resultbs = bs2.get(0, bs.size());
+ assertEquals(resultbs, bs2, "equality principle 3 ");
+
+ bs = newInstance(0);
+ assertEquals(0, bs.length(), "Test1: Wrong length,");
+ assertEquals(0, bs.size(), "Test1: Wrong size,");
+
+ bs.get(0, 2);
+ assertEquals(0, bs.length(), "Test2: Wrong length,");
+ assertEquals(0, bs.size(), "Test2: Wrong size,");
+
+ bs.get(60, 64);
+ assertEquals(0, bs.length(), "Test3: Wrong length,");
+ assertEquals(0, bs.size(), "Test3: Wrong size,");
+
+ bs.get(64, 120);
+ assertEquals(0, bs.length(), "Test4: Wrong length,");
+ assertEquals(0, bs.size(), "Test4: Wrong size,");
+
+ bs.set(25);
+ assertEquals(26, bs.length(), "Test5: Wrong length,");
+ assertEquals(64, bs.size(), "Test5: Wrong size,");
+
+ bs.get(60, 64);
+ assertEquals(26, bs.length(), "Test6: Wrong length,");
+ assertEquals(64, bs.size(), "Test6: Wrong size,");
+
+ bs.get(64, 120);
+ assertEquals(64, bs.size(), "Test7: Wrong size,");
+ assertEquals(26, bs.length(), "Test7: Wrong length,");
+
+ bs.get(80);
+ assertEquals(64, bs.size(), "Test8: Wrong size,");
+ assertEquals(26, bs.length(), "Test8: Wrong length,");
+
+ bs.get(25);
+ assertEquals(64, bs.size(), "Test9: Wrong size,");
+ assertEquals(26, bs.length(), "Test9: Wrong length,");
+
+ }
+
+ /**
+ * Tests {@link FluentBitSet#hashCode()}.
+ */
+ @Test
+ public void test_hashCode() {
+ // Test for method int java.util.BitSet.hashCode()
+ final FluentBitSet bs = (FluentBitSet) eightFbs.clone();
+ bs.clear(2);
+ bs.clear(6);
+ assertEquals(bs.bitSet().hashCode(), bs.hashCode(), "BitSet returns wrong hash value");
+ bs.set(10);
+ bs.clear(3);
+ assertEquals(97, bs.hashCode(), "BitSet returns wrong hash value");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#intersects(FluentBitSet)}.
+ */
+ @Test
+ public void test_intersects() {
+ // Test for method boolean java.util.BitSet.intersects(BitSet)
+ final FluentBitSet bs = newInstance(500);
+ bs.set(5);
+ bs.set(63);
+ bs.set(64);
+ bs.set(71, 110);
+ bs.set(127, 130);
+ bs.set(192);
+ bs.set(450);
+
+ final FluentBitSet bs2 = newInstance(8);
+ assertFalse(bs.intersects(bs2), "Test1: intersects() returned incorrect value");
+ assertFalse(bs2.intersects(bs), "Test1: intersects() returned incorrect value");
+
+ bs2.set(4);
+ assertFalse(bs.intersects(bs2), "Test2: intersects() returned incorrect value");
+ assertFalse(bs2.intersects(bs), "Test2: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(5);
+ assertTrue(bs.intersects(bs2), "Test3: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs), "Test3: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(63);
+ assertTrue(bs.intersects(bs2), "Test4: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs), "Test4: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(80);
+ assertTrue(bs.intersects(bs2), "Test5: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs), "Test5: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(127);
+ assertTrue(bs.intersects(bs2), "Test6: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs), "Test6: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(192);
+ assertTrue(bs.intersects(bs2), "Test7: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs), "Test7: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(450);
+ assertTrue(bs.intersects(bs2), "Test8: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs), "Test8: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(500);
+ assertFalse(bs.intersects(bs2), "Test9: intersects() returned incorrect value");
+ assertFalse(bs2.intersects(bs), "Test9: intersects() returned incorrect value");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#intersects(BitSet)}.
+ */
+ @Test
+ public void test_intersects_BitSet() {
+ // Test for method boolean java.util.BitSet.intersects(BitSet)
+ final FluentBitSet bs = newInstance(500);
+ bs.set(5);
+ bs.set(63);
+ bs.set(64);
+ bs.set(71, 110);
+ bs.set(127, 130);
+ bs.set(192);
+ bs.set(450);
+
+ final FluentBitSet bs2 = newInstance(8);
+ assertFalse(bs.intersects(bs2.bitSet()), "Test1: intersects() returned incorrect value");
+ assertFalse(bs2.intersects(bs.bitSet()), "Test1: intersects() returned incorrect value");
+
+ bs2.set(4);
+ assertFalse(bs.intersects(bs2.bitSet()), "Test2: intersects() returned incorrect value");
+ assertFalse(bs2.intersects(bs.bitSet()), "Test2: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(5);
+ assertTrue(bs.intersects(bs2.bitSet()), "Test3: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs.bitSet()), "Test3: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(63);
+ assertTrue(bs.intersects(bs2.bitSet()), "Test4: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs.bitSet()), "Test4: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(80);
+ assertTrue(bs.intersects(bs2.bitSet()), "Test5: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs.bitSet()), "Test5: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(127);
+ assertTrue(bs.intersects(bs2.bitSet()), "Test6: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs.bitSet()), "Test6: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(192);
+ assertTrue(bs.intersects(bs2.bitSet()), "Test7: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs.bitSet()), "Test7: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(450);
+ assertTrue(bs.intersects(bs2.bitSet()), "Test8: intersects() returned incorrect value");
+ assertTrue(bs2.intersects(bs.bitSet()), "Test8: intersects() returned incorrect value");
+
+ bs2.clear();
+ bs2.set(500);
+ assertFalse(bs.intersects(bs2.bitSet()), "Test9: intersects() returned incorrect value");
+ assertFalse(bs2.intersects(bs.bitSet()), "Test9: intersects() returned incorrect value");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#isEmpty()}.
+ */
+ @Test
+ public void test_isEmpty() {
+ final FluentBitSet bs = newInstance(500);
+ assertTrue(bs.isEmpty(), "Test: isEmpty() returned wrong value");
+
+ // at bitset element 0
+ bs.set(3);
+ assertFalse(bs.isEmpty(), "Test0: isEmpty() returned wrong value");
+
+ // at bitset element 1
+ bs.clear();
+ bs.set(12);
+ assertFalse(bs.isEmpty(), "Test1: isEmpty() returned wrong value");
+
+ // at bitset element 2
+ bs.clear();
+ bs.set(128);
+ assertFalse(bs.isEmpty(), "Test2: isEmpty() returned wrong value");
+
+ // boundary testing
+ bs.clear();
+ bs.set(459);
+ assertFalse(bs.isEmpty(), "Test3: isEmpty() returned wrong value");
+
+ bs.clear();
+ bs.set(511);
+ assertFalse(bs.isEmpty(), "Test4: isEmpty() returned wrong value");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#length()}.
+ */
+ @Test
+ public void test_length() {
+ final FluentBitSet bs = newInstance();
+ assertEquals(0, bs.length(), "BitSet returned wrong length");
+ bs.set(5);
+ assertEquals(6, bs.length(), "BitSet returned wrong length");
+ bs.set(10);
+ assertEquals(11, bs.length(), "BitSet returned wrong length");
+ bs.set(432);
+ assertEquals(433, bs.length(), "BitSet returned wrong length");
+ bs.set(300);
+ assertEquals(433, bs.length(), "BitSet returned wrong length");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#nextClearBit(int)}.
+ */
+ @Test
+ public void test_nextClearBitI() {
+ // Test for method int java.util.BitSet.nextSetBit()
+ final FluentBitSet bs = newInstance(500);
+ bs.set(0, bs.size() - 1); // ensure all the bits from 0 to bs.size()
+ // -1
+ bs.set(bs.size() - 1); // are set to true
+ bs.clear(5);
+ bs.clear(32);
+ bs.clear(63);
+ bs.clear(64);
+ bs.clear(71, 110);
+ bs.clear(127, 130);
+ bs.clear(193);
+ bs.clear(450);
+ try {
+ bs.nextClearBit(-1);
+ fail("Expected IndexOutOfBoundsException for negative index");
+ } catch (final IndexOutOfBoundsException e) {
+ // correct behavior
+ }
+ assertEquals(5, bs.nextClearBit(0), "nextClearBit() returned the wrong value");
+ assertEquals(5, bs.nextClearBit(5), "nextClearBit() returned the wrong value");
+ assertEquals(32, bs.nextClearBit(6), "nextClearBit() returned the wrong value");
+ assertEquals(32, bs.nextClearBit(32), "nextClearBit() returned the wrong value");
+ assertEquals(63, bs.nextClearBit(33), "nextClearBit() returned the wrong value");
+
+ // boundary tests
+ assertEquals(63, bs.nextClearBit(63), "nextClearBit() returned the wrong value");
+ assertEquals(64, bs.nextClearBit(64), "nextClearBit() returned the wrong value");
+
+ // at bitset element 1
+ assertEquals(71, bs.nextClearBit(65), "nextClearBit() returned the wrong value");
+ assertEquals(71, bs.nextClearBit(71), "nextClearBit() returned the wrong value");
+ assertEquals(72, bs.nextClearBit(72), "nextClearBit() returned the wrong value");
+ assertEquals(127, bs.nextClearBit(110), "nextClearBit() returned the wrong value");
+
+ // boundary tests
+ assertEquals(127, bs.nextClearBit(127), "nextClearBit() returned the wrong value");
+ assertEquals(128, bs.nextClearBit(128), "nextClearBit() returned the wrong value");
+
+ // at bitset element 2
+ assertEquals(193, bs.nextClearBit(130), "nextClearBit() returned the wrong value");
+ assertEquals(193, bs.nextClearBit(191), "nextClearBit() returned the wrong value");
+
+ assertEquals(193, bs.nextClearBit(192), "nextClearBit() returned the wrong value");
+ assertEquals(193, bs.nextClearBit(193), "nextClearBit() returned the wrong value");
+ assertEquals(450, bs.nextClearBit(194), "nextClearBit() returned the wrong value");
+ assertEquals(450, bs.nextClearBit(255), "nextClearBit() returned the wrong value");
+ assertEquals(450, bs.nextClearBit(256), "nextClearBit() returned the wrong value");
+ assertEquals(450, bs.nextClearBit(450), "nextClearBit() returned the wrong value");
+
+ // bitset has 1 still the end of bs.size() -1, but calling nextClearBit
+ // with any index value
+ // after the last true bit should return bs.size(),
+ assertEquals(512, bs.nextClearBit(451), "nextClearBit() returned the wrong value");
+ assertEquals(512, bs.nextClearBit(511), "nextClearBit() returned the wrong value");
+ assertEquals(512, bs.nextClearBit(512), "nextClearBit() returned the wrong value");
+
+ // if the index is larger than bs.size(), nextClearBit should return
+ // index;
+ assertEquals(513, bs.nextClearBit(513), "nextClearBit() returned the wrong value");
+ assertEquals(800, bs.nextClearBit(800), "nextClearBit() returned the wrong value");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#nextSetBit(int)}.
+ */
+ @Test
+ public void test_nextSetBitI() {
+ // Test for method int java.util.BitSet.nextSetBit()
+ final FluentBitSet bs = newInstance(500);
+ bs.set(5);
+ bs.set(32);
+ bs.set(63);
+ bs.set(64);
+ bs.set(71, 110);
+ bs.set(127, 130);
+ bs.set(193);
+ bs.set(450);
+ try {
+ bs.nextSetBit(-1);
+ fail("Expected IndexOutOfBoundsException for negative index");
+ } catch (final IndexOutOfBoundsException e) {
+ // correct behavior
+ }
+ assertEquals(5, bs.nextSetBit(0), "nextSetBit() returned the wrong value");
+ assertEquals(5, bs.nextSetBit(5), "nextSetBit() returned the wrong value");
+ assertEquals(32, bs.nextSetBit(6), "nextSetBit() returned the wrong value");
+ assertEquals(32, bs.nextSetBit(32), "nextSetBit() returned the wrong value");
+ assertEquals(63, bs.nextSetBit(33), "nextSetBit() returned the wrong value");
+
+ // boundary tests
+ assertEquals(63, bs.nextSetBit(63), "nextSetBit() returned the wrong value");
+ assertEquals(64, bs.nextSetBit(64), "nextSetBit() returned the wrong value");
+
+ // at bitset element 1
+ assertEquals(71, bs.nextSetBit(65), "nextSetBit() returned the wrong value");
+ assertEquals(71, bs.nextSetBit(71), "nextSetBit() returned the wrong value");
+ assertEquals(72, bs.nextSetBit(72), "nextSetBit() returned the wrong value");
+ assertEquals(127, bs.nextSetBit(110), "nextSetBit() returned the wrong value");
+
+ // boundary tests
+ assertEquals(127, bs.nextSetBit(127), "nextSetBit() returned the wrong value");
+ assertEquals(128, bs.nextSetBit(128), "nextSetBit() returned the wrong value");
+
+ // at bitset element 2
+ assertEquals(193, bs.nextSetBit(130), "nextSetBit() returned the wrong value");
+
+ assertEquals(193, bs.nextSetBit(191), "nextSetBit() returned the wrong value");
+ assertEquals(193, bs.nextSetBit(192), "nextSetBit() returned the wrong value");
+ assertEquals(193, bs.nextSetBit(193), "nextSetBit() returned the wrong value");
+ assertEquals(450, bs.nextSetBit(194), "nextSetBit() returned the wrong value");
+ assertEquals(450, bs.nextSetBit(255), "nextSetBit() returned the wrong value");
+ assertEquals(450, bs.nextSetBit(256), "nextSetBit() returned the wrong value");
+ assertEquals(450, bs.nextSetBit(450), "nextSetBit() returned the wrong value");
+
+ assertEquals(-1, bs.nextSetBit(451), "nextSetBit() returned the wrong value");
+ assertEquals(-1, bs.nextSetBit(511), "nextSetBit() returned the wrong value");
+ assertEquals(-1, bs.nextSetBit(512), "nextSetBit() returned the wrong value");
+ assertEquals(-1, bs.nextSetBit(800), "nextSetBit() returned the wrong value");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#or(FluentBitSet)}.
+ */
+ @Test
+ public void test_or() {
+ // Test for method void java.util.BitSet.or(BitSet)
+ FluentBitSet bs = newInstance(128);
+ bs.or(eightFbs);
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "OR failed to set bits");
+ }
+
+ bs = newInstance(0);
+ bs.or(eightFbs);
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "OR(0) failed to set bits");
+ }
+
+ eightFbs.clear(5);
+ bs = newInstance(128);
+ bs.or(eightFbs);
+ assertFalse(bs.get(5), "OR set a bit which should be off");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#or(BitSet)}.
+ */
+ @Test
+ public void test_or_BitSet() {
+ // Test for method void java.util.BitSet.or(BitSet)
+ FluentBitSet bs = newInstance(128);
+ bs.or(eightFbs.bitSet());
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "OR failed to set bits");
+ }
+
+ bs = newInstance(0);
+ bs.or(eightFbs.bitSet());
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "OR(0) failed to set bits");
+ }
+
+ eightFbs.clear(5);
+ bs = newInstance(128);
+ bs.or(eightFbs.bitSet());
+ assertFalse(bs.get(5), "OR set a bit which should be off");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#or(FluentBitSet)}.
+ */
+ @Test
+ public void test_or_FluentBitSetArray() {
+ // Test for method void java.util.BitSet.or(BitSet)
+ FluentBitSet bs = newInstance(128);
+ bs.or(new FluentBitSet[] {eightFbs});
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "OR failed to set bits");
+ }
+
+ bs = newInstance(0);
+ bs.or(new FluentBitSet[] {eightFbs});
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "OR(0) failed to set bits");
+ }
+
+ eightFbs.clear(5);
+ bs = newInstance(128);
+ bs.or(new FluentBitSet[] {eightFbs});
+ assertFalse(bs.get(5), "OR set a bit which should be off");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#previousClearBit(int)}.
+ */
+ @Test
+ public void test_previousClearBit() {
+ final FluentBitSet bs = newInstance();
+ assertEquals(1, bs.previousClearBit(1), "previousClearBit");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#previousSetBit(int)}.
+ */
+ @Test
+ public void test_previousSetBit() {
+ final FluentBitSet bs = newInstance();
+ assertEquals(-1, bs.previousSetBit(1), "previousSetBit");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#set(int, int)}.
+ */
+ @Test
+ public void test_setII() {
+ final FluentBitSet bitset = newInstance(30);
+ bitset.set(29, 29);
+
+ // Test for method void java.util.BitSet.set(int, int)
+ // pos1 and pos2 are in the same bitset element
+ FluentBitSet bs = newInstance(16);
+ bs.set(5);
+ bs.set(15);
+ bs.set(7, 11);
+ for (int i = 0; i < 7; i++) {
+ if (i == 5) {
+ assertTrue(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+ for (int i = 7; i < 11; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ for (int i = 11; i < bs.size(); i++) {
+ if (i == 15) {
+ assertTrue(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+
+ // pos1 and pos2 is in the same bitset element, boundry testing
+ bs = newInstance(16);
+ bs.set(7, 64);
+ assertEquals(64, bs.size(), "Failed to grow BitSet");
+ for (int i = 0; i < 7; i++) {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ for (int i = 7; i < 64; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ assertFalse(bs.get(64), "Shouldn't have set bit 64");
+
+ // more boundary testing
+ bs = newInstance(32);
+ bs.set(0, 64);
+ for (int i = 0; i < 64; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ assertFalse(bs.get(64), "Shouldn't have set bit 64");
+
+ bs = newInstance(32);
+ bs.set(0, 65);
+ for (int i = 0; i < 65; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ assertFalse(bs.get(65), "Shouldn't have set bit 65");
+
+ // pos1 and pos2 are in two sequential bitset elements
+ bs = newInstance(128);
+ bs.set(7);
+ bs.set(110);
+ bs.set(9, 74);
+ for (int i = 0; i < 9; i++) {
+ if (i == 7) {
+ assertTrue(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+ for (int i = 9; i < 74; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ for (int i = 74; i < bs.size(); i++) {
+ if (i == 110) {
+ assertTrue(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+
+ // pos1 and pos2 are in two non-sequential bitset elements
+ bs = newInstance(256);
+ bs.set(7);
+ bs.set(255);
+ bs.set(9, 219);
+ for (int i = 0; i < 9; i++) {
+ if (i == 7) {
+ assertTrue(bs.get(i), "Shouldn't have set flipped " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+
+ for (int i = 9; i < 219; i++) {
+ assertTrue(bs.get(i), "failed to set bit " + i);
+ }
+
+ for (int i = 219; i < 255; i++) {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+
+ assertTrue(bs.get(255), "Shouldn't have flipped bit 255");
+
+ // test illegal args
+ bs = newInstance(10);
+ try {
+ bs.set(-1, 3);
+ fail("Test1: Attempt to flip with negative index failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // Correct behavior
+ }
+
+ try {
+ bs.set(2, -1);
+ fail("Test2: Attempt to flip with negative index failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // Correct behavior
+ }
+
+ bs.set(2, 2);
+ assertFalse(bs.get(2), "Bit got set incorrectly ");
+
+ try {
+ bs.set(4, 2);
+ fail("Test4: Attempt to flip with illegal args failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // Correct behavior
+ }
+ }
+
+ /**
+ * Tests {@link FluentBitSet#set(int, int, boolean)}.
+ */
+ @Test
+ public void test_setIIZ() {
+ // Test for method void java.util.BitSet.set(int, int, boolean)
+ eightFbs.set(3, 6, false);
+ assertTrue(!eightFbs.get(3) && !eightFbs.get(4) && !eightFbs.get(5), "Should have set bits 3, 4, and 5 to false");
+
+ eightFbs.set(3, 6, true);
+ assertTrue(eightFbs.get(3) && eightFbs.get(4) && eightFbs.get(5), "Should have set bits 3, 4, and 5 to true");
+
+ }
+
+ /**
+ * Tests {@link FluentBitSet#setInclusive(int, int)}.
+ */
+ @Test
+ public void test_setInclusive() {
+ final FluentBitSet bitset = newInstance(30);
+ bitset.set(29, 29);
+
+ // Test for method void java.util.BitSet.set(int, int)
+ // pos1 and pos2 are in the same bitset element
+ FluentBitSet bs = newInstance(16);
+ bs.set(5);
+ bs.set(15);
+ bs.setInclusive(7, 11);
+ for (int i = 0; i < 7; i++) {
+ if (i == 5) {
+ assertTrue(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+ for (int i = 7; i < 12; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ for (int i = 12; i < bs.size(); i++) {
+ if (i == 15) {
+ assertTrue(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+
+ // pos1 and pos2 is in the same bitset element, boundry testing
+ bs = newInstance(16);
+ bs.setInclusive(7, 64);
+ assertEquals(128, bs.size(), "Failed to grow BitSet");
+ for (int i = 0; i < 7; i++) {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ for (int i = 7; i < 65; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ assertFalse(bs.get(65), "Shouldn't have set bit 64");
+
+ // more boundary testing
+ bs = newInstance(32);
+ bs.setInclusive(0, 64);
+ for (int i = 0; i < 65; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ assertFalse(bs.get(65), "Shouldn't have set bit 64");
+
+ bs = newInstance(32);
+ bs.setInclusive(0, 65);
+ for (int i = 0; i < 66; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ assertFalse(bs.get(66), "Shouldn't have set bit 65");
+
+ // pos1 and pos2 are in two sequential bitset elements
+ bs = newInstance(128);
+ bs.set(7);
+ bs.set(110);
+ bs.setInclusive(9, 74);
+ for (int i = 0; i < 9; i++) {
+ if (i == 7) {
+ assertTrue(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+ for (int i = 9; i < 75; i++) {
+ assertTrue(bs.get(i), "Failed to set bit " + i);
+ }
+ for (int i = 75; i < bs.size(); i++) {
+ if (i == 110) {
+ assertTrue(bs.get(i), "Shouldn't have flipped bit " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+
+ // pos1 and pos2 are in two non-sequential bitset elements
+ bs = newInstance(256);
+ bs.set(7);
+ bs.set(255);
+ bs.setInclusive(9, 219);
+ for (int i = 0; i < 9; i++) {
+ if (i == 7) {
+ assertTrue(bs.get(i), "Shouldn't have set flipped " + i);
+ } else {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+ }
+
+ for (int i = 9; i < 220; i++) {
+ assertTrue(bs.get(i), "failed to set bit " + i);
+ }
+
+ for (int i = 220; i < 255; i++) {
+ assertFalse(bs.get(i), "Shouldn't have set bit " + i);
+ }
+
+ assertTrue(bs.get(255), "Shouldn't have flipped bit 255");
+
+ // test illegal args
+ bs = newInstance(10);
+ try {
+ bs.setInclusive(-1, 3);
+ fail("Test1: Attempt to flip with negative index failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // Correct behavior
+ }
+
+ try {
+ bs.setInclusive(2, -1);
+ fail("Test2: Attempt to flip with negative index failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // Correct behavior
+ }
+
+ bs.setInclusive(2, 2);
+ assertFalse(bs.get(3), "Bit got set incorrectly ");
+
+ try {
+ bs.setInclusive(4, 2);
+ fail("Test4: Attempt to flip with illegal args failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // Correct behavior
+ }
+ }
+
+ /**
+ * Tests {@link FluentBitSet#set(int)}.
+ */
+ @Test
+ public void test_setInt() {
+ // Test for method void java.util.BitSet.set(int)
+
+ FluentBitSet bs = newInstance();
+ bs.set(8);
+ assertTrue(bs.get(8), "Failed to set bit");
+
+ try {
+ bs.set(-1);
+ fail("Attempt to set at negative index failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // Correct behavior
+ }
+
+ // Try setting a bit on a 64 boundary
+ bs.set(128);
+ assertEquals(192, bs.size(), "Failed to grow BitSet");
+ assertTrue(bs.get(128), "Failed to set bit");
+
+ bs = newInstance(64);
+ for (int i = bs.size(); --i >= 0;) {
+ bs.set(i);
+ assertTrue(bs.get(i), "Incorrectly set");
+ assertEquals(i + 1, bs.length(), "Incorrect length");
+ for (int j = bs.size(); --j > i;) {
+ assertFalse(bs.get(j), "Incorrectly set bit " + j);
+ }
+ for (int j = i; --j >= 0;) {
+ assertFalse(bs.get(j), "Incorrectly set bit " + j);
+ }
+ bs.clear(i);
+ }
+
+ bs = newInstance(0);
+ assertEquals(0, bs.length(), "Test1: Wrong length");
+ bs.set(0);
+ assertEquals(1, bs.length(), "Test2: Wrong length");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#set(int...)}.
+ */
+ @Test
+ public void test_setIntArray() {
+ // Test for method void java.util.BitSet.set(int)
+
+ FluentBitSet bs = newInstance();
+ bs.set(new int[] {8});
+ assertTrue(bs.get(8), "Failed to set bit");
+
+ try {
+ bs.set(new int[] {-1});
+ fail("Attempt to set at negative index failed to generate exception");
+ } catch (final IndexOutOfBoundsException e) {
+ // Correct behavior
+ }
+
+ // Try setting a bit on a 64 boundary
+ bs.set(new int[] {128});
+ assertEquals(192, bs.size(), "Failed to grow BitSet");
+ assertTrue(bs.get(128), "Failed to set bit");
+
+ bs = newInstance(64);
+ for (int i = bs.size(); --i >= 0;) {
+ bs.set(new int[] {i});
+ assertTrue(bs.get(i), "Incorrectly set");
+ assertEquals(i + 1, bs.length(), "Incorrect length");
+ for (int j = bs.size(); --j > i;) {
+ assertFalse(bs.get(j), "Incorrectly set bit " + j);
+ }
+ for (int j = i; --j >= 0;) {
+ assertFalse(bs.get(j), "Incorrectly set bit " + j);
+ }
+ bs.clear(i);
+ }
+
+ bs = newInstance(0);
+ assertEquals(0, bs.length(), "Test1: Wrong length");
+ bs.set(new int[] {0});
+ assertEquals(1, bs.length(), "Test2: Wrong length");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#set(int, boolean)}.
+ */
+ @Test
+ public void test_setIZ() {
+ // Test for method void java.util.BitSet.set(int, boolean)
+ eightFbs.set(5, false);
+ assertFalse(eightFbs.get(5), "Should have set bit 5 to true");
+
+ eightFbs.set(5, true);
+ assertTrue(eightFbs.get(5), "Should have set bit 5 to false");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#setInclusive(int, int)}.
+ */
+ @Test
+ public void test_setRangeInclusive() {
+ // Test for method int java.util.BitSet.size()
+ assertEquals(64, eightFbs.size(), "Returned incorrect size");
+ eightFbs.set(129);
+ assertTrue(eightFbs.size() >= 129, "Returned incorrect size");
+
+ }
+
+ /**
+ * Tests {@link FluentBitSet#size()}.
+ */
+ @Test
+ public void test_size() {
+ // Test for method int java.util.BitSet.size()
+ assertEquals(64, eightFbs.size(), "Returned incorrect size");
+ eightFbs.set(129);
+ assertTrue(eightFbs.size() >= 129, "Returned incorrect size");
+
+ }
+
+ /**
+ * Tests {@link FluentBitSet#previousSetBit(int)}.
+ */
+ @Test
+ public void test_stream() {
+ final FluentBitSet bs = newInstance();
+ assertEquals(0, bs.stream().count(), "stream");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#previousSetBit(int)}.
+ */
+ @Test
+ public void test_toByteArray() {
+ final FluentBitSet bs = newInstance();
+ assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, bs.toByteArray(), "stream");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#previousSetBit(int)}.
+ */
+ @Test
+ public void test_toLongArray() {
+ final FluentBitSet bs = newInstance();
+ assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, bs.toLongArray(), "stream");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#toString()}.
+ */
+ @Test
+ public void test_toString() {
+ // Test for method java.lang.String java.util.BitSet.toString()
+ assertEquals("{0, 1, 2, 3, 4, 5, 6, 7}", eightFbs.toString(), "Returned incorrect string representation");
+ eightFbs.clear(2);
+ assertEquals("{0, 1, 3, 4, 5, 6, 7}", eightFbs.toString(), "Returned incorrect string representation");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#xor(FluentBitSet)}.
+ */
+ @Test
+ public void test_xor() {
+ // Test for method void java.util.BitSet.xor(BitSet)
+
+ FluentBitSet bs = (FluentBitSet) eightFbs.clone();
+ bs.xor(eightFbs);
+ for (int i = 0; i < 8; i++) {
+ assertFalse(bs.get(i), "XOR failed to clear bits");
+ }
+
+ bs.xor(eightFbs);
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "XOR failed to set bits");
+ }
+
+ bs = newInstance(0);
+ bs.xor(eightFbs);
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "XOR(0) failed to set bits");
+ }
+
+ bs = newInstance();
+ bs.set(63);
+ assertEquals("{63}", bs.toString(), "Test highest bit");
+ }
+
+ /**
+ * Tests {@link FluentBitSet#xor(BitSet)}.
+ */
+ @Test
+ public void test_xor_BitSet() {
+ // Test for method void java.util.BitSet.xor(BitSet)
+
+ FluentBitSet bs = (FluentBitSet) eightFbs.clone();
+ bs.xor(eightFbs.bitSet());
+ for (int i = 0; i < 8; i++) {
+ assertFalse(bs.get(i), "XOR failed to clear bits");
+ }
+
+ bs.xor(eightFbs.bitSet());
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "XOR failed to set bits");
+ }
+
+ bs = newInstance(0);
+ bs.xor(eightFbs.bitSet());
+ for (int i = 0; i < 8; i++) {
+ assertTrue(bs.get(i), "XOR(0) failed to set bits");
+ }
+
+ bs = newInstance();
+ bs.set(63);
+ assertEquals("{63}", bs.toString(), "Test highest bit");
+ }
+
+}
diff --git a/src/test/resources/java.policy b/src/test/resources/java.policy
new file mode 100644
index 000000000..587518d2a
--- /dev/null
+++ b/src/test/resources/java.policy
@@ -0,0 +1,375 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+//
+// Allows unit tests to run with a Java Security Manager
+//
+// Tested from Eclipse 3.7 with the CLI:
+//
+// -Djava.security.manager -Djava.security.policy=file:src/test/resources/java.policy
+//
+// Tested from Maven 3.0.3 with the Surfire 2.8.1 configuration:
+//
+// <argLine>-Djava.security.manager -Djava.security.policy=${basedir}/src/test/resources/java.policy</argLine>
+//
+// This policy file documents why each permission is granted by listing exceptions in comments.
+//
+// This policy file grants permission as narrowly as possible.
+//
+
+grant {
+
+// Found using Eclipse 3.7
+// java.security.AccessControlException: access denied (java.io.FilePermission C:\Users\ggregory\AppData\Local\Temp\testNames8413758989552151476.txt read)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
+// at java.security.AccessController.checkPermission(AccessController.java:427)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
+// at java.io.FileInputStream.<init>(FileInputStream.java:100)
+// at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.readTestNames(RemoteTestRunner.java:336)
+// at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.defaultInit(RemoteTestRunner.java:251)
+// at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.init(RemoteTestRunner.java:212)
+// at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
+
+ permission java.io.FilePermission "${java.io.tmpdir}/-", "read";
+
+
+// Found using Eclipse 3.7
+// java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:58691 connect,resolve)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
+// at java.security.AccessController.checkPermission(AccessController.java:427)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkConnect(SecurityManager.java:1034)
+// at java.net.Socket.connect(Socket.java:518)
+// at java.net.Socket.connect(Socket.java:474)
+// at java.net.Socket.<init>(Socket.java:371)
+// at java.net.Socket.<init>(Socket.java:184)
+// at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.connect(RemoteTestRunner.java:570)
+// at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:381)
+// at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
+
+ permission java.net.SocketPermission "localhost", "connect,resolve";
+
+
+// All others found using Surefire 2.8.1
+// java.security.AccessControlException: access denied (java.io.FilePermission C:\svn\org\apache\commons\trunks-proper\lang\target\surefire\surefire795889196143891944tmp read)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
+// at java.io.FileInputStream.<init>(FileInputStream.java:100)
+// at org.apache.maven.surefire.booter.SystemPropertyManager.loadProperties(SystemPropertyManager.java:62)
+// at org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties(SystemPropertyManager.java:69)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:56)
+
+ permission java.io.FilePermission "target/surefire/*", "read";
+
+
+// java.security.AccessControlException: access denied (java.util.PropertyPermission user.dir write)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.System.setProperty(System.java:725)
+// at org.apache.maven.surefire.booter.PropertiesWrapper.setAsSystemProperties(PropertiesWrapper.java:60)
+// at org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties(SystemPropertyManager.java:70)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:56)
+
+ permission java.util.PropertyPermission "user.dir", "write";
+
+
+// Found using Surefire 2.8.1
+// java.security.AccessControlException: access denied (java.util.PropertyPermission localRepository write)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.System.setProperty(System.java:725)
+// at org.apache.maven.surefire.booter.PropertiesWrapper.setAsSystemProperties(PropertiesWrapper.java:60)
+// at org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties(SystemPropertyManager.java:70)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:56)
+
+ permission java.util.PropertyPermission "localRepository", "write";
+
+
+// java.security.AccessControlException: access denied (java.util.PropertyPermission basedir write)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.System.setProperty(System.java:725)
+// at org.apache.maven.surefire.booter.PropertiesWrapper.setAsSystemProperties(PropertiesWrapper.java:60)
+// at org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties(SystemPropertyManager.java:70)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:56)
+
+ permission java.util.PropertyPermission "basedir", "write";
+
+
+// java.security.AccessControlException: access denied (java.util.PropertyPermission surefire.test.class.path write)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.System.setProperty(System.java:725)
+// at org.apache.maven.surefire.booter.Classpath.writeToSystemProperty(Classpath.java:112)
+// at org.apache.maven.surefire.booter.SurefireStarter.writeSurefireTestClasspathProperty(SurefireStarter.java:118)
+// at org.apache.maven.surefire.booter.SurefireStarter.createInProcessTestClassLoader(SurefireStarter.java:98)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:85)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)}
+
+// java.lang.reflect.UndeclaredThrowableException
+// at $Proxy0.invoke(Unknown Source)
+// at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+//Caused by: java.lang.reflect.InvocationTargetException
+// at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+// at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+// at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+// at java.lang.reflect.Method.invoke(Method.java:597)
+// at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
+// ... 4 more
+//Caused by: java.security.AccessControlException: access denied (java.util.PropertyPermission surefire.junit4.upgradecheck read)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1285)
+// at java.lang.System.getProperty(System.java:650)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.isJunit4UpgradeCheck(JUnit4Provider.java:193)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.upgradeCheck(JUnit4Provider.java:174)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:91)
+
+ permission java.util.PropertyPermission "*", "write, read";
+
+
+// java.security.AccessControlException: access denied (java.util.PropertyPermission java.class.path read)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1285)
+// at java.lang.System.getProperty(System.java:650)
+// at org.apache.maven.surefire.booter.SurefireStarter.createInProcessTestClassLoader(SurefireStarter.java:105)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:85)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+//
+
+// java.security.AccessControlException: access denied (java.util.PropertyPermission java.class.path write)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.System.setProperty(System.java:725)
+// at org.apache.maven.surefire.booter.Classpath.writeToSystemProperty(Classpath.java:112)
+// at org.apache.maven.surefire.booter.SurefireStarter.createInProcessTestClassLoader(SurefireStarter.java:106)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:85)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+
+ permission java.util.PropertyPermission "java.class.path", "read, write";
+
+
+// java.security.AccessControlException: access denied (java.io.FilePermission C:\Users\ggregory\.m2\repository\org\apache\maven\surefire\surefire-junit4\2.8.1\surefire-junit4-2.8.1.jar read)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
+// at java.io.File.isDirectory(File.java:752)
+// at java.io.File.toURL(File.java:623)
+// at org.apache.maven.surefire.util.UrlUtils.getURL(UrlUtils.java:67)
+// at org.apache.maven.surefire.booter.Classpath.getAsUrlList(Classpath.java:100)
+// at org.apache.maven.surefire.booter.ClasspathConfiguration.createClassLoader(ClasspathConfiguration.java:151)
+// at org.apache.maven.surefire.booter.ClasspathConfiguration.createClassLoaderSEE(ClasspathConfiguration.java:139)
+// at org.apache.maven.surefire.booter.ClasspathConfiguration.createSurefireClassLoader(ClasspathConfiguration.java:131)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:89)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+
+ permission java.io.FilePermission "${user.home}/.m2/repository/org/apache/maven/surefire/surefire-junit4/2.8.1/surefire-junit4-2.8.1.jar", "read";
+ permission java.io.FilePermission "${user.home}/.m2/repository/org/apache/maven/surefire/surefire-junit4/2.9/surefire-junit4-2.9.jar", "read";
+
+
+// java.security.AccessControlException: access denied (java.io.FilePermission C:\Users\ggregory\.m2\repository\org\apache\maven\surefire\surefire-api\2.8.1\surefire-api-2.8.1.jar read)
+//at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+//at java.security.AccessController.checkPermission(AccessController.java:546)
+//at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+//at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
+//at java.io.File.isDirectory(File.java:752)
+//at java.io.File.toURL(File.java:623)
+//at org.apache.maven.surefire.util.UrlUtils.getURL(UrlUtils.java:67)
+//at org.apache.maven.surefire.booter.Classpath.getAsUrlList(Classpath.java:100)
+//at org.apache.maven.surefire.booter.ClasspathConfiguration.createClassLoader(ClasspathConfiguration.java:151)
+//at org.apache.maven.surefire.booter.ClasspathConfiguration.createClassLoaderSEE(ClasspathConfiguration.java:139)
+//at org.apache.maven.surefire.booter.ClasspathConfiguration.createSurefireClassLoader(ClasspathConfiguration.java:131)
+//at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:89)
+//at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+
+ permission java.io.FilePermission "${user.home}/.m2/repository/org/apache/maven/surefire/surefire-api/2.8.1/surefire-api-2.8.1.jar", "read";
+ permission java.io.FilePermission "${user.home}/.m2/repository/org/apache/maven/surefire/surefire-api/2.9/surefire-api-2.9.jar", "read";
+
+
+// java.security.AccessControlException: access denied (java.lang.RuntimePermission createClassLoader)
+//at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+//at java.security.AccessController.checkPermission(AccessController.java:546)
+//at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+//at java.lang.SecurityManager.checkCreateClassLoader(SecurityManager.java:594)
+//at java.lang.ClassLoader.checkCreateClassLoader(ClassLoader.java:178)
+//at java.lang.ClassLoader.<init>(ClassLoader.java:207)
+//at java.security.SecureClassLoader.<init>(SecureClassLoader.java:70)
+//at java.net.URLClassLoader.<init>(URLClassLoader.java:84)
+//at org.apache.maven.surefire.booter.IsolatedClassLoader.<init>(IsolatedClassLoader.java:43)
+//at org.apache.maven.surefire.booter.ClasspathConfiguration.createClassLoader(ClasspathConfiguration.java:152)
+//at org.apache.maven.surefire.booter.ClasspathConfiguration.createClassLoaderSEE(ClasspathConfiguration.java:139)
+//at org.apache.maven.surefire.booter.ClasspathConfiguration.createSurefireClassLoader(ClasspathConfiguration.java:131)
+//at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:89)
+//at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+
+ permission java.lang.RuntimePermission "createClassLoader";
+
+// java.security.AccessControlException: access denied (java.lang.RuntimePermission setContextClassLoader)
+//at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+//at java.security.AccessController.checkPermission(AccessController.java:546)
+//at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+//at java.lang.Thread.setContextClassLoader(Thread.java:1394)
+//at org.apache.maven.surefire.booter.ProviderFactory.createProvider(ProviderFactory.java:61)
+//at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:146)
+//at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
+//at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+
+ permission java.lang.RuntimePermission "setContextClassLoader";
+
+// java.security.AccessControlException: access denied (java.lang.RuntimePermission setIO)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.System.checkIO(System.java:225)
+// at java.lang.System.setOut(System.java:147)
+// at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:162)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+
+ permission java.lang.RuntimePermission "setIO";
+
+
+// java.lang.reflect.UndeclaredThrowableException
+// at $Proxy0.invoke(Unknown Source)
+// at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+//Caused by: java.lang.reflect.InvocationTargetException
+// at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+// at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+// at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+// at java.lang.reflect.Method.invoke(Method.java:597)
+// at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
+// ... 4 more
+//Caused by: java.security.AccessControlException: access denied (java.io.FilePermission C:\svn\org\apache\commons\trunks-proper\lang\target\test-classes read)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
+// at java.io.File.exists(File.java:731)
+// at org.apache.maven.surefire.util.DefaultDirectoryScanner.collectTests(DefaultDirectoryScanner.java:118)
+// at org.apache.maven.surefire.util.DefaultDirectoryScanner.locateTestClasses(DefaultDirectoryScanner.java:71)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.scanClassPath(JUnit4Provider.java:168)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:88)
+// ... 9 more
+
+ permission java.io.FilePermission "${user.dir}/target/test-classes", "read";
+ permission java.io.FilePermission "${user.dir}/target/test-classes/-", "read";
+
+
+// java.lang.reflect.UndeclaredThrowableException
+// at $Proxy0.invoke(Unknown Source)
+// at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+//Caused by: java.lang.reflect.InvocationTargetException
+// at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+// at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+// at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+// at java.lang.reflect.Method.invoke(Method.java:597)
+// at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
+// ... 4 more
+//Caused by: java.security.AccessControlException: access denied (java.lang.RuntimePermission accessDeclaredMembers)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkMemberAccess(SecurityManager.java:1662)
+// at java.lang.Class.checkMemberAccess(Class.java:2157)
+// at java.lang.Class.getDeclaredMethods(Class.java:1790)
+// at org.apache.maven.surefire.common.junit4.JUnit4TestChecker.checkforTestAnnotatedMethod(JUnit4TestChecker.java:83)
+// at org.apache.maven.surefire.common.junit4.JUnit4TestChecker.isValidJUnit4Test(JUnit4TestChecker.java:72)
+// at org.apache.maven.surefire.common.junit4.JUnit4TestChecker.accept(JUnit4TestChecker.java:52)
+// at org.apache.maven.surefire.util.DefaultDirectoryScanner.locateTestClasses(DefaultDirectoryScanner.java:80)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.scanClassPath(JUnit4Provider.java:168)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:88)
+
+ permission java.lang.RuntimePermission "accessDeclaredMembers";
+
+
+// java.lang.reflect.UndeclaredThrowableException
+// at $Proxy0.invoke(Unknown Source)
+//Running org.apache.commons.lang3.AnnotationUtilsTest
+// at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+//Caused by: java.lang.reflect.InvocationTargetException
+// at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+// at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+// at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+// at java.lang.reflect.Method.invoke(Method.java:597)
+// at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
+// ... 4 more
+//Caused by: java.security.AccessControlException: access denied (java.io.FilePermission C:\svn\org\apache\commons\trunks-proper\lang\target\surefire-reports read)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
+// at java.io.File.exists(File.java:731)
+// at java.io.File.mkdirs(File.java:1181)
+// at org.apache.maven.surefire.report.AbstractFileReporter.testSetStarting(AbstractFileReporter.java:59)
+// at org.apache.maven.surefire.report.MulticastingReporter.testSetStarting(MulticastingReporter.java:45)
+// at org.apache.maven.surefire.report.TestSetRunListener.testSetStarting(TestSetRunListener.java:131)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:115)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:101)
+
+// java.lang.reflect.UndeclaredThrowableException
+// at $Proxy0.invoke(Unknown Source)
+//Running org.apache.commons.lang3.AnnotationUtilsTest
+// at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150)
+// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
+// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
+//Caused by: java.lang.reflect.InvocationTargetException
+// at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+// at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+// at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+// at java.lang.reflect.Method.invoke(Method.java:597)
+// at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
+// ... 4 more
+//Caused by: java.security.AccessControlException: access denied (java.io.FilePermission C:\svn\org\apache\commons\trunks-proper\lang\target\surefire-reports write)
+// at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
+// at java.security.AccessController.checkPermission(AccessController.java:546)
+// at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
+// at java.lang.SecurityManager.checkWrite(SecurityManager.java:962)
+// at java.io.File.mkdir(File.java:1155)
+// at java.io.File.mkdirs(File.java:1184)
+// at org.apache.maven.surefire.report.AbstractFileReporter.testSetStarting(AbstractFileReporter.java:59)
+// at org.apache.maven.surefire.report.MulticastingReporter.testSetStarting(MulticastingReporter.java:45)
+// at org.apache.maven.surefire.report.TestSetRunListener.testSetStarting(TestSetRunListener.java:131)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:115)
+// at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:101)
+// ... 9 more
+
+ permission java.io.FilePermission "target/surefire-reports", "read, write";
+ permission java.io.FilePermission "target/surefire-reports/*", "read, write";
+
+};
+
+
diff --git a/src/test/resources/lang-708-input.txt b/src/test/resources/lang-708-input.txt
new file mode 100644
index 000000000..4e75f67a6
--- /dev/null
+++ b/src/test/resources/lang-708-input.txt
@@ -0,0 +1 @@
+[{"geonameFeatureClass":"L","values":{"eu":"Mundua","ro":"Pamânt","it":"Globo","ca":"el món","tr":"Yeryüzü","no":"Jorden","hu":"Föld","lv":"Zeme","de":"Welt","el":"Υδρόγειος","fi":"Maa","la":"Terra","fr":"Monde","eo":"Mondo","en":"World","ru":"Земля","es":"el planeta","nl":"Aarde"},"geonameFeatureCode":"AREA","_id":32,"name":"","auto":true,"type":"GEO","geonameId":6295630,"valueCode":""},{"geonameFeatureClass":"L","values":{"ro":"Europa","zh":"欧洲","ca":"Europa","vi":"Châu Âu","tr":"Avrupa","no":"Europa","hu":"Európa","lv":"Eiropa","hi":"यूरोप","lt":"Europa","bs":"Evropa","ga":"an Eoraip","th":"ยุโรป","id":"Eropa","de":"Europa","fi":"Eurooppa","fr":"Europe","sv":"Europa","bg":"Европа","da":"Europa","eu":"Europa","is":"Evrópa","it":"Europa","cy":"Ewrop","ar":"أوروبا","se":"Eurohpá","he":"אירופה","cs":"Evropa","el":"Ευρώπη","nb":"Europa","pl":"Europa","la":"Europa","pt":"Europa","eo":"Eŭropo","en":"Europe","ru":"Европа","es":"Europa","ja":"ヨーロッパ","nl":"Europa"},"geonameFeatureCode":"CONT","_id":33,"name":"","auto":true,"type":"GEO","geonameId":6255148,"valueCode":""},{"geonameFeatureClass":"A","values":{"no":"Spania","nn":"Spania","fy":"Spanje","gd":"An Spàinn","ga":"An Spáinn","oc":"Espanha","arc":"ܐܣܦܢܝܐ","fi":"Espanja","fr":"Espagne","fo":"Spania","udm":"Испания","os":"Испани","he":"ספרד","gn":"Epaña","gl":"España","gv":"Yn Spaainey","pl":"Hiszpania","gu":"સ્પેઇન","lo":"ສະເປນ","ln":"Espania","vi":"Tây Ban Nha","dz":"Spain","pms":"Spagna","lv":"Spānija","lt":"Ispanija","vo":"Spanyän","de":"Spanien","mg":"Espaina","fur":"Spagne","mk":"Шпанија","ml":"സ്പെയ്ന്\u200D","ceb":"Espanya","mi":"Pāniora","uk":"Іспанія","eu":"Espainia","mr":"स्पेन","ug":"ئىسپانىيە","mt":"Spanja","ms":"Sepanyol","ur":"سپین","fa":"اسپانیا","ty":"Paniora","new":"स्पेन","na":"Pain","el":"Ισπανία","nb":"Spania","ne":"स्पेन","vls":"Spanje","eo":"Hispanio","en":"Kingdom of Spain","et":"Hispaania","es":"la Madre Patria","nl":"Spanje","vec":"Spagna","to":"Sepeni","ca":"Espanya","tl":"Espanya","tr":"İspanya","tg":"Испониё","haw":"Sepania","bs":"Španija","br":"Spagn","th":"ประเทศสเปน","bn":"স্পেন","bo":"སི་པན།","ta":"ஸ்பெயின்","sv":"Spanien","bg":"Испания","ka":"ესპანეთი","st":"Spain","sw":"Hispania","be":"Іспанія","kw":"Spayn","sl":"Španija","sk":"Španielsko","da":"Spanien","ang":"Spēonland","nds":"Spanien","ks":"Spēna","so":"Isbeyn","ku":"Spanya","sr":"Шпанија","sq":"Spanja","ko":"에스파냐","sc":"Ispagna","cy":"Sbaen","se":"Espánjja","sh":"Španija","cv":"Испани","km":"អេស្ប៉ាញ","cs":"Španělsko","li":"Spanje","co":"Spagna","default":"Spain","jbo":"sangu'e","la":"Hesperia","ru":"Испания","lb":"Spuenien","sco":"Spain","tet":"España","scn":"Spagna","hr":"Španjolska","zh":"西班牙","ro":"Spania","rm":"Spagna","ht":"Espay","hu":"Spanyolország","ast":"España","hi":"स्पेन","hsb":"Španiska","nah":"Caxtillān","war":"Espanya","lad":"Espanya","id":"Spanyol","ia":"Espania","nrm":"Espangne","hy":"Իսպանիա","qu":"Ispaña","ilo":"Espania","az":"İspaniya","is":"Spánn","it":"Spagna","tpi":"Spen","ar":"أسبانيا","io":"Hispania","pam":"Espanya","frp":"Èspagne","am":"እስፓንያ","an":"España","csb":"Szpańskô","pt":"Espanha","ja":"スペイン","ps":"اسپانيا","yi":"שפאניע","af":"Spanje"},"geonameFeatureCode":"PCLI","_id":260,"name":"","auto":true,"type":"GEO","geonameId":2510769,"valueCode":""},{"geonameFeatureClass":"A","values":{"ca":"Andalusia","tr":"Endülüs","krc":"Андалусия","no":"Andalucía","fy":"Andalûsje","bs":"Andaluzija","br":"Andalouzia","ext":"Andaluzia","ga":"An Andalúis","th":"แคว้นอันดาลูซีอา","bn":"আন্দালুসিয়া","oc":"Andalosia","ka":"ანდალუსია","sv":"Andalusien","fr":"Andalousie","bg":"Андалусия","glk":"آندالوسیا","be":"Андалусія","kw":"Andalousi","sk":"Andalúzia","os":"Андалуси","da":"Andalusien","sr":"Андалузија","ku":"Endulus","ko":"안달루시아 지방","he":"אנדלוסיה","sh":"Andaluzija","arz":"اندلوسيا","cs":"Andalusie","default":"Andalusia","stq":"Andalusien","la":"Vandalitia","pl":"Andaluzja","ru":"Андалусия","lb":"Andalusien","tet":"Andaluzia","got":"𐍅𐌰𐌽𐌳𐌰𐌻𐌹𐍄𐌾𐌰","hr":"Andaluzija","zh":"安達魯西亞","ro":"Andaluzia","hu":"Andalúzia","pms":"Andalusìa","lv":"Andalūzija","lt":"Andalūzija","nah":"Andalucia","lad":"Andaluziya","de":"Andalusien","als":"Andalusien","qu":"Andalusiya","hy":"Անդալուզիա","eu":"Andaluzia","is":"Andalúsía","uk":"Андалусія","az":"Andalusiya","mr":"आंदालुसिया","ug":"Andalusiye","fa":"اندلس","ar":"أندلوسيا","rmy":"Andalusiya","io":"Andaluzia","el":"Ανδαλουσία","frp":"Andalosie","pt":"Andaluzia","eo":"Andaluzio","en":"Andalusia","et":"Andaluusia","es":"Andalucía","ja":"アンダルシア州","nl":"Andalusië","af":"Andalusië","vec":"Andalusìa"},"geonameFeatureCode":"ADM1","_id":261,"name":"","auto":true,"type":"GEO","geonameId":2593109,"valueCode":""},{"geonameFeatureClass":"A","values":{"de":"Granada","default":"Province of Granada","fr":"Grenade","en":"Province of Granada","es":"Provincia de Granada","ja":"グラナダ"},"geonameFeatureCode":"ADM2","_id":262,"name":"","auto":true,"type":"GEO","geonameId":2517115,"valueCode":""},{"geonameFeatureClass":"A","values":{"default":"Monachil"},"geonameFeatureCode":"ADM3","_id":263,"name":"","auto":true,"type":"GEO","geonameId":6357744,"valueCode":""},{"geonameFeatureClass":"P","values":{"default":"Sierra Nevada"},"geonameFeatureCode":"PPL","_id":264,"name":"","auto":true,"type":"GEO","geonameId":6544329,"valueCode":""}] \ No newline at end of file