diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e964244 --- /dev/null +++ b/.gitignore @@ -0,0 +1,301 @@ +## Core latex/pdflatex auxiliary files: +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.fmt +*.fot +*.cb +*.cb2 +.*.lb + +## Intermediate documents: +*.dvi +*.xdv +*-converted-to.* +# these rules might exclude image files for figures etc. +# *.ps +# *.eps +# *.pdf + +## Generated if empty string is given at "Please type another file name for output:" +.pdf + +## Bibliography auxiliary files (bibtex/biblatex/biber): +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.run.xml + +## Build tool auxiliary files: +*.fdb_latexmk +*.synctex +*.synctex(busy) +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync + +## Build tool directories for auxiliary files +# latexrun +latex.out/ + +## Auxiliary and intermediate files from other packages: +# algorithms +*.alg +*.loa + +# achemso +acs-*.bib + +# amsthm +*.thm + +# beamer +*.nav +*.pre +*.snm +*.vrb + +# changes +*.soc + +# comment +*.cut + +# cprotect +*.cpt + +# elsarticle (documentclass of Elsevier journals) +*.spl + +# endnotes +*.ent + +# fixme +*.lox + +# feynmf/feynmp +*.mf +*.mp +*.t[1-9] +*.t[1-9][0-9] +*.tfm + +#(r)(e)ledmac/(r)(e)ledpar +*.end +*.?end +*.[1-9] +*.[1-9][0-9] +*.[1-9][0-9][0-9] +*.[1-9]R +*.[1-9][0-9]R +*.[1-9][0-9][0-9]R +*.eledsec[1-9] +*.eledsec[1-9]R +*.eledsec[1-9][0-9] +*.eledsec[1-9][0-9]R +*.eledsec[1-9][0-9][0-9] +*.eledsec[1-9][0-9][0-9]R + +# glossaries +*.acn +*.acr +*.glg +*.glo +*.gls +*.glsdefs +*.lzo +*.lzs +*.slg +*.slo +*.sls + +# uncomment this for glossaries-extra (will ignore makeindex's style files!) +# *.ist + +# gnuplot +*.gnuplot +*.table + +# gnuplottex +*-gnuplottex-* + +# gregoriotex +*.gaux +*.glog +*.gtex + +# htlatex +*.4ct +*.4tc +*.idv +*.lg +*.trc +*.xref + +# hyperref +*.brf + +# knitr +*-concordance.tex +# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files +# *.tikz +*-tikzDictionary + +# listings +*.lol + +# luatexja-ruby +*.ltjruby + +# makeidx +*.idx +*.ilg +*.ind + +# minitoc +*.maf +*.mlf +*.mlt +*.mtc[0-9]* +*.slf[0-9]* +*.slt[0-9]* +*.stc[0-9]* + +# minted +_minted* +*.pyg + +# morewrites +*.mw + +# newpax +*.newpax + +# nomencl +*.nlg +*.nlo +*.nls + +# pax +*.pax + +# pdfpcnotes +*.pdfpc + +# sagetex +*.sagetex.sage +*.sagetex.py +*.sagetex.scmd + +# scrwfile +*.wrt + +# svg +svg-inkscape/ + +# sympy +*.sout +*.sympy +sympy-plots-for-*.tex/ + +# pdfcomment +*.upa +*.upb + +# pythontex +*.pytxcode +pythontex-files-*/ + +# tcolorbox +*.listing + +# thmtools +*.loe + +# TikZ & PGF +*.dpth +*.md5 +*.auxlock + +# titletoc +*.ptc + +# todonotes +*.tdo + +# vhistory +*.hst +*.ver + +# easy-todo +*.lod + +# xcolor +*.xcp + +# xmpincl +*.xmpi + +# xindy +*.xdy + +# xypic precompiled matrices and outlines +*.xyc +*.xyd + +# endfloat +*.ttt +*.fff + +# Latexian +TSWLatexianTemp* + +## Editors: +# WinEdt +*.bak +*.sav + +# Texpad +.texpadtmp + +# LyX +*.lyx~ + +# Kile +*.backup + +# gummi +.*.swp + +# KBibTeX +*~[0-9]* + +# TeXnicCenter +*.tps + +# auto folder when using emacs and auctex +./auto/* +*.el + +# expex forward references with \gathertags +*-tags.tex + +# standalone packages +*.sta + +# Makeindex log files +*.lpz + +# xwatermark package +*.xwm + +# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib +# option is specified. Footnotes are the stored in a file with suffix Notes.bib. +# Uncomment the next line to have this generated file ignored. +#*Notes.bib diff --git a/after/.gitattributes b/after/.gitattributes new file mode 100644 index 0000000..a354649 --- /dev/null +++ b/after/.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/after/.github/dependabot.yml b/after/.github/dependabot.yml new file mode 100644 index 0000000..5b47509 --- /dev/null +++ b/after/.github/dependabot.yml @@ -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. + +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/after/.github/workflows/maven.yml b/after/.github/workflows/maven.yml new file mode 100644 index 0000000..9101359 --- /dev/null +++ b/after/.github/workflows/maven.yml @@ -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. + +name: Java CI + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + strategy: + matrix: + java: [ 8, 11, 15 ] + experimental: [false] + include: + - java: 16-ea + experimental: true + - java: 17-ea + experimental: true + + steps: + - uses: actions/checkout@v2.3.4 + - uses: actions/cache@v2.1.4 + 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@v1.4.3 + with: + java-version: ${{ matrix.java }} + - name: Build with Maven + run: mvn -V -Ddoclint=all --file pom.xml --no-transfer-progress diff --git a/after/.gitignore b/after/.gitignore new file mode 100644 index 0000000..c9b169e --- /dev/null +++ b/after/.gitignore @@ -0,0 +1,28 @@ +# 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 + +# Maven repository cache for docker container +.m2/ + +# infer tmp folder +infer-out/tmp/ +infer-out/bugs.txt +infer-out/logs diff --git a/after/.travis.yml b/after/.travis.yml new file mode 100644 index 0000000..5e905d6 --- /dev/null +++ b/after/.travis.yml @@ -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. + +language: java + +cache: + directories: + - $HOME/.m2 + +jdk: + - openjdk8 + - openjdk11 + - openjdk15 + - openjdk-ea + +matrix: + allow_failures: + - jdk: openjdk-ea + +script: + - mvn -V --no-transfer-progress + +after_success: + - mvn -V --no-transfer-progress clean test jacoco:report coveralls:report -Ptravis-jacoco javadoc:javadoc -Ddoclint=all diff --git a/after/CONTRIBUTING.md b/after/CONTRIBUTING.md new file mode 100644 index 0000000..18d9c28 --- /dev/null +++ b/after/CONTRIBUTING.md @@ -0,0 +1,115 @@ + + +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://help.github.com/articles/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` or `trunk` 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://help.github.com/) ++ [GitHub pull request documentation](https://help.github.com/articles/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/after/Jenkinsfile b/after/Jenkinsfile new file mode 100644 index 0000000..36799d0 --- /dev/null +++ b/after/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 ${env.BUILD_URL} 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 ${env.BUILD_URL} 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 ${env.BUILD_URL} to view the results. +""", + to: "notifications@commons.apache.org", + recipientProviders: [[$class: 'DevelopersRecipientProvider']] + ) + } + } + } + } +} diff --git a/after/LICENSE.txt b/after/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/after/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/after/NOTICE.txt b/after/NOTICE.txt new file mode 100644 index 0000000..3d4c690 --- /dev/null +++ b/after/NOTICE.txt @@ -0,0 +1,5 @@ +Apache Commons Lang +Copyright 2001-2021 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (https://www.apache.org/). diff --git a/after/README.md b/after/README.md new file mode 100644 index 0000000..5238ce5 --- /dev/null +++ b/after/README.md @@ -0,0 +1,108 @@ + + +Apache Commons Lang +=================== + +[![Travis-CI Status](https://travis-ci.org/apache/commons-lang.svg)](https://travis-ci.org/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://coveralls.io/repos/apache/commons-lang/badge.svg)](https://coveralls.io/r/apache/commons-lang) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.commons/commons-lang3/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.apache.commons/commons-lang3/) +[![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) + +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 + + org.apache.commons + commons-lang3 + 3.12.0 + +``` + +Contributing +------------ + +We accept Pull Requests via GitHub. The [developer mailing list][ml] 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 clean test```. + +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 Twitter Account](https://twitter.com/ApacheCommons) ++ `#apache-commons` IRC channel on `irc.freenode.org` + +[ml]:https://commons.apache.org/mail-lists.html diff --git a/after/RELEASE-NOTES.txt b/after/RELEASE-NOTES.txt new file mode 100644 index 0000000..7ffd650 --- /dev/null +++ b/after/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-SNAPSHOT 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 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). +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 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 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 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 Memomizer. 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 @ToStringEclude 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 (http://openjdk.java.net/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 http://openjdk.java.net/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 inifinite 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 does'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 formating 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 formating 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 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[] intput, 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 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 Entitys like Ö are not matched properly against its + ISO8859-1 representation +o LANG-659: EntityArrays typo: {"\u2122", "−"}, // 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("") +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/after/SECURITY.md b/after/SECURITY.md new file mode 100644 index 0000000..51943ba --- /dev/null +++ b/after/SECURITY.md @@ -0,0 +1,17 @@ + +The Apache Commons security page is [https://commons.apache.org/security.html](https://commons.apache.org/security.html). diff --git a/after/docker-infer.sh b/after/docker-infer.sh new file mode 100755 index 0000000..cf48148 --- /dev/null +++ b/after/docker-infer.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +mkdir -pv "${SCRIPT_DIR}/.m2" + +# Cache .m2 directory in local bind mount +# I am on an ICE right now. German Wi-Fi is the wurst +docker run --rm -it \ + -v "${SCRIPT_DIR}:/tools/home" \ + -v "${SCRIPT_DIR}/.m2:/root/.m2" \ + bugcounting/satools:y23 \ + /tools/home/entrypoint.sh + diff --git a/after/docker-start.sh b/after/docker-start.sh new file mode 100755 index 0000000..20d4721 --- /dev/null +++ b/after/docker-start.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +mkdir -pv "${SCRIPT_DIR}/.m2" + +# Cache .m2 directory in local bind mount +# I am on an ICE right now. German Wi-Fi is the wurst +docker run --rm -it \ + -v "${SCRIPT_DIR}:/tools/home" \ + -v "${SCRIPT_DIR}/.m2:/root/.m2" \ + bugcounting/satools:y23 + diff --git a/after/entrypoint.sh b/after/entrypoint.sh new file mode 100755 index 0000000..2d20dcb --- /dev/null +++ b/after/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +cd /tools/home +/tools/infer/bin/infer -- mvn compile -Drat.skip=true diff --git a/after/infer-out/.global.tenv b/after/infer-out/.global.tenv new file mode 100644 index 0000000..f2eb2de Binary files /dev/null and b/after/infer-out/.global.tenv differ diff --git a/after/infer-out/.infer_runstate.json b/after/infer-out/.infer_runstate.json new file mode 100644 index 0000000..5dc8b3a --- /dev/null +++ b/after/infer-out/.infer_runstate.json @@ -0,0 +1 @@ +{"run_sequence":[{"date":"2023-04-11 09:38:59.468025+02:00","command":"run","infer_version":{"major":1,"minor":1,"patch":0,"commit":"f9b6f2b"}}],"results_dir_format":"db_filename: infer-out/results.db\ndb_schema: \n CREATE TABLE IF NOT EXISTS procedures\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , attr_kind INTEGER NOT NULL\n , source_file TEXT NOT NULL\n , proc_attributes BLOB NOT NULL\n , cfg BLOB\n , callees BLOB NOT NULL\n )\n ;\n\n CREATE TABLE IF NOT EXISTS source_files\n ( source_file TEXT PRIMARY KEY\n , type_environment BLOB NOT NULL\n , integer_type_widths BLOB\n , procedure_names BLOB NOT NULL\n , freshly_captured INT NOT NULL )\n ;\n\n CREATE TABLE IF NOT EXISTS specs\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , analysis_summary BLOB NOT NULL\n , report_summary BLOB NOT NULL\n )\n ;\n\n CREATE TABLE IF NOT EXISTS model_specs\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , analysis_summary BLOB NOT NULL\n , report_summary BLOB NOT NULL\n )\n ","should_merge_capture":false} \ No newline at end of file diff --git a/after/infer-out/config-impact-report.json b/after/infer-out/config-impact-report.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/after/infer-out/config-impact-report.json @@ -0,0 +1 @@ +[] diff --git a/after/infer-out/costs-report.json b/after/infer-out/costs-report.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/after/infer-out/costs-report.json @@ -0,0 +1 @@ +[] diff --git a/after/infer-out/racerd/MultiBackgroundInitializer.java.3c5ea2a404a5c4f06e9ee03799aa696b.issue b/after/infer-out/racerd/MultiBackgroundInitializer.java.3c5ea2a404a5c4f06e9ee03799aa696b.issue new file mode 100644 index 0000000..1cc2800 Binary files /dev/null and b/after/infer-out/racerd/MultiBackgroundInitializer.java.3c5ea2a404a5c4f06e9ee03799aa696b.issue differ diff --git a/after/infer-out/report.json b/after/infer-out/report.json new file mode 100644 index 0000000..6dae3d0 --- /dev/null +++ b/after/infer-out/report.json @@ -0,0 +1 @@ +[{"bug_type":"NULL_DEREFERENCE","qualifier":"object returned by `getAllInterfaces(cls)` could be null and is dereferenced at line 72.","severity":"ERROR","line":72,"column":-1,"procedure":"org.apache.commons.lang3.AnnotationUtils$1.getShortClassName(java.lang.Class):java.lang.String","procedure_start_line":71,"file":"src/main/java/org/apache/commons/lang3/AnnotationUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/AnnotationUtils.java","line_number":71,"column_number":-1,"description":"start of procedure getShortClassName(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/AnnotationUtils.java","line_number":72,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":593,"column_number":-1,"description":"start of procedure getAllInterfaces(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":594,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":595,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":601,"column_number":-1,"description":"return from a call to List ClassUtils.getAllInterfaces(Class)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/AnnotationUtils.java","line_number":72,"column_number":-1,"description":""}],"key":"AnnotationUtils.java|getShortClassName|NULL_DEREFERENCE","node_key":"75a3105ef036e14d28c2a0df35d92164","hash":"a08533ab61d2fb9215626d7b14af646c","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object returned by `getAllInterfaces(cls)` could be null and is dereferenced at line 126.","severity":"ERROR","line":126,"column":-1,"procedure":"org.apache.commons.lang3.reflect.FieldUtils.getField(java.lang.Class,java.lang.CharSequence,boolean):java.lang.reflect.Field","procedure_start_line":87,"file":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":87,"column_number":-1,"description":"start of procedure getField(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":88,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":89,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3718,"column_number":-1,"description":"start of procedure isNotBlank(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"return from a call to boolean StringUtils.isNotBlank(CharSequence)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":89,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":156,"column_number":-1,"description":"start of procedure isTrue(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":157,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":160,"column_number":-1,"description":"return from a call to void Validate.isTrue(boolean,String,Object[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":105,"column_number":-1,"description":"Taking false branch"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":125,"column_number":-1,"description":""},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":126,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":593,"column_number":-1,"description":"start of procedure getAllInterfaces(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":594,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":595,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":601,"column_number":-1,"description":"return from a call to List ClassUtils.getAllInterfaces(Class)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":126,"column_number":-1,"description":""}],"key":"FieldUtils.java|getField|NULL_DEREFERENCE","node_key":"75a3105ef036e14d28c2a0df35d92164","hash":"26ade70057d66fd8a78cf3be90aa9efd","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object `null` is dereferenced by call to `toString(...)` at line 131.","severity":"ERROR","line":131,"column":-1,"procedure":"org.apache.commons.lang3.builder.ReflectionToStringBuilder.toString(java.lang.Object):java.lang.String","procedure_start_line":131,"file":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","line_number":131,"column_number":-1,"description":"start of procedure toString(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","line_number":309,"column_number":-1,"description":"start of procedure toString(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","line_number":312,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","line_number":554,"column_number":-1,"description":"start of procedure ReflectionToStringBuilder(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","line_number":437,"column_number":-1,"description":"start of procedure checkNotNull(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","line_number":438,"column_number":-1,"description":""},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","line_number":438,"column_number":-1,"description":"return from a call to Object ReflectionToStringBuilder.checkNotNull(Object)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java","line_number":554,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":249,"column_number":-1,"description":"start of procedure ToStringBuilder(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":250,"column_number":-1,"description":"Taking false branch"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":253,"column_number":-1,"description":"Taking true branch"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":254,"column_number":-1,"description":"Skipping StringBuffer(...): unknown method"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":256,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":257,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":258,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":260,"column_number":-1,"description":""},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":408,"column_number":-1,"description":"start of procedure appendStart(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":409,"column_number":-1,"description":"Taking true branch"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":410,"column_number":-1,"description":""},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1475,"column_number":-1,"description":"start of procedure appendClassName(...)"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1476,"column_number":-1,"description":"Taking true branch"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1476,"column_number":-1,"description":"Taking true branch"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1477,"column_number":-1,"description":""},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":219,"column_number":-1,"description":"start of procedure register(...)"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":220,"column_number":-1,"description":"Taking true branch"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":221,"column_number":-1,"description":""},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":190,"column_number":-1,"description":"start of procedure getRegistry()"},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":191,"column_number":-1,"description":"return from a call to Map ToStringStyle.getRegistry()"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":222,"column_number":-1,"description":"Taking false branch"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":225,"column_number":-1,"description":""},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":190,"column_number":-1,"description":"start of procedure getRegistry()"},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":191,"column_number":-1,"description":"return from a call to Map ToStringStyle.getRegistry()"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":225,"column_number":-1,"description":"Skipping put(...): unknown method"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":227,"column_number":-1,"description":"return from a call to void ToStringStyle.register(Object)"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1478,"column_number":-1,"description":"Taking true branch"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1479,"column_number":-1,"description":""},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1613,"column_number":-1,"description":"start of procedure getShortClassName(...)"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1614,"column_number":-1,"description":""},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":197,"column_number":-1,"description":"start of procedure getShortClassName(...)"},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":198,"column_number":-1,"description":"Taking false branch"},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":201,"column_number":-1,"description":"Skipping getShortClassName(...): empty list of specs"},{"level":8,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":229,"column_number":-1,"description":"Definition of getShortClassName(...)"},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":201,"column_number":-1,"description":"return from a call to String ClassUtils.getShortClassName(Class)"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1614,"column_number":-1,"description":"return from a call to String ToStringStyle.getShortClassName(Class)"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1479,"column_number":-1,"description":"Skipping append(...): unknown method"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1484,"column_number":-1,"description":"return from a call to void ToStringStyle.appendClassName(StringBuffer,Object)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":411,"column_number":-1,"description":""},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1492,"column_number":-1,"description":"start of procedure appendIdentityHashCode(...)"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1493,"column_number":-1,"description":""},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1669,"column_number":-1,"description":"start of procedure isUseIdentityHashCode()"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1670,"column_number":-1,"description":"return from a call to boolean ToStringStyle.isUseIdentityHashCode()"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1493,"column_number":-1,"description":"Taking true branch"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1493,"column_number":-1,"description":"Taking true branch"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1494,"column_number":-1,"description":""},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":219,"column_number":-1,"description":"start of procedure register(...)"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":220,"column_number":-1,"description":"Taking true branch"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":221,"column_number":-1,"description":""},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":190,"column_number":-1,"description":"start of procedure getRegistry()"},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":191,"column_number":-1,"description":"return from a call to Map ToStringStyle.getRegistry()"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":222,"column_number":-1,"description":"Taking false branch"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":225,"column_number":-1,"description":""},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":190,"column_number":-1,"description":"start of procedure getRegistry()"},{"level":7,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":191,"column_number":-1,"description":"return from a call to Map ToStringStyle.getRegistry()"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":225,"column_number":-1,"description":"Skipping put(...): unknown method"},{"level":6,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":227,"column_number":-1,"description":"return from a call to void ToStringStyle.register(Object)"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1495,"column_number":-1,"description":"Skipping append(...): unknown method"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1496,"column_number":-1,"description":"Skipping append(...): unknown method"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1498,"column_number":-1,"description":"return from a call to void ToStringStyle.appendIdentityHashCode(StringBuffer,Object)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":412,"column_number":-1,"description":""},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1505,"column_number":-1,"description":"start of procedure appendContentStart(...)"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1506,"column_number":-1,"description":"Skipping append(...): unknown method"}],"key":"ReflectionToStringBuilder.java|toString|NULL_DEREFERENCE","node_key":"b891320c3f80cf32f82e24e6e38f8d8b","hash":"68c5cd0bcb5dafd56fe43082e56d8c25","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object returned by `org.apache.commons.lang3.time.DurationUtils.LONG_TO_INT_RANGE.fit(valueOf(duration.toMillis()))` could be null and is dereferenced at line 142.","severity":"ERROR","line":142,"column":-1,"procedure":"org.apache.commons.lang3.time.DurationUtils.toMillisInt(java.time.Duration):int","procedure_start_line":139,"file":"src/main/java/org/apache/commons/lang3/time/DurationUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/time/DurationUtils.java","line_number":139,"column_number":-1,"description":"start of procedure toMillisInt(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/time/DurationUtils.java","line_number":140,"column_number":-1,"description":"Skipping requireNonNull(...): unknown method"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/time/DurationUtils.java","line_number":142,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":469,"column_number":-1,"description":"start of procedure fit(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":471,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":472,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":337,"column_number":-1,"description":"start of procedure isAfter(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":338,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":339,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":341,"column_number":-1,"description":"return from a call to boolean Range.isAfter(Object)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":472,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":474,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":366,"column_number":-1,"description":"start of procedure isBefore(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":367,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":368,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":370,"column_number":-1,"description":"return from a call to boolean Range.isBefore(Object)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":474,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Range.java","line_number":477,"column_number":-1,"description":"return from a call to Object Range.fit(Object)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/time/DurationUtils.java","line_number":142,"column_number":-1,"description":""}],"key":"DurationUtils.java|toMillisInt|NULL_DEREFERENCE","node_key":"bfbfa1a82447adb1fa12916066fec94d","hash":"f2ac400c043669366dd89608e233049a","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object `chars` last assigned on line 177 could be null and is dereferenced at line 181.","severity":"ERROR","line":181,"column":-1,"procedure":"org.apache.commons.lang3.CharSetUtils.modify(java.lang.String,java.lang.String[],boolean):java.lang.String","procedure_start_line":176,"file":"src/main/java/org/apache/commons/lang3/CharSetUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/CharSetUtils.java","line_number":176,"column_number":-1,"description":"start of procedure modify(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/CharSetUtils.java","line_number":177,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/CharSet.java","line_number":155,"column_number":-1,"description":"start of procedure getInstance(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/CharSet.java","line_number":156,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/CharSet.java","line_number":157,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/CharSet.java","line_number":165,"column_number":-1,"description":"return from a call to CharSet CharSet.getInstance(String[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/CharSetUtils.java","line_number":178,"column_number":-1,"description":"Skipping StringBuilder(...): unknown method"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/CharSetUtils.java","line_number":179,"column_number":-1,"description":"Skipping toCharArray(): unknown method"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/CharSetUtils.java","line_number":180,"column_number":-1,"description":"Taking true branch"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/CharSetUtils.java","line_number":181,"column_number":-1,"description":""}],"key":"CharSetUtils.java|modify|NULL_DEREFERENCE","node_key":"b4e36bdef71c9740469c08083ca60d1d","hash":"dfd85b815d94a1dcd43ebcf7e6796784","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object `null` is dereferenced by call to `ToStringBuilder(...)` at line 223.","severity":"ERROR","line":223,"column":-1,"procedure":"org.apache.commons.lang3.builder.ToStringBuilder.(java.lang.Object)","procedure_start_line":223,"file":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":223,"column_number":-1,"description":"start of procedure ToStringBuilder(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":249,"column_number":-1,"description":"start of procedure ToStringBuilder(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":250,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":253,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":256,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":257,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":258,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java","line_number":260,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":408,"column_number":-1,"description":"start of procedure appendStart(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":409,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":410,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1475,"column_number":-1,"description":"start of procedure appendClassName(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1476,"column_number":-1,"description":"Taking false branch"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1484,"column_number":-1,"description":"return from a call to void ToStringStyle.appendClassName(StringBuffer,Object)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":411,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1492,"column_number":-1,"description":"start of procedure appendIdentityHashCode(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1493,"column_number":-1,"description":""},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1669,"column_number":-1,"description":"start of procedure isUseIdentityHashCode()"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1670,"column_number":-1,"description":"return from a call to boolean ToStringStyle.isUseIdentityHashCode()"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1493,"column_number":-1,"description":"Taking true branch"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1493,"column_number":-1,"description":"Taking true branch"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1494,"column_number":-1,"description":""},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":219,"column_number":-1,"description":"start of procedure register(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":220,"column_number":-1,"description":"Taking true branch"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":221,"column_number":-1,"description":""},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":190,"column_number":-1,"description":"start of procedure getRegistry()"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":191,"column_number":-1,"description":"return from a call to Map ToStringStyle.getRegistry()"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":222,"column_number":-1,"description":"Taking false branch"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":225,"column_number":-1,"description":""},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":190,"column_number":-1,"description":"start of procedure getRegistry()"},{"level":5,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":191,"column_number":-1,"description":"return from a call to Map ToStringStyle.getRegistry()"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":225,"column_number":-1,"description":"Skipping put(...): unknown method"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":227,"column_number":-1,"description":"return from a call to void ToStringStyle.register(Object)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java","line_number":1495,"column_number":-1,"description":"Skipping append(...): unknown method"}],"key":"ToStringBuilder.java||NULL_DEREFERENCE","node_key":"873ce38868811a59e7b77550b94ee768","hash":"c33b1be7a981b70a3dd03e70f87a0d64","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object `field` last assigned on line 338 could be null and is dereferenced by call to `readStaticField(...)` at line 341.","severity":"ERROR","line":341,"column":-1,"procedure":"org.apache.commons.lang3.reflect.FieldUtils.readStaticField(java.lang.Class,java.lang.CharSequence,boolean):java.lang.Object","procedure_start_line":337,"file":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":337,"column_number":-1,"description":"start of procedure readStaticField(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":338,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":87,"column_number":-1,"description":"start of procedure getField(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":88,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Class,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":89,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3718,"column_number":-1,"description":"start of procedure isNotBlank(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"return from a call to boolean StringUtils.isNotBlank(CharSequence)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":89,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":156,"column_number":-1,"description":"start of procedure isTrue(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":157,"column_number":-1,"description":"Taking false branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":160,"column_number":-1,"description":"return from a call to void Validate.isTrue(boolean,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":105,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":107,"column_number":-1,"description":"Skipping getDeclaredField(...): unknown method"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":110,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":111,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":105,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":125,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":126,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":593,"column_number":-1,"description":"start of procedure getAllInterfaces(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":594,"column_number":-1,"description":"Taking false branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":598,"column_number":-1,"description":"Skipping LinkedHashSet(): unknown method"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":599,"column_number":-1,"description":"Skipping getAllInterfaces(...): empty list of specs"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":610,"column_number":-1,"description":"Definition of getAllInterfaces(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":601,"column_number":-1,"description":"return from a call to List ClassUtils.getAllInterfaces(Class)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":126,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":136,"column_number":-1,"description":"return from a call to Field FieldUtils.getField(Class,String,boolean)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":339,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":341,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":295,"column_number":-1,"description":"start of procedure readStaticField(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":296,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Field,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":297,"column_number":-1,"description":"Skipping getModifiers(): unknown method"}],"key":"FieldUtils.java|readStaticField|NULL_DEREFERENCE","node_key":"265ba86214678cae77b0c9ab2eedcb6d","hash":"4a6851ca490c7078176fc1511ed64532","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object `field` last assigned on line 382 could be null and is dereferenced by call to `readStaticField(...)` at line 385.","severity":"ERROR","line":385,"column":-1,"procedure":"org.apache.commons.lang3.reflect.FieldUtils.readDeclaredStaticField(java.lang.Class,java.lang.String,boolean):java.lang.Object","procedure_start_line":381,"file":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":381,"column_number":-1,"description":"start of procedure readDeclaredStaticField(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":382,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":170,"column_number":-1,"description":"start of procedure getDeclaredField(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":171,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":172,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3718,"column_number":-1,"description":"start of procedure isNotBlank(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"return from a call to boolean StringUtils.isNotBlank(CharSequence)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":172,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":156,"column_number":-1,"description":"start of procedure isTrue(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":157,"column_number":-1,"description":"Taking false branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":160,"column_number":-1,"description":"return from a call to void Validate.isTrue(boolean,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":175,"column_number":-1,"description":"Skipping getDeclaredField(...): unknown method"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":176,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":85,"column_number":-1,"description":"start of procedure isAccessible(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":86,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":86,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":86,"column_number":-1,"description":"Taking false branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":86,"column_number":-1,"description":"return from a call to boolean MemberUtils.isAccessible(Member)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":176,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":177,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":180,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":187,"column_number":-1,"description":"return from a call to Field FieldUtils.getDeclaredField(Class,String,boolean)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":383,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":385,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":295,"column_number":-1,"description":"start of procedure readStaticField(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":296,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Field,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":297,"column_number":-1,"description":"Skipping getModifiers(): unknown method"}],"key":"FieldUtils.java|readDeclaredStaticField|NULL_DEREFERENCE","node_key":"265ba86214678cae77b0c9ab2eedcb6d","hash":"01c619e497c6ce88168fb559f30d2359","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object returned by `primitiveToWrapper(varArgComponentType)` could be null and is dereferenced by call to `newInstance(...)` at line 486.","severity":"ERROR","line":486,"column":-1,"procedure":"org.apache.commons.lang3.reflect.MethodUtils.getVarArgs(java.lang.Object[],java.lang.Class[]):java.lang.Object[]","procedure_start_line":469,"file":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":469,"column_number":-1,"description":"start of procedure getVarArgs(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":470,"column_number":-1,"description":"Taking false branch"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":477,"column_number":-1,"description":""},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":480,"column_number":-1,"description":"Skipping arraycopy(...): unknown method"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":483,"column_number":-1,"description":"Skipping getComponentType(): unknown method"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":484,"column_number":-1,"description":""},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":486,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":950,"column_number":-1,"description":"start of procedure primitiveToWrapper(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":951,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":952,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":955,"column_number":-1,"description":"return from a call to Class ClassUtils.primitiveToWrapper(Class)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":486,"column_number":-1,"description":""}],"key":"MethodUtils.java|getVarArgs|NULL_DEREFERENCE","node_key":"22104a214f27e4c1b8cb15bc1875b6bf","hash":"422a75b0b6d9ace02897252f7005da12","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object `field` last assigned on line 596 could be null and is dereferenced by call to `writeStaticField(...)` at line 599.","severity":"ERROR","line":599,"column":-1,"procedure":"org.apache.commons.lang3.reflect.FieldUtils.writeStaticField(java.lang.Class,java.lang.CharSequence,java.lang.Object,boolean):void","procedure_start_line":594,"file":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":594,"column_number":-1,"description":"start of procedure writeStaticField(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":596,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":87,"column_number":-1,"description":"start of procedure getField(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":88,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Class,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":89,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3718,"column_number":-1,"description":"start of procedure isNotBlank(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"return from a call to boolean StringUtils.isNotBlank(CharSequence)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":89,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":156,"column_number":-1,"description":"start of procedure isTrue(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":157,"column_number":-1,"description":"Taking false branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":160,"column_number":-1,"description":"return from a call to void Validate.isTrue(boolean,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":105,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":107,"column_number":-1,"description":"Skipping getDeclaredField(...): unknown method"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":110,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":111,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":105,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":125,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":126,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":593,"column_number":-1,"description":"start of procedure getAllInterfaces(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":594,"column_number":-1,"description":"Taking false branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":598,"column_number":-1,"description":"Skipping LinkedHashSet(): unknown method"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":599,"column_number":-1,"description":"Skipping getAllInterfaces(...): empty list of specs"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":610,"column_number":-1,"description":"Definition of getAllInterfaces(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/ClassUtils.java","line_number":601,"column_number":-1,"description":"return from a call to List ClassUtils.getAllInterfaces(Class)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":126,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":136,"column_number":-1,"description":"return from a call to Field FieldUtils.getField(Class,String,boolean)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":597,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":599,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":549,"column_number":-1,"description":"start of procedure writeStaticField(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":550,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Field,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":551,"column_number":-1,"description":"Skipping getModifiers(): unknown method"}],"key":"FieldUtils.java|writeStaticField|NULL_DEREFERENCE","node_key":"de2d9a76245ad9462bfe064598214247","hash":"3a0df1472de26afb639a96cc7a964a56","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object `field` last assigned on line 641 could be null and is dereferenced by call to `writeField(...)` at line 644.","severity":"ERROR","line":644,"column":-1,"procedure":"org.apache.commons.lang3.reflect.FieldUtils.writeDeclaredStaticField(java.lang.Class,java.lang.String,java.lang.Object,boolean):void","procedure_start_line":639,"file":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":639,"column_number":-1,"description":"start of procedure writeDeclaredStaticField(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":641,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":170,"column_number":-1,"description":"start of procedure getDeclaredField(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":171,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":172,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3718,"column_number":-1,"description":"start of procedure isNotBlank(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/StringUtils.java","line_number":3719,"column_number":-1,"description":"return from a call to boolean StringUtils.isNotBlank(CharSequence)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":172,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":156,"column_number":-1,"description":"start of procedure isTrue(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":157,"column_number":-1,"description":"Taking false branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":160,"column_number":-1,"description":"return from a call to void Validate.isTrue(boolean,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":175,"column_number":-1,"description":"Skipping getDeclaredField(...): unknown method"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":176,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":85,"column_number":-1,"description":"start of procedure isAccessible(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":86,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":86,"column_number":-1,"description":"Taking true branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":86,"column_number":-1,"description":"Taking false branch"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java","line_number":86,"column_number":-1,"description":"return from a call to boolean MemberUtils.isAccessible(Member)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":176,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":177,"column_number":-1,"description":"Taking false branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":180,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":187,"column_number":-1,"description":"return from a call to Field FieldUtils.getDeclaredField(Class,String,boolean)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":642,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":644,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":682,"column_number":-1,"description":"start of procedure writeField(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":684,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":4,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Member,String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java","line_number":685,"column_number":-1,"description":"Taking true branch"}],"key":"FieldUtils.java|writeDeclaredStaticField|NULL_DEREFERENCE","node_key":"de2d9a76245ad9462bfe064598214247","hash":"0758984e6ec9244726192dfb6c39e168","bug_type_hum":"Null Dereference"},{"bug_type":"NULL_DEREFERENCE","qualifier":"object `classes` last assigned on line 986 could be null and is dereferenced at line 987.","severity":"ERROR","line":987,"column":-1,"procedure":"org.apache.commons.lang3.reflect.MethodUtils.getAnnotation(java.lang.reflect.Method,java.lang.Class,boolean,boolean):java.lang.annotation.Annotation","procedure_start_line":973,"file":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":973,"column_number":-1,"description":"start of procedure getAnnotation(...)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":976,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":977,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":223,"column_number":-1,"description":"start of procedure notNull(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":""},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"start of procedure Validate$Lambda$_49_3(...)"},{"level":3,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Validate$Lambda$_49_3.(String,Object[])"},{"level":2,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Supplier Validate.callsite_org.apache.commons.lang3.Validate$Lambda$_49_3(String,Object[])"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/Validate.java","line_number":224,"column_number":-1,"description":"return from a call to Object Validate.notNull(Object,String,Object[])"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":978,"column_number":-1,"description":"Taking false branch"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":982,"column_number":-1,"description":"Skipping getAnnotation(...): unknown method"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":984,"column_number":-1,"description":"Taking true branch"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":984,"column_number":-1,"description":"Taking true branch"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":985,"column_number":-1,"description":"Skipping getDeclaringClass(): unknown method"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":986,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":1012,"column_number":-1,"description":"start of procedure getAllSuperclassesAndInterfaces(...)"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":1013,"column_number":-1,"description":"Taking true branch"},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":1014,"column_number":-1,"description":""},{"level":1,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":1034,"column_number":-1,"description":"return from a call to List MethodUtils.getAllSuperclassesAndInterfaces(Class)"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java","line_number":987,"column_number":-1,"description":""}],"key":"MethodUtils.java|getAnnotation|NULL_DEREFERENCE","node_key":"75a3105ef036e14d28c2a0df35d92164","hash":"86b319231c71a4e6dd8d4304c3a4141d","bug_type_hum":"Null Dereference"},{"bug_type":"THREAD_SAFETY_VIOLATION","qualifier":"Read/Write race. Non-private method `MultiBackgroundInitializer.getTaskCount()` reads without synchronization from container `this.childInitializers` via call to `Map.values()`. Potentially races with write in method `MultiBackgroundInitializer.addInitializer(...)`.\n Reporting because another access to the same memory occurs on a background thread, although this access may not.","severity":"WARNING","line":160,"column":-1,"procedure":"org.apache.commons.lang3.concurrent.MultiBackgroundInitializer.getTaskCount():int","procedure_start_line":0,"file":"src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java","bug_trace":[{"level":0,"filename":"src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java","line_number":160,"column_number":-1,"description":""},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java","line_number":160,"column_number":-1,"description":"Read of container `this.childInitializers` via call to `values`"},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java","line_number":142,"column_number":-1,"description":""},{"level":0,"filename":"src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java","line_number":142,"column_number":-1,"description":"Write to container `this.childInitializers` via call to `put`"}],"key":"MultiBackgroundInitializer.java|getTaskCount|THREAD_SAFETY_VIOLATION","hash":"851f0ff7931bb774dadc2c77cb1fe087","bug_type_hum":"Thread Safety Violation","access":"hJWmvgAAALcAAAAIAAAAPgAAACqgsAEAoAD/kglRc3JjL21haW4vamF2YS9vcmcvYXBhY2hlL2NvbW1vbnMvbGFuZzMvY29uY3VycmVudC9NdWx0aUJhY2tncm91bmRJbml0aWFsaXplci5qYXZhoLABAI4A/5IJUXNyYy9tYWluL2phdmEvb3JnL2FwYWNoZS9jb21tb25zL2xhbmczL2NvbmN1cnJlbnQvTXVsdGlCYWNrZ3JvdW5kSW5pdGlhbGl6ZXIuamF2YUA="}] diff --git a/after/infer-out/report.txt b/after/infer-out/report.txt new file mode 100644 index 0000000..c94c443 --- /dev/null +++ b/after/infer-out/report.txt @@ -0,0 +1,122 @@ +#0 DONE +src/main/java/org/apache/commons/lang3/AnnotationUtils.java:72: error: Null Dereference + object returned by `getAllInterfaces(cls)` could be null and is dereferenced at line 72. + 70. @Override + 71. protected String getShortClassName(final Class cls) { + 72. > for (final Class iface : ClassUtils.getAllInterfaces(cls)) { + 73. if (Annotation.class.isAssignableFrom(iface)) { + 74. return "@" + iface.getName(); + +#1 DONE +src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:126: error: Null Dereference + object returned by `getAllInterfaces(cls)` could be null and is dereferenced at line 126. + 124. // superclass field. + 125. Field match = null; + 126. > for (final Class class1 : ClassUtils.getAllInterfaces(cls)) { + 127. try { + 128. final Field test = class1.getField(fieldName); + +#2 +src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java:131: error: Null Dereference + object `null` is dereferenced by call to `toString(...)` at line 131. + 129. */ + 130. public static String toString(final Object object) { + 131. > return toString(object, null, false, false, null); + 132. } + 133. + +#3 false positive, LONG_TO_INT_RANGE.fit never returns null (may throw NPE when param is null, never the case though as actual param is boxed primitive long) +src/main/java/org/apache/commons/lang3/time/DurationUtils.java:142: error: Null Dereference + object returned by `org.apache.commons.lang3.time.DurationUtils.LONG_TO_INT_RANGE.fit(valueOf(duration.toMillis()))` could be null and is dereferenced at line 142. + 140. Objects.requireNonNull(duration, "duration"); + 141. // intValue() does not do a narrowing conversion here + 142. > return LONG_TO_INT_RANGE.fit(Long.valueOf(duration.toMillis())).intValue(); + 143. } + 144. + +#4 false positive, java.lang.String.toCharArray() always returns a non-null char[] object +src/main/java/org/apache/commons/lang3/CharSetUtils.java:181: error: Null Dereference + object `chars` last assigned on line 177 could be null and is dereferenced at line 181. + 179. final char[] chrs = str.toCharArray(); + 180. for (final char chr : chrs) { + 181. > if (chars.contains(chr) == expect) { + 182. buffer.append(chr); + 183. } + +#5 +src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java:223: error: Null Dereference + object `null` is dereferenced by call to `ToStringBuilder(...)` at line 223. + 221. */ + 222. public ToStringBuilder(final Object object) { + 223. > this(object, null, null); + 224. } + 225. + +#6 false positive, Validate.notNull(field, ...) throws NPE if field is null thus if the method is call field is non-null +src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:341: error: Null Dereference + object `field` last assigned on line 338 could be null and is dereferenced by call to `readStaticField(...)` at line 341. + 339. Validate.notNull(field, "Cannot locate field '%s' on %s", fieldName, cls); + 340. // already forced access above, don't repeat it here: + 341. > return readStaticField(field, false); + 342. } + 343. + +#7 false positive, Validate.notNull(field, ...) throws NPE if field is null thus if the method is call field is non-null +src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:385: error: Null Dereference + object `field` last assigned on line 382 could be null and is dereferenced by call to `readStaticField(...)` at line 385. + 383. Validate.notNull(field, "Cannot locate declared field %s.%s", cls.getName(), fieldName); + 384. // already forced access above, don't repeat it here: + 385. > return readStaticField(field, false); + 386. } + 387. + +#8 DONE +src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java:486: error: Null Dereference + object returned by `primitiveToWrapper(varArgComponentType)` could be null and is dereferenced by call to `newInstance(...)` at line 486. + 484. final int varArgLength = args.length - methodParameterTypes.length + 1; + 485. + 486. > Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength); + 487. // Copy the variadic arguments into the varargs array. + 488. System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength); + +#9 false positive, Validate.notNull(field, ...) throws NPE if field is null thus if the method is call field is non-null +src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:599: error: Null Dereference + object `field` last assigned on line 596 could be null and is dereferenced by call to `writeStaticField(...)` at line 599. + 597. Validate.notNull(field, "Cannot locate field %s on %s", fieldName, cls); + 598. // already forced access above, don't repeat it here: + 599. > writeStaticField(field, value, false); + 600. } + 601. + +#10 false positive, Validate.notNull(field, ...) throws NPE if field is null thus if the method is call field is non-null +src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:644: error: Null Dereference + object `field` last assigned on line 641 could be null and is dereferenced by call to `writeField(...)` at line 644. + 642. Validate.notNull(field, "Cannot locate declared field %s.%s", cls.getName(), fieldName); + 643. // already forced access above, don't repeat it here: + 644. > writeField(field, (Object) null, value, false); + 645. } + 646. + +#11 false positive, getAllSuperclassesAndInterfaces(mcls) is non-null if mcls is non-null, mcls is non-null as java.lang.reflect.Method.getDeclaringClass() is always non-null +src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java:987: error: Null Dereference + object `classes` last assigned on line 986 could be null and is dereferenced at line 987. + 985. final Class mcls = method.getDeclaringClass(); + 986. final List> classes = getAllSuperclassesAndInterfaces(mcls); + 987. > for (final Class acls : classes) { + 988. final Method equivalentMethod = (ignoreAccess ? MethodUtils.getMatchingMethod(acls, method.getName(), method.getParameterTypes()) + 989. : MethodUtils.getMatchingAccessibleMethod(acls, method.getName(), method.getParameterTypes())); + +#12 +src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java:160: warning: Thread Safety Violation + Read/Write race. Non-private method `MultiBackgroundInitializer.getTaskCount()` reads without synchronization from container `this.childInitializers` via call to `Map.values()`. Potentially races with write in method `MultiBackgroundInitializer.addInitializer(...)`. + Reporting because another access to the same memory occurs on a background thread, although this access may not. + 158. int result = 1; + 159. + 160. > for (final BackgroundInitializer bi : childInitializers.values()) { + 161. result += bi.getTaskCount(); + 162. } + +Found 13 issues + Issue Type(ISSUED_TYPE_ID): # + Null Dereference(NULL_DEREFERENCE): 12 + Thread Safety Violation(THREAD_SAFETY_VIOLATION): 1 diff --git a/after/infer-out/results.db b/after/infer-out/results.db new file mode 100644 index 0000000..5008c55 Binary files /dev/null and b/after/infer-out/results.db differ diff --git a/after/infer-out/results.db-shm b/after/infer-out/results.db-shm new file mode 100644 index 0000000..7f7726d Binary files /dev/null and b/after/infer-out/results.db-shm differ diff --git a/after/infer-out/results.db-wal b/after/infer-out/results.db-wal new file mode 100644 index 0000000..6105161 Binary files /dev/null and b/after/infer-out/results.db-wal differ diff --git a/after/pom.xml b/after/pom.xml new file mode 100644 index 0000000..167a85a --- /dev/null +++ b/after/pom.xml @@ -0,0 +1,1008 @@ + + + + + org.apache.commons + commons-parent + 52 + + 4.0.0 + commons-lang3 + 3.12.0 + Apache Commons Lang + + 2001 + + 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. + + + https://commons.apache.org/proper/commons-lang/ + + + jira + https://issues.apache.org/jira/browse/LANG + + + + scm:git:http://gitbox.apache.org/repos/asf/commons-lang.git + scm:git:https://gitbox.apache.org/repos/asf/commons-lang.git + https://gitbox.apache.org/repos/asf?p=commons-lang.git + commons-lang-3.12.0 + + + + + Daniel Rall + dlr + dlr@finemaltcoding.com + CollabNet, Inc. + + Java Developer + + + + Stephen Colebourne + scolebourne + scolebourne@joda.org + SITA ATS Ltd + 0 + + Java Developer + + + + Henri Yandell + bayard + bayard@apache.org + + + Java Developer + + + + Steven Caswell + scaswell + stevencaswell@apache.org + + + Java Developer + + -5 + + + Robert Burrell Donkin + rdonkin + rdonkin@apache.org + + + Java Developer + + + + Gary D. Gregory + ggregory + ggregory@apache.org + -5 + + Java Developer + + + + Fredrik Westermarck + fredrik + + + + Java Developer + + + + James Carman + jcarman + jcarman@apache.org + Carman Consulting, Inc. + + Java Developer + + + + Niall Pemberton + niallp + + Java Developer + + + + Matt Benson + mbenson + + Java Developer + + + + Joerg Schaible + joehni + joerg.schaible@gmx.de + + Java Developer + + +1 + + + Oliver Heger + oheger + oheger@apache.org + +1 + + Java Developer + + + + Paul Benedict + pbenedict + pbenedict@apache.org + + Java Developer + + + + Benedikt Ritter + britter + britter@apache.org + + Java Developer + + + + Duncan Jones + djones + djones@apache.org + 0 + + Java Developer + + + + Loic Guibert + lguibert + lguibert@apache.org + +4 + + Java Developer + + + + Rob Tompkins + chtompki + chtompki@apache.org + -5 + + Java Developer + + + + + + C. Scott Ananian + + + Chris Audley + + + Stephane Bailliez + + + Michael Becke + + + Benjamin Bentmann + + + Ola Berg + + + Nathan Beyer + + + Stefan Bodewig + + + Janek Bogucki + + + Mike Bowler + + + Sean Brown + + + Alexander Day Chaffee + + + Al Chou + + + Greg Coladonato + + + Maarten Coene + + + Justin Couch + + + Michael Davey + + + Norm Deane + + + Morgan Delagrange + + + Ringo De Smet + + + Russel Dittmar + + + Steve Downey + + + Matthias Eichel + + + Christopher Elkins + + + Chris Feldhacker + + + Roland Foerther + + + Pete Gieser + + + Jason Gritman + + + Matthew Hawthorne + + + Michael Heuer + + + Chas Honton + + + Chris Hyzer + + + Paul Jack + + + Marc Johnson + + + Shaun Kalley + + + Tetsuya Kaneuchi + + + Nissim Karpenstein + + + Ed Korthof + + + Holger Krauth + + + Rafal Krupinski + + + Rafal Krzewski + + + David Leppik + + + Eli Lindsey + + + Sven Ludwig + + + Craig R. McClanahan + + + Rand McNeely + + + Hendrik Maryns + + + Dave Meikle + + + Nikolay Metchev + + + Kasper Nielsen + + + Tim O'Brien + + + Brian S O'Neill + + + Andrew C. Oliver + + + Alban Peignier + + + Moritz Petersen + + + Dmitri Plotnikov + + + Neeme Praks + + + Eric Pugh + + + Stephen Putman + + + Travis Reeder + + + Antony Riley + + + Valentin Rocher + + + Scott Sanders + + + James Sawle + + + Ralph Schaer + + + Henning P. Schmiedehausen + + + Sean Schofield + + + Robert Scholte + + + Reuben Sivan + + + Ville Skytta + + + David M. Sledge + + + Michael A. Smith + + + Jan Sorensen + + + Glen Stampoultzis + + + Scott Stanchfield + + + Jon S. Stevens + + + Sean C. Sullivan + + + Ashwin Suresh + + + Helge Tesgaard + + + Arun Mammen Thomas + + + Masato Tezuka + + + Daniel Trebbien + + + Jeff Varszegi + + + Chris Webb + + + Mario Winterer + + + Stepan Koltsov + + + Holger Hoffstatte + + + Derek C. Ashmore + + + Sebastien Riou + + + Allon Mureinik + + + Adam Hooper + + + Chris Karcher + + + Michael Osipov + + + Thiago Andrade + + + Jonathan Baker + + + Mikhail Mazursky + + + Fabian Lange + + + Michał Kordas + + + Felipe Adorno + + + Adrian Ber + + + Mark Dacek + + + Peter Verhas + + + Jin Xu + + + + + + + org.junit + junit-bom + 5.7.1 + pom + import + + + + + + + + + org.junit.jupiter + junit-jupiter + test + + + org.junit-pioneer + junit-pioneer + 1.3.0 + test + + + org.hamcrest + hamcrest + 2.2 + test + + + + org.easymock + easymock + 4.2 + test + + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + com.google.code.findbugs + jsr305 + 3.0.2 + test + + + + + + apache.website + Apache Commons Site + scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-lang/ + + + + + -Xmx512m + ISO-8859-1 + UTF-8 + 1.8 + 1.8 + + lang + lang3 + org.apache.commons.lang3 + + 3.12.0 + (Java 8+) + + 2.6 + (Requires Java 1.2 or later) + + commons-lang-${commons.release.2.version} + LANG + 12310481 + + lang + https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-lang + site-content + utf-8 + + 3.1.2 + 8.40 + src/site/resources/checkstyle + + 4.2.0 + 4.2.1 + false + true + + + 1.27 + benchmarks + + 0.8.6 + 3.0.0-M5 + 3.2.0 + 0.15.2 + + + 3.11 + RC1 + true + scm:svn:https://dist.apache.org/repos/dist/dev/commons/lang + Gary Gregory + 86fdc7e2a11262cb + + + + + clean package apache-rat:check checkstyle:check japicmp:cmp spotbugs:check javadoc:javadoc + + + + org.apache.rat + apache-rat-plugin + + + site-content/** + src/site/resources/.htaccess + src/site/resources/download_lang.cgi + src/site/resources/release-notes/RELEASE-NOTES-*.txt + src/test/resources/lang-708-input.txt + + + + + + + + maven-javadoc-plugin + + ${maven.compiler.source} + true + true + + https://docs.oracle.com/javase/8/docs/api/ + http://docs.oracle.com/javaee/6/api/ + + + + true + true + + + + + + create-javadoc-jar + + javadoc + jar + + package + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${commons.surefire.version} + + + plain + + + **/*Test.java + + random + + + + + + + maven-assembly-plugin + + + src/assembly/bin.xml + src/assembly/src.xml + + gnu + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + + ${commons.module.name} + + + + + + org.apache.maven.plugins + maven-scm-publish-plugin + + + javadocs + + + + + maven-checkstyle-plugin + ${checkstyle.plugin.version} + + ${checkstyle.configdir}/checkstyle.xml + true + false + + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.plugin.version} + + + com.github.spotbugs + spotbugs + ${spotbugs.impl.version} + + + + ${basedir}/spotbugs-exclude-filter.xml + + + + org.apache.felix + maven-bundle-plugin + + + biz.aQute.bnd + biz.aQute.bndlib + 5.3.0 + + + + + + + + + + maven-checkstyle-plugin + ${checkstyle.plugin.version} + + ${checkstyle.configdir}/checkstyle.xml + true + false + + + + + checkstyle + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.plugin.version} + + ${basedir}/spotbugs-exclude-filter.xml + + + + maven-pmd-plugin + 3.14.0 + + ${maven.compiler.target} + + + + org.codehaus.mojo + taglist-maven-plugin + 2.4 + + + + + Needs Work + + + TODO + exact + + + FIXME + exact + + + XXX + exact + + + + + Noteable Markers + + + NOTE + exact + + + NOPMD + exact + + + NOSONAR + exact + + + + + + + + + + + + + setup-checkout + + + site-content + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + prepare-checkout + pre-site + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + java9+ + + [9,) + + + + -Xmx512m --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED + + true + + + + java13+ + + [13,) + + + + true + + + + java15 + + + 15 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + org/apache/commons/lang3/time/Java15BugFastDateParserTest.java + + + + + + + + + benchmark + + true + org.apache + + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + benchmark + test + + exec + + + test + java + + -classpath + + org.openjdk.jmh.Main + -rf + json + -rff + target/jmh-result.${benchmark}.json + ${benchmark} + + + + + + + + + + diff --git a/after/spotbugs-exclude-filter.xml b/after/spotbugs-exclude-filter.xml new file mode 100644 index 0000000..be00732 --- /dev/null +++ b/after/spotbugs-exclude-filter.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/after/src/assembly/bin.xml b/after/src/assembly/bin.xml new file mode 100644 index 0000000..fb1e07c --- /dev/null +++ b/after/src/assembly/bin.xml @@ -0,0 +1,46 @@ + + + bin + + tar.gz + zip + + false + + + + LICENSE.txt + NOTICE.txt + RELEASE-NOTES.txt + README.md + CONTRIBUTING.md + + + + target + + + *.jar + + + + target/site/apidocs + apidocs + + + diff --git a/after/src/assembly/src.xml b/after/src/assembly/src.xml new file mode 100644 index 0000000..1a9ad19 --- /dev/null +++ b/after/src/assembly/src.xml @@ -0,0 +1,44 @@ + + + src + + tar.gz + zip + + ${project.artifactId}-${commons.release.version}-src + + + + .travis.yml + checkstyle.xml + checkstyle-suppressions.xml + spotbugs-exclude-filter.xml + LICENSE.txt + NOTICE.txt + pom.xml + PROPOSAL.html + RELEASE-NOTES.txt + README.md + CONTRIBUTING.md + + + + src + + + diff --git a/after/src/changes/changes.xml b/after/src/changes/changes.xml new file mode 100644 index 0000000..9d5e846 --- /dev/null +++ b/after/src/changes/changes.xml @@ -0,0 +1,1223 @@ + + + + + + + + + Apache Commons Lang Release Notes + + + + + + Correct implementation of RandomUtils.nextLong(long, long) + Restore handling of collections for non-JSON ToStringStyle #610. + ContextedException Javadoc add missing semicolon #581. + Resolve JUnit pioneer transitive dependencies using JUnit BOM. + NumberUtilsTest - incorrect types in min/max tests #634. + Improve StringUtils.stripAccents conversion of remaining accents. + StringUtils.countMatches - clarify Javadoc. + Remove redundant argument from substring call. + BigDecimal is created when you pass it the min and max values, #642. + ArrayUtils.contains() and indexOf() fail to handle Double.NaN #647. + ArrayUtils contains() and indexOf() fail to handle Float.NaN # #561. + Fix potential NPE in TypeUtils.isAssignable(Type, ParameterizedType, Map, Type>). + TypeUtils.isAssignable returns wrong result for GenericArrayType and ParameterizedType, #643. + testGetAllFields and testGetFieldsWithAnnotation sometimes fail. + Fix Javadoc for SystemUtils.isJavaVersionAtMost() #638. + Fix StringUtils.unwrap throws StringIndexOutOfBoundsException #636. + Fix formatting of isAnyBlank() and isAnyEmpty(). #513. + TypeUtils. containsTypeVariables does not support GenericArrayType #661. + Javadoc of some methods incorrectly refers to another method, #667, #668. #670. + Refine StringUtils.lastIndexOfIgnoreCase #664. + Refine StringUtils.abbreviate #663. + Refine StringUtils.isNumericSpace #573. + Refine StringUtils.deleteWhitespace #569. + Correction in Javadoc of some methods. #673 + Javadoc for RandomStringUtils.random() letters, numbers parameters is wrong. + Correct markup in Javadoc for unbalanced braces #679. + MethodUtils.invokeMethod NullPointerException in case of null in args list #680. + Fix 2 digit week year formatting #688. + Fix broken Javadoc links to commons-text #712. + Add and use ThreadUtils.sleep(Duration). + Add and use ThreadUtils.join(Thread, Duration). + Add ObjectUtils.wait(Duration). + + Add BooleanUtils.booleanValues(). + Add BooleanUtils.primitiveValues(). + Add StringUtils.containsAnyIgnoreCase(CharSequence, CharSequence...). + Add StopWatch.getStopTime(). + More test coverage for CharSequenceUtils. #631. + ArrayUtils.toPrimitive(Object) does not support boolean and other types #607. + Add fluent-style ArraySorter. + Add and use LocaleUtils.toLocale(Locale) to avoid NPEs. + Add FailableShortSupplier, handy for JDBC APIs. + Add JavaVersion.JAVA_17. + Add missing boolean[] join method #686. + Add StringUtils.substringBefore(String, int). + Add Range.INTEGER. + Add DurationUtils. + Introduce the use of @Nonnull, and @Nullable, and the Objects class as a helper tool. + Add and use true and false String constants #714. + Add and use ObjectUtils.requireNonEmpty() #716. + + Enable Dependabot #587. + Bump junit-jupiter from 5.6.2 to 5.7.0. + Bump spotbugs from 4.1.2 to 4.2.1, #627, #671, #708. + Bump spotbugs-maven-plugin from 4.0.0 to 4.2.0, #593, #596, #609, #623, #632, #692. + Bump biz.aQute.bndlib from 5.1.1 to 5.3.0 #592, #628, #715. + Bump junit-pioneer from 0.6.0 to 1.1.0, #589, #597, #600, #624, #625, #662. + Bump checkstyle from 8.34 to 8.40, #594, #614, #637, #665, #706. + Bump actions/checkout from v2.3.1 to v2.3.4 #601, #639. + Bump actions/setup-java from v1.4.0 to v1.4.2 #612. + Update commons.jacoco.version 0.8.5 to 0.8.6 (Fixes Java 15 builds). + Update maven-surefire-plugin 2.22.2 -> 3.0.0-M5. + Bump maven-pmd-plugin from 3.13.0 to 3.14.0 #660. + Bump jmh.version from 1.21 to 1.27 #674. + Update commons.japicmp.version 0.14.3 -> 0.15.2. + Processor.java: check enum equality with == instead of .equals() method #690. + Bump junit-pioneer from 1.1.0 to 1.3.0 #702. + Bump maven-checkstyle-plugin from 3.1.1 to 3.1.2 #705. + Bump actions/cache from v2 to v2.1.4 #710. + Bump junit-bom from 5.7.0 to 5.7.1 #707. + Minor Improvements #701. + Minor Improvement: Add final variable.try to make the code read-only #700. + Minor Improvement: Remove redundant initializer #699. + Use own validator ObjectUtils.anyNull to check null String input #718. + + + Refine test output for FastDateParserTest + CharSequenceUtils.lastIndexOf : remake it + remove encoding and docEncoding and use inherited values from commons-parent + Fix Javadoc for StringUtils.appendIfMissingIgnoreCase() #507. + Simplify null checks in Pair.hashCode() using Objects.hashCode(). #517. + Simplify null checks in Triple.hashCode() using Objects.hashCode(). #516. + Simplify some if statements in StringUtils. #521. + Simplify a null check in the private replaceEach() method of StringUtils. #514. + Replace some usages of the ternary operator with calls to Math.max() and Math.min() #512. + (Javadoc) Fix return tag for throwableOf*() methods #518. + Add ArrayUtils.isSameLength() to compare more array types #430. + CharSequenceUtils.regionMatches is wrong dealing with Georgian. + Added the Locks class as a convenient possibility to deal with locked objects. + Add to Functions: FailableBooleanSupplier, FailableIntSupplier, FailableLongSupplier, FailableDoubleSupplier, and so on. + Add ArrayUtils.get(T[], index, T) to provide an out-of-bounds default value. + Optimize ArrayUtils::isArrayIndexValid method. #551. + Use List.sort instead of Collection.sort #546. + Use StandardCharsets.UTF_8 #548. + Use Collections.singletonList insteadof Arrays.asList when there be only one element. #549. + Refine Javadoc #545. + Change array style from `int a[]` to `int[] a` #537. + Change from addAll to constructors for some List #536. + Fix typos #539. + Ignored exception `ignored`, should not be called so #540. + Simplify if as some conditions are covered by others #543. + StringUtils.replaceEachRepeatedly gives IllegalStateException #505. + Add JavaVersion enum constants for Java 14 and 15. #553. + Add JavaVersion enum constants for Java 16. + Use Java 8 lambdas and Map operations. + Change removeLastFieldSeparator to use endsWith #550. + Change a Pattern to a static final field, for not letting it compile each time the function invoked. #542. + Add ImmutablePair factory methods left() and right(). + Add ObjectUtils.toString(Object, Supplier<String>). + Fixed Javadocs for setTestRecursive() #556. + ToStringBuilder.reflectionToString - Wrong JSON format when object has a List of Enum. + [JSON string for maps] ToStringBuilder.reflectionToString doesnt render nested maps correctly. + Make org.apache.commons.lang3.CharSequenceUtils.toCharArray(CharSequence) public. + Add org.apache.commons.lang3.StringUtils.substringAfter(String, int). + Add org.apache.commons.lang3.StringUtils.substringAfterLast(String, int). + Correct Javadocs of methods that use Validate.notNull() and replace some uses of Validate.isTrue() with Validate.notNull(). #525. + Add allNull() and anyNull() methods to ObjectUtils. #522. + org.apache.commons:commons-parent 50 -> 51. + org.junit-pioneer:junit-pioneer 0.5.4 -> 0.6.0. + org.junit.jupiter:junit-jupiter 5.6.0 -> 5.6.2. + com.github.spotbugs:spotbugs 4.0.0 -> 4.0.6. + com.puppycrawl.tools:checkstyle 8.29 -> 8.34. + commons.surefire.version 3.0.0-M4 -> 3.0.0-M5.. + + + + Make test more stable by wrapping assertions in hashset. + Generate Javadoc jar on build. + Add ExceptionUtils.throwableOfType(Throwable, Class) and friends. + Add EMPTY_ARRAY constants to classes in org.apache.commons.lang3.tuple. + Add null-safe StringUtils APIs to wrap String#getBytes([Charset|String]). + Add zero arg constructor for org.apache.commons.lang3.NotImplementedException. + Add ArrayUtils.addFirst() methods. + Remove redundant if statements in join methods #411. + Trivial: year of release for 3.9 says 2018, should be 2019 + Use synchronize on a set created with Collections.synchronizedSet before iterating + Add Range.fit(T) to fit a value into a range. + commons.japicmp.version 0.13.1 -> 0.14.1. + junit-jupiter 5.5.0 -> 5.5.1. + Added Functions.as*, and tests thereof, as suggested by Peter Verhas + StringUtils.unwrap incorrect throw StringIndexOutOfBoundsException. + Add getters for lhs and rhs objects in DiffResult #451. + Generify builder classes Diffable, DiffBuilder, and DiffResult #452. + Add ClassLoaderUtils with toString() implementations #453. + Add null-safe APIs as StringUtils.toRootLowerCase(String) and StringUtils.toRootUpperCase(String) #456. + StringIndexOutOfBoundsException in StringUtils.replaceIgnoreCase #423. + StringUtils.removeIgnoreCase("İa", "a") throws IndexOutOfBoundsException #423. + junit-jupiter 5.5.1 -> 5.5.2. + Corrected usage examples in Javadocs #458. + Improve Javadoc based on the discussion of the GitHub PR #459. + maven-checkstyle-plugin 3.0.0 -> 3.1.0. + Update documentation related to the issue LANG-696 #449. + AnnotationUtils little cleanup #467. + Add org.apache.commons.lang3.time.Calendars. + Add EnumUtils getEnum() methods with default values #475. + Added indexesOf methods and simplified removeAllOccurences #471. + Add support of lambda value evaluation for defaulting methods #416. + StringUtils abbreviate returns String of length greater than maxWidth #477. + Test may fail due to a different order of fields returned by reflection api #480. + Update test dependency: org.easymock:easymock 4.0.2 -> 4.1. + Update test dependency: org.hamcrest:hamcrest 2.1 -> 2.2. + Update test dependency: org.junit-pioneer:junit-pioneer 0.3.0 -> 0.4.2. + Update build dependency: com.puppycrawl.tools:checkstyle 8.18 -> 8.27. + Sort fields in ReflectionToStringBuilder for deterministic order #481. + Update POM parent: org.apache.commons:commons-parent 48 -> 50. + BooleanUtils Javadoc #469. + Functions Javadoc #466. + Add factory methods to Pair classes with Map.Entry input. #454. + Add StopWatch convenience APIs to format times and create a simple instance. + Allow a StopWatch to carry an optional message. + Add ComparableUtils #398. + Add org.apache.commons.lang3.SystemUtils.getUserName(). + Add ObjectToStringComparator. #483. + Add org.apache.commons.lang3.arch.Processor.Arch.getLabel(). + Add IS_JAVA_14 and IS_JAVA_15 to org.apache.commons.lang3.SystemUtils. + ObjectUtils: Get first non-null supplier value. + Added the Streams class, and Functions.stream() as an accessor thereof. + org.easymock:easymock 4.1 -> 4.2. + org.junit-pioneer:junit-pioneer 0.4.2 -> 0.5.4. + org.junit.jupiter:junit-jupiter 5.5.2 -> 5.6.0. + Use Javadoc {@code} instead of pre tags. #490. + ExceptionUtilsTest to 100% #486. + MethodUtils will throw a NPE if invokeMethod() is called for a var-args method #407. + Reuse own code in Functions.java #493. + MethodUtils.getAnnotation() with searchSupers = true does not work if super is generic #494. + Avoid unnecessary allocation in StringUtils.wrapIfMissing. #496. + Internally use Validate.notNull(foo, ...) instead of Validate.isTrue(foo != null, ...). + Add 1 and 0 in toBooleanObject(final String str) #502. + Remove an redundant argument check in NumberUtils #504. + Deprecate org.apache.commons.lang3.ArrayUtils.removeAllOccurences(*) for org.apache.commons.lang3.ArrayUtils.removeAllOccurrences(*). + + + + FieldUtils.removeFinalModifier(Field, boolean), in java 12 + throw exception because the final modifier is no longer mutable. + Switch coverage from cobertura to jacoco. + Javadoc pointing to Commons RNG. + Add more SystemUtils.IS_JAVA_XX variants. + Adding the Functions class. + Update to JUnit 5 + Add @FunctionalInterface to ThreadPredicate and ThreadGroupPredicate + Update Java Language requirement to 1.8 + Add isEmpty method to ObjectUtils + Add null-safe StringUtils.valueOf(char[]) to delegate to String.valueOf(char[]) + Add API org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost(JavaVersion) + Consolidate the StringUtils equals and equalsIgnoreCase Javadoc and implementation + (doc) Fix javadoc for 'startIndex' parameter of StringUtils.join() methods. GitHub PR #412. + + + + Restore BundleSymbolicName for OSGi + + + + FastDateParser too strict on abbreviated short month symbols + JsonToStringStyle does not escape string names + JsonToStringStyle does not escape double quote in a string value + New Java version ("11") must be handled + ExceptionUtils#getRootCause(Throwable t) should return t if no lower level cause exists + NumberUtils.isNumber assumes number starting with Zero + defaultString(final String str) in StringUtils to reuse defaultString(final String str, final String defaultStr) + Parsing Json Array failed + Fix TypeUtils#parameterize to work correctly with narrower-typed array + Fix EventCountCircuitBreaker increment batch + NumberUtils.createNumber() throws StringIndexOutOfBoundsException instead of NumberFormatException + WordUtils.wrap throws StringIndexOutOfBoundsException when wrapLength is Integer.MAX_VALUE. + Typo in JavaDoc for lastIndexOf + ObjectUtils.identityToString(Object) and friends should allocate builders and buffers with a size + EnumUtils.getEnumIgnoreCase and isValidEnumIgnoreCase methods added + Add ToStringSummary annotation + Add bypass option for classes to recursive and reflective EqualsBuilder + Improve Javadoc for StringUtils.isAnyEmpty(null) + Add API SystemUtils.String getEnvironmentVariable(final String name, final String defaultValue) + org.apache.commons.lang3.SystemUtils should not write to System.err. + Add RegexUtils class instead of overloading methods in StringUtils that take a regex to take precompiled Pattern. + StringUtils.join() with support for List<?> with configurable start/end indices. + Methods for getting first non empty or non blank value + Remove checks for java versions below the minimum supported one + Null/index safe get methods for ArrayUtils + Rounding utilities for converting to BigDecimal + + + + Fix tests DateUtilsTest for Java 9 with en_GB locale + Fix NullPointerException in isJavaVersionAtLeast on Java 10, add SystemUtils.IS_JAVA_10, add JavaVersion.JAVA_10 + StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf + ConstructorUtils.invokeConstructor(Class, Object...) regression + EqualsBuilder#isRegistered: swappedPair construction bug + org.apache.commons.lang3.time.FastDateParser should use toUpperCase(Locale) + Improve StringUtils#replace throughput + Remove deprecation from RandomStringUtils + ExceptionUtils.getThrowableList() is using deprecated ExceptionUtils.getCause() + TimeZone.getTimeZone() in FastDateParser causes resource contention (PR #296.) + Add methods to ObjectUtils to get various forms of class names in a null-safe manner + + + + Add Automatic-Module-Name MANIFEST entry for Java 9 compatibility + Add NUL Byte To CharUtils + Fix test failures in IBM JDK 8 for ToStringBuilderTest + Add method in StringUtils to determine if string contains both mixed cased characters + Deprecate CharEncoding in favour of java.nio.charset.StandardCharsets + MultilineRecursiveToStringStyle StackOverflowError when object is an array + Increase test coverage of ToStringBuilder class to 100% + Add a method in StringUtils to extract only digits out of input string + Implement HashSetvBitSetTest using JMH + Add JMH maven dependencies + Add null filter to ReflectionToStringBuilder + LocaleUtils#toLocale does not support language followed by UN M.49 numeric-3 area code followed by variant + Clarify or improve behavior of int-based indexOf methods in StringUtils + Add method for converting string to an array of code points + RandomStringUtils random method can overflow and return characters outside of specified range + Add methods to insert arrays into arrays at an index + WordUtils.wrap throws StringIndexOutOfBoundsException + RandomStringUtils#random can enter infinite loop if end parameter is to small + NullPointerException in FastDateParser$TimeZoneStrategy + Javadoc of StringUtils.ordinalIndexOf is contradictory. + StringUtils#join(T...): warning: [unchecked] Possible heap pollution from parameterized vararg type T + Multiple calls of org.apache.commons.lang3.concurrent.LazyInitializer.initialize() are possible. + StrBuilder#replaceAll ArrayIndexOutOfBoundsException + BooleanUtils javadoc issues + ArrayUtils#add confusing example in javadoc + StringUtils#isAnyEmpty and #isAnyBlank should return false for an empty array + Add StringUtils#unwrap + Add support for recursive comparison to EqualsBuilder#reflectionEquals + Add a reflection-based variant of DiffBuilder + Implementation of a Memomizer + Add ArrayUtils#toStringArray method + StringUtils#abbreviate should support 'custom ellipses' parameter + Add StringUtils#isAllEmpty and #isAllBlank methods + Increase test coverage of org.apache.commons.lang3.ArrayUtils + StrSubstitutor should state its thread safety + StringUtils#getLevenshteinDistance reduce memory consumption + Update Java requirement from Java 6 to 7. + StringUtils should use toXxxxCase(int) rather than toXxxxCase(char) + Add SystemUtils.getHostName() API. + Moving apache-rat-plugin configuration into pluginManagement + TypeUtils.toString() doesn't handle primitive and Object arrays correctly + LocaleUtils#toLocale does not support language followed by UN M.49 numeric-3 area code + Build failures when building with Java 9 EA + javadoc creation broken with Java 8 + Deprecate classes/methods moved to commons-text + MethodUtils.invokeMethod throws ArrayStoreException if using varargs arguments and smaller types than the method defines + Add ArchUtils - An utility class for the "os.arch" system property + Add shuffle methods to ArrayUtils + Add MethodUtils#findAnnotation and extend MethodUtils#getMethodsWithAnnotation for non-public, super-class and interface methods + Add ImmutablePair.nullPair() + Add ImmutableTriple.nullTriple() + + + + Added a tryAcquire() method to TimedSemaphore. + Added a new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils + Add DateUtils.toCalendar(Date, TimeZone) + Add WordUtils.wrap overload with customizable breakable character + Add method removeIgnoreCase(String, String) to StringUtils + ArrayUtils.contains returns false for instances of subtypes + Prepare Java 9 detection + Rename NumberUtils.isNumber, isCreatable to better reflect createNumber. Also, accommodated for "+" symbol as prefix in isCreatable and isNumber. + CompareToBuilder.append(Object, Object, Comparator) method is too big to be inlined + Remove unnecessary synchronization from registry lookup in EqualsBuilder and HashCodeBuilder + Extend RandomStringUtils with methods that generate strings between a min and max length + Handle "void" in ClassUtils.getClass() + SerializationUtils#deserialize has unnecessary code and a comment for that + Javadoc for ArrayUtils.isNotEmpty() is slightly misleading + Add APIs StringUtils.wrapIfMissing(String, char|String) + TypeUtils.isAssignable throws NullPointerException when fromType has type variables and toType generic superclass specifies type variable + StringUtils#normalizeSpace does not trim the string anymore + SerializationUtils.ClassLoaderAwareObjectInputStream should use static initializer to initialize primitiveTypes map + [GitHub issue #170] Add RandomUtils#nextBoolean() method + FastDatePrinter Memory allocation regression + FastDatePrinter generates extra Date objects + Fix precision loss on NumberUtils.createNumber(String) + HashCodeBuilder.append(Object,Object) is too big to be inlined, which prevents whole builder to be scalarized + Add a circuit breaker implementation + Add StringUtils.truncate() + Enhance MethodUtils to allow invocation of private methods + Fix implementation of StringUtils.getJaroWinklerDistance() + Fix dead links in StringUtils.getLevenshteinDistance() javadoc + "\u2284":"nsub" mapping missing from EntityArrays#HTML40_EXTENDED_ESCAPE + Simplify ArrayUtils removeElements by using new decrementAndGet() method + Add getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/decrementAndGet/addAndGet in Mutable* classes + Optimize BitField constructor implementation + Improve CharSetUtils.squeeze() performance + Add RandomStringUtils#randomGraph and #randomPrint which match corresponding regular expression class + StringUtils#startsWithAny/endsWithAny is case sensitive - documented as case insensitive + Add StopWatch#getTime(TimeUnit) + Add methods to ObjectUtils class to check for null elements in the array + Prefer Throwable.getCause() in ExceptionUtils.getCause() + DiffBuilder add method to allow appending from a DiffResult + Improve ArrayUtils removeElements time complexity to O(n) + getLevenshteinDistance with a threshold: optimize implementation if the strings lengths differ more than the threshold + Add SystemUtils.IS_OS_WINDOWS_10 property + DiffBuilder: Add null check on fieldName when appending Object or Object[] + ArrayUtils.removeAll(Object array, int... indices) should do the clone, not its callers + Performance improvements for NumberUtils.isParsable + StringUtils.stripAccents should remove accents from "Ł" and "ł". + EqualsBuilder.append(Object,Object) is too big to be inlined, which prevents whole builder to be scalarized + NumberUtils.createNumber() behaves inconsistently with NumberUtils.isNumber() + Add support for varargs in ConstructorUtils, MemberUtils, and MethodUtils + Add methods to check numbers against NaN and inifinite to Validate + Fix for incorrect comment on StringUtils.containsIgnoreCase method + Fix typo on appendIfMissing javadoc + Add tests for missed branches in DateUtils + parseDateStrictly does't pass specified locale + FastDateFormat doesn't respect summer daylight in some localized strings + z/OS identification in SystemUtils + StringUtils#startsWithAny has error in Javadoc + StrSubstitutor can preserve escapes + Remove Ant-based build + FastDateFormat support of the week-year component (uppercase 'Y') + Limit max heap memory for consistent Travis CI build + Fix NullPointerException in FastDateParser$TimeZoneStrategy + ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1 (correct answer should be 0); revert fix for LANG-1077 + Clarify Javadoc of StringUtils.containsAny() + Add StringUtils methods to compare a string to multiple strings + Add remove by regular expression methods in StringUtils + Making replacePattern/removePattern methods null safe in StringUtils + Add replace by regular expression methods in StringUtils + Add compare methods in StringUtils + Add sugar to RandomUtils + Replace StringBuilder with String concatenation for better optimization + Deprecate SystemUtils.FILE_SEPARATOR and SystemUtils.PATH_SEPARATOR + FastDateFormat APIs that use a StringBuilder + Ability to throw checked exceptions without declaring them + Several predefined ISO FastDateFormats in DateFormatUtils are incorrect + StringIndexOutOfBoundsException or field over-write for large year fields in FastDateParser + Implement ParsePosition api for FastDateParser + StrLookup.systemPropertiesLookup() no longer reacts on changes on system properties + EnumUtils *BitVector issue with more than 32 values Enum + Capitalize javadoc is incorrect + Add check for duplicate event listener in EventListenerSupport + FastDateParser_TimeZoneStrategyTest#testTimeZoneStrategyPattern fails on Windows with German Locale + Add method containsAllWords to WordUtils + ReflectionToStringBuilder doesn't throw IllegalArgumentException when the constructor's object param is null + Inconsistent behavior of swap for malformed inputs + StringUtils join with var args + Fix critical issues reported by SonarQube + StrBuilder.equals(StrBuilder) doesn't check for null inputs + Add ThreadUtils - A utility class which provides helper methods related to java.lang.Thread + Add annotations to exclude fields from ReflectionEqualsBuilder, ReflectionToStringBuilder and ReflectionHashCodeBuilder + Use JUnit rules to set and reset the default Locale and TimeZone + JsonToStringStyle doesn't handle chars and objects correctly + DateFormatUtilsTest.testSMTP depends on the default Locale + Unit test FastDatePrinterTimeZonesTest needs a timezone set + CLONE - DateFormatUtils.format does not correctly change Calendar TimeZone in certain situations + DateUtilsTest.testLang530 fails for some timezones + TypeUtils.ParameterizedType#equals doesn't work with wildcard types + Add rotate(string, int) method to StringUtils + StringUtils.repeat('z', -1) throws NegativeArraySizeException + Add swap and shift operations for arrays to ArrayUtils + TypeUtils.parameterizeWithOwner - wrong format descriptor for "invalid number of type parameters". + MultilineRecursiveToStringStyle largely unusable due to being package-private. + StringUtils.uncapitalize performance improvement + CharSet.getInstance documentation does not clearly explain how to include negation character in set + Change nullToEmpty methods to generics + Fix FindBugs warnings in DurationFormatUtils + Add a method to ArrayUtils for removing all occurrences of a given element + Fix parsing edge cases in FastDateParser + StringUtils#equals fails with Index OOBE on non-Strings with identical leading prefix + There are no tests for CharSequenceUtils.regionMatches + StringUtils.ordinalIndexOf: Add missing right parenthesis in Javadoc example + Incorrect Javadoc StringUtils.containsAny(CharSequence, CharSequence...) + Added new property IS_OS_MAC_OSX_EL_CAPITAN in SystemUtils + + + + Support OS X versions in SystemUtils + SystemUtils.IS_OS_WINDOWS_2008, VISTA are incorrect + Parse test fails for TimeZone America/Sao_Paulo + Add SystemUtils.IS_JAVA_1_9 + Make logic for comparing OS versions in SystemUtils smarter + Shutdown thread pools in test cases + FastDateParser and FastDatePrinter support 'X' format + Avoid memory allocation when using date formating to StringBuffer + Possible performance improvement on string escape functions + Exception while using ExtendedMessageFormat and escaping braces + Avoid String allocation in StrBuilder.append(CharSequence) + Update maven-checkstyle-plugin to 2.14 + Update org.easymock:easymock to 3.3.1 + Update maven-pmd-plugin to 3.4 + Update maven-antrun-plugin to 1.8 + Wrong formating of time zones with daylight saving time in FastDatePrinter + Performance improvements for StringEscapeUtils + Add ClassUtils.getAbbreviatedName() + FastDateParser does not set error indication in ParsePosition + FastDateParser does not handle excess hours as per SimpleDateFormat + FastDateParser error - timezones not handled correctly + NumberUtils#createNumber() returns positive BigDecimal when negative Float is expected + DiffBuilder.append(String, Object left, Object right) does not do a left.equals(right) check + StrSubstitutor.replaceSystemProperties does not work consistently + Add option to disable the "objectsTriviallyEqual" test in DiffBuilder + Add (T) casts to get unit tests to pass in old JDK + Add JsonToStringStyle implementation to ToStringStyle + Add NoClassNameToStringStyle implementation of ToStringStyle + Fix wrong examples in Javadoc of StringUtils.replaceEachRepeatedly(...), StringUtils.replaceEach(...) + Add StringUtils.containsAny(CharSequence, CharSequence...) method + Read wrong component type of array in add in ArrayUtils + StringUtils.ordinalIndexOf("aaaaaa", "aa", 2) != 3 in StringUtils + Duplicated "0x" check in createBigInteger in NumberUtils + StringUtils.abbreviate description doesn't agree with the examples + Multiline recursive to string style + Add isSorted() to ArrayUtils + Fix MethodUtilsTest so it does not depend on JDK method ordering + CompareToBuilder's doc doesn't specify precedence of fields it uses in performing comparisons + ParseException when trying to parse UTC dates with Z as zone designator using DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT + Javadoc for EqualsBuilder.reflectionEquals() is unclear + Improve performance of normalize space + Add StringUtils.countMatches(CharSequence, char) + org.apache.commons.lang3.SystemUtils#isJavaVersionAtLeast should return true by default + Provide methods to retrieve all fields/methods annotated with a specific type + Bring static method references in StringUtils to consistent style + NumberUtils#isParsable method(s) + Use non-ASCII digits in Javadoc examples for StringUtils.isNumeric + Change min/max methods in NumberUtils/IEEE754rUtils from array input parameters to varargs + Add fuzzy String matching logic to StringUtils + Add wrap (with String or char) to StringUtils + Extend DurationFormatUtils#formatDurationISO default pattern to match #formatDurationHMS + Fixing NumberUtils JAVADoc comments for max methods + Better Javadoc for BitField class + DurationFormatUtils#formatDurationHMS implementation does not correspond to Javadoc and vice versa + DurationFormatUtils are not able to handle negative durations/periods + ISO 8601 misspelled throughout the Javadocs + Add zero copy read method to StrBuilder + Add zero copy write method to StrBuilder + Javadoc is not clear on preferred pattern to instantiate FastDateParser / FastDatePrinter + FastDateParser should be case insensitive + Fix bug with stripping spaces on last line in WordUtils.wrap() + Add method org.apache.commons.lang3.reflect.MethodUtils.invokeExactMethod(Object, String) + Add method org.apache.commons.lang3.reflect.MethodUtils.invokeMethod(Object, String) + + + + NumberUtils#isNumber() returns false for "0.0", "0.4790", et al + Add org.apache.commons.lang3.SystemUtils.IS_JAVA_1_8 + + + + DateUtils.getFragmentInDays(Date, Calendar.MONTH) returns wrong days + DurationFormatUtils does not describe format string fully + DurationFormatUtils#lexx does not detect unmatched quote char + DurationFormatUtils does not handle large durations correctly + DurationFormatUtils.formatDuration(61999, "s.SSSS") - ms field size should be 4 digits + Failing tests with Java 8 b128 + + + + ReflectionToStringBuilder.toString does not debug 3rd party object fields within 3rd party object + Add methods for removing all invalid characters according to XML 1.0 and XML 1.1 in an input string to StringEscapeUtils + NumericEntityEscaper incorrectly encodes supplementary characters + Make some private fields final + NumberUtils#isNumber(String) fails to reject invalid Octal numbers + NumberUtils#isNumber does not allow for hex 0XABCD + StringUtils.toEncodedString(byte[], Charset) needlessly throws UnsupportedEncodingException + Add APIs MutableBoolean setTrue() and setFalse() + ConstantInitializerTest fails when building with IBM JDK 7 + Add SerializationUtils.roundtrip(T extends Serializable) to serialize then deserialize + org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field) does not clean up after itself + FastDateParser javadoc incorrectly states that SimpleDateFormat is used internally + There should be a DifferenceBuilder with a ReflectionDifferenceBuilder implementation + uncaught PatternSyntaxException in FastDateFormat on Android + Improve Javadoc of WordUtils.wrap methods + Add the Jaro-Winkler string distance algorithm to StringUtils + StringUtils.getLevenshteinDistance with too big of a threshold returns wrong result + Test DurationFormatUtilsTest.testEdgeDuration fails in JDK 1.6, 1.7 and 1.8, BRST time zone + ConstructorUtils.getAccessibleConstructor() Does Not Check the Accessibility of Enclosing Classes + Fragments are wrong by 1 day when using fragment YEAR or MONTH + New class ClassPathUtils with methods for turning FQN into resource path + Move Documentation from user guide to package-info files + Convert package.html files to package-info.java files + FastDateParser does not handle two digit year parsing like SimpleDateFormat + FastDateParserTest.testParses does not test FastDateParser + Fix deprecation warnings + EnumUtils.generateBitVector needs a "? extends" + Validate: add inclusiveBetween and exclusiveBetween overloads for primitive types + New RandomUtils class + Wrong locale handling in LocaleUtils.toLocale() + Add IBM OS/400 detection + + + + Fix missing Hamcrest dependency in Ant Build + Test failure in LocaleUtilsTest when building with JDK 8 + Test failure in FastDateParserTest and FastDateFormat_ParserTest when building with JDK8 + Build fails with test failures when building with JDK 8 + + + + Add removeFinalModifier to FieldUtils + Method returns number of inheritance hops between parent and subclass + Spelling fixes + Misleading Javadoc comment in StrBuilderReader class + OctalUnescaper tried to parse all of \279 + OctalUnescaper had bugs when parsing octals starting with a zero + EqualsBuilder returned true when comparing arrays, even when the elements are different + Added isStarted, isSuspended and isStopped to StopWatch + Fixed exception when combining custom and choice format in ExtendedMessageFormat + Added StringUtils.isBlank/isEmpty CharSequence... methods + Added ArrayUtils.reverse(array, from, to) methods + StringUtils.toString(byte[], String) deprecated in favour of a new StringUtils.toString(byte[], CharSet) + RandomStringUtils.random javadoc was incorrectly promising letters and numbers would, as opposed to may, appear + BooleanUtils.xor(boolean...) produces wrong results + StringUtils.normalizeSpace now handles non-breaking spaces (Unicode 00A0) + Redundant check for zero in HashCodeBuilder ctor + StrSubstitutor now supports default values for variables + Adding .gitignore to commons-lang + Add ObjectUtils.toIdentityString methods that support StringBuilder, StrBuilder, and Appendable + BooleanUtils.toBoolean(String str) javadoc is not updated + LocaleUtils test fails with new Locale "ja_JP_JP_#u-ca-japanese" of JDK7 + StrSubstitutor does not support StringBuilder or CharSequence + Method createNumber from NumberUtils doesn't work for floating point numbers other than Float + FastDateFormat does not use the locale specific cache correctly + Simplify FastDateFormat; eliminate boxing + LookupTranslator now works with implementations of CharSequence other than String + ClassUtils.getShortName(String) will now only do a reverse lookup for array types + Added CharSetUtils.containsAny(String, String) + Provide CharSequenceUtils.regionMatches with a proper green implementation instead of inefficiently converting to Strings + Added escape/unescapeJson to StringEscapeUtils + Added appendIfMissing and prependIfMissing methods to StringUtils + NumberUtils.createNumber() Javadoc says it does not work for octal numbers + Fixed URLs in javadoc to point to new oracle.com pages + Add StringUtils.LF and StringUtils.CR values + Add FieldUtils getAllFields() to return all the fields defined in the given class and super classes + LocaleUtils.toLocale does not parse strings starting with an underscore + StrBuilder should support StringBuilder as an input parameter + StringEscapeUtils.escapeJava() and escapeEcmaScript() do not output the escaped surrogate pairs that are Java parsable + StringIndexOutOfBoundsException in CharSequenceTranslator + Code refactoring in NumberUtils + NumberUtils#createBigInteger does not allow for hex and octal numbers + NumberUtils#createNumber - does not allow for hex numbers to be larger than Long + StringUtils join APIs for primitives + FastDateFormat and FastDatePrinter generates Date objects wastefully + Spelling fixes + Fix examples contained in javadoc of StringUtils.center methods + Add StringUtils API to call String.replaceAll in DOTALL a.k.a. single-line mode + ArrayUtils removeElements methods use unnecessary HashSet + ArrayUtils removeElements methods clone temporary index arrays unnecessarily + FastDateParser does not handle unterminated quotes correctly + FastDateParser does not handle white-space properly + FastDateParser could use \Q \E to quote regexes + FastDateParser does not handle non-Gregorian calendars properly + FastDateParser does not handle non-ASCII digits correctly + Create StrBuilder APIs similar to String.format(String, Object...) + NumberUtils#createNumber - bad behavior for leading "--" + FastDateFormat's "z" pattern does not respect timezone of Calendar instances passed to format() + Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8 + StringUtils.equalsIgnoreCase doesn't check string reference equality + StringUtils.join() endIndex, bugged for loop + RandomStringUtils throws confusing IAE when end <= start + RandomStringUtils.random(count, 0, 0, false, false, universe, random) always throws java.lang.ArrayIndexOutOfBoundsException + LocaleUtils - unnecessary recursive call in SyncAvoid class. + Javadoc bug in DateUtils#ceiling for Calendar and Object versions. + DateUtils#parseDate uses default locale; add Locale support + Use generics in SerializationUtils + SerializationUtils throws ClassNotFoundException when cloning primitive classes + StringUtils equals() relies on undefined behavior + Documentation bug: StringUtils.split + jar contains velocity template of release notes + TypeUtilsTest contains incorrect type assignability assertion + TypeUtils.getTypeArguments() misses type arguments for partially-assigned classes + ImmutablePair doc contains nonsense text + ClassUtils.PACKAGE_SEPARATOR Javadoc contains garbage text + EventListenerSupport.ProxyInvocationHandler no longer defines serialVersionUID + StrBuilder is now serializable + Fix Javadoc Ant warnings + NumberUtils does not handle Long Hex numbers + Javadoc bug in static inner class DateIterator + Add Triple class (ternary version of Pair) + FastDateFormat supports parse methods + + + + Add API StringUtils.toString(byte[] intput, String charsetName) + Add an example with whitespace in StringUtils.defaultIfEmpty + Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and isPrimitiveOrWrapper(Class<?>) + Fix createLong() so it behaves like createInteger() + Include the actual type in the Validate.isInstance and isAssignableFrom exception messages + Incorrect Bundle-SymbolicName in Manifest + Deprecating chomp(String, String) + NumberUtils does not handle upper-case hex: 0X and -0X + StringUtils throws java.security.AccessControlException on Google App Engine + Ant build has wrong component.name + CharUtils static final array CHAR_STRING is not needed to compute CHAR_STRING_ARRAY + Document that the Mutable numbers don't work as expected with String.format + SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system + + + + SerializationUtils.clone: Fallback to context classloader if class not found in current classloader. + ToStringBuilderTest.testReflectionHierarchyArrayList fails with IBM JDK 6. + StringEscapeUtils.escapeXml(input) wrong when input contains characters in Supplementary Planes. + StringEscapeUtils.escapeEcmaScript from lang3 cuts off long unicode string. + Improve exception message when StringUtils.replaceEachRepeatedly detects recursion. + Specify source encoding for Ant build. + Complement ArrayUtils.addAll() variants with by-index and by-value removal methods. + Add Range<T> Range<T>.intersectionWith(Range<T>). + Add mode and median Comparable... methods to ObjectUtils. + Add BooleanUtils.and + or varargs methods. + EnumSet -> bit vector. + The CHAR_ARRAY cache in CharUtils duplicates the cache in java.lang.Character. + Deprecate CharUtils.toCharacterObject(char) in favor of java.lang.Character.valueOf(char). + Missing method getRawMessage for ContextedException and ContextedRuntimeException. + Use internal Java's Number caches instead creating new objects. + + + + StringEscapeUtils.escapeXml(input) outputs wrong results when an input contains characters in Supplementary Planes. + build.xml Java 1.5+ updates. + swapCase and *capitalize speedups. + CharSetUtils.squeeze() speedup. + StringUtils doc/comment spelling fixes. + Increase test coverage of FieldUtils read methods and tweak Javadoc. + Add includeantruntime=false to javac targets to quell warnings in ant 1.8.1 and better (and modest performance gain). + StringIndexOutOfBoundsException when calling unescapeHtml4("&#03"). + StringEscapeUtils.escapeEcmaScript from lang3 cuts off long Unicode string. + StringUtils.join throws NPE when toString returns null for one of objects in collection. + Add FormattableUtils class. + Add ClassUtils.getSimpleName() methods. + Add hashCodeMulti varargs method. + Removed DateUtils.UTC_TIME_ZONE. + Convert more of the StringUtils API to take CharSequence. + EqualsBuilder synchronizes on HashCodeBuilder. + StringUtils.isAlpha, isAlphanumeric and isNumeric now return false for "". + Add support for ConcurrentMap.putIfAbsent(). + Documented potential NPE if auto-boxing occurs for some BooleanUtils methods. + DateUtils.isSameLocalTime compares using 12 hour clock and not 24 hour. + Extend exception handling in ConcurrentUtils to runtime exceptions. + SystemUtils.getJavaVersionAsFloat throws StringIndexOutOfBoundsException on Android runtime/Dalvik VM. + WordUtils.abbreviate() removed. + Doc bug in DateUtils#ceiling. + StringEscapeUtils.unescapeJava doesn't handle octal escapes and Unicode with extra u. + org.apache.commons.lang3.math.Fraction does not reduce (Integer.MIN_VALUE, 2^k). + org.apache.commons.lang3.math.Fraction does not always succeed in multiplyBy and divideBy. + Change ObjectUtils min() & max() functions to use varargs rather than just two parameters. + Add a Null-safe compare() method to ObjectUtils. + NumberUtils.isNumber(String) is not right when the String is "1.1L". + EntityArrays typo: {"\u2122", "&minus;"}, // minus sign, U+2212 ISOtech. + Some Entitys like &Ouml; are not matched properly against its ISO8859-1 representation. + Example StringUtils.indexOfAnyBut("zzabyycdxx", '') = 0 incorrect. + Add StringUtils.defaultIfBlank(). + Provide a very basic ConcurrentInitializer implementation. + Support lazy initialization using atomic variables. + Enhance StrSubstitutor to support nested ${var-${subvr}} expansion. + Provide documentation about the new concurrent package. + Charset may not be threadsafe, because the HashSet is not synch. + StringEscapeUtils.escapeXML() can't process UTF-16 supplementary characters. + StringUtils.endsWithAny method. + Add AnnotationUtils. + BooleanUtils.toBooleanObject to support single character input. + FastDateFormat.format() outputs incorrect week of year because locale isn't respected. + StrSubstitutor should also handle the default properties of a java.util.Properties class. + Javadoc StringUtils.left() claims to throw on negative len, but doesn't. + Add normalizeSpace to StringUtils. + NumberUtils createNumber throws a StringIndexOutOfBoundsException when argument containing "e" and "E" is passed in. + + NOTE: The below were included in the Commons Lang 3.0-beta release. + Convert StringUtils API to take CharSequence. + Push down WordUtils to "text" sub-package. + Extend exception handling in ConcurrentUtils to runtime exceptions. + Some StringUtils methods should take an int character instead of char to use String API features. + EqualsBuilder causes StackOverflowException. + DefaultExceptionContext overwrites values in recursive situations. + ContextedRuntimeException no longer an 'unchecked' exception. + Add Builder Interface / Update Builders to Implement It. + Javadoc is incorrect for public static int lastIndexOf(String str, String searchStr). + ClassUtils.getClass(): Allow Dots as Inner Class Separators. + DateUtils equal & compare functions up to most significant field. + Remove JDK 1.2/1.3 bug handling in StringUtils.indexOf(String, String, int). + Create a basic Pair<L, R> class. + exception.DefaultExceptionContext.getFormattedExceptionMessage catches Throwable. + Provide an implementation of the ThreadFactory interface. + Add new Validate methods. + ArrayUtils.add(T[] array, T element) can create unexpected ClassCastException. + Do the test cases really still require main() and suite() methods?. + @SuppressWarnings("unchecked") is used too generally. + Improve StrLookup API documentation. + Change Java package name. + Change Maven groupId. + New TimedSemaphore class. + Added validState validation method. + Added isAssignableFrom and isInstanceOf validation methods. + Add TypeUtils class to provide utility code for working with generic types. + Replace Range classes with generic version. + Use Iterable on API instead of Collection. + Add methods to Validate to check whether the index is valid for the array/list/string. + Add ability to create a Future for a constant. + Replace StringBuffer with StringBuilder. + Make NumericEntityEscaper immutable. + Compile commons.lang for CDC 1.1/Foundation 1.1. + Add ArrayUtils.toArray to create generic arrays. + Validate: support for validating blank strings. + Add a concurrent package. + Mutable classes should implement an appropriately typed Mutable interface. + Better EnumUtils. + StringEscapeUtils.unescapeJava should support \u+ notation. + Rewrite StringEscapeUtils. + bring ArrayUtils.isEmpty to the generics world. + Add support for background initialization. + Add support for the handling of ExecutionExceptions. + Add StringEscapeUtils.escapeText() methods. + Addition of ContextedException and ContextedRuntimeException. + A generic implementation of the Lazy initialization pattern. + Remove code that does not hold enough value to remain. + Remove code handled now by the JDK. + StrSubstitutor now supports substitution in variable names. + Possible race-conditions in hashCode of the range classes. + StringEscapeUtils.escapeHtml incorrectly converts Unicode characters above U+00FFFF into 2 characters. + Document where in SVN trunk is. + StopWatch does not resist to system time changes. + Fixes for thread safety. + Refactor Validate.java to eliminate code redundancy. + Lower Ascii Characters don't get encoded by Entities.java. + StringUtils.emptyToNull. + StringEscapeUtils.escapeHTML() does not escape chars (0x00-0x20). + Remove @deprecateds. + Add ClassUtils.isAssignable() variants with autoboxing. + Improve Javadoc for StringUtils class. + Javadoc incorrect for StringUtils.endsWithIgnoreCase. + Investigate for vararg usages. + JDK 1.5 build/runtime failure on LANG-393 (EqualsBuilder). + LeftOf/RightOfNumber in Range convenience methods necessary. + ExceptionUtils not thread-safe. + ObjectUtils.coalesce. + StrBuilder should implement CharSequence and Appendable. + StringEscapeUtils.escapeHtml() escapes multibyte characters like Chinese, Japanese, etc. + Finally start using generics. + StrBuilder does not implement clone(). + EnumUtils for JDK 5.0. + Wish : method unaccent. + MutableBigDecimal and MutableBigInteger. + StringEscaper.escapeXml() escapes characters > 0x7f. + Depend on JDK 1.5+. + + + + BooleanUtils: use same optimization in toBooleanObject(String) as in toBoolean(String). + ClassUtils: allow Dots as Inner Class Separators in getClass(). + DateUtils: equal and compare functions up to most significant field. + DateUtils: provide a Date to Calendar convenience method. + ObjectUtils: add clone methods to ObjectUtils. + ObjectUtils: add a Null-safe compare() method. + ObjectUtils: add notEqual() method. + StrBuilder: implement clone() method. + StringUtils: add a normalizeSpace() method. + StringUtils: add endsWithAny() method. + StringUtils: add defaultIfBlank() method. + StrSubstitutor: add a replace(String, Properties) variant. + StrSubstitutor: support substitution in variable names. + Use StrBuilder instead of StringBuffer to improve performance where sync. is not an issue. + CharSet: make the underlying set synchronized. + CompareToBuilder: fix passing along compareTransients to the reflectionCompare method. + ExtendedMessageFormat doesn't override equals(Object). + FastDateFormat: fix to properly include the locale when formatting a Date. + NumberUtils: createNumber() throws a StringIndexOutOfBoundsException when argument containing "e" and "E" is passed in. + StringUtils methods do not handle Unicode 2.0+ supplementary characters correctly. + SystemUtils: getJavaVersionAsFloat throws StringIndexOutOfBoundsException on Android runtime/Dalvik VM. + MemberUtils: getMatchingAccessibleMethod does not correctly handle inheritance and method overloading. + Javadoc is incorrect for lastIndexOf() method. + Javadoc for HashCodeBuilder.append(boolean) does not match implementation. + Javadoc StringUtils.left() claims to throw an exception on negative lenth, but doesn't. + Javadoc - document thread safety. + Test for StringUtils replaceChars() icelandic characters. + + + + ArrayUtils - add isNotEmpty() methods. + ArrayUtils - add nullToEmpty() methods. + CharRange - provide an iterator that lets you walk the chars in the range. + CharRange - add more readable static builder methods. + ClassUtils - new isAssignable() methods with autoboxing. + ClassUtils - add support to getShortClassName and getPackageName for arrays. + DateUtils - add ceiling() method. + DateUtils - add parseDateStrictly() method. + EqualsBuilder - add reset() method. + NumberUtils - add toByte() and toShort() methods. + Mutable numbers - add string constructors. + MutableBoolean - add toBoolean(), isTrue() and isFalse() methods. + StrBuilder - add appendSeparator() methods with an alternative default separator if the StrBuilder is currently empty. + SystemUtils - add IS_OS_WINDOWS_7 constant. + SystemUtils - add IS_JAVA_1_7 constant for JDK 1.7. + StringUtils - add abbreviateMiddle() method. + StringUtils - add indexOfIgnoreCase() and lastIndexOfIgnoreCase() methods. + StringUtils - add isAllUpperCase() and isAllLowerCase() methods. + StringUtils - add lastOrdinalIndexOf() method to complement the existing ordinalIndexOf() method. + StringUtils - add repeat() method. + StringUtils - add startsWithAny() method. + StringUtils - add upperCase(String, Locale) and lowerCase(String, Locale) methods. + New Reflection package containing ConstructorUtils, FieldUtils, MemberUtils and MethodUtils. + ArrayUtils - addAll() does not handle mixed types very well. + CharSet - Synchronizing the COMMON Map so that getInstance doesn't miss a put from a subclass in another thread. + ClassUtils - improving performance of getAllInterfaces. + ClassUtils - toClass() throws NullPointerException on null array element. + DateUtils - Fix parseDate() cannot parse ISO8601 dates produced by FastDateFormat. + DateUtils - round() doesn't work correct for Calendar.AM_PM. + DateUtils - improve tests. + Entities - multithreaded initialization. + Entities - missing final modifiers; thread-safety issues. + EnumUtils - getEnum() doesn't work well in 1.5+. + ExceptionUtils - use immutable lock target. + ExtendedMessageFormat - OutOfMemory with a pattern containing single quotes. + FastDateFormat - call getTime() on a calendar to ensure timezone is in the right state. + FastDateFormat - Remove unused field. + LocaleUtils - Initialization of available locales in LocaleUtils can be deferred. + NumberUtils - createNumber() thows a StringIndexOutOfBoundsException when only an "l" is passed in. + NumberUtils - isNumber(String) and createNumber(String) both modified to support '2.'. + StringUtils - improve handling of case-insensitive Strings. + StringUtils - replaceEach() no longer NPEs when null appears in the last String[]. + StringUtils - correct Javadoc for startsWith() and startsWithIgnoreCase(). + StringEscapeUtils - escapeJava() escapes '/' characters. + StringEscapeUtils - change escapeJavaStyleString() to throw UnhandledException instead swallowing IOException and returning null. + WordUtils - fix StringIndexOutOfBoundsException when lower is greater than the String length. + StrBuilder - Performance improvement by doubling the size of the String in ensureCapacity. + Compare, Equals and HashCode builders - use ArrayUtils to avoid creating a temporary List. + EqualsBuilder - removing the special handling of BigDecimal (LANG-393) to use compareTo instead of equals because it creates an inequality with HashCodeBuilder. + HashCodeBuilder - Performance improvement: check for isArray to short-circuit the 9 instanceof checks. + HashCodeBuilder - Changing the hashCode() method to return toHashCode(). + HashCodeBuilder - reflectionHashCode() can generate incorrect hashcodes. + HashCodeBuilder and ToStringStyle - use of ThreadLocal causes memory leaks in container environments. + ToStringBuilder - make default style thread-safe. + RandomUtils - nextLong() always produces even numbers. + RandomUtils - RandomUtils tests are failing frequently. + + + + ClassUtils.getShortClassName(String) inefficient. + Shouldn't Commons Lang's StringUtils have a "common" string method?. + FastDateFormat getDateInstance() and getDateTimeInstance() assume Locale.getDefault() won't change. + OSGi-ify Lang. + StrBuilder appendFixedWidth does not handle nulls. + infinite loop in Fraction.reduce when numerator == 0. + FastDateFormat thread safety. + ClassUtils.getShortClassName and ClassUtils.getPackageName and class of array. + LocaleUtils.toLocale() rejects strings with only language+variant. + Enum is not thread-safe. + BooleanUtils.toBoolean() - invalid drop-thru in case statement causes StringIndexOutOfBoundsException. + ArrayUtils.toClass. + Why does appendIdentityToString return null?. + NumberUtils.min(floatArray) returns wrong value if floatArray[0] happens to be Float.NaN. + Dates.round() behaves incorrectly for minutes and seconds. + StringUtils.length(String) returns null-safe length. + adding a StringUtils.replace method that takes an array or List of replacement strings. + Adding functionality to DateUtils to allow direct setting of various fields. + Add escaping for CSV columns to StringEscapeUtils. + StringUtils: startsWith / endsWith / startsWithIgnoreCase / endsWithIgnoreCase / removeStartIgnoreCase / removeEndIgnoreCase methods. + Extension to ClassUtils: Obtain the primitive class from a wrapper. + Javadoc bugs - cannot find object. + Optimize HashCodeBuilder.append(Object). + https://commons.apache.org/lang/developerguide.html "Building" section is incorrect and incomplete. + Ambiguous / confusing names in StringUtils replace* methods. + Add new splitByWholeSeparatorPreserveAllTokens() methods to StringUtils. + Add getStartTime to StopWatch. + Perhaps add containsAny() methods?. + Javadoc Example for EqualsBuilder is questionable. + EqualsBuilder don't compare BigDecimals correctly. + Split camel case strings. + Add Calendar flavour format methods to DateFormatUtils. + Calculating A date fragment in any time-unit. + Memory usage improvement for StringUtils#getLevenshteinDistance(). + Add ExtendedMessageFormat to org.apache.commons.lang.text. + StringEscapeUtils.escapeJavaScript() method did not escape '/' into '\/', it will make IE render page uncorrectly. + Add toArray() method to IntRange and LongRange classes. + add SystemUtils.IS_OS_WINDOWS_VISTA field. + Pointless synchronized in ThreadLocal.initialValue should be removed. + ToStringStyle Javadoc should show examples of styles. + Documentation bug for ignoreEmptyTokens accessors in StrTokenizer. + BooleanUtils toBooleanObject Javadoc does not match implementation. + truncateNicely method which avoids truncating in the middle of a word. + + + + Use of enum prevents a classloader from being garbage collected resuling in out of memory exceptions. + NumberUtils.max(byte[]) and NumberUtils.min(byte[]) are missing. + Null-safe comparison methods for finding most recent / least recent dates. + StopWatch: suspend() acts as split(), if followed by stop(). + StrBuilder.replaceAll and StrBuilder.deleteAll can throw ArrayIndexOutOfBoundsException. + Bug in method appendFixedWidthPadRight of class StrBuilder causes an ArrayIndexOutOfBoundsException. + ToStringBuilder throws StackOverflowError when an Object cycle exists. + Create more tests to test out the +=31 replacement code in DurationFormatUtils. + StrBuilder contains usages of thisBuf.length when they should use size. + Enum Javadoc: 1) outline 5.0 native Enum migration 2) warn not to use the switch() , 3) point out approaches for persistence and gui. + Wrong behavior of Entities.unescape. + NumberUtils.createNumber throws NumberFormatException for one digit long. + NullPointerException in isAvailableLocale(Locale). + FastDateFormat.mRules is not transient or serializable. + StringUtils.join should allow you to pass a range for it (so it only joins a part of the array). + Refactor Entities methods. + Tests fail to pass when building with Maven 2. + DurationFormatUtils returns wrong result. + unescapeXml("&12345678;") should be "&12345678;". + Optimize StringEscapeUtils.unescapeXml(String). + BooleanUtils isNotTrue/isNotFalse. + Extra StrBuilder methods. + Add a pair of StringUtils.substringsBetween;String[] methods. + HashCodeBuilder throws java.lang.StackOverflowError when an object contains a cycle. + Wish for StringUtils.join(Collection, *). + + + + StrBuilderTest#testReplaceStringString fails. + EqualsBuilder.append(Object[], Object[]) crashes with a NullPointerException if an element of the first array is null. + Serialization - not backwards compatible. + Replace Clover with Cobertura. + ValuedEnum.compareTo(Object other) not typesafe - it easily could be... + LocaleUtils test fails under Mustang. + Javadoc example for StringUtils.splitByWholeSeparator incorrect. + PADDING array in StringUtils overflows on '\uffff'. + ClassUtils.primitiveToWrapper and Void. + unit test for org.apache.commons.lang.text.StrBuilder. + DateUtils.truncate method is buggy when dealing with DST switching hours. + RandomStringUtils.random() family of methods create invalid Unicode sequences. + StringUtils#getLevenshteinDistance() performance is sub-optimal. + Wrong length check in StrTokenizer.StringMatcher. + ExceptionUtils goes into infinite loop in getThrowables is throwable.getCause() == throwable. + FastDateFormat: wrong format for date "01.01.1000". + Unclear Javadoc for DateUtils.iterator(). + Memory "leak" in StringUtils. + StringEscapeUtils should expose escape*() methods taking Writer argument. + Fraction.toProperString() returns -1/1 for -1. + DurationFormatUtils.formatDurationWords "11 <unit>s" gets converted to "11 <unit>". + Performance modifications on StringUtils.replace. + StringEscapeUtils.unescapeHtml skips first entity after standalone ampersand. + DurationFormatUtils.formatPeriod() returns the wrong result. + Request for MutableBoolean implementation. + New method for EqualsBuilder. + New ExceptionUtils method setCause(). + Add Mutable<Type> to<Type>() methods. + Provides a Class.getPublicMethod which returns public invocable Method. + Using ReflectionToStringBuilder and excluding secure fields. + add generic add method to DateUtils. + Tokenizer Enhancements: reset input string, static CSV/TSV factories. + Trivial cleanup of Javadoc in various files. + CompositeFormat. + Performance boost for RandomStringUtils. + Enhanced Class.forName version. + Add StringUtils.containsIgnoreCase(...). + Support char array converters on ArrayUtils. + DurationFormatUtils.formatDurationISO() Javadoc is missing T in duration string between date and time part. + Minor build and checkstyle changes. + Javadoc errors on StringUtils.splitPreserveAllTokens(String, char). + EscapeUtil.escapeHtml() should clarify that it does not escape ' chars to &apos;. + Add methods and tests to StrBuilder. + replace() length calculation improvement. + New interpolation features. + Implementation of escape/unescapeHtml methods with Writer. + CompareToBuilder excludeFields for reflection method. + Add WordUtils.getInitials(String). + Error in an example in the Javadoc of the StringUtils.splitPreserveAllTokens() method. + ToStringBuilder/HashCodeBuilder Javadoc code examples. + Cannot build tests from latest SVN. + minor Javadoc improvements for StringUtils.stripXxx() methods. + Javadoc for StringUtils.removeEnd is incorrect. + Minor tweak to fix of bug # 26616. + + + + make optional parameters in FastDateFormat really optional. + Nestable.indexOfThrowable(Class) uses Class.equals() to match. + buffer under/overrun on Strings.strip, stripStart & stripEnd. + ToStringStyle.setArrayEnd(String) doesn't replace null with empty string. + New class proposal: CharacterEncoding. + SystemUtils fails init on HP-UX. + Javadoc - 'four basic XML entities' should be 5 (apos is missing). + o.a.c.lang.enum.ValuedEnum: 'enum'is a keyword in JDK1.5.0. + StringEscapeUtils.unescapeHtml() doesn't handle an empty entity. + EqualsBuilder.append(Object[], Object[]) incorrectly checks that rhs[i] is instance of lhs[i]'s class. + Method enums.Enum.equals(Object o) doesn't work correctly. + ExceptionUtils.addCauseMethodName(String) does not check for duplicates. + Make StopWatch validate state transitions. + enum package is not compatible with 1.5 jdk. + WordUtils capitalizeFully() throws a null pointer exception. + ValuedEnum. + parseDate class from HttpClient's DateParser class. + ArrayUtils.isEquals() throws ClassCastException when array1 and array2 are different dimension. + ClassCastException in Enum.equals(Object). + FastDateFormat year bug. + unbalanced ReflectionToStringBuilder. + FastDateFormat.getDateInstance(int, Locale) always uses the pattern from the first invocation. + ReflectionToStringBuilder.toString(null) throws exception by design. + Make ClassUtils methods null-safe and not throw an IAE. + StringUtils.split ignores empty items. + EqualsBuilder.append(Object[], Object[]) throws NPE. + ArrayUtils.addAll doesn't always return new array. + Enum.equals does not handle different class loaders. + Add SystemUtils.AWT_TOOLKIT and others. + Throwable cause for NotImplementedException. + ClassUtils.primitivesToWrappers method. + public static boolean DateUtils.equals(Date dt1, Date dt2) ?. + Documentation error in StringUtils.replace. + DateUtils constants should be long. + DateUtils.truncate() is off by one hour when using a date in DST switch 'zone'. + StringEscapeUtils.unescapeHtml() doesn't handle hex entities. + new StringUtils.replaceChars behaves differently from old CharSetUtils.translate. + last substring returned by StringUtils.split( String, String, int ) is too long. + Can't subclass EqualsBuilder because isEquals is private. + new StringUtils.split methods that split on the whole separator string. + New method for converting a primitive Class to its corresponding wrapper Class. + Add convenience format(long) methods to FastDateFormat. + Enum's outer class may not be loaded for EnumUtils. + WordUtils.capitalizeFully(String str) should take a delimiter. + Make Javadoc crosslinking configurable. + Minor Javadoc fixes for StringUtils.contains(String, String). + Error in Javadoc for StringUtils.chomp(String, String). + StringUtils.defaultString: Documentation error. + Add hashCode-support to class ObjectUtils. + add another "known method" to ExceptionUtils. + Enhancement of ExceptionUtils.CAUSE_METHOD_NAMES. + DateUtils.truncate oddity at the far end of the Date spectrum. + add getLength() method to ArrayUtils. + Validate.java: fixes comment skew, removes unused loop counter. + StringUtils.isAsciiPrintable(). + ExceptionUtils: new getCause() methodname (for tomcat-exception). + fixes 75 typos. + mutable numbers. + Javadoc fixes for ClassUtils. + Add StringUtils.nIndexOf?. + Javadoc fixes for CharSetUtils. + Remove redundant check for null separator in StringUtils#join. + Class and Package Comparators for ClassUtils. + add remove methods to ArrayUtils. + WordUtils capitalize improvement. + add isEmpty method to ArrayUtils. + lang.math.Fraction class deficiencies. + Add methods to ArrayUtils: add at end and insert-like ops. + Add SystemUtils methods for directory properties. + Add method that validates Collection elements are a certain type. + elapsed time formatting utility method. + + + + Infinite loop in ToStringBuilder.reflectionToString for inner classes. + NumberUtils.createBigDecimal("") NPE in Sun 1.3.1_08. + Rationalize StringUtils slice functions. + SystemUtils.IS_OS_OS2 Javadoc is wrong. + A small, but important Javadoc fix for Fraction proper whole and numerator. + Adding tolerance to double[] search methods in ArrayUtils. + lang.builder classes Javadoc edits (mostly typo fixes). + StringUtils Javadoc and test enhancements. + SystemUtils.IS_OS_*, IS_JAVA_* are always false. + Improve util.Validate tests. + maven-beta10 checkstyle problem. + StringUtils.chopNewLine - StringIndexOutOfBoundsException. + ToStringBuilder doesn't work well in subclasses. + static option for reversing the stacktrace. + NullPointerException in CompareToBuilder. + RandomStringUtils.randomAlpha methods omit 'z'. + test.time fails in Japanese (non-us) locale. + NumberUtils.isNumber allows illegal trailing characters. + Improve Javadoc and overflow behavior of Fraction. + RandomStringUtils infloops with length > 1. + test.lang fails if compiled with non iso-8859-1 locales. + SystemUtils does not play nice in an Applet. + time unit tests fail on Sundays. + java.lang.ExceptionInInitializerError thrown by JVMRandom constructor. + StringUtils.chomp does not match Perl. + patch and test case fixing problem with RandomStringUtils.random(). + General case: infinite loop: ToStringBuilder.reflectionToString. + Should ToStringBuilder.reflectionToString handle arrays?. + EnumUtils nit: The import java.io.Serializable is never used. + Example in Javadoc for ToStringBuilder wrong for append. + Added class hierarchy support to HashCodeBuilder.reflectionHashCode(). + ExceptionUtils new methods. + Infinite loop in StringUtils.replace(text, repl, with) + FIX. + StackOverflow due to ToStringBuilder. + No Javadoc for NestableDelegate. + Specify initial size for Enum's HashMap. + Enum does not support inner sub-classes. + Removed compile warning in ObjectUtils. + SystemUtils.IS_JAVA_1_5 Javadoc is wrong. + NumberRange inaccurate for Long, etc. + Hierarchy support in ToStringBuilder.reflectionToString(). + StringUtils.countMatches loops forever if substring empty. + Javadoc fixes (remove @links to non-public identifiers). + Add Javadoc examples and tests for StringUtils. + Make NumberUtils null handling consistent. + Unused field 'startFinal' in DateIterator. + reduce object creation in ToStringBuilder. + Improved tests, Javadoc for CharSetUtils, StringEscapeUtils. + NumberUtils min/max, BooleanUtils.xor, and ArrayUtils toPrimitive and toObject. + Javadoc, tests improvements for CharSet, CharSetUtils. + StringUtil enhancement. + Javadoc nit. + Additional Lang Method Suggestions. + Make NestableDelegate methods public instead of package private. + Missing @since tags. + Refactored reflection feature of ToStringBuilder into new ReflectionToStringBuilder. + Typo in documentation. + Patch for Javadoc. + Add join(..., char c) to StringUtils (and some performance fixes). Even contains tests!. + Resurrect the WordWrapUtils from commons-sandbox/utils. + EnumTest fails on Linux Sun JDK 1.3.0. + What to do with FastDateFormat unused private constructors. + Added class hierarchy support to CompareToBuilder.reflectionCompare(). + Removed compile warning in FastDateFormat. + typo in the Javadoc example code. + MethodUtils: Removed unused code/unused local vars. + Hierarchy support in EqualsBuilder.reflectionEquals(). + Javadoc Errata. + ArrayUtils.contains(). + More flexibility for getRootCause in ExceptionUtils. + + + + NumberRange.getMaximum returns minimum. + Enum constructor validations. + NestableException/Delegate is not serializable. + split using null and max less than actual token count adds "null". + ExceptionUtils cannot handle J2EE-Exception in a default way. + + + + + + + diff --git a/after/src/changes/release-notes.vm b/after/src/changes/release-notes.vm new file mode 100644 index 0000000..9e311eb --- /dev/null +++ b/after/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("(?Helper methods for working with {@link Annotation} instances.

+ * + *

This class contains various utility methods that make working with + * annotations simpler.

+ * + *

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

+ * + *

#ThreadSafe#

+ * + * @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) { + final List> interfaces = ClassUtils.getAllInterfaces(cls); + if (interfaces == null) { + return StringUtils.EMPTY; + } + + for (final Class iface : interfaces) { + if (Annotation.class.isAssignableFrom(iface)) { + return "@" + iface.getName(); + } + } + return StringUtils.EMPTY; + } + + /** + * {@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); + } + + }; + + /** + *

{@code AnnotationUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used statically.

+ * + *

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

+ */ + 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 type1 = a1.annotationType(); + final Class 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 IllegalAccessException | InvocationTargetException 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 {@code 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 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 RuntimeException ex) { + throw ex; + } catch (final Exception ex) { + throw new RuntimeException(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 RuntimeException ex) { + throw ex; + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + return builder.build(); + } + + /** + *

Checks if the specified type is permitted as an annotation member.

+ * + *

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.

+ * + * @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 (value.getClass().isArray()) { + 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/after/src/main/java/org/apache/commons/lang3/ArchUtils.java b/after/src/main/java/org/apache/commons/lang3/ArchUtils.java new file mode 100644 index 0000000..20ba2fb --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/ArchUtils.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.stream.Stream; + +import org.apache.commons.lang3.arch.Processor; + +/** + * An utility class for the os.arch System Property. The class defines methods for + * identifying the architecture of the current JVM. + *

+ * Important: The os.arch System Property returns the architecture used by the JVM + * not of the operating system. + *

+ * @since 3.6 + */ +public class ArchUtils { + + private static final Map 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(); + } + + 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) { + Stream.of(keys).forEach(e -> addProcessor(e, processor)); + } + + /** + * Returns a {@link Processor} object of the current JVM. + * + *

+ * Important: The os.arch System Property returns the architecture used by the JVM + * not of the operating system. + *

+ * + * @return A {@link Processor} when supported, else {@code null}. + */ + public static Processor getProcessor() { + return getProcessor(SystemUtils.OS_ARCH); + } + + /** + * Returns a {@link Processor} object the given value {@link String}. The {@link String} must be + * like a value returned by the os.arch System Property. + * + * @param value A {@link String} like a value returned by the 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/after/src/main/java/org/apache/commons/lang3/ArraySorter.java b/after/src/main/java/org/apache/commons/lang3/ArraySorter.java new file mode 100644 index 0000000..2208bd9 --- /dev/null +++ b/after/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 the array type. + * @param array the array to sort. + * @return the given array. + * @see Arrays#sort(Object[]) + */ + public static T[] sort(final T[] array) { + Arrays.sort(array); + return array; + } + + /** + * Sorts and returns the given array. + * + * @param 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[] sort(final T[] array, final Comparator comparator) { + Arrays.sort(array, comparator); + return array; + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/ArrayUtils.java b/after/src/main/java/org/apache/commons/lang3/ArrayUtils.java new file mode 100644 index 0000000..6914abf --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/ArrayUtils.java @@ -0,0 +1,9654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.HashMap; +import java.util.Map; +import java.util.Random; + +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; + +/** + *

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

This class tries to handle {@code null} input gracefully. + * An exception will not be thrown for a {@code null} + * array input. However, an Object array that contains a {@code null} + * element may throw an exception. Each method documents its behavior. + * + *

#ThreadSafe# + * @since 2.0 + */ +public class ArrayUtils { + + /** + * An empty immutable {@code boolean} array. + */ + public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; + + /** + * An empty immutable {@code Boolean} array. + */ + public static final Boolean[] EMPTY_BOOLEAN_OBJECT_ARRAY = new Boolean[0]; + + /** + * An empty immutable {@code byte} array. + */ + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** + * An empty immutable {@code Byte} array. + */ + public static final Byte[] EMPTY_BYTE_OBJECT_ARRAY = new Byte[0]; + + /** + * An empty immutable {@code char} array. + */ + public static final char[] EMPTY_CHAR_ARRAY = new char[0]; + + /** + * An empty immutable {@code Character} array. + */ + public static final Character[] EMPTY_CHARACTER_OBJECT_ARRAY = new Character[0]; + + /** + * An empty immutable {@code Class} array. + */ + public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + /** + * An empty immutable {@code double} array. + */ + public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; + + /** + * An empty immutable {@code Double} array. + */ + public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = new Double[0]; + + /** + * An empty immutable {@code Field} array. + * + * @since 3.10 + */ + public static final Field[] EMPTY_FIELD_ARRAY = new Field[0]; + + /** + * An empty immutable {@code float} array. + */ + public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; + + /** + * An empty immutable {@code Float} array. + */ + public static final Float[] EMPTY_FLOAT_OBJECT_ARRAY = new Float[0]; + + /** + * An empty immutable {@code int} array. + */ + public static final int[] EMPTY_INT_ARRAY = new int[0]; + + /** + * An empty immutable {@code Integer} array. + */ + public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = new Integer[0]; + + /** + * An empty immutable {@code long} array. + */ + public static final long[] EMPTY_LONG_ARRAY = new long[0]; + + /** + * An empty immutable {@code Long} array. + */ + public static final Long[] EMPTY_LONG_OBJECT_ARRAY = new Long[0]; + + /** + * An empty immutable {@code Method} array. + * + * @since 3.10 + */ + public static final Method[] EMPTY_METHOD_ARRAY = new Method[0]; + + /** + * An empty immutable {@code Object} array. + */ + public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + /** + * An empty immutable {@code short} array. + */ + public static final short[] EMPTY_SHORT_ARRAY = new short[0]; + + /** + * An empty immutable {@code Short} array. + */ + public static final Short[] EMPTY_SHORT_OBJECT_ARRAY = new Short[0]; + + /** + * An empty immutable {@code String} array. + */ + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + + /** + * An empty immutable {@code Throwable} array. + * + * @since 3.10 + */ + public static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0]; + + /** + * An empty immutable {@code Type} array. + * + * @since 3.10 + */ + public static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; + + /** + * The index value when an element is not found in a list or array: {@code -1}. + * This value is returned by methods in this class and can also be used in comparisons with values returned by + * various method from {@link java.util.List}. + */ + public static final int INDEX_NOT_FOUND = -1; + + /** + *

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

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

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

+     * ArrayUtils.add(null, true)          = [true]
+     * ArrayUtils.add([true], false)       = [true, false]
+     * ArrayUtils.add([true, false], true) = [true, false, true]
+     * 
+ * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static boolean[] add(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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add(null, 0, true)          = [true]
+     * ArrayUtils.add([true], 0, false)       = [false, true]
+     * ArrayUtils.add([false], 1, true)       = [false, true]
+     * ArrayUtils.add([true, false], 1, true) = [true, true, false]
+     * 
+ * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index > array.length). + * @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. + * + *

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

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

+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static byte[] add(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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add([1], 0, 2)         = [2, 1]
+     * ArrayUtils.add([2, 6], 2, 3)      = [2, 6, 3]
+     * ArrayUtils.add([2, 6], 0, 1)      = [1, 2, 6]
+     * ArrayUtils.add([2, 6, 3], 2, 1)   = [2, 6, 1, 3]
+     * 
+ * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + * @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. + * + *

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

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

+     * ArrayUtils.add(null, '0')       = ['0']
+     * ArrayUtils.add(['1'], '0')      = ['1', '0']
+     * ArrayUtils.add(['1', '0'], '1') = ['1', '0', '1']
+     * 
+ * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static char[] add(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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add(null, 0, 'a')            = ['a']
+     * ArrayUtils.add(['a'], 0, 'b')           = ['b', 'a']
+     * ArrayUtils.add(['a', 'b'], 0, 'c')      = ['c', 'a', 'b']
+     * ArrayUtils.add(['a', 'b'], 1, 'k')      = ['a', 'k', 'b']
+     * ArrayUtils.add(['a', 'b', 'c'], 1, 't') = ['a', 't', 'b', 'c']
+     * 
+ * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + * @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. + * + *

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

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

+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static double[] add(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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add([1.1], 0, 2.2)              = [2.2, 1.1]
+     * ArrayUtils.add([2.3, 6.4], 2, 10.5)        = [2.3, 6.4, 10.5]
+     * ArrayUtils.add([2.6, 6.7], 0, -4.8)        = [-4.8, 2.6, 6.7]
+     * ArrayUtils.add([2.9, 6.0, 0.3], 2, 1.0)    = [2.9, 6.0, 1.0, 0.3]
+     * 
+ * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + * @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. + * + *

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

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

+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static float[] add(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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add([1.1f], 0, 2.2f)               = [2.2f, 1.1f]
+     * ArrayUtils.add([2.3f, 6.4f], 2, 10.5f)        = [2.3f, 6.4f, 10.5f]
+     * ArrayUtils.add([2.6f, 6.7f], 0, -4.8f)        = [-4.8f, 2.6f, 6.7f]
+     * ArrayUtils.add([2.9f, 6.0f, 0.3f], 2, 1.0f)   = [2.9f, 6.0f, 1.0f, 0.3f]
+     * 
+ * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + * @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. + * + *

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

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

+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static int[] add(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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add([1], 0, 2)         = [2, 1]
+     * ArrayUtils.add([2, 6], 2, 10)     = [2, 6, 10]
+     * ArrayUtils.add([2, 6], 0, -4)     = [-4, 2, 6]
+     * ArrayUtils.add([2, 6, 3], 2, 1)   = [2, 6, 1, 3]
+     * 
+ * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + * @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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add([1L], 0, 2L)           = [2L, 1L]
+     * ArrayUtils.add([2L, 6L], 2, 10L)      = [2L, 6L, 10L]
+     * ArrayUtils.add([2L, 6L], 0, -4L)      = [-4L, 2L, 6L]
+     * ArrayUtils.add([2L, 6L, 3L], 2, 1L)   = [2L, 6L, 1L, 3L]
+     * 
+ * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + * @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. + * + *

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

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

+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static long[] add(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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add([1], 0, 2)         = [2, 1]
+     * ArrayUtils.add([2, 6], 2, 10)     = [2, 6, 10]
+     * ArrayUtils.add([2, 6], 0, -4)     = [-4, 2, 6]
+     * ArrayUtils.add([2, 6, 3], 2, 1)   = [2, 6, 1, 3]
+     * 
+ * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + * @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. + * + *

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

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

+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static short[] add(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). + * + *

This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.add(null, 0, null)      = 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"]
+     * 
+ * + * @param the component type of the array + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index > array.length). + * @throws IllegalArgumentException if both array and element are null + * @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[] add(final T[] array, final int index, final T element) { + Class clss = null; + if (array != null) { + clss = array.getClass().getComponentType(); + } else if (element != null) { + clss = element.getClass(); + } else { + throw new IllegalArgumentException("Array and element cannot both be null"); + } + @SuppressWarnings("unchecked") // the add method creates an array of type clss, which is type T + final T[] newArray = (T[]) add(array, index, element, clss); + return newArray; + } + + /** + *

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

The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array. + * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element, unless the element itself is null, + * in which case the return type is Object[] + * + *

+     * ArrayUtils.add(null, null)      = 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"]
+     * 
+ * + * @param the component type of the array + * @param array the array to "add" the element to, may be {@code null} + * @param element the object to add, may be {@code null} + * @return A new array containing the existing elements plus the new element + * The returned array type will be that of the input array (unless null), + * in which case it will have the same type as the element. + * If both are null, an IllegalArgumentException is thrown + * @since 2.1 + * @throws IllegalArgumentException if both arguments are null + */ + public static T[] add(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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new boolean[] array. + * @since 2.1 + */ + public static boolean[] addAll(final boolean[] array1, final boolean... array2) { + if (array1 == null) { + return clone(array2); + } else 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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new byte[] array. + * @since 2.1 + */ + public static byte[] addAll(final byte[] array1, final byte... array2) { + if (array1 == null) { + return clone(array2); + } else 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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new char[] array. + * @since 2.1 + */ + public static char[] addAll(final char[] array1, final char... array2) { + if (array1 == null) { + return clone(array2); + } else 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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new double[] array. + * @since 2.1 + */ + public static double[] addAll(final double[] array1, final double... array2) { + if (array1 == null) { + return clone(array2); + } else 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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new float[] array. + * @since 2.1 + */ + public static float[] addAll(final float[] array1, final float... array2) { + if (array1 == null) { + return clone(array2); + } else 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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new int[] array. + * @since 2.1 + */ + public static int[] addAll(final int[] array1, final int... array2) { + if (array1 == null) { + return clone(array2); + } else 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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new long[] array. + * @since 2.1 + */ + public static long[] addAll(final long[] array1, final long... array2) { + if (array1 == null) { + return clone(array2); + } else 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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new short[] array. + * @since 2.1 + */ + public static short[] addAll(final short[] array1, final short... array2) { + if (array1 == null) { + return clone(array2); + } else 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. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(null, null)     = null
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * ArrayUtils.addAll([null], [null]) = [null, null]
+     * ArrayUtils.addAll(["a", "b", "c"], ["1", "2", "3"]) = ["a", "b", "c", "1", "2", "3"]
+     * 
+ * + * @param the component type of the array + * @param array1 the first array whose elements are added to the new array, may be {@code null} + * @param array2 the second array whose elements are added to the new array, may be {@code null} + * @return The new array, {@code null} if both arrays are {@code null}. + * The type of the new array is the type of the first array, + * unless the first array is null, in which case the type is the same as the second array. + * @since 2.1 + * @throws IllegalArgumentException if the array types are incompatible + */ + public static T[] addAll(final T[] array1, @SuppressWarnings("unchecked") final T... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final Class type1 = array1.getClass().getComponentType(); + @SuppressWarnings("unchecked") // OK, because array is of type T + final T[] joinedArray = (T[]) Array.newInstance(type1, array1.length + array2.length); + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + try { + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + } catch (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. + * + *

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

+ * + *

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

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

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

+ * + *

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

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

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

+ * + *

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

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

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

+ * + *

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

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

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

+ * + *

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

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

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

+ * + *

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

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

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

+ * + *

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

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

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

+ * + *

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

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

+ * The new array contains the same elements of the input array plus the given element in the first positioaddFirstaddFirstaddFirstn. The + * component type of the new array is the same as that of the input array. + *

+ * + *

+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the + * element, unless the element itself is null, in which case the return type is Object[] + *

+ * + *
+     * ArrayUtils.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"]
+     * 
+ * + * @param the component type of the array + * @param array the array to "add" the element to, may be {@code null} + * @param element the object to add, may be {@code null} + * @return A new array containing the existing elements plus the new element The returned array type will be that of + * the input array (unless null), in which case it will have the same type as the element. If both are null, + * an IllegalArgumentException is thrown + * @since 3.10 + * @throws IllegalArgumentException if both arguments are null + */ + public static 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}. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static boolean[] clone(final boolean[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static byte[] clone(final byte[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static char[] clone(final char[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static double[] clone(final double[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static float[] clone(final float[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static int[] clone(final int[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static long[] clone(final long[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static short[] clone(final short[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + // Clone + //----------------------------------------------------------------------- + /** + *

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

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

This method returns {@code null} for a {@code null} input array. + * + * @param the component type of the array + * @param array the array to shallow clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static T[] clone(final T[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

Checks if the value is in the given array. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final boolean[] array, final boolean valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + /** + *

Checks if the value is in the given array. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final byte[] array, final byte valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + /** + *

Checks if the value is in the given array. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + * @since 2.1 + */ + public static boolean contains(final char[] array, final char valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + /** + *

Checks if the value is in the given array. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(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). + * + *

The method returns {@code false} if a {@code null} array + * is passed in. + * + * @param array the array to search + * @param valueToFind the value to find + * @param tolerance the array contains the tolerance of the search + * @return true if value falling within tolerance is in array + */ + public static boolean contains(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. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final float[] array, final float valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + /** + *

Checks if the value is in the given array. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final int[] array, final int valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + /** + *

Checks if the value is in the given array. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final long[] array, final long valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + /** + *

Checks if the object is in the given array. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param objectToFind the object to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final Object[] array, final Object objectToFind) { + return indexOf(array, objectToFind) != INDEX_NOT_FOUND; + } + + /** + *

Checks if the value is in the given array. + * + *

The method returns {@code false} if a {@code null} array is passed in. + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final short[] array, final short valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + /** + * 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 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 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 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 get(final T[] array, final int index, final T defaultValue) { + return isArrayIndexValid(array, index) ? array[index] : defaultValue; + } + + //----------------------------------------------------------------------- + /** + *

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

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

+     * ArrayUtils.getLength(null)            = 0
+     * ArrayUtils.getLength([])              = 0
+     * ArrayUtils.getLength([null])          = 1
+     * ArrayUtils.getLength([true, false])   = 2
+     * ArrayUtils.getLength([1, 2, 3])       = 3
+     * ArrayUtils.getLength(["a", "b", "c"]) = 3
+     * 
+ * + * @param array the array to retrieve the length from, may be null + * @return The length of the array, or {@code 0} if the array is {@code null} + * @throws IllegalArgumentException if the object argument is not an array. + * @since 2.1 + */ + public static int getLength(final Object array) { + if (array == null) { + return 0; + } + return Array.getLength(array); + } + + /** + *

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

Multi-dimensional primitive arrays are also handled correctly by this method. + * + * @param array the array to get a hash code for, {@code null} returns zero + * @return a hash code for the array + */ + public static int hashCode(final Object array) { + return new HashCodeBuilder().append(array).toHashCode(); + } + + /** + * Finds the indices of the given value in the array. + * + *

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the 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. + * + *

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

+ * + *

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return 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. + * + *

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return 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. + * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

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

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return 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. + * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

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

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return 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. + * + *

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

+ * + *

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param tolerance tolerance of the search + * @return 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. + * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

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

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

+ * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

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

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return 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. + * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return empty BitSet.

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

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return 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. + * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

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

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return 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. + * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

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

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param objectToFind the object to find, may be {@code null} + * @return 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. + * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

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

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

+ * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return 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. + * + *

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

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

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

Finds the index of the given value in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} + * array input + */ + public static int indexOf(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; + } + + // byte IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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; + } + + // char IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + * @since 2.1 + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + * @since 2.1 + */ + public static int indexOf(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; + } + + // double IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param tolerance tolerance of the search + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @param tolerance tolerance of the search + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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; + } + + // float IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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; + } + + // int IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ +public static int indexOf(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; +} + + // long IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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; + } + + // Object IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given object in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param objectToFind the object to find, may be {@code null} + * @return the index of the object within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param objectToFind the object to find, may be {@code null} + * @param startIndex the index to start searching at + * @return the index of the object within the array starting at the index, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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; + } + + // short IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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. + * + *

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

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}). + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @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).

+ * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param 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[] 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 type = array.getClass().getComponentType(); + @SuppressWarnings("unchecked") // OK, because array and values are of type T + final + T[] result = (T[]) Array.newInstance(type, 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; + } + + /** + * Returns whether a given array can safely be accessed at the given index. + * + *
+     * ArrayUtils.isArrayIndexValid(null, 0)       = false
+     * ArrayUtils.isArrayIndexValid([], 0)         = false
+     * ArrayUtils.isArrayIndexValid(["a"], 0)      = true
+     * 
+ * + * @param 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 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 getLength(array) == 0; + } + + /** + *

Checks if an array of primitive bytes is empty or {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final byte[] array) { + return getLength(array) == 0; + } + + // IndexOf search + // ---------------------------------------------------------------------- + + /** + *

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 getLength(array) == 0; + } + + /** + *

Checks if an array of primitive doubles is empty or {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final double[] array) { + return getLength(array) == 0; + } + + /** + *

Checks if an array of primitive floats is empty or {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final float[] array) { + return getLength(array) == 0; + } + + /** + *

Checks if an array of primitive ints is empty or {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final int[] array) { + return getLength(array) == 0; + } + + + + /** + *

Checks if an array of primitive longs is empty or {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final long[] array) { + return getLength(array) == 0; + } + + // ---------------------------------------------------------------------- + /** + *

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 getLength(array) == 0; + } + + /** + *

Checks if an array of primitive shorts is empty or {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final short[] array) { + return getLength(array) == 0; + } + + /** + *

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

Multi-dimensional primitive arrays are also handled correctly by this method. + * + * @param array1 the left hand array to compare, may be {@code null} + * @param array2 the right hand array to compare, may be {@code null} + * @return {@code true} if the arrays are equal + * @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 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 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}. + * + *

Any multi-dimensional aspects of the arrays are ignored. + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + * @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}. + * + *

Any multi-dimensional aspects of the arrays are ignored. + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(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 + * multi-dimensional arrays. + * + * @param array1 the first array, must not be {@code null} + * @param array2 the second array, must not be {@code null} + * @return {@code true} if type of arrays matches + * @throws IllegalArgumentException if either array is {@code null} + */ + public static boolean isSameType(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 (array == null || array.length < 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; + } + + /** + *

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 byte[] array) { + if (array == null || array.length < 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; + } + + /** + *

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 char[] array) { + if (array == null || array.length < 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 (array == null || array.length < 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 (array == null || array.length < 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 (array == null || array.length < 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 (array == null || array.length < 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 (array == null || array.length < 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 the datatype of the array to check, it must implement {@code Comparable} + * @return whether the array is sorted + * @since 3.4 + */ + public static > boolean isSorted(final T[] array) { + return isSorted(array, Comparable::compareTo); + } + + /** + *

This method checks whether the provided array is sorted according to the provided {@code Comparator}. + * + * @param array the array to check + * @param comparator the {@code Comparator} to compare over + * @param the datatype of the array + * @return whether the array is sorted + * @since 3.4 + */ + public static boolean isSorted(final T[] array, final Comparator comparator) { + if (comparator == null) { + throw new IllegalArgumentException("Comparator should not be null."); + } + + if (array == null || array.length < 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. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) if + * {@code null} array input. + * + * @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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than + * the array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

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

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

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

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

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

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @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. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param tolerance tolerance of the search + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

Finds the last index of the given value in the array starting at the given index. + * This method will return the index of the last value which falls between the region + * defined by valueToFind - tolerance and valueToFind + tolerance. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else 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. + * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

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

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

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

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

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

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than + * the array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param objectToFind the object to find, may be {@code null} + * @param startIndex the start index to 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) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + if (objectToFind == null) { + for (int i = startIndex; i >= 0; i--) { + if (array[i] == null) { + return i; + } + } + } else if (array.getClass().getComponentType().isInstance(objectToFind)) { + for (int i = startIndex; i >= 0; i--) { + if (objectToFind.equals(array[i])) { + return i; + } + } + } + return INDEX_NOT_FOUND; + } + + /** + *

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

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + * + * @param array the array to 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. + * + *

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

A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array. + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to 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) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static boolean[] nullToEmpty(final boolean[] array) { + if (isEmpty(array)) { + return EMPTY_BOOLEAN_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Boolean[] nullToEmpty(final Boolean[] array) { + if (isEmpty(array)) { + return EMPTY_BOOLEAN_OBJECT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static byte[] nullToEmpty(final byte[] array) { + if (isEmpty(array)) { + return EMPTY_BYTE_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Byte[] nullToEmpty(final Byte[] array) { + if (isEmpty(array)) { + return EMPTY_BYTE_OBJECT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static char[] nullToEmpty(final char[] array) { + if (isEmpty(array)) { + return EMPTY_CHAR_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Character[] nullToEmpty(final Character[] array) { + if (isEmpty(array)) { + return EMPTY_CHARACTER_OBJECT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 3.2 + */ + public static Class[] nullToEmpty(final Class[] array) { + if (isEmpty(array)) { + return EMPTY_CLASS_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static double[] nullToEmpty(final double[] array) { + if (isEmpty(array)) { + return EMPTY_DOUBLE_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Double[] nullToEmpty(final Double[] array) { + if (isEmpty(array)) { + return EMPTY_DOUBLE_OBJECT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static float[] nullToEmpty(final float[] array) { + if (isEmpty(array)) { + return EMPTY_FLOAT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Float[] nullToEmpty(final Float[] array) { + if (isEmpty(array)) { + return EMPTY_FLOAT_OBJECT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static int[] nullToEmpty(final int[] array) { + if (isEmpty(array)) { + return EMPTY_INT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Integer[] nullToEmpty(final Integer[] array) { + if (isEmpty(array)) { + return EMPTY_INTEGER_OBJECT_ARRAY; + } + return array; + } + + // Primitive/Object array converters + // ---------------------------------------------------------------------- + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static long[] nullToEmpty(final long[] array) { + if (isEmpty(array)) { + return EMPTY_LONG_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Long[] nullToEmpty(final Long[] array) { + if (isEmpty(array)) { + return EMPTY_LONG_OBJECT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Object[] nullToEmpty(final Object[] array) { + if (isEmpty(array)) { + return EMPTY_OBJECT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static short[] nullToEmpty(final short[] array) { + if (isEmpty(array)) { + return EMPTY_SHORT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Short[] nullToEmpty(final Short[] array) { + if (isEmpty(array)) { + return EMPTY_SHORT_OBJECT_ARRAY; + } + return array; + } + + /** + *

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

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

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class. + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static String[] nullToEmpty(final String[] array) { + if (isEmpty(array)) { + return EMPTY_STRING_ARRAY; + } + return array; + } + + // nullToEmpty + //----------------------------------------------------------------------- + /** + *

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

This method returns an empty array for a {@code null} input array. + * + * @param array the array to check for {@code null} or empty + * @param type the class representation of the desired array + * @param 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[] nullToEmpty(final T[] array, final Class 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; + } + + /** + *

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

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove([true], 0)              = []
+     * ArrayUtils.remove([true, false], 0)       = [false]
+     * ArrayUtils.remove([true, false], 1)       = [true]
+     * ArrayUtils.remove([true, true, false], 1) = [true, false]
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static boolean[] remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove([1], 0)          = []
+     * ArrayUtils.remove([1, 0], 0)       = [0]
+     * ArrayUtils.remove([1, 0], 1)       = [1]
+     * ArrayUtils.remove([1, 0, 1], 1)    = [1, 1]
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static byte[] remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove(['a'], 0)           = []
+     * ArrayUtils.remove(['a', 'b'], 0)      = ['b']
+     * ArrayUtils.remove(['a', 'b'], 1)      = ['a']
+     * ArrayUtils.remove(['a', 'b', 'c'], 1) = ['a', 'c']
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static char[] remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove([1.1], 0)           = []
+     * ArrayUtils.remove([2.5, 6.0], 0)      = [6.0]
+     * ArrayUtils.remove([2.5, 6.0], 1)      = [2.5]
+     * ArrayUtils.remove([2.5, 6.0, 3.8], 1) = [2.5, 3.8]
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static double[] remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove([1.1], 0)           = []
+     * ArrayUtils.remove([2.5, 6.0], 0)      = [6.0]
+     * ArrayUtils.remove([2.5, 6.0], 1)      = [2.5]
+     * ArrayUtils.remove([2.5, 6.0, 3.8], 1) = [2.5, 3.8]
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static float[] remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove([1], 0)         = []
+     * ArrayUtils.remove([2, 6], 0)      = [6]
+     * ArrayUtils.remove([2, 6], 1)      = [2]
+     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static int[] remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove([1], 0)         = []
+     * ArrayUtils.remove([2, 6], 0)      = [6]
+     * ArrayUtils.remove([2, 6], 1)      = [2]
+     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static long[] remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified. + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + private static Object remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove([1], 0)         = []
+     * ArrayUtils.remove([2, 6], 0)      = [6]
+     * ArrayUtils.remove([2, 6], 1)      = [2]
+     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static short[] remove(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). + * + *

This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.remove(["a"], 0)           = []
+     * ArrayUtils.remove(["a", "b"], 0)      = ["b"]
+     * ArrayUtils.remove(["a", "b"], 1)      = ["a"]
+     * ArrayUtils.remove(["a", "b", "c"], 1) = ["a", "c"]
+     * 
+ * + * @param the component type of the array + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + @SuppressWarnings("unchecked") // remove() always creates an array of the same type as its input + public static T[] remove(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll([true, false, true], 0, 2) = [false]
+     * ArrayUtils.removeAll([true, false, true], 1, 2) = [true]
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static boolean[] removeAll(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll([1], 0)             = []
+     * ArrayUtils.removeAll([2, 6], 0)          = [6]
+     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
+     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static byte[] removeAll(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll([1], 0)             = []
+     * ArrayUtils.removeAll([2, 6], 0)          = [6]
+     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
+     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static char[] removeAll(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll([1], 0)             = []
+     * ArrayUtils.removeAll([2, 6], 0)          = [6]
+     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
+     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static double[] removeAll(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll([1], 0)             = []
+     * ArrayUtils.removeAll([2, 6], 0)          = [6]
+     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
+     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static float[] removeAll(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll([1], 0)             = []
+     * ArrayUtils.removeAll([2, 6], 0)          = [6]
+     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
+     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static int[] removeAll(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll([1], 0)             = []
+     * ArrayUtils.removeAll([2, 6], 0)          = [6]
+     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
+     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static long[] removeAll(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll([1], 0)             = []
+     * ArrayUtils.removeAll([2, 6], 0)          = [6]
+     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
+     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
+     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+     * 
+ * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static short[] removeAll(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. + * + *

This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array. + * + *

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

+     * ArrayUtils.removeAll(["a", "b", "c"], 0, 2) = ["b"]
+     * ArrayUtils.removeAll(["a", "b", "c"], 1, 2) = ["a"]
+     * 
+ * + * @param the component type of the array + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + @SuppressWarnings("unchecked") // removeAll() always creates an array of the same type as its input + public static T[] removeAll(final T[] array, final int... indices) { + return (T[]) removeAll((Object) array, indices); + } + + /** + * Removes the occurrences of the specified element from the specified boolean array. + * + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param 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[] 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. + * + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

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

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contains such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param 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[] 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, true)                = null
+     * ArrayUtils.removeElement([], true)                  = []
+     * ArrayUtils.removeElement([true], false)             = [true]
+     * ArrayUtils.removeElement([true, false], false)      = [true]
+     * ArrayUtils.removeElement([true, false, true], true) = [false, true]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static boolean[] removeElement(final boolean[] array, final boolean element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, 1)        = null
+     * ArrayUtils.removeElement([], 1)          = []
+     * ArrayUtils.removeElement([1], 0)         = [1]
+     * ArrayUtils.removeElement([1, 0], 0)      = [1]
+     * ArrayUtils.removeElement([1, 0, 1], 1)   = [0, 1]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static byte[] removeElement(final byte[] array, final byte element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, 'a')            = null
+     * ArrayUtils.removeElement([], 'a')              = []
+     * ArrayUtils.removeElement(['a'], 'b')           = ['a']
+     * ArrayUtils.removeElement(['a', 'b'], 'a')      = ['b']
+     * ArrayUtils.removeElement(['a', 'b', 'a'], 'a') = ['b', 'a']
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static char[] removeElement(final char[] array, final char element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, 1.1)            = null
+     * ArrayUtils.removeElement([], 1.1)              = []
+     * ArrayUtils.removeElement([1.1], 1.2)           = [1.1]
+     * ArrayUtils.removeElement([1.1, 2.3], 1.1)      = [2.3]
+     * ArrayUtils.removeElement([1.1, 2.3, 1.1], 1.1) = [2.3, 1.1]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static double[] removeElement(final double[] array, final double element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, 1.1)            = null
+     * ArrayUtils.removeElement([], 1.1)              = []
+     * ArrayUtils.removeElement([1.1], 1.2)           = [1.1]
+     * ArrayUtils.removeElement([1.1, 2.3], 1.1)      = [2.3]
+     * ArrayUtils.removeElement([1.1, 2.3, 1.1], 1.1) = [2.3, 1.1]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static float[] removeElement(final float[] array, final float element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, 1)      = null
+     * ArrayUtils.removeElement([], 1)        = []
+     * ArrayUtils.removeElement([1], 2)       = [1]
+     * ArrayUtils.removeElement([1, 3], 1)    = [3]
+     * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static int[] removeElement(final int[] array, final int element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, 1)      = null
+     * ArrayUtils.removeElement([], 1)        = []
+     * ArrayUtils.removeElement([1], 2)       = [1]
+     * ArrayUtils.removeElement([1, 3], 1)    = [3]
+     * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static long[] removeElement(final long[] array, final long element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, 1)      = null
+     * ArrayUtils.removeElement([], 1)        = []
+     * ArrayUtils.removeElement([1], 2)       = [1]
+     * ArrayUtils.removeElement([1, 3], 1)    = [3]
+     * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static short[] removeElement(final short[] array, final short element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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 contains + * such an element, no elements are removed from the array. + * + *

This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array. + * + *

+     * ArrayUtils.removeElement(null, "a")            = null
+     * ArrayUtils.removeElement([], "a")              = []
+     * ArrayUtils.removeElement(["a"], "b")           = ["a"]
+     * ArrayUtils.removeElement(["a", "b"], "a")      = ["b"]
+     * ArrayUtils.removeElement(["a", "b", "a"], "a") = ["b", "a"]
+     * 
+ * + * @param the component type of the array + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static T[] removeElement(final T[] array, final Object element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, true, false)               = null
+     * ArrayUtils.removeElements([], true, false)                 = []
+     * ArrayUtils.removeElements([true], false, false)            = [true]
+     * ArrayUtils.removeElements([true, false], true, true)       = [false]
+     * ArrayUtils.removeElements([true, false, true], true)       = [false, true]
+     * ArrayUtils.removeElements([true, false, true], true, true) = [false]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static boolean[] removeElements(final boolean[] array, final boolean... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, 1, 2)      = null
+     * ArrayUtils.removeElements([], 1, 2)        = []
+     * ArrayUtils.removeElements([1], 2, 3)       = [1]
+     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
+     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
+     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static byte[] removeElements(final byte[] array, final byte... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final Map 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, 1, 2)      = null
+     * ArrayUtils.removeElements([], 1, 2)        = []
+     * ArrayUtils.removeElements([1], 2, 3)       = [1]
+     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
+     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
+     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static char[] removeElements(final char[] array, final char... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, 1, 2)      = null
+     * ArrayUtils.removeElements([], 1, 2)        = []
+     * ArrayUtils.removeElements([1], 2, 3)       = [1]
+     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
+     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
+     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static double[] removeElements(final double[] array, final double... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, 1, 2)      = null
+     * ArrayUtils.removeElements([], 1, 2)        = []
+     * ArrayUtils.removeElements([1], 2, 3)       = [1]
+     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
+     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
+     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static float[] removeElements(final float[] array, final float... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, 1, 2)      = null
+     * ArrayUtils.removeElements([], 1, 2)        = []
+     * ArrayUtils.removeElements([1], 2, 3)       = [1]
+     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
+     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
+     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static int[] removeElements(final int[] array, final int... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, 1, 2)      = null
+     * ArrayUtils.removeElements([], 1, 2)        = []
+     * ArrayUtils.removeElements([1], 2, 3)       = [1]
+     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
+     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
+     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static long[] removeElements(final long[] array, final long... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, 1, 2)      = null
+     * ArrayUtils.removeElements([], 1, 2)        = []
+     * ArrayUtils.removeElements([1], 2, 3)       = [1]
+     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
+     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
+     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+     * 
+ * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static short[] removeElements(final short[] array, final short... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap 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. + * + *

This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array. + * + *

+     * ArrayUtils.removeElements(null, "a", "b")            = null
+     * ArrayUtils.removeElements([], "a", "b")              = []
+     * ArrayUtils.removeElements(["a"], "b", "c")           = ["a"]
+     * ArrayUtils.removeElements(["a", "b"], "a", "c")      = ["b"]
+     * ArrayUtils.removeElements(["a", "b", "a"], "a")      = ["b", "a"]
+     * ArrayUtils.removeElements(["a", "b", "a"], "a", "a") = ["b"]
+     * 
+ * + * @param the component type of the array + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + @SafeVarargs + public static T[] removeElements(final T[] array, final T... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap 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. + * + *

This method does nothing for a {@code null} input array. + * + * @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. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>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. + * + *

This method does nothing for a {@code null} input array. + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final byte[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

+ * Reverses the order of the given array in the given range. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>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. + * + *

This method does nothing for a {@code null} input array. + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final char[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

+ * Reverses the order of the given array in the given range. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>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. + * + *

This method does nothing for a {@code null} input array. + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final double[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

+ * Reverses the order of the given array in the given range. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>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. + * + *

This method does nothing for a {@code null} input array. + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final float[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

+ * Reverses the order of the given array in the given range. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>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. + * + *

This method does nothing for a {@code null} input array. + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final int[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

+ * Reverses the order of the given array in the given range. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>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. + * + *

This method does nothing for a {@code null} input array. + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final long[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

+ * Reverses the order of the given array in the given range. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>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. + * + *

There is no special handling for multi-dimensional arrays. + * + *

This method does nothing for a {@code null} input array. + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final Object[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

+ * Reverses the order of the given array in the given range. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Under value (<0) is promoted to 0, over value (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Under value (< start index) results in no + * change. Over value (>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. + * + *

This method does nothing for a {@code null} input array. + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final short[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

+ * Reverses the order of the given array in the given range. + * + *

+ * This method does nothing for a {@code null} input array. + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>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++; + } + } + + /** + * Shifts the order of the given boolean array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given boolean array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given byte array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given char array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given double array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given float array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given int array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given long array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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; + } + } + } + + // Shift + //----------------------------------------------------------------------- + /** + * Shifts the order of the given array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @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) { + return; + } + shift(array, 0, array.length, offset); + } + + /** + * Shifts the order of a series of elements in the given short array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>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) { + return; + } + if (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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final boolean[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final byte[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final char[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final double[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final float[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final int[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final long[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final Object[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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 Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final short[] array) { + shuffle(array, new 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 Fisher-Yates shuffle algorithm + * @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. + * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output. + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @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. + * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output. + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @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. + * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output. + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @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. + * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output. + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @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. + * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output. + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @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. + * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output. + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @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. + * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output. + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @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. + * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output. + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @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; + } + + // Subarrays + //----------------------------------------------------------------------- + /** + *

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

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

The component type of the subarray is always the same as + * that of the input array. Thus, if the input is an array of type + * {@code Date}, the following usage is envisaged: + * + *

+     * Date[] someDates = (Date[]) ArrayUtils.subarray(allDates, 2, 5);
+     * 
+ * + * @param the component type of the array + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(Object[], int, int) + */ + public static 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 type = array.getClass().getComponentType(); + if (newSize <= 0) { + @SuppressWarnings("unchecked") // OK, because array is of type T + final T[] emptyArray = (T[]) Array.newInstance(type, 0); + return emptyArray; + } + @SuppressWarnings("unchecked") // OK, because array is of type T + final + T[] subarray = (T[]) Array.newInstance(type, newSize); + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + * Swaps two elements in the given boolean array. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given boolean array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([true, false, true, false], 0, 2, 1) -> [true, false, true, false]
  • + *
  • ArrayUtils.swap([true, false, true, false], 0, 0, 1) -> [true, false, true, false]
  • + *
  • ArrayUtils.swap([true, false, true, false], 0, 2, 2) -> [true, false, true, false]
  • + *
  • ArrayUtils.swap([true, false, true, false], -3, 2, 2) -> [true, false, true, false]
  • + *
  • ArrayUtils.swap([true, false, true, false], 0, 3, 3) -> [false, false, true, true]
  • + *
+ * + * @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. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given byte array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @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. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given char array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @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. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given double array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @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. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given float array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @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. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given int array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @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. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([true, false, true], 0, 2) -> [true, false, true]
  • + *
  • ArrayUtils.swap([true, false, true], 0, 0) -> [true, false, true]
  • + *
  • ArrayUtils.swap([true, false, true], 1, 0) -> [false, true, true]
  • + *
  • ArrayUtils.swap([true, false, true], 0, 5) -> [true, false, true]
  • + *
  • ArrayUtils.swap([true, false, true], -1, 1) -> [false, true, true]
  • + *
+ * + * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given long array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @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; + } + } + + // Swap + //----------------------------------------------------------------------- + /** + * Swaps two elements in the given array. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap(["1", "2", "3"], 0, 2) -> ["3", "2", "1"]
  • + *
  • ArrayUtils.swap(["1", "2", "3"], 0, 0) -> ["1", "2", "3"]
  • + *
  • ArrayUtils.swap(["1", "2", "3"], 1, 0) -> ["2", "1", "3"]
  • + *
  • ArrayUtils.swap(["1", "2", "3"], 0, 5) -> ["1", "2", "3"]
  • + *
  • ArrayUtils.swap(["1", "2", "3"], -1, 1) -> ["2", "1", "3"]
  • + *
+ * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap(["1", "2", "3", "4"], 0, 2, 1) -> ["3", "2", "1", "4"]
  • + *
  • ArrayUtils.swap(["1", "2", "3", "4"], 0, 0, 1) -> ["1", "2", "3", "4"]
  • + *
  • ArrayUtils.swap(["1", "2", "3", "4"], 2, 0, 2) -> ["3", "4", "1", "2"]
  • + *
  • ArrayUtils.swap(["1", "2", "3", "4"], -3, 2, 2) -> ["3", "4", "1", "2"]
  • + *
  • ArrayUtils.swap(["1", "2", "3", "4"], 0, 3, 3) -> ["4", "2", "3", "1"]
  • + *
+ * + * @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. + * + *

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

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @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) { + if (isEmpty(array)) { + return; + } + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given short array. + * + *

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.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @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; + } + } + + // Generic array + //----------------------------------------------------------------------- + /** + *

Create a type-safe generic array. + * + *

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

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

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

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

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

Note, this method makes only sense to provide arguments of the same type so that the + * compiler can deduce the type of the array itself. While it is possible to select the + * type explicitly like in + * {@code Number[] array = ArrayUtils.<Number>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)}}. + * + * @param the array's element type + * @param items the varargs array items, null allowed + * @return the array, not null unless a null array is passed in + * @since 3.0 + */ + public static T[] toArray(@SuppressWarnings("unchecked") final T... items) { + return items; + } + + // To map + //----------------------------------------------------------------------- + /** + *

Converts the given array into a {@link java.util.Map}. Each element of the array + * must be either a {@link java.util.Map.Entry} or an Array, containing at least two + * elements, where the first element is used as key and the second as + * value. + * + *

This method can be used to initialize: + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array an array whose elements are either a {@link java.util.Map.Entry} or + * an Array containing at least two elements, may be {@code null} + * @return a {@code Map} that was created from the array + * @throws IllegalArgumentException if one element of this Array is + * itself an Array containing less then two elements + * @throws IllegalArgumentException if the array contains elements other + * than {@link java.util.Map.Entry} and an Array + */ + public static Map toMap(final Object[] array) { + if (array == null) { + return null; + } + final Map map = new HashMap<>((int) (array.length * 1.5)); + for (int i = 0; i < array.length; i++) { + 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. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code boolean} array + * @return a {@code Boolean} array, {@code null} if null array input + */ + public static Boolean[] toObject(final boolean[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BOOLEAN_OBJECT_ARRAY; + } + final Boolean[] result = new Boolean[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = (array[i] ? Boolean.TRUE : Boolean.FALSE); + } + return result; + } + + /** + *

Converts an array of primitive bytes to objects. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code byte} array + * @return a {@code Byte} array, {@code null} if null array input + */ + public static Byte[] toObject(final byte[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BYTE_OBJECT_ARRAY; + } + final Byte[] result = new Byte[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Byte.valueOf(array[i]); + } + return result; + } + + /** + *

Converts an array of primitive chars to objects. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code char} array + * @return a {@code Character} array, {@code null} if null array input + */ + public static Character[] toObject(final char[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_CHARACTER_OBJECT_ARRAY; + } + final Character[] result = new Character[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Character.valueOf(array[i]); + } + return result; + } + + /** + *

Converts an array of primitive doubles to objects. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code double} array + * @return a {@code Double} array, {@code null} if null array input + */ + public static Double[] toObject(final double[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_DOUBLE_OBJECT_ARRAY; + } + final Double[] result = new Double[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Double.valueOf(array[i]); + } + return result; + } + + /** + *

Converts an array of primitive floats to objects. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code float} array + * @return a {@code Float} array, {@code null} if null array input + */ + public static Float[] toObject(final float[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_FLOAT_OBJECT_ARRAY; + } + final Float[] result = new Float[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Float.valueOf(array[i]); + } + return result; + } + + /** + *

Converts an array of primitive ints to objects. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array an {@code int} array + * @return an {@code Integer} array, {@code null} if null array input + */ + public static Integer[] toObject(final int[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_INTEGER_OBJECT_ARRAY; + } + final Integer[] result = new Integer[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Integer.valueOf(array[i]); + } + return result; + } + + /** + *

Converts an array of primitive longs to objects. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code long} array + * @return a {@code Long} array, {@code null} if null array input + */ + public static Long[] toObject(final long[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_LONG_OBJECT_ARRAY; + } + final Long[] result = new Long[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Long.valueOf(array[i]); + } + return result; + } + + /** + *

Converts an array of primitive shorts to objects. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code short} array + * @return a {@code Short} array, {@code null} if null array input + */ + public static Short[] toObject(final short[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_SHORT_OBJECT_ARRAY; + } + final Short[] result = new Short[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Short.valueOf(array[i]); + } + return result; + } + + // Boolean array converters + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Booleans to primitives. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Boolean} array, may be {@code null} + * @return a {@code boolean} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static boolean[] toPrimitive(final Boolean[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BOOLEAN_ARRAY; + } + final boolean[] result = new boolean[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].booleanValue(); + } + return result; + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Boolean} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code boolean} array, {@code null} if null array input + */ + public static boolean[] toPrimitive(final Boolean[] array, final boolean valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BOOLEAN_ARRAY; + } + final boolean[] result = new boolean[array.length]; + for (int i = 0; i < array.length; i++) { + final Boolean b = array[i]; + result[i] = (b == null ? valueForNull : b.booleanValue()); + } + return result; + } + + // Byte array converters + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Bytes to primitives. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Byte} array, may be {@code null} + * @return a {@code byte} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static byte[] toPrimitive(final Byte[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BYTE_ARRAY; + } + final byte[] result = new byte[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].byteValue(); + } + return result; + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Byte} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code byte} array, {@code null} if null array input + */ + public static byte[] toPrimitive(final Byte[] array, final byte valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BYTE_ARRAY; + } + final byte[] result = new byte[array.length]; + for (int i = 0; i < array.length; i++) { + final Byte b = array[i]; + result[i] = (b == null ? valueForNull : b.byteValue()); + } + return result; + } + + // Character array converters + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Characters to primitives. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Character} array, may be {@code null} + * @return a {@code char} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static char[] toPrimitive(final Character[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_CHAR_ARRAY; + } + final char[] result = new char[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].charValue(); + } + return result; + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Character} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code char} array, {@code null} if null array input + */ + public static char[] toPrimitive(final Character[] array, final char valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_CHAR_ARRAY; + } + final char[] result = new char[array.length]; + for (int i = 0; i < array.length; i++) { + final Character b = array[i]; + result[i] = (b == null ? valueForNull : b.charValue()); + } + return result; + } + + // Double array converters + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Doubles to primitives. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Double} array, may be {@code null} + * @return a {@code double} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static double[] toPrimitive(final Double[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_DOUBLE_ARRAY; + } + final double[] result = new double[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].doubleValue(); + } + return result; + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Double} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code double} array, {@code null} if null array input + */ + public static double[] toPrimitive(final Double[] array, final double valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_DOUBLE_ARRAY; + } + final double[] result = new double[array.length]; + for (int i = 0; i < array.length; i++) { + final Double b = array[i]; + result[i] = (b == null ? valueForNull : b.doubleValue()); + } + return result; + } + + // Float array converters + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Floats to primitives. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Float} array, may be {@code null} + * @return a {@code float} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static float[] toPrimitive(final Float[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_FLOAT_ARRAY; + } + final float[] result = new float[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].floatValue(); + } + return result; + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Float} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code float} array, {@code null} if null array input + */ + public static float[] toPrimitive(final Float[] array, final float valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_FLOAT_ARRAY; + } + final float[] result = new float[array.length]; + for (int i = 0; i < array.length; i++) { + final Float b = array[i]; + result[i] = (b == null ? valueForNull : b.floatValue()); + } + return result; + } + + // Int array converters + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Integers to primitives. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Integer} array, may be {@code null} + * @return an {@code int} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static int[] toPrimitive(final Integer[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_INT_ARRAY; + } + final int[] result = new int[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].intValue(); + } + return result; + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Integer} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return an {@code int} array, {@code null} if null array input + */ + public static int[] toPrimitive(final Integer[] array, final int valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_INT_ARRAY; + } + final int[] result = new int[array.length]; + for (int i = 0; i < array.length; i++) { + final Integer b = array[i]; + result[i] = (b == null ? valueForNull : b.intValue()); + } + return result; + } + + // Long array converters + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Longs to primitives. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Long} array, may be {@code null} + * @return a {@code long} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static long[] toPrimitive(final Long[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_LONG_ARRAY; + } + final long[] result = new long[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].longValue(); + } + return result; + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Long} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code long} array, {@code null} if null array input + */ + public static long[] toPrimitive(final Long[] array, final long valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_LONG_ARRAY; + } + final long[] result = new long[array.length]; + for (int i = 0; i < array.length; i++) { + 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. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @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; + } + + // Short array converters + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Shorts to primitives. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Short} array, may be {@code null} + * @return a {@code byte} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static short[] toPrimitive(final Short[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_SHORT_ARRAY; + } + final short[] result = new short[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].shortValue(); + } + return result; + } + + /** + *

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

This method returns {@code null} for a {@code null} input array. + * + * @param array a {@code Short} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code byte} array, {@code null} if null array input + */ + public static short[] toPrimitive(final Short[] array, final short valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_SHORT_ARRAY; + } + final short[] result = new short[array.length]; + for (int i = 0; i < array.length; i++) { + final Short b = array[i]; + result[i] = (b == null ? valueForNull : b.shortValue()); + } + return result; + } + + // Basic methods handling multi-dimensional arrays + //----------------------------------------------------------------------- + /** + *

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

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

The format is that of Java source code, for example {@code {a,b}}. + * + * @param array the array to get a toString for, may be {@code null} + * @return a String representation of the array, '{}' if null array input + */ + public static String toString(final Object array) { + return toString(array, "{}"); + } + + /** + *

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

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

The format is that of Java source code, for example {@code {a,b}}. + * + * @param array the array to get a toString for, may be {@code null} + * @param stringIfNull the String to return if the array is {@code null} + * @return a String representation of the array + */ + public static String toString(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.

+ * + *

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

+ * + * @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 array contains {@code null} + * @since 3.6 + */ + public static String[] toStringArray(final Object[] array) { + if (array == null) { + return null; + } else 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.

+ * + *

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

+ * + * @param array the Object[] to be processed, may be null + * @param valueForNullElements the value to insert if {@code null} is found + * @return a {@code 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; + } else 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})}. + * + *

This constructor is public to permit tools that require a JavaBean instance + * to operate. + */ + public ArrayUtils() { + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/BitField.java b/after/src/main/java/org/apache/commons/lang3/BitField.java new file mode 100644 index 0000000..dc70df4 --- /dev/null +++ b/after/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}.

+ * + *

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

+ * + *

As an example, consider a car painting machine that accepts + * paint instructions as integers. Bit fields can be used to encode this:

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

Using these {@code BitField} instances, a paint instruction can be + * encoded into an integer:

+ * + *
+ *    int paintInstruction = 0;
+ *    paintInstruction = red.setValue(paintInstruction, 35);
+ *    paintInstruction = green.setValue(paintInstruction, 100);
+ *    paintInstruction = blue.setValue(paintInstruction, 255);
+ *
+ * + *

Flags and data can be retrieved from the integer:

+ * + *
+ *    // 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
+ *
+ * + * @since 2.0 + */ +public class BitField { + + private final int _mask; + private final int _shift_count; + + /** + *

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) { + _mask = mask; + _shift_count = mask == 0 ? 0 : Integer.numberOfTrailingZeros(mask); + } + + /** + *

Obtains the value for the specified BitField, appropriately + * shifted right.

+ * + *

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

+ * + * @see #setValue(int,int) + * @param holder the int data containing the bits we're interested + * in + * @return the selected bits, shifted right appropriately + */ + public int getValue(final int holder) { + return getRawValue(holder) >> _shift_count; + } + + /** + *

Obtains the value for the specified BitField, appropriately + * shifted right, as a short.

+ * + *

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

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

Obtains the value for the specified BitField, 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.

+ * + *

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

+ * + * @param holder the int data containing the bits we're interested + * in + * @return {@code true} if any of the bits are set, + * else {@code false} + */ + public boolean isSet(final int holder) { + return (holder & _mask) != 0; + } + + /** + *

Returns whether all of the bits are set or not.

+ * + *

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

+ * + * @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 << _shift_count) & _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/after/src/main/java/org/apache/commons/lang3/BooleanUtils.java b/after/src/main/java/org/apache/commons/lang3/BooleanUtils.java new file mode 100644 index 0000000..bc7234e --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/BooleanUtils.java @@ -0,0 +1,1139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + *

Operations on boolean primitives and Boolean objects.

+ * + *

This class tries to handle {@code null} input gracefully. + * An exception will not be thrown for a {@code null} input. + * Each method documents its behavior in more detail.

+ * + *

#ThreadSafe#

+ * @since 2.0 + */ +public class BooleanUtils { + /** + * 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.

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

+ * + *
+     *   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
+     * 
+ * + * @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. + * @throws IllegalArgumentException if {@code array} contains a {@code null} + * @since 3.0.1 + */ + public static Boolean and(final Boolean... array) { + ObjectUtils.requireNonEmpty(array, "array"); + try { + final boolean[] primitive = ArrayUtils.toPrimitive(array); + return and(primitive) ? Boolean.TRUE : Boolean.FALSE; + } catch (final NullPointerException ex) { + throw new IllegalArgumentException("The array must not contain any null elements"); + } + } + + /** + * 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; + } + + /** + *

Checks if a {@code Boolean} value is {@code false}, + * handling {@code null} by returning {@code false}.

+ * + *
+     *   BooleanUtils.isFalse(Boolean.TRUE)  = false
+     *   BooleanUtils.isFalse(Boolean.FALSE) = true
+     *   BooleanUtils.isFalse(null)          = false
+     * 
+ * + * @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 {@code Boolean} value is not {@code false}, + * handling {@code null} by returning {@code true}.

+ * + *
+     *   BooleanUtils.isNotFalse(Boolean.TRUE)  = true
+     *   BooleanUtils.isNotFalse(Boolean.FALSE) = false
+     *   BooleanUtils.isNotFalse(null)          = true
+     * 
+ * + * @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 {@code Boolean} value is not {@code true}, + * handling {@code null} by returning {@code true}.

+ * + *
+     *   BooleanUtils.isNotTrue(Boolean.TRUE)  = false
+     *   BooleanUtils.isNotTrue(Boolean.FALSE) = true
+     *   BooleanUtils.isNotTrue(null)          = true
+     * 
+ * + * @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 {@code Boolean} value is {@code true}, + * handling {@code null} by returning {@code false}.

+ * + *
+     *   BooleanUtils.isTrue(Boolean.TRUE)  = true
+     *   BooleanUtils.isTrue(Boolean.FALSE) = false
+     *   BooleanUtils.isTrue(null)          = false
+     * 
+ * + * @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.

+ * + *

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

+ * + *

NOTE: This returns {@code null} and will throw a {@code NullPointerException} + * if unboxed to a boolean.

+ * + *
+     *   BooleanUtils.negate(Boolean.TRUE)  = Boolean.FALSE;
+     *   BooleanUtils.negate(Boolean.FALSE) = Boolean.TRUE;
+     *   BooleanUtils.negate(null)          = null;
+     * 
+ * + * @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 an 'or' operation on a set of booleans.

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

+ * + *
+     *   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
+     * 
+ * + * @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. + * @throws IllegalArgumentException if {@code array} contains a {@code null} + * @since 3.0.1 + */ + public static Boolean or(final Boolean... array) { + ObjectUtils.requireNonEmpty(array, "array"); + try { + final boolean[] primitive = ArrayUtils.toPrimitive(array); + return or(primitive) ? Boolean.TRUE : Boolean.FALSE; + } catch (final NullPointerException ex) { + throw new IllegalArgumentException("The array must not contain any null elements"); + } + } + + /** + * 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}.

+ * + *
+     *   BooleanUtils.toBoolean(Boolean.TRUE)  = true
+     *   BooleanUtils.toBoolean(Boolean.FALSE) = false
+     *   BooleanUtils.toBoolean(null)          = false
+     * 
+ * + * @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}.

+ * + *
+     *   BooleanUtils.toBoolean(0) = false
+     *   BooleanUtils.toBoolean(1) = true
+     *   BooleanUtils.toBoolean(2) = true
+     * 
+ * + * @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.

+ * + *

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.

+ * + *
+     *   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
+     * 
+ * + * @param value the {@code 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.

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

+ * + *

{@code 'true'}, {@code 'on'}, {@code 'y'}, {@code 't'} or {@code 'yes'} + * (case insensitive) will return {@code true}. Otherwise, + * {@code false} is returned.

+ * + *

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

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

+ * + *
+     *   BooleanUtils.toBoolean("true", "true", "false")  = true
+     *   BooleanUtils.toBoolean("false", "true", "false") = false
+     * 
+ * + * @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; + } else if (str == falseString) { + return false; + } else if (str != null) { + if (str.equals(trueString)) { + return true; + } else 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}.

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

+ * + *
+     *   BooleanUtils.toBoolean(0) = Boolean.FALSE
+     *   BooleanUtils.toBoolean(1) = Boolean.TRUE
+     *   BooleanUtils.toBoolean(2) = Boolean.TRUE
+     * 
+ * + * @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.

+ * + *

NOTE: This method may return {@code null} and may throw a {@code NullPointerException} + * if unboxed to a {@code boolean}.

+ * + *

The checks are done first for the {@code trueValue}, then for the {@code falseValue} and + * finally for the {@code nullValue}.

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

+ * + *

{@code null} will be converted to {@code null}.

+ * + *

NOTE: This method may return {@code null} and may throw a {@code NullPointerException} + * if unboxed to a {@code boolean}.

+ * + *
+     *   BooleanUtils.toBooleanObject(Integer.valueOf(0))    = Boolean.FALSE
+     *   BooleanUtils.toBooleanObject(Integer.valueOf(1))    = Boolean.TRUE
+     *   BooleanUtils.toBooleanObject(Integer.valueOf(null)) = null
+     * 
+ * + * @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.

+ * + *

NOTE: This method may return {@code null} and may throw a {@code NullPointerException} + * if unboxed to a {@code boolean}.

+ * + *

The checks are done first for the {@code trueValue}, then for the {@code falseValue} and + * finally for the {@code nullValue}.

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

+ * + *

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

+ * + *

NOTE: This method may return {@code null} and may throw a {@code NullPointerException} + * if unboxed to a {@code boolean}.

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

+ * + *

NOTE: This method may return {@code null} and may throw a {@code NullPointerException} + * if unboxed to a {@code boolean}.

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

+ * + *
+     *   BooleanUtils.toInteger(true)  = 1
+     *   BooleanUtils.toInteger(false) = 0
+     * 
+ * + * @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.

+ * + *
+     *   BooleanUtils.toInteger(true, 1, 0)  = 1
+     *   BooleanUtils.toInteger(false, 1, 0) = 0
+     * 
+ * + * @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.

+ * + *
+     *   BooleanUtils.toInteger(Boolean.TRUE, 1, 0, 2)  = 1
+     *   BooleanUtils.toInteger(Boolean.FALSE, 1, 0, 2) = 0
+     *   BooleanUtils.toInteger(null, 1, 0, 2)          = 2
+     * 
+ * + * @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}.

+ * + *
+     *   BooleanUtils.toIntegerObject(true)  = Integer.valueOf(1)
+     *   BooleanUtils.toIntegerObject(false) = Integer.valueOf(0)
+     * 
+ * + * @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.

+ * + *
+     *   BooleanUtils.toIntegerObject(true, Integer.valueOf(1), Integer.valueOf(0))  = Integer.valueOf(1)
+     *   BooleanUtils.toIntegerObject(false, Integer.valueOf(1), Integer.valueOf(0)) = Integer.valueOf(0)
+     * 
+ * + * @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}.

+ * + *

{@code null} will be converted to {@code null}.

+ * + *
+     *   BooleanUtils.toIntegerObject(Boolean.TRUE)  = Integer.valueOf(1)
+     *   BooleanUtils.toIntegerObject(Boolean.FALSE) = Integer.valueOf(0)
+     * 
+ * + * @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.

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

+ * + *
+     *   BooleanUtils.toString(true, "true", "false")   = "true"
+     *   BooleanUtils.toString(false, "true", "false")  = "false"
+     * 
+ * + * @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.

+ * + *
+     *   BooleanUtils.toString(Boolean.TRUE, "true", "false", null)   = "true"
+     *   BooleanUtils.toString(Boolean.FALSE, "true", "false", null)  = "false"
+     *   BooleanUtils.toString(null, "true", "false", null)           = null;
+     * 
+ * + * @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'}.

+ * + *
+     *   BooleanUtils.toStringOnOff(true)   = "on"
+     *   BooleanUtils.toStringOnOff(false)  = "off"
+     * 
+ * + * @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}.

+ * + *
+     *   BooleanUtils.toStringOnOff(Boolean.TRUE)  = "on"
+     *   BooleanUtils.toStringOnOff(Boolean.FALSE) = "off"
+     *   BooleanUtils.toStringOnOff(null)          = null;
+     * 
+ * + * @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'}.

+ * + *
+     *   BooleanUtils.toStringTrueFalse(true)   = "true"
+     *   BooleanUtils.toStringTrueFalse(false)  = "false"
+     * 
+ * + * @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}.

+ * + *
+     *   BooleanUtils.toStringTrueFalse(Boolean.TRUE)  = "true"
+     *   BooleanUtils.toStringTrueFalse(Boolean.FALSE) = "false"
+     *   BooleanUtils.toStringTrueFalse(null)          = null;
+     * 
+ * + * @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'}.

+ * + *
+     *   BooleanUtils.toStringYesNo(true)   = "yes"
+     *   BooleanUtils.toStringYesNo(false)  = "no"
+     * 
+ * + * @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}.

+ * + *
+     *   BooleanUtils.toStringYesNo(Boolean.TRUE)  = "yes"
+     *   BooleanUtils.toStringYesNo(Boolean.FALSE) = "no"
+     *   BooleanUtils.toStringYesNo(null)          = null;
+     * 
+ * + * @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); + } + + /** + *

Performs an xor on a set of booleans.

+ * + *
+     *   BooleanUtils.xor(true, true)   = false
+     *   BooleanUtils.xor(false, false) = false
+     *   BooleanUtils.xor(true, false)  = true
+     * 
+ * + * @param array an array of {@code 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"); + // 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.

+ * + *
+     *   BooleanUtils.xor(new Boolean[] { Boolean.TRUE, Boolean.TRUE })   = Boolean.FALSE
+     *   BooleanUtils.xor(new Boolean[] { Boolean.FALSE, Boolean.FALSE }) = Boolean.FALSE
+     *   BooleanUtils.xor(new Boolean[] { Boolean.TRUE, Boolean.FALSE })  = Boolean.TRUE
+     *   BooleanUtils.xor(Boolean.TRUE, Boolean.FALSE, Boolean.FALSE)     = Boolean.TRUE
+     * 
+ * + * @param array an array of {@code Boolean}s + * @return the result of the xor operations + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty. + * @throws IllegalArgumentException if {@code array} contains a {@code null} + */ + public static Boolean xor(final Boolean... array) { + ObjectUtils.requireNonEmpty(array, "array"); + try { + final boolean[] primitive = ArrayUtils.toPrimitive(array); + return xor(primitive) ? Boolean.TRUE : Boolean.FALSE; + } catch (final NullPointerException ex) { + throw new IllegalArgumentException("The array must not contain any null elements"); + } + } + + /** + *

{@code BooleanUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code BooleanUtils.negate(true);}.

+ * + *

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

+ */ + public BooleanUtils() { + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/CharEncoding.java b/after/src/main/java/org/apache/commons/lang3/CharEncoding.java new file mode 100644 index 0000000..9f0d2e0 --- /dev/null +++ b/after/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.

+ * + *

According to JRE character + * encoding names:

+ * + *

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

+ * + * @see JRE character encoding names + * @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.

+ * + *

Every implementation of the Java platform is required to support this character encoding.

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

+ * + *

Every implementation of the Java platform is required to support this character encoding.

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

+ * + *

Every implementation of the Java platform is required to support this character encoding.

+ */ + public static final String UTF_16 = "UTF-16"; + + /** + *

Sixteen-bit Unicode Transformation Format, big-endian byte order.

+ * + *

Every implementation of the Java platform is required to support this character encoding.

+ */ + public static final String UTF_16BE = "UTF-16BE"; + + /** + *

Sixteen-bit Unicode Transformation Format, little-endian byte order.

+ * + *

Every implementation of the Java platform is required to support this character encoding.

+ */ + public static final String UTF_16LE = "UTF-16LE"; + + /** + *

Eight-bit Unicode Transformation Format.

+ * + *

Every implementation of the Java platform is required to support this character encoding.

+ */ + public static final String UTF_8 = "UTF-8"; + + /** + *

Returns whether the named charset is supported.

+ * + *

This is similar to + * java.nio.charset.Charset.isSupported(String) but handles more formats

+ * + * @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/after/src/main/java/org/apache/commons/lang3/CharRange.java b/after/src/main/java/org/apache/commons/lang3/CharRange.java new file mode 100644 index 0000000..36d8595 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/CharRange.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; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + *

A contiguous range of characters, optionally negated.

+ * + *

Instances are immutable.

+ * + *

#ThreadSafe#

+ * @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, 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 = new CharRange[0]; + + /** + *

Constructs a {@code CharRange} over a set of characters, + * optionally negating the range.

+ * + *

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

+ * + *

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

+ * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @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 {@code 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 {@code CharRange} over a single character.

+ * + *

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

+ * + * @param ch only character in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange isNot(final char ch) { + return new CharRange(ch, ch, true); + } + + /** + *

Constructs a {@code CharRange} over a set of characters.

+ * + *

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

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

Constructs a negated {@code CharRange} over a set of characters.

+ * + *

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

+ * + *

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

+ * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange isNotIn(final char start, final char end) { + return new CharRange(start, end, true); + } + + // Accessors + //----------------------------------------------------------------------- + /** + *

Gets the start character for this character range.

+ * + * @return the start char (inclusive) + */ + public char getStart() { + return this.start; + } + + /** + *

Gets the end character for this character range.

+ * + * @return the end char (inclusive) + */ + public char getEnd() { + return this.end; + } + + /** + *

Is this {@code CharRange} negated.

+ * + *

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

+ * + * @return {@code true} if negated + */ + public boolean isNegated() { + return negated; + } + + // Contains + //----------------------------------------------------------------------- + /** + *

Is the character specified contained in this range.

+ * + * @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 IllegalArgumentException if {@code null} input + */ + public boolean contains(final CharRange range) { + Validate.notNull(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; + } + + // Expansions + //----------------------------------------------------------------------- + /** + *

Returns an iterator which can be used to walk through the characters described by this range.

+ * + *

#NotThreadSafe# the iterator is not thread-safe

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

#NotThreadSafe#

+ */ + private static class CharacterIterator implements Iterator { + /** The current character */ + private char current; + + private final CharRange range; + private boolean hasNext; + + /** + * Constructs a new iterator for the character range. + * + * @param r The character range + */ + private CharacterIterator(final CharRange r) { + range = r; + hasNext = true; + + if (range.negated) { + if (range.start == 0) { + if (range.end == Character.MAX_VALUE) { + // This range is an empty set + hasNext = false; + } else { + current = (char) (range.end + 1); + } + } else { + current = 0; + } + } else { + current = range.start; + } + } + + /** + * Prepares the next character in the range. + */ + private void prepareNext() { + if (range.negated) { + if (current == Character.MAX_VALUE) { + hasNext = false; + } else if (current + 1 == range.start) { + if (range.end == Character.MAX_VALUE) { + hasNext = false; + } else { + current = (char) (range.end + 1); + } + } else { + current = (char) (current + 1); + } + } else if (current < range.end) { + current = (char) (current + 1); + } else { + hasNext = false; + } + } + + /** + * Has the iterator not reached the end character yet? + * + * @return {@code true} if the iterator has yet to reach the character date + */ + @Override + public boolean hasNext() { + return hasNext; + } + + /** + * Returns the next character in the iteration + * + * @return {@code 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/after/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java b/after/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java new file mode 100644 index 0000000..b08cb54 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java @@ -0,0 +1,383 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + + /** + *

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

+ * + *

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

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

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

+ * + *

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

+ * + * @param cs the specified subsequence, null returns null + * @param start the start index, inclusive, valid + * @return a new subsequence, may be null + * @throws IndexOutOfBoundsException if {@code start} is negative or if + * {@code start} is greater than {@code length()} + */ + public static CharSequence subSequence(final CharSequence cs, final int start) { + return cs == null ? null : cs.subSequence(start, cs.length()); + } + + //----------------------------------------------------------------------- + /** + * Returns the index within {@code cs} of the first occurrence of the + * specified character, starting the search at the specified index. + *

+ * 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 k such that: + *

+     * (this.charAt(k) == searchChar) && (k >= start)
+     * 
+ * is true. For other values of {@code searchChar}, it is the + * smallest value k such that: + *
+     * (this.codePointAt(k) == searchChar) && (k >= start)
+     * 
+ * is true. In either case, if no such character occurs inm {@code cs} + * at or after position {@code start}, then + * {@code -1} is returned. + * + *

+ * 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 + * {@code 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. + * + *

All indices are specified in {@code char} values + * (Unicode code units). + * + * @param cs the {@code CharSequence} to be processed, not null + * @param searchChar the char to be searched for + * @param start the start index, negative starts at the string start + * @return the index where the search char was found, -1 if not found + * @since 3.6 updated to behave more like {@code 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 indexOf(CharSequence methods) as a green implementation of indexOf. + * + * @param cs the {@code CharSequence} to be processed + * @param searchChar the {@code CharSequence} to be searched for + * @param start the start index + * @return the index where the search sequence was found + */ + static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) { + if (cs instanceof String) { + return ((String) cs).indexOf(searchChar.toString(), start); + } else if (cs instanceof StringBuilder) { + return ((StringBuilder) cs).indexOf(searchChar.toString(), start); + } else 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 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 k such that: + *

+     * (this.charAt(k) == searchChar) && (k <= start)
+     * 
+ * is true. For other values of {@code searchChar}, it is the + * largest value k such that: + *
+     * (this.codePointAt(k) == searchChar) && (k <= start)
+     * 
+ * is true. In either case, if no such character occurs in {@code cs} + * at or before position {@code start}, then {@code -1} is returned. + * + *

All indices are specified in {@code char} values + * (Unicode code units). + * + * @param cs the {@code CharSequence} to be processed + * @param searchChar the char to be searched for + * @param start the start index, negative returns -1, beyond length starts at end + * @return the index where the search char was found, -1 if not found + * @since 3.6 updated to behave more like {@code 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; + } + + static final int TO_STRING_LIMIT = 16; + + /** + * Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf + * + * @param cs the {@code CharSequence} to be processed + * @param searchChar the {@code CharSequence} to 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); + } else if (cs instanceof StringBuilder) { + return ((StringBuilder) cs).lastIndexOf((String) searchChar, start); + } else 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 < 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); + } else if (cs instanceof StringBuilder) { + return ((StringBuilder) cs).lastIndexOf(searchChar.toString(), start); + } else 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; + } + } + } + + 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; + } + + /** + * Converts the given CharSequence to a char[]. + * + * @param source the {@code 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; + } + + /** + * Green implementation of regionMatches. + * + * @param cs the {@code CharSequence} to be processed + * @param ignoreCase whether or not to be case insensitive + * @param thisStart the index to start on the {@code cs} CharSequence + * @param substring the {@code CharSequence} to be looked for + * @param start the index to start on the {@code substring} CharSequence + * @param length character length of the region + * @return whether the region matched + */ + static boolean regionMatches(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; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/CharSet.java b/after/src/main/java/org/apache/commons/lang3/CharSet.java new file mode 100644 index 0000000..84e6c12 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/CharSet.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; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + *

A set of characters.

+ * + *

Instances are immutable, but instances of subclasses may not be.

+ * + *

#ThreadSafe#

+ * @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 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 set = Collections.synchronizedSet(new HashSet<>()); + + //----------------------------------------------------------------------- + /** + *

Factory method to create a new CharSet using a special syntax.

+ * + *
    + *
  • {@code null} or empty string ("") + * - set containing no characters
  • + *
  • Single character, such as "a" + * - set containing just that character
  • + *
  • Multi character, such as "a-e" + * - set containing characters from one character to the other
  • + *
  • Negated, such as "^a" or "^a-e" + * - set containing all characters except those defined
  • + *
  • Combinations, such as "abe-g" + * - set containing all the characters from the individual sets
  • + *
+ * + *

The matching order is:

+ *
    + *
  1. Negated multi character range, such as "^a-e" + *
  2. Ordinary multi character range, such as "a-e" + *
  3. Negated single character, such as "^a" + *
  4. Ordinary single character, such as "a" + *
+ * + *

Matching works left to right. Once a match is found the + * search starts again from the next character.

+ * + *

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

+ * + *

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.

+ * + *

The set of characters represented is the union of the specified ranges.

+ * + *

There are two ways to add a literal negation character ({@code ^}):

+ *
    + *
  • As the last character in a string, e.g. {@code CharSet.getInstance("a-z^")}
  • + *
  • As a separate element, e.g. {@code CharSet.getInstance("^", "a-z")}
  • + *
+ * + *

Examples using the negation character:

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

All CharSet objects returned by this method will be immutable.

+ * + * @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) { + for (final String s : set) { + add(s); + } + } + + //----------------------------------------------------------------------- + /** + *

Add a set definition string to the {@code 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 {@code 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) { + for (final CharRange range : set) { + if (range.contains(ch)) { + return true; + } + } + } + return false; + } + + // Basics + //----------------------------------------------------------------------- + /** + *

Compares two {@code CharSet} objects, returning true if they represent + * exactly the same set of characters defined in the same way.

+ * + *

The two sets {@code abc} and {@code a-c} are not + * equal according to this method.

+ * + * @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/after/src/main/java/org/apache/commons/lang3/CharSetUtils.java b/after/src/main/java/org/apache/commons/lang3/CharSetUtils.java new file mode 100644 index 0000000..e75a6c8 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/CharSetUtils.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; + +/** + *

Operations on {@code CharSet} instances.

+ * + *

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.

+ * + *

#ThreadSafe#

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

+ * + *
+     * 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
+     * 
+ * + * @see CharSet#getInstance(java.lang.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.

+ * + *
+     * 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
+     * 
+ * + * @see CharSet#getInstance(java.lang.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) { + if (strings != null) { + for (final String s : strings) { + if (StringUtils.isNotEmpty(s)) { + return false; + } + } + } + return true; + } + + /** + *

Takes an argument in set-syntax, see evaluateSet, + * and deletes any of characters present in the specified string.

+ * + *
+     * CharSetUtils.delete(null, *)        = null
+     * CharSetUtils.delete("", *)          = ""
+     * CharSetUtils.delete(*, null)        = *
+     * CharSetUtils.delete(*, "")          = *
+     * CharSetUtils.delete("hello", "hl")  = "eo"
+     * CharSetUtils.delete("hello", "le")  = "ho"
+     * 
+ * + * @see CharSet#getInstance(java.lang.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.

+ * + *
+     * CharSetUtils.keep(null, *)        = null
+     * CharSetUtils.keep("", *)          = ""
+     * CharSetUtils.keep(*, null)        = ""
+     * CharSetUtils.keep(*, "")          = ""
+     * CharSetUtils.keep("hello", "hl")  = "hll"
+     * CharSetUtils.keep("hello", "le")  = "ell"
+     * 
+ * + * @see CharSet#getInstance(java.lang.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.

+ * + *
+     * CharSetUtils.squeeze(null, *)        = null
+     * CharSetUtils.squeeze("", *)          = ""
+     * CharSetUtils.squeeze(*, null)        = *
+     * CharSetUtils.squeeze(*, "")          = *
+     * CharSetUtils.squeeze("hello", "k-p") = "helo"
+     * CharSetUtils.squeeze("hello", "a-e") = "hello"
+     * 
+ * + * @see CharSet#getInstance(java.lang.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);}.

+ * + *

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

+ */ + public CharSetUtils() { + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/CharUtils.java b/after/src/main/java/org/apache/commons/lang3/CharUtils.java new file mode 100644 index 0000000..b980443 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/CharUtils.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; + +/** + *

Operations on char primitives and Character objects.

+ * + *

This class tries to handle {@code null} input gracefully. + * An exception will not be thrown for a {@code null} input. + * Each method documents its behavior in more detail.

+ * + *

#ThreadSafe#

+ * @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 JLF: Escape Sequences + * for Character and String Literals + * @since 2.2 + */ + public static final char LF = '\n'; + + /** + * Carriage return characterf CR ('\r', Unicode 000d). + * + * @see JLF: Escape Sequences + * for Character and String Literals + * @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 { + for (char c = 0; c < CHAR_STRING_ARRAY.length; c++) { + CHAR_STRING_ARRAY[c] = String.valueOf(c); + } + } + + /** + *

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

+ * + *

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

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

Converts the character to a Character.

+ * + *

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

+ * + *
+     *   CharUtils.toCharacterObject(' ')  = ' '
+     *   CharUtils.toCharacterObject('A')  = 'A'
+     * 
+ * + * @deprecated Java 5 introduced {@link Character#valueOf(char)} which caches chars 0 through 127. + * @param ch the character to convert + * @return a Character of the specified character + */ + @Deprecated + public static Character toCharacterObject(final char ch) { + return Character.valueOf(ch); + } + + /** + *

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

+ * + *

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

+ * + *
+     *   CharUtils.toCharacterObject(null) = null
+     *   CharUtils.toCharacterObject("")   = null
+     *   CharUtils.toCharacterObject("A")  = 'A'
+     *   CharUtils.toCharacterObject("BA") = 'B'
+     * 
+ * + * @param str the character to convert + * @return the Character value of the first letter of the String + */ + public static Character toCharacterObject(final String str) { + if (StringUtils.isEmpty(str)) { + return null; + } + return Character.valueOf(str.charAt(0)); + } + + //----------------------------------------------------------------------- + /** + *

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

+ * + *
+     *   CharUtils.toChar(' ')  = ' '
+     *   CharUtils.toChar('A')  = 'A'
+     *   CharUtils.toChar(null) throws IllegalArgumentException
+     * 
+ * + * @param ch the character to convert + * @return the char value of the Character + * @throws NullPointerException if the Character is null + */ + public static char toChar(final Character ch) { + Validate.notNull(ch, "ch"); + return ch.charValue(); + } + + /** + *

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

+ * + *
+     *   CharUtils.toChar(null, 'X') = 'X'
+     *   CharUtils.toChar(' ', 'X')  = ' '
+     *   CharUtils.toChar('A', 'X')  = 'A'
+     * 
+ * + * @param ch the character to convert + * @param defaultValue the value to use if the Character is null + * @return the char value of the Character or the default if null + */ + public static char toChar(final Character ch, final char defaultValue) { + if (ch == null) { + return defaultValue; + } + return ch.charValue(); + } + + //----------------------------------------------------------------------- + /** + *

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

+ * + *
+     *   CharUtils.toChar("A")  = 'A'
+     *   CharUtils.toChar("BA") = 'B'
+     *   CharUtils.toChar(null) throws IllegalArgumentException
+     *   CharUtils.toChar("")   throws IllegalArgumentException
+     * 
+ * + * @param str the character to convert + * @return the char value of the first letter of the String + * @throws 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.

+ * + *
+     *   CharUtils.toChar(null, 'X') = 'X'
+     *   CharUtils.toChar("", 'X')   = 'X'
+     *   CharUtils.toChar("A", 'X')  = 'A'
+     *   CharUtils.toChar("BA", 'X') = 'B'
+     * 
+ * + * @param str the character to convert + * @param defaultValue the value to use if the Character is null + * @return the char value of the first letter of the String or the default if null + */ + public static char toChar(final String str, final char defaultValue) { + if (StringUtils.isEmpty(str)) { + return defaultValue; + } + return str.charAt(0); + } + + //----------------------------------------------------------------------- + /** + *

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

+ * + *

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

+ * + *
+     *   CharUtils.toIntValue('3')  = 3
+     *   CharUtils.toIntValue('A')  throws IllegalArgumentException
+     * 
+ * + * @param ch the character to convert + * @return the int value of the character + * @throws IllegalArgumentException if the character is not ASCII numeric + */ + public static int toIntValue(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.

+ * + *

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

+ * + *
+     *   CharUtils.toIntValue('3', -1)  = 3
+     *   CharUtils.toIntValue('A', -1)  = -1
+     * 
+ * + * @param ch the character to convert + * @param defaultValue the default value to use if the character is not numeric + * @return the int value of the character + */ + public static int toIntValue(final char ch, final int defaultValue) { + if (!isAsciiNumeric(ch)) { + return defaultValue; + } + return ch - 48; + } + + /** + *

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

+ * + *

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

+ * + *
+     *   CharUtils.toIntValue('3')  = 3
+     *   CharUtils.toIntValue(null) throws IllegalArgumentException
+     *   CharUtils.toIntValue('A')  throws IllegalArgumentException
+     * 
+ * + * @param ch the character to convert, not null + * @return the int value of the character + * @throws NullPointerException if the Character is null + * @throws IllegalArgumentException if the Character is not ASCII numeric + */ + public static int toIntValue(final Character ch) { + Validate.notNull(ch, "ch"); + return toIntValue(ch.charValue()); + } + + /** + *

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

+ * + *

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

+ * + *
+     *   CharUtils.toIntValue(null, -1) = -1
+     *   CharUtils.toIntValue('3', -1)  = 3
+     *   CharUtils.toIntValue('A', -1)  = -1
+     * 
+ * + * @param ch the character to convert + * @param defaultValue the default value to use if the character is not numeric + * @return the int value of the character + */ + public static int toIntValue(final Character ch, final int defaultValue) { + if (ch == null) { + return defaultValue; + } + return toIntValue(ch.charValue(), defaultValue); + } + + //----------------------------------------------------------------------- + /** + *

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

+ * + *

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

+ * + *
+     *   CharUtils.toString(' ')  = " "
+     *   CharUtils.toString('A')  = "A"
+     * 
+ * + * @param ch the character to convert + * @return a String containing the one specified character + */ + public static String toString(final char ch) { + if (ch < 128) { + return CHAR_STRING_ARRAY[ch]; + } + return new String(new char[] {ch}); + } + + /** + *

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

+ * + *

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

+ * + *

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

+ * + *
+     *   CharUtils.toString(null) = null
+     *   CharUtils.toString(' ')  = " "
+     *   CharUtils.toString('A')  = "A"
+     * 
+ * + * @param ch the character to convert + * @return a String containing the one specified character + */ + public static String toString(final Character ch) { + if (ch == null) { + return null; + } + return toString(ch.charValue()); + } + + //-------------------------------------------------------------------------- + /** + *

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

+ * + *

This format is the Java source code format.

+ * + *
+     *   CharUtils.unicodeEscaped(' ') = "\u0020"
+     *   CharUtils.unicodeEscaped('A') = "\u0041"
+     * 
+ * + * @param ch the character to convert + * @return the escaped Unicode string + */ + public static String unicodeEscaped(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'.

+ * + *

This format is the Java source code format.

+ * + *

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

+ * + *
+     *   CharUtils.unicodeEscaped(null) = null
+     *   CharUtils.unicodeEscaped(' ')  = "\u0020"
+     *   CharUtils.unicodeEscaped('A')  = "\u0041"
+     * 
+ * + * @param ch the character to convert, may be null + * @return the escaped Unicode string, null if null input + */ + public static String unicodeEscaped(final Character ch) { + if (ch == null) { + return null; + } + return unicodeEscaped(ch.charValue()); + } + + //-------------------------------------------------------------------------- + /** + *

Checks whether the character is ASCII 7 bit.

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

Checks whether the character is ASCII 7 bit printable.

+ * + *
+     *   CharUtils.isAsciiPrintable('a')  = true
+     *   CharUtils.isAsciiPrintable('A')  = true
+     *   CharUtils.isAsciiPrintable('3')  = true
+     *   CharUtils.isAsciiPrintable('-')  = true
+     *   CharUtils.isAsciiPrintable('\n') = false
+     *   CharUtils.isAsciiPrintable('©') = false
+     * 
+ * + * @param ch the character to check + * @return true if between 32 and 126 inclusive + */ + public static boolean isAsciiPrintable(final char ch) { + return ch >= 32 && ch < 127; + } + + /** + *

Checks whether the character is ASCII 7 bit control.

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

Checks whether the character is ASCII 7 bit alphabetic.

+ * + *
+     *   CharUtils.isAsciiAlpha('a')  = true
+     *   CharUtils.isAsciiAlpha('A')  = true
+     *   CharUtils.isAsciiAlpha('3')  = false
+     *   CharUtils.isAsciiAlpha('-')  = false
+     *   CharUtils.isAsciiAlpha('\n') = false
+     *   CharUtils.isAsciiAlpha('©') = false
+     * 
+ * + * @param ch the character to check + * @return true if between 65 and 90 or 97 and 122 inclusive + */ + public static boolean isAsciiAlpha(final char ch) { + return isAsciiAlphaUpper(ch) || isAsciiAlphaLower(ch); + } + + /** + *

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

+ * + *
+     *   CharUtils.isAsciiAlphaUpper('a')  = false
+     *   CharUtils.isAsciiAlphaUpper('A')  = true
+     *   CharUtils.isAsciiAlphaUpper('3')  = false
+     *   CharUtils.isAsciiAlphaUpper('-')  = false
+     *   CharUtils.isAsciiAlphaUpper('\n') = false
+     *   CharUtils.isAsciiAlphaUpper('©') = false
+     * 
+ * + * @param ch the character to check + * @return true if between 65 and 90 inclusive + */ + public static boolean isAsciiAlphaUpper(final char ch) { + return ch >= 'A' && ch <= 'Z'; + } + + /** + *

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

+ * + *
+     *   CharUtils.isAsciiAlphaLower('a')  = true
+     *   CharUtils.isAsciiAlphaLower('A')  = false
+     *   CharUtils.isAsciiAlphaLower('3')  = false
+     *   CharUtils.isAsciiAlphaLower('-')  = false
+     *   CharUtils.isAsciiAlphaLower('\n') = false
+     *   CharUtils.isAsciiAlphaLower('©') = false
+     * 
+ * + * @param ch the character to check + * @return true if between 97 and 122 inclusive + */ + public static boolean isAsciiAlphaLower(final char ch) { + return ch >= 'a' && ch <= 'z'; + } + + /** + *

Checks whether the character is ASCII 7 bit numeric.

+ * + *
+     *   CharUtils.isAsciiNumeric('a')  = false
+     *   CharUtils.isAsciiNumeric('A')  = false
+     *   CharUtils.isAsciiNumeric('3')  = true
+     *   CharUtils.isAsciiNumeric('-')  = false
+     *   CharUtils.isAsciiNumeric('\n') = false
+     *   CharUtils.isAsciiNumeric('©') = false
+     * 
+ * + * @param ch the character to check + * @return true if between 48 and 57 inclusive + */ + public static boolean isAsciiNumeric(final char ch) { + return ch >= '0' && ch <= '9'; + } + + /** + *

Checks whether the character is ASCII 7 bit numeric.

+ * + *
+     *   CharUtils.isAsciiAlphanumeric('a')  = true
+     *   CharUtils.isAsciiAlphanumeric('A')  = true
+     *   CharUtils.isAsciiAlphanumeric('3')  = true
+     *   CharUtils.isAsciiAlphanumeric('-')  = false
+     *   CharUtils.isAsciiAlphanumeric('\n') = false
+     *   CharUtils.isAsciiAlphanumeric('©') = false
+     * 
+ * + * @param ch the character to check + * @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive + */ + public static boolean isAsciiAlphanumeric(final char ch) { + return isAsciiAlpha(ch) || isAsciiNumeric(ch); + } + + /** + *

Compares two {@code char} values numerically. This is the same functionality as provided in Java 7.

+ * + * @param x the first {@code char} to compare + * @param y the second {@code char} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 + */ + public static int compare(final char x, final char y) { + return x-y; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/Charsets.java b/after/src/main/java/org/apache/commons/lang3/Charsets.java new file mode 100644 index 0000000..c80bb90 --- /dev/null +++ b/after/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. + *

+ * Provides utilities for {@link Charset}. + *

+ *

+ * Package private since Apache Commons IO already provides a Charsets because {@link Charset} is in + * {@code java.nio.charset}. + *

+ * + * @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/after/src/main/java/org/apache/commons/lang3/ClassLoaderUtils.java b/after/src/main/java/org/apache/commons/lang3/ClassLoaderUtils.java new file mode 100644 index 0000000..a87156a --- /dev/null +++ b/after/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/after/src/main/java/org/apache/commons/lang3/ClassPathUtils.java b/after/src/main/java/org/apache/commons/lang3/ClassPathUtils.java new file mode 100644 index 0000000..3e1adda --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/ClassPathUtils.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; + +/** + * Operations regarding the classpath. + * + *

The methods of this class do not allow {@code null} inputs.

+ * + * @since 3.3 + */ +//@Immutable +public class ClassPathUtils { + + /** + *

{@code ClassPathUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * {@code ClassPathUtils.toFullyQualifiedName(MyClass.class, "MyClass.properties");}.

+ * + *

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

+ */ + public ClassPathUtils() { + } + + /** + * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context. + * + *

Note that this method does not check whether the resource actually exists. + * It only constructs the name. + * Null inputs are not allowed.

+ * + *
+     * ClassPathUtils.toFullyQualifiedName(StringUtils.class, "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties"
+     * 
+ * + * @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 java.lang.NullPointerException if either {@code context} or {@code resourceName} is null. + */ + public static String toFullyQualifiedName(final Class context, final String resourceName) { + Validate.notNull(context, "context" ); + Validate.notNull(resourceName, "resourceName"); + return toFullyQualifiedName(context.getPackage(), resourceName); + } + + /** + * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context. + * + *

Note that this method does not check whether the resource actually exists. + * It only constructs the name. + * Null inputs are not allowed.

+ * + *
+     * ClassPathUtils.toFullyQualifiedName(StringUtils.class.getPackage(), "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties"
+     * 
+ * + * @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 java.lang.NullPointerException if either {@code context} or {@code resourceName} is null. + */ + public static String toFullyQualifiedName(final Package context, final String resourceName) { + Validate.notNull(context, "context" ); + Validate.notNull(resourceName, "resourceName"); + return context.getName() + "." + resourceName; + } + + /** + * Returns the fully qualified path for the resource with name {@code resourceName} relative to the given context. + * + *

Note that this method does not check whether the resource actually exists. + * It only constructs the path. + * Null inputs are not allowed.

+ * + *
+     * ClassPathUtils.toFullyQualifiedPath(StringUtils.class, "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties"
+     * 
+ * + * @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 java.lang.NullPointerException if either {@code context} or {@code resourceName} is null. + */ + public static String toFullyQualifiedPath(final Class context, final String resourceName) { + Validate.notNull(context, "context" ); + Validate.notNull(resourceName, "resourceName"); + return toFullyQualifiedPath(context.getPackage(), resourceName); + } + + + /** + * Returns the fully qualified path for the resource with name {@code resourceName} relative to the given context. + * + *

Note that this method does not check whether the resource actually exists. + * It only constructs the path. + * Null inputs are not allowed.

+ * + *
+     * ClassPathUtils.toFullyQualifiedPath(StringUtils.class.getPackage(), "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties"
+     * 
+ * + * @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 java.lang.NullPointerException if either {@code context} or {@code resourceName} is null. + */ + public static String toFullyQualifiedPath(final Package context, final String resourceName) { + Validate.notNull(context, "context" ); + Validate.notNull(resourceName, "resourceName"); + return context.getName().replace('.', '/') + "/" + resourceName; + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/ClassUtils.java b/after/src/main/java/org/apache/commons/lang3/ClassUtils.java new file mode 100644 index 0000000..e6a0931 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/ClassUtils.java @@ -0,0 +1,1562 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Collections; +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.Set; + +import org.apache.commons.lang3.mutable.MutableObject; + +/** + *

Operates on classes without using reflection.

+ * + *

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

+ * + *

The notion of a {@code canonical name} includes the human + * readable name for the type, for example {@code int[]}. The + * non-canonical method variants work with the JVM names, such as + * {@code [I}.

+ * + * @since 2.0 + */ +public class ClassUtils { + + /** + * Inclusivity literals for {@link #hierarchy(Class, Interfaces)}. + * @since 3.2 + */ + public enum Interfaces { + + /** Includes interfaces. */ + INCLUDE, + + /** Excludes interfaces. */ + EXCLUDE + } + + /** + * The package separator character: {@code '.' == {@value}}. + */ + public static final char PACKAGE_SEPARATOR_CHAR = '.'; + + /** + * The package separator String: {@code "."}. + */ + public static final String PACKAGE_SEPARATOR = String.valueOf(PACKAGE_SEPARATOR_CHAR); + + /** + * The inner class separator character: {@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 {@code Class}es. + */ + private static final Map> 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 {@code Class}es to their corresponding wrapper {@code Class}. + */ + private static final Map, 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 {@code Class}es to their corresponding primitive types. + */ + private static final Map, Class> wrapperPrimitiveMap = new HashMap<>(); + static { + for (final Map.Entry, Class> entry : primitiveWrapperMap.entrySet()) { + final Class primitiveClass = entry.getKey(); + final Class wrapperClass = entry.getValue(); + if (!primitiveClass.equals(wrapperClass)) { + wrapperPrimitiveMap.put(wrapperClass, primitiveClass); + } + } + } + + /** + * Maps a primitive class name to its corresponding abbreviation used in array class names. + */ + private static final Map abbreviationMap; + + /** + * Maps an abbreviation used in array class names to corresponding primitive class name. + */ + private static final Map reverseAbbreviationMap; + // Feed abbreviation maps + static { + final Map m = new HashMap<>(); + m.put("int", "I"); + m.put("boolean", "Z"); + m.put("float", "F"); + m.put("long", "J"); + m.put("short", "S"); + m.put("byte", "B"); + m.put("double", "D"); + m.put("char", "C"); + final Map r = new HashMap<>(); + for (final Map.Entry e : m.entrySet()) { + r.put(e.getValue(), e.getKey()); + } + abbreviationMap = Collections.unmodifiableMap(m); + reverseAbbreviationMap = Collections.unmodifiableMap(r); + } + + /** + *

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

+ * + *

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

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

Gets the class name of the {@code object} without the package name or names.

+ * + *

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

+ * + * @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 {@code Class}.

+ * + *

This method simply gets the name using {@code Class.getName()} and then calls + * {@link #getShortClassName(Class)}. See relevant notes there.

+ * + * @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 minus the package name from a String.

+ * + *

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

+ * + *

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()}

+ * + *

Note that this method is called from the {@link #getShortClassName(Class)} method using the string + * returned by {@code Class.getName()}.

+ * + *

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.

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

+ * + *

It is to note that this method is overloaded and in case the argument {@code object} is a + * {@code 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.

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

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

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

+ * + * @param object the class to get the package name for, may be null + * @param valueIfNull the value to return if null + * @return the package name of the object, or the null value + */ + public static String getPackageName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; + } + return getPackageName(object.getClass()); + } + + /** + *

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

+ * + * @param cls the class to get the package name for, may be {@code null}. + * @return the package name or an empty string + */ + public static String getPackageName(final Class cls) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getPackageName(cls.getName()); + } + + /** + *

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

+ * + *

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

+ *

If the class is unpackaged, return an empty string.

+ * + * @param className the className to get the package name for, may be {@code null} + * @return the package name or an empty string + */ + public static String getPackageName(String className) { + if (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); + } + + // Abbreviated name + // ---------------------------------------------------------------------- + /** + *

Gets the abbreviated name of a {@code 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 <= 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 {@code String}.

+ * + *

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

+ * + *

The abbreviation algorithm will shorten the class name, usually without + * significant loss of meaning.

+ * + *

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.

+ *

Only package names are shortened, the class simple name remains untouched. (See examples.)

+ *

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.

+ *

If the class name can be shortened then + * the final length will be at most {@code lengthHint} characters.

+ *

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

+ * + * + * + * + * + * + * + * + * + *
Examples
classNamelenreturn
null 1""
"java.lang.String" 5"j.l.String"
"java.lang.String"15"j.lang.String"
"java.lang.String"30"java.lang.String"
"org.apache.commons.lang3.ClassUtils"18"o.a.c.l.ClassUtils"
+ * + * @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); + } + + /** + *

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}

+ * + *

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.

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

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

+ * + * @param cls the class to look up, may be {@code null} + * @return the {@code List} of superclasses in order going up from this one + * {@code null} if null input + */ + public static List> getAllSuperclasses(final Class cls) { + if (cls == null) { + return null; + } + final List> classes = new ArrayList<>(); + Class superclass = cls.getSuperclass(); + while (superclass != null) { + classes.add(superclass); + superclass = superclass.getSuperclass(); + } + return classes; + } + + /** + *

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

+ * + *

The order is determined by looking through each interface in turn as + * declared in the source file and following its hierarchy up. Then each + * superclass is considered in the same way. Later duplicates are ignored, + * so the order is maintained.

+ * + * @param cls the class to look up, may be {@code null} + * @return the {@code List} of interfaces in order, + * {@code null} if null input + */ + public static List> getAllInterfaces(final Class cls) { + if (cls == null) { + return null; + } + + final LinkedHashSet> 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 {@code Set} of interfaces for the class + */ + private static void getAllInterfaces(Class cls, final HashSet> interfacesFound) { + while (cls != null) { + final Class[] interfaces = cls.getInterfaces(); + + for (final Class i : interfaces) { + if (interfacesFound.add(i)) { + getAllInterfaces(i, interfacesFound); + } + } + + cls = cls.getSuperclass(); + } + } + + // Convert list + // ---------------------------------------------------------------------- + /** + *

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

+ * + *

A new {@code List} is returned. If the class name cannot be found, {@code null} + * is stored in the {@code List}. If the class name in the {@code List} is + * {@code null}, {@code null} is stored in the output {@code List}.

+ * + * @param classNames the classNames to change + * @return a {@code List} of Class objects corresponding to the class names, + * {@code null} if null input + * @throws ClassCastException if classNames contains a non String entry + */ + public static List> convertClassNamesToClasses(final List classNames) { + if (classNames == null) { + return null; + } + final List> classes = new ArrayList<>(classNames.size()); + for (final String className : classNames) { + try { + classes.add(Class.forName(className)); + } catch (final Exception ex) { + classes.add(null); + } + } + return classes; + } + + /** + *

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

+ * + *

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

+ * + * @param classes the classes to change + * @return a {@code List} of class names corresponding to the Class objects, + * {@code null} if null input + * @throws ClassCastException if {@code classes} contains a non-{@code Class} entry + */ + public static List convertClassesToClassNames(final List> classes) { + if (classes == null) { + return null; + } + final List classNames = new ArrayList<>(classes.size()); + for (final Class cls : classes) { + if (cls == null) { + classNames.add(null); + } else { + classNames.add(cls.getName()); + } + } + return classNames; + } + + // Is assignable + // ---------------------------------------------------------------------- + /** + *

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

+ * + *

This method calls {@link #isAssignable(Class, Class) isAssignable} for each + * Class pair in the input arrays. It can be used to check if a set of arguments + * (the first parameter) are suitably compatible with a set of method parameter types + * (the second parameter).

+ * + *

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

+ * + *

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

+ * + *

{@code Null} may be assigned to any reference type. This method will + * return {@code true} if {@code null} is passed in and the toClass is + * non-primitive.

+ * + *

Specifically, this method tests whether the type represented by the + * specified {@code Class} parameter can be converted to the type + * represented by this {@code Class} object via an identity conversion + * widening primitive or widening reference conversion. See + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

+ * + *

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

+ * + * @param classArray the array of Classes to check, may be {@code null} + * @param toClassArray the array of Classes to try to assign into, may be {@code null} + * @return {@code true} if assignment possible + */ + public static boolean isAssignable(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.

+ * + *

This method calls {@link #isAssignable(Class, Class) isAssignable} for each + * Class pair in the input arrays. It can be used to check if a set of arguments + * (the first parameter) are suitably compatible with a set of method parameter types + * (the second parameter).

+ * + *

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

+ * + *

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

+ * + *

{@code Null} may be assigned to any reference type. This method will + * return {@code true} if {@code null} is passed in and the toClass is + * non-primitive.

+ * + *

Specifically, this method tests whether the type represented by the + * specified {@code Class} parameter can be converted to the type + * represented by this {@code Class} object via an identity conversion + * widening primitive or widening reference conversion. See + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

+ * + * @param classArray the array of Classes to check, may be {@code null} + * @param toClassArray the array of Classes to try to assign into, may be {@code null} + * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers + * @return {@code true} if assignment possible + */ + public static boolean isAssignable(Class[] classArray, Class[] toClassArray, 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; + } + + /** + * 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); + } + + /** + *

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

+ * + *

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

+ * + *

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

+ * + *

{@code Null} may be assigned to any reference type. This method + * will return {@code true} if {@code null} is passed in and the + * toClass is non-primitive.

+ * + *

Specifically, this method tests whether the type represented by the + * specified {@code Class} parameter can be converted to the type + * represented by this {@code Class} object via an identity conversion + * widening primitive or widening reference conversion. See + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

+ * + *

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

+ * + * @param cls the Class to check, may be null + * @param toClass the Class to try to assign into, returns false if null + * @return {@code true} if assignment possible + */ + public static boolean isAssignable(final Class cls, final Class toClass) { + return isAssignable(cls, toClass, true); + } + + /** + *

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

+ * + *

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

+ * + *

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

+ * + *

{@code Null} may be assigned to any reference type. This method + * will return {@code true} if {@code null} is passed in and the + * toClass is non-primitive.

+ * + *

Specifically, this method tests whether the type represented by the + * specified {@code Class} parameter can be converted to the type + * represented by this {@code Class} object via an identity conversion + * widening primitive or widening reference conversion. See + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

+ * + * @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)) { + return Integer.TYPE.equals(toClass) + || Long.TYPE.equals(toClass) + || Float.TYPE.equals(toClass) + || Double.TYPE.equals(toClass); + } + if (Short.TYPE.equals(cls)) { + return Integer.TYPE.equals(toClass) + || Long.TYPE.equals(toClass) + || Float.TYPE.equals(toClass) + || Double.TYPE.equals(toClass); + } + if (Byte.TYPE.equals(cls)) { + return Short.TYPE.equals(toClass) + || Integer.TYPE.equals(toClass) + || Long.TYPE.equals(toClass) + || Float.TYPE.equals(toClass) + || Double.TYPE.equals(toClass); + } + // should never get here + return false; + } + return toClass.isAssignableFrom(cls); + } + + /** + *

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

+ * + *

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

+ * + * @param cls the class to convert, may be null + * @return the wrapper class for {@code cls} or {@code cls} if + * {@code cls} is not a primitive. {@code null} if null input. + * @since 2.1 + */ + public static Class primitiveToWrapper(final Class cls) { + Class convertedClass = cls; + if (cls != null && cls.isPrimitive()) { + convertedClass = primitiveWrapperMap.get(cls); + } + return convertedClass; + } + + /** + *

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

+ * + * @param classes the class array to convert, may be null or empty + * @return an array which contains for each given class, the wrapper class or + * the original class if class is not a primitive. {@code null} if null input. + * Empty array if an empty array passed in. + * @since 2.1 + */ + public static Class[] primitivesToWrappers(final Class... classes) { + if (classes == null) { + return null; + } + + if (classes.length == 0) { + return classes; + } + + final Class[] convertedClasses = new Class[classes.length]; + for (int i = 0; i < classes.length; i++) { + convertedClasses[i] = primitiveToWrapper(classes[i]); + } + return convertedClasses; + } + + /** + *

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

+ * + *

This method is the counter part of {@code primitiveToWrapper()}. + * If the passed in class is a wrapper class for a primitive type, this + * primitive type will be returned (e.g. {@code Integer.TYPE} for + * {@code Integer.class}). For other classes, or if the parameter is + * null, the return value is null.

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

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

+ * + *

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

+ * + * @param classes the class array to convert, may be null or empty + * @return an array which contains for each given class, the primitive class or + * null if the original class is not a wrapper class. {@code null} if null input. + * Empty array if an empty array passed in. + * @see #wrapperToPrimitive(Class) + * @since 2.4 + */ + public static Class[] wrappersToPrimitives(final Class... classes) { + if (classes == null) { + return null; + } + + if (classes.length == 0) { + return classes; + } + + final Class[] convertedClasses = new Class[classes.length]; + for (int i = 0; i < classes.length; i++) { + convertedClasses[i] = wrapperToPrimitive(classes[i]); + } + return convertedClasses; + } + + // Inner class + // ---------------------------------------------------------------------- + /** + *

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

+ * + * @param cls the class to check, may be null + * @return {@code true} if the class is an inner or static nested class, + * false if not or {@code null} + */ + public static boolean isInnerClass(final Class cls) { + return cls != null && cls.getEnclosingClass() != null; + } + + // Class loading + // ---------------------------------------------------------------------- + /** + * Returns the class represented by {@code className} using the + * {@code classLoader}. This implementation supports the syntaxes + * "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}", + * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}". + * + * @param classLoader the class loader to use to load the class + * @param className the class name + * @param initialize whether the class must be initialized + * @return the class represented by {@code className} using the {@code classLoader} + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass( + final ClassLoader classLoader, final String className, final boolean initialize) throws ClassNotFoundException { + try { + final Class clazz; + if (namePrimitiveMap.containsKey(className)) { + clazz = namePrimitiveMap.get(className); + } else { + clazz = Class.forName(toCanonicalName(className), initialize, classLoader); + } + return clazz; + } 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 ex2) { // NOPMD + // ignore exception + } + } + + throw ex; + } + } + + /** + * Returns the (initialized) class represented by {@code className} + * using the {@code classLoader}. This implementation supports + * the syntaxes "{@code java.util.Map.Entry[]}", + * "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", + * and "{@code [Ljava.util.Map$Entry;}". + * + * @param classLoader the class loader to use to load the class + * @param className the class name + * @return the class represented by {@code className} using the {@code classLoader} + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(final ClassLoader classLoader, final String className) throws ClassNotFoundException { + return getClass(classLoader, className, true); + } + + /** + * Returns the (initialized) class represented by {@code className} + * using the current thread's context class loader. This implementation + * supports the syntaxes "{@code java.util.Map.Entry[]}", + * "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", + * and "{@code [Ljava.util.Map$Entry;}". + * + * @param className the class name + * @return the class represented by {@code className} using the current thread's context class loader + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(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 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); + } + + // Public method + // ---------------------------------------------------------------------- + /** + *

Returns the desired Method much like {@code Class.getMethod}, however + * it ensures that the returned Method is from a public class or interface and not + * from an anonymous inner class. This means that the Method is invokable and + * doesn't fall foul of Java bug + * 4071957).

+ * + *
+     *  Set set = Collections.unmodifiableSet(...);
+     *  Method method = ClassUtils.getPublicMethod(set.getClass(), "isEmpty",  new Class[0]);
+     *  Object result = method.invoke(set, new Object[]);
+     *  
+ * + * @param cls the class to check, not null + * @param methodName the name of the method + * @param parameterTypes the list of parameters + * @return the method + * @throws NullPointerException if the class is null + * @throws SecurityException if a 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 (Modifier.isPublic(declaredMethod.getDeclaringClass().getModifiers())) { + return declaredMethod; + } + + final List> candidateClasses = new ArrayList<>(getAllInterfaces(cls)); + candidateClasses.addAll(getAllSuperclasses(cls)); + + for (final Class candidateClass : candidateClasses) { + if (!Modifier.isPublic(candidateClass.getModifiers())) { + 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)); + } + + // ---------------------------------------------------------------------- + /** + * Converts a class name to a JLS style class name. + * + * @param className the class name + * @return the converted name + */ + private static String toCanonicalName(String className) { + className = StringUtils.deleteWhitespace(className); + Validate.notNull(className, "className"); + if (className.endsWith("[]")) { + final StringBuilder classNameBuffer = new StringBuilder(); + while (className.endsWith("[]")) { + className = className.substring(0, className.length() - 2); + classNameBuffer.append("["); + } + final String abbreviation = abbreviationMap.get(className); + if (abbreviation != null) { + classNameBuffer.append(abbreviation); + } else { + classNameBuffer.append("L").append(className).append(";"); + } + className = classNameBuffer.toString(); + } + return className; + } + + /** + *

Converts an array of {@code Object} in to an array of {@code Class} objects. + * If any of these objects is null, a null element will be inserted into the array.

+ * + *

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

+ * + * @param array an {@code Object} array + * @return a {@code Class} array, {@code null} if null array input + * @since 2.4 + */ + public static Class[] toClass(final Object... array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return ArrayUtils.EMPTY_CLASS_ARRAY; + } + final Class[] classes = new Class[array.length]; + for (int i = 0; i < array.length; i++) { + classes[i] = array[i] == null ? null : array[i].getClass(); + } + return classes; + } + + // Short canonical name + // ---------------------------------------------------------------------- + /** + *

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

+ * + * @param object the class to get the short name for, may be null + * @param valueIfNull the value to return if null + * @return the canonical name of the object without the package name, or the null value + * @since 2.4 + */ + public static String getShortCanonicalName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; + } + return getShortCanonicalName(object.getClass().getName()); + } + + /** + *

Gets the canonical class name for a {@code 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 {@code 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 {@code 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 {@code 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; + } + + /** + *

Gets the canonical name minus the package name from a {@code 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 + */ + public static String getShortCanonicalName(final Class cls) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getShortCanonicalName(cls.getName()); + } + + /** + *

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

+ * + *

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

+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples
return valueinput
{@code ""}{@code (String)null}
{@code "Map.Entry"}{@code java.util.Map.Entry.class.getName()}
{@code "Entry"}{@code java.util.Map.Entry.class.getCanonicalName()}
{@code "ClassUtils"}{@code "org.apache.commons.lang3.ClassUtils"}
{@code "ClassUtils[]"}{@code "[Lorg.apache.commons.lang3.ClassUtils;"}
{@code "ClassUtils[][]"}{@code "[[Lorg.apache.commons.lang3.ClassUtils;"}
{@code "ClassUtils[]"}{@code "org.apache.commons.lang3.ClassUtils[]"}
{@code "ClassUtils[][]"}{@code "org.apache.commons.lang3.ClassUtils[][]"}
{@code "int[]"}{@code "[I"}
{@code "int[]"}{@code int[].class.getCanonicalName()}
{@code "int[]"}{@code int[].class.getName()}
{@code "int[][]"}{@code "[[I"}
{@code "int[]"}{@code "int[]"}
{@code "int[][]"}{@code "int[][]"}
+ * + * @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)); + } + + // Package name + // ---------------------------------------------------------------------- + /** + *

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

+ * + * @param object the class to get the package name for, may be null + * @param valueIfNull the value to return if null + * @return the package name of the object, or the null value + * @since 2.4 + */ + public static String getPackageCanonicalName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; + } + return getPackageCanonicalName(object.getClass().getName()); + } + + /** + *

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

+ * + * @param cls the class to get the package name for, may be {@code null}. + * @return the package name or an empty string + * @since 2.4 + */ + public static String getPackageCanonicalName(final Class cls) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getPackageCanonicalName(cls.getName()); + } + + /** + *

Gets the package name from the class name.

+ * + *

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

+ *

If the class is in the default package, return an empty string.

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

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

+ * + *

The method does not change the {@code $} separators in case + * the class is inner class.

+ * + *

Example: + *

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

+ * + * @param className the name of class + * @return canonical form of class name + * @since 2.4 + */ + private static String getCanonicalName(String className) { + className = StringUtils.deleteWhitespace(className); + if (className == null) { + return null; + } + 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(); + } + + /** + * 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> 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> hierarchy(final Class type, final Interfaces interfacesBehavior) { + final Iterable> classes = () -> { + final MutableObject> next = new MutableObject<>(type); + return new Iterator>() { + + @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> seenInterfaces = new HashSet<>(); + final Iterator> wrapped = classes.iterator(); + + return new Iterator>() { + Iterator> interfaces = Collections.>emptySet().iterator(); + + @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> currentInterfaces = new LinkedHashSet<>(); + walkInterfaces(currentInterfaces, nextSuperclass); + interfaces = currentInterfaces.iterator(); + return nextSuperclass; + } + + private void walkInterfaces(final Set> addTo, final Class c) { + for (final Class iface : c.getInterfaces()) { + if (!seenInterfaces.contains(iface)) { + addTo.add(iface); + } + walkInterfaces(addTo, iface); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + }; + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/Conversion.java b/after/src/main/java/org/apache/commons/lang3/Conversion.java new file mode 100644 index 0000000..e710701 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/Conversion.java @@ -0,0 +1,1563 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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. + *

+ *

+ * The methods names follow a naming rule:
+ * {@code [source endianness][source bit ordering]To[destination endianness][destination bit ordering]} + *

+ *

+ * Source/destination type fields is one of the following: + *

+ *
    + *
  • binary: an array of booleans
  • + *
  • byte or byteArray
  • + *
  • int or intArray
  • + *
  • long or longArray
  • + *
  • hex: a String containing hexadecimal digits (lowercase in destination)
  • + *
  • hexDigit: a Char containing a hexadecimal digit (lowercase in destination)
  • + *
  • uuid
  • + *
+ *

+ * Endianness field: little endian is the default, in this case the field is absent. In case of + * big endian, the field is "Be".
Bit ordering: Lsb0 is the default, in this case the field + * is absent. In case of Msb0, the field is "Msb0". + *

+ *

+ * Example: intBeMsb0ToHex convert an int with big endian byte order and Msb0 bit order into its + * hexadecimal string representation + *

+ *

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

+ *

+ * Development status: work on going, only a part of the little endian, Lsb0 methods implemented + * so far. + *

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

+ *

+ * '1' is converted to 1 + *

+ * + * @param hexDigit the hexadecimal digit to convert + * @return an int equals to {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static int hexDigitToInt(final char hexDigit) { + final int digit = Character.digit(hexDigit, 16); + if (digit < 0) { + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + } + return digit; + } + + /** + *

+ * Converts a hexadecimal digit into an int using the Msb0 bit ordering. + *

+ *

+ * '1' is converted to 8 + *

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

+ * Converts a hexadecimal digit into binary (represented as boolean array) using the default + * (Lsb0) bit ordering. + *

+ *

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

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

+ * Converts a hexadecimal digit into binary (represented as boolean array) using the Msb0 + * bit ordering. + *

+ *

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

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

+ * Converts binary (represented as boolean array) to a hexadecimal digit using the default + * (Lsb0) bit ordering. + *

+ *

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

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

+ *

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

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

+ *

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

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

+ *

+ * (1, 0, 0, 0) is converted as follow: '8' (1, 0, 0, 1, 1, 0, 1, 0) with srcPos = 3 is converted + * to 'D' + *

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

+ *

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

+ * + * @param 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. + *

+ *

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

+ * + * @param 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 binaryBeMsb0ToHexDigit(boolean[] src, int srcPos) { + if (src.length == 0) { + throw new IllegalArgumentException("Cannot convert an empty array."); + } + final int beSrcPos = src.length - 1 - srcPos; + final int srcLen = Math.min(4, beSrcPos + 1); + final boolean[] paddedSrc = new boolean[4]; + System.arraycopy(src, beSrcPos + 1 - srcLen, paddedSrc, 4 - srcLen, srcLen); + src = paddedSrc; + srcPos = 0; + if (src[srcPos]) { + if (src.length > srcPos + 1 && src[srcPos + 1]) { + if (src.length > srcPos + 2 && src[srcPos + 2]) { + return src.length > srcPos + 3 && src[srcPos + 3] ? 'f' : 'e'; + } + return src.length > srcPos + 3 && src[srcPos + 3] ? 'd' : 'c'; + } + if (src.length > srcPos + 2 && src[srcPos + 2]) { + return src.length > srcPos + 3 && src[srcPos + 3] ? 'b' : 'a'; + } + return src.length > srcPos + 3 && src[srcPos + 3] ? '9' : '8'; + } + if (src.length > srcPos + 1 && src[srcPos + 1]) { + if (src.length > srcPos + 2 && src[srcPos + 2]) { + return src.length > srcPos + 3 && src[srcPos + 3] ? '7' : '6'; + } + return src.length > srcPos + 3 && src[srcPos + 3] ? '5' : '4'; + } + if (src.length > srcPos + 2 && src[srcPos + 2]) { + return src.length > srcPos + 3 && src[srcPos + 3] ? '3' : '2'; + } + return src.length > srcPos + 3 && src[srcPos + 3] ? '1' : '0'; + } + + /** + *

+ * Converts the 4 lsb of an int to a hexadecimal digit. + *

+ *

+ * 0 returns '0' + *

+ *

+ * 1 returns '1' + *

+ *

+ * 10 returns 'A' and so on... + *

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

+ *

+ * 0 returns '0' + *

+ *

+ * 1 returns '8' + *

+ *

+ * 10 returns '5' and so on... + *

+ * + * @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/after/src/main/java/org/apache/commons/lang3/EnumUtils.java b/after/src/main/java/org/apache/commons/lang3/EnumUtils.java new file mode 100644 index 0000000..1df4301 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/EnumUtils.java @@ -0,0 +1,379 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + *

Utility library to provide helper methods for Java enums.

+ * + *

#ThreadSafe#

+ * + * @since 3.0 + */ +public class EnumUtils { + + private static final String NULL_ELEMENTS_NOT_PERMITTED = "null elements not permitted"; + private static final String CANNOT_STORE_S_S_VALUES_IN_S_BITS = "Cannot store %s %s values in %s bits"; + private static final String S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE = "%s does not seem to be an Enum type"; + private static final String ENUM_CLASS_MUST_BE_DEFINED = "EnumClass must be defined."; + + /** + * Validate {@code enumClass}. + * @param 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 > Class asEnum(final Class enumClass) { + Validate.notNull(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 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 > Class checkBitVectorable(final Class 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.

+ * + *

This generates a value that is usable by {@link EnumUtils#processBitVector}.

+ * + *

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.

+ * + * @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 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 > long generateBitVector(final Class 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.

+ * + *

This generates a value that is usable by {@link EnumUtils#processBitVector}.

+ * + *

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.

+ * + * @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 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 > long generateBitVector(final Class enumClass, final Iterable values) { + checkBitVectorable(enumClass); + Validate.notNull(values); + long total = 0; + for (final E constant : values) { + Validate.notNull(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.

+ * + *

This generates a value that is usable by {@link EnumUtils#processBitVectors}.

+ * + *

Use this method if you have more than 64 values in your Enum.

+ * + * @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 the type of the enumeration + * @return a long[] whose values provide a binary representation of the given set of enum values + * with 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 > long[] generateBitVectors(final Class enumClass, final E... values) { + asEnum(enumClass); + Validate.noNullElements(values); + final EnumSet 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.

+ * + *

This generates a value that is usable by {@link EnumUtils#processBitVectors}.

+ * + *

Use this method if you have more than 64 values in your Enum.

+ * + * @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 the type of the enumeration + * @return a long[] whose values provide a binary representation of the given set of enum values + * with 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 > long[] generateBitVectors(final Class enumClass, final Iterable values) { + asEnum(enumClass); + Validate.notNull(values); + final EnumSet condensed = EnumSet.noneOf(enumClass); + for (final E constant : values) { + Validate.notNull(constant, NULL_ELEMENTS_NOT_PERMITTED); + condensed.add(constant); + } + 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.

+ * + *

This method differs from {@link Enum#valueOf} in that it does not throw an exception + * for an invalid enum name.

+ * + * @param 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 getEnum(final Class enumClass, final String enumName) { + return getEnum(enumClass, enumName, null); + } + + /** + *

Gets the enum for the class, returning {@code defaultEnum} if not found.

+ * + *

This method differs from {@link Enum#valueOf} in that it does not throw an exception + * for an invalid enum name.

+ * + * @param 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 getEnum(final Class 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.

+ * + *

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.

+ * + * @param 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 getEnumIgnoreCase(final Class enumClass, final String enumName) { + return getEnumIgnoreCase(enumClass, enumName, null); + } + + /** + *

Gets the enum for the class, returning {@code defaultEnum} if not found.

+ * + *

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.

+ * + * @param 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 getEnumIgnoreCase(final Class enumClass, final String enumName, final E defaultEnum) { + if (enumName == null || !enumClass.isEnum()) { + return defaultEnum; + } + for (final E each : enumClass.getEnumConstants()) { + if (each.name().equalsIgnoreCase(enumName)) { + return each; + } + } + return defaultEnum; + } + + /** + *

Gets the {@code List} of enums.

+ * + *

This method is useful when you need a list of enums rather than an array.

+ * + * @param 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 > List getEnumList(final Class enumClass) { + return new ArrayList<>(Arrays.asList(enumClass.getEnumConstants())); + } + + /** + *

Gets the {@code Map} of enums by name.

+ * + *

This method is useful when you need a map of enums by name.

+ * + * @param 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 > Map getEnumMap(final Class enumClass) { + final Map map = new LinkedHashMap<>(); + for (final E e: enumClass.getEnumConstants()) { + map.put(e.name(), e); + } + return map; + } + + /** + *

Checks if the specified name is a valid enum for the class.

+ * + *

This method differs from {@link Enum#valueOf} in that checks if the name is + * a valid enum without needing to catch the exception.

+ * + * @param 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 > boolean isValidEnum(final Class enumClass, final String enumName) { + return getEnum(enumClass, enumName) != null; + } + + /** + *

Checks if the specified name is a valid enum for the class.

+ * + *

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.

+ * + * @param 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 > boolean isValidEnumIgnoreCase(final Class 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.

+ * + *

If you store this value, beware any changes to the enum that would affect ordinal values.

+ * @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 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 > EnumSet processBitVector(final Class 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.

+ * + *

If you store this value, beware any changes to the enum that would affect ordinal values.

+ * @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, least significant digits rightmost, not {@code null} + * @param 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 > EnumSet processBitVectors(final Class enumClass, final long... values) { + final EnumSet results = EnumSet.noneOf(asEnum(enumClass)); + final long[] lvalues = ArrayUtils.clone(Validate.notNull(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/after/src/main/java/org/apache/commons/lang3/Functions.java b/after/src/main/java/org/apache/commons/lang3/Functions.java new file mode 100644 index 0000000..f3ec266 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/Functions.java @@ -0,0 +1,693 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.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: + * + *
+ * {@code
+ *     Consumer consumer = m -> {
+ *         try {
+ *             m.invoke(o, args);
+ *         } catch (Throwable t) {
+ *             throw Functions.rethrow(t);
+ *         }
+ *     };
+ * }
+ * + *

+ * By replacing a {@link java.util.function.Consumer Consumer<O>} with a {@link FailableConsumer + * FailableConsumer<O,? extends Throwable>}, this can be written like follows: + *

+ * + *
+ * {@code
+ *   Functions.accept((m) -> m.invoke(o,args));
+ * }
+ * + *

+ * Obviously, the second version is much more concise and the spirit of Lambda expressions is met better than the second + * version. + *

+ * @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 {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Consumed type 1. + * @param Consumed type 2. + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailableBiConsumer}. + */ + @Deprecated + @FunctionalInterface + public interface FailableBiConsumer { + + /** + * 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 {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Input type 1. + * @param Input type 2. + * @param Return type. + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailableBiFunction}. + */ + @Deprecated + @FunctionalInterface + public interface FailableBiFunction { + + /** + * 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 {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Predicate type 1. + * @param Predicate type 2. + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailableBiPredicate}. + */ + @Deprecated + @FunctionalInterface + public interface FailableBiPredicate { + + /** + * 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 {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Return type. + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailableCallable}. + */ + @Deprecated + @FunctionalInterface + public interface FailableCallable { + + /** + * 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 {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Consumed type 1. + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailableConsumer}. + */ + @Deprecated + @FunctionalInterface + public interface FailableConsumer { + + /** + * 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 {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Input type 1. + * @param Return type. + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailableFunction}. + */ + @Deprecated + @FunctionalInterface + public interface FailableFunction { + + /** + * 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 {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Predicate type 1. + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailablePredicate}. + */ + @Deprecated + @FunctionalInterface + public interface FailablePredicate { + + /** + * 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 {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailableRunnable}. + */ + @Deprecated + @FunctionalInterface + public interface FailableRunnable { + + /** + * Runs the function. + * + * @throws T Thrown when the function fails. + */ + void run() throws T; + } + + /** + * A functional interface like {@link Supplier} that declares a {@code Throwable}. + * + *

TODO for 4.0: Move to org.apache.commons.lang3.function.

+ * + * @param Return type. + * @param Thrown exception. + * @deprecated Use {@link org.apache.commons.lang3.function.FailableSupplier}. + */ + @Deprecated + @FunctionalInterface + public interface FailableSupplier { + + /** + * 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 the type of the first argument the consumer accepts + * @param the type of the second argument the consumer accepts + * @param the type of checked exception the consumer may throw + */ + public static void accept(final FailableBiConsumer 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 the type the consumer accepts + * @param the type of checked exception the consumer may throw + */ + public static void accept(final FailableConsumer 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 the type of the first argument the function accepts + * @param the type of the second argument the function accepts + * @param the return type of the function + * @param the type of checked exception the function may throw + * @return the value returned from the function + */ + public static O apply(final FailableBiFunction 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 the type of the argument the function accepts + * @param the return type of the function + * @param the type of checked exception the function may throw + * @return the value returned from the function + */ + public static O apply(final FailableFunction function, final I input) { + return get(() -> function.apply(input)); + } + + /** + * Converts the given {@link FailableBiConsumer} into a standard {@link BiConsumer}. + * + * @param the type of the first argument of the consumers + * @param the type of the second argument of the consumers + * @param consumer a failable {@code BiConsumer} + * @return a standard {@code BiConsumer} + * @since 3.10 + */ + public static BiConsumer asBiConsumer(final FailableBiConsumer consumer) { + return (input1, input2) -> accept(consumer, input1, input2); + } + + /** + * Converts the given {@link FailableBiFunction} into a standard {@link BiFunction}. + * + * @param the type of the first argument of the input of the functions + * @param the type of the second argument of the input of the functions + * @param the type of the output of the functions + * @param function a {@code FailableBiFunction} + * @return a standard {@code BiFunction} + * @since 3.10 + */ + public static BiFunction asBiFunction(final FailableBiFunction function) { + return (input1, input2) -> apply(function, input1, input2); + } + + /** + * Converts the given {@link FailableBiPredicate} into a standard {@link BiPredicate}. + * + * @param the type of the first argument used by the predicates + * @param the type of the second argument used by the predicates + * @param predicate a {@code FailableBiPredicate} + * @return a standard {@code BiPredicate} + * @since 3.10 + */ + public static BiPredicate asBiPredicate(final FailableBiPredicate predicate) { + return (input1, input2) -> test(predicate, input1, input2); + } + + /** + * Converts the given {@link FailableCallable} into a standard {@link Callable}. + * + * @param the type used by the callables + * @param callable a {@code FailableCallable} + * @return a standard {@code Callable} + * @since 3.10 + */ + public static Callable asCallable(final FailableCallable callable) { + return () -> call(callable); + } + + /** + * Converts the given {@link FailableConsumer} into a standard {@link Consumer}. + * + * @param the type used by the consumers + * @param consumer a {@code FailableConsumer} + * @return a standard {@code Consumer} + * @since 3.10 + */ + public static Consumer asConsumer(final FailableConsumer consumer) { + return input -> accept(consumer, input); + } + + /** + * Converts the given {@link FailableFunction} into a standard {@link Function}. + * + * @param the type of the input of the functions + * @param the type of the output of the functions + * @param function a {code FailableFunction} + * @return a standard {@code Function} + * @since 3.10 + */ + public static Function asFunction(final FailableFunction function) { + return input -> apply(function, input); + } + + /** + * Converts the given {@link FailablePredicate} into a standard {@link Predicate}. + * + * @param the type used by the predicates + * @param predicate a {@code FailablePredicate} + * @return a standard {@code Predicate} + * @since 3.10 + */ + public static Predicate asPredicate(final FailablePredicate predicate) { + return input -> test(predicate, input); + } + + /** + * Converts the given {@link FailableRunnable} into a standard {@link Runnable}. + * + * @param runnable a {@code FailableRunnable} + * @return a standard {@code 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 the type supplied by the suppliers + * @param supplier a {@code FailableSupplier} + * @return a standard {@code Supplier} + * @since 3.10 + */ + public static Supplier asSupplier(final FailableSupplier supplier) { + return () -> get(supplier); + } + + /** + * Calls a callable and rethrows any exception as a {@link RuntimeException}. + * + * @param callable the callable to call + * @param the return type of the callable + * @param the type of checked exception the callable may throw + * @return the value returned from the callable + */ + public static O call(final FailableCallable callable) { + return get(callable::call); + } + + /** + * Invokes a supplier, and returns the result. + * + * @param supplier The supplier to invoke. + * @param The suppliers output type. + * @param 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 get(final FailableSupplier 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 The type of checked exception, which the supplier can throw. + * @return The boolean, which has been created by the supplier + */ + private static boolean getAsBoolean(final FailableBooleanSupplier 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 + * {@code RuntimeException} or {@code Error} then the argument will be rethrown without modification. If the + * exception is {@code IOException} then it will be wrapped into a {@code UncheckedIOException}. In every other + * cases the exception will be wrapped into a {@code + * UndeclaredThrowableException} + *

+ * + *

+ * 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: + *

+ * + *
+     * throw rethrow(myUncheckedException);
+ * + *

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

+ * + * @param throwable The throwable to rethrow ossibly 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; + } else if (throwable instanceof Error) { + throw (Error) throwable; + } else if (throwable instanceof IOException) { + throw new UncheckedIOException((IOException) throwable); + } else { + throw new UndeclaredThrowableException(throwable); + } + } + + /** + * Runs a runnable and rethrows any exception as a {@link RuntimeException}. + * + * @param runnable The runnable to run + * @param the type of checked exception the runnable may throw + */ + public static void run(final FailableRunnable 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 + * + *
+     * Functions.stream(collection.stream());
+ * + * @param collection The collection, which is being converted into a {@link FailableStream}. + * @param The collections element type. (In turn, the result streams element type.) + * @return The created {@link FailableStream}. + * @since 3.10 + */ + public static FailableStream stream(final Collection 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 The streams element type. + * @return The created {@link FailableStream}. + * @since 3.10 + */ + public static FailableStream stream(final Stream 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 the type of the first argument the predicate tests + * @param the type of the second argument the predicate tests + * @param the type of checked exception the predicate may throw + * @return the boolean value returned by the predicate + */ + public static boolean test(final FailableBiPredicate 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 the type of argument the predicate tests + * @param the type of checked exception the predicate may throw + * @return the boolean value returned by the predicate + */ + public static boolean test(final FailablePredicate 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 all + * 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 first failure (AKA + * {@link Throwable} is rethrown. Example use: + * + *
+     * {@code
+     *     final FileInputStream fis = new FileInputStream("my.file");
+     *     Functions.tryWithResources(useInputStream(fis), null, () -> fis.close());
+     * }
+ * + * @param action The action to execute. This object will 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. All 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 action, + final FailableConsumer errorHandler, + final FailableRunnable... resources) { + final FailableConsumer actualErrorHandler; + if (errorHandler == null) { + actualErrorHandler = Functions::rethrow; + } else { + actualErrorHandler = errorHandler; + } + if (resources != null) { + for (final FailableRunnable failableRunnable : resources) { + Objects.requireNonNull(failableRunnable, "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 all + * 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 first failure (AKA + * {@link Throwable} is rethrown. Example use: + * + *
+     * {@code
+     *     final FileInputStream fis = new FileInputStream("my.file");
+     *     Functions.tryWithResources(useInputStream(fis), () -> fis.close());
+     * }
+ * + * @param action The action to execute. This object will always be invoked. + * @param resources The resource actions to execute. All 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 action, + final FailableRunnable... resources) { + tryWithResources(action, null, resources); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/JavaVersion.java b/after/src/main/java/org/apache/commons/lang3/JavaVersion.java new file mode 100644 index 0000000..7b8f729 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/JavaVersion.java @@ -0,0 +1,324 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 + * java.specification.version 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"), + + /** + * 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.

+ * + *

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

+ * + * @param requiredVersion the version to check against, not null + * @return true if this version is equal to or greater than the specified version + */ + public boolean atLeast(final JavaVersion requiredVersion) { + return this.value >= requiredVersion.value; + } + + //----------------------------------------------------------------------- + /** + *

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

+ * + *

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

+ * + * @param requiredVersion the version to check against, not null + * @return true if this version is equal to or greater than the specified version + * @since 3.9 + */ + public boolean atMost(final JavaVersion requiredVersion) { + return this.value <= requiredVersion.value; + } + + /** + * Transforms the given string with a Java version number to the + * corresponding constant of this enumeration class. This method is used + * internally. + * + * @param nom the Java version as string + * @return the corresponding enumeration constant or null if the + * version is unknown + */ + // helper for static importing + static JavaVersion getJavaVersion(final String nom) { + return get(nom); + } + + /** + * Transforms the given string with a Java version number to the + * corresponding constant of this enumeration class. This method is used + * internally. + * + * @param versionStr the Java version as string + * @return the corresponding enumeration constant or null 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; + 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.

+ * + *

For example, {@code "1.5"}.

+ * + * @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")); + if (v > 0) { + return v; + } + return 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(".")) { + final String[] toParse = value.split("\\."); + if (toParse.length >= 2) { + return NumberUtils.toFloat(toParse[0] + '.' + toParse[1], defaultReturnValue); + } + } else { + return NumberUtils.toFloat(value, defaultReturnValue); + } + return defaultReturnValue; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/LocaleUtils.java b/after/src/main/java/org/apache/commons/lang3/LocaleUtils.java new file mode 100644 index 0000000..b57e3c0 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/LocaleUtils.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; + +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; + +/** + *

Operations to assist when working with a {@link Locale}.

+ * + *

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.

+ * + * @since 2.2 + */ +public class LocaleUtils { + + // class to avoid synchronization (Init on demand) + static class SyncAvoid { + /** Unmodifiable list of available locales. */ + private static final List AVAILABLE_LOCALE_LIST; + /** Unmodifiable set of available locales. */ + private static final Set AVAILABLE_LOCALE_SET; + + static { + final List 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> cLanguagesByCountry = + new ConcurrentHashMap<>(); + + /** Concurrent map of country locales by language. */ + private static final ConcurrentMap> cCountriesByLanguage = + new ConcurrentHashMap<>(); + + /** + *

Obtains an unmodifiable list of installed locales.

+ * + *

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.

+ * + * @return the unmodifiable list of available locales + */ + public static List availableLocaleList() { + return SyncAvoid.AVAILABLE_LOCALE_LIST; + } + + /** + *

Obtains an unmodifiable set of installed locales.

+ * + *

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.

+ * + * @return the unmodifiable set of available locales + */ + public static Set availableLocaleSet() { + return SyncAvoid.AVAILABLE_LOCALE_SET; + } + + /** + *

Obtains the list of countries supported for a given language.

+ * + *

This method takes a language code and searches to find the + * countries available for that language. Variant locales are removed.

+ * + * @param languageCode the 2 letter language code, null returns empty + * @return an unmodifiable List of Locale objects, not null + */ + public static List countriesByLanguage(final String languageCode) { + if (languageCode == null) { + return Collections.emptyList(); + } + List countries = cCountriesByLanguage.get(languageCode); + if (countries == null) { + countries = new ArrayList<>(); + final List locales = availableLocaleList(); + for (final Locale locale : locales) { + if (languageCode.equals(locale.getLanguage()) && + !locale.getCountry().isEmpty() && + locale.getVariant().isEmpty()) { + countries.add(locale); + } + } + countries = Collections.unmodifiableList(countries); + cCountriesByLanguage.putIfAbsent(languageCode, countries); + countries = cCountriesByLanguage.get(languageCode); + } + return countries; + } + + /** + *

Checks if the locale specified is in the list 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 availableLocaleList().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.

+ * + *

This method takes a country code and searches to find the + * languages available for that country. Variant locales are removed.

+ * + * @param countryCode the 2 letter country code, null returns empty + * @return an unmodifiable List of Locale objects, not null + */ + public static List languagesByCountry(final String countryCode) { + if (countryCode == null) { + return Collections.emptyList(); + } + List langs = cLanguagesByCountry.get(countryCode); + if (langs == null) { + langs = new ArrayList<>(); + final List locales = availableLocaleList(); + for (final Locale locale : locales) { + if (countryCode.equals(locale.getCountry()) && + locale.getVariant().isEmpty()) { + langs.add(locale); + } + } + langs = Collections.unmodifiableList(langs); + cLanguagesByCountry.putIfAbsent(countryCode, langs); + langs = cLanguagesByCountry.get(countryCode); + } + return langs; + } + + /** + *

Obtains the list of locales to search through when performing + * a locale search.

+ * + *
+     * localeLookupList(Locale("fr", "CA", "xxx"))
+     *   = [Locale("fr", "CA", "xxx"), Locale("fr", "CA"), Locale("fr")]
+     * 
+ * + * @param locale the locale to start from + * @return the unmodifiable list of Locale objects, 0 being locale, not null + */ + public static List localeLookupList(final Locale locale) { + return localeLookupList(locale, locale); + } + + /** + *

Obtains the list of locales to search through when performing + * a locale search.

+ * + *
+     * localeLookupList(Locale("fr", "CA", "xxx"), Locale("en"))
+     *   = [Locale("fr", "CA", "xxx"), Locale("fr", "CA"), Locale("fr"), Locale("en"]
+     * 
+ * + *

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.

+ * + * @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 localeLookupList(final Locale locale, final Locale defaultLocale) { + final List 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.split("_", -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.

+ * + *

This method takes the string format of a locale and creates the + * locale object from it.

+ * + *
+     *   LocaleUtils.toLocale("")           = new Locale("", "")
+     *   LocaleUtils.toLocale("en")         = new Locale("en", "")
+     *   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")   (#)
+     * 
+ * + *

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

+ * + *

This method validates the input strictly. + * The language code must be lowercase. + * The country code must be uppercase. + * The separator must be an underscore. + * The length must be correct. + *

+ * + * @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) { + 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 == '_') { + 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) != '_') { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + return new Locale(StringUtils.EMPTY, str.substring(1, 3), str.substring(4)); + } + + return parseLocale(str); + } + + /** + *

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

+ * + *

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

+ */ + public LocaleUtils() { + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/NotImplementedException.java b/after/src/main/java/org/apache/commons/lang3/NotImplementedException.java new file mode 100644 index 0000000..2ad7463 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/NotImplementedException.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 {@code UnsupportedOperationException} + * by providing a more semantically rich description of the problem.

+ * + *

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

+ * + *
+ * 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);
+ *   }
+ * }
+ * 
+ * + * 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; + + 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/after/src/main/java/org/apache/commons/lang3/ObjectUtils.java b/after/src/main/java/org/apache/commons/lang3/ObjectUtils.java new file mode 100644 index 0000000..4e0b250 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/ObjectUtils.java @@ -0,0 +1,1350 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.TreeSet; +import java.util.function.Supplier; + +import org.apache.commons.lang3.exception.CloneFailedException; +import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.commons.lang3.text.StrBuilder; +import org.apache.commons.lang3.time.DurationUtils; + +/** + *

Operations on {@code Object}.

+ * + *

This class tries to handle {@code null} input gracefully. + * An exception will generally not be thrown for a {@code null} input. + * Each method documents its behavior in more detail.

+ * + *

#ThreadSafe#

+ * @since 1.0 + */ +//@Immutable +@SuppressWarnings("deprecation") // deprecated class StrBuilder is imported +// because it is part of the signature of deprecated methods +public class ObjectUtils { + + // Null + //----------------------------------------------------------------------- + /** + *

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

+ * + *

For example, in a {@code HashMap} the + * {@link java.util.HashMap#get(java.lang.Object)} method returns + * {@code null} if the {@code Map} contains {@code null} or if there is + * no matching key. The {@code Null} placeholder can be used to distinguish + * between these two cases.

+ * + *

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

+ */ + public static class Null implements Serializable { + /** + * Required for serialization support. Declare serialization compatibility with Commons Lang 1.0 + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 7092611880189329093L; + + /** + * Restricted constructor - singleton. + */ + Null() { + } + + /** + *

Ensure singleton.

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

+ * + *

For example, in a {@code HashMap} the + * {@link java.util.HashMap#get(java.lang.Object)} method returns + * {@code null} if the {@code Map} contains {@code null} or if there + * is no matching key. The {@code Null} placeholder can be used to + * distinguish between these two cases.

+ * + *

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

+ * + *

This instance is Serializable.

+ */ + public static final Null NULL = new Null(); + + /** + * Checks if all values in the array are not {@code nulls}. + * + *

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

+ * + *
+     * 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
+     * 
+ * + * @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) { + if (values == null) { + return false; + } + + for (final Object val : values) { + if (val == null) { + return false; + } + } + + return true; + } + + /** + * Checks if all values in the given array are {@code null}. + * + *

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

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

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

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

+ * If any of the values are {@code null} or the array is {@code null}, + * then {@code true} is returned, otherwise {@code false} is returned. + *

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

Clone an object.

+ * + * @param the type of the object + * @param obj the object to clone, null returns null + * @return the clone if the object implements {@link Cloneable} otherwise {@code null} + * @throws CloneFailedException if the object is cloneable and the clone operation fails + * @since 3.0 + */ + public static T clone(final T obj) { + if (obj instanceof Cloneable) { + final Object result; + if (obj.getClass().isArray()) { + final Class componentType = obj.getClass().getComponentType(); + if (componentType.isPrimitive()) { + 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.

+ * + *

This method is similar to {@link #clone(Object)}, but will return the provided + * instance as the return value instead of {@code null} if the instance + * is not cloneable. This is more convenient if the caller uses different + * implementations (e.g. of a service) and some of the implementations do not allow concurrent + * processing or have state. In such cases the implementation can simply provide a proper + * clone implementation and the caller's code does not have to change.

+ * + * @param the type of the object + * @param obj the object to clone, null returns null + * @return the clone if the object implements {@link Cloneable} otherwise the object itself + * @throws CloneFailedException if the object is cloneable and the clone operation fails + * @since 3.0 + */ + public static T cloneIfPossible(final T obj) { + final T clone = clone(obj); + return clone == null ? obj : clone; + } + + /** + *

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

+ * + * @param type of the values processed by this method + * @param c1 the first comparable, may be null + * @param c2 the second comparable, may be null + * @return a negative value if c1 < c2, zero if c1 = c2 + * and a positive value if c1 > c2 + */ + public static > int compare(final T c1, final T c2) { + return compare(c1, c2, false); + } + + /** + *

Null safe comparison of Comparables.

+ * + * @param type of the values processed by this method + * @param c1 the first comparable, may be null + * @param c2 the second comparable, may be null + * @param nullGreater if true {@code null} is considered greater + * than a non-{@code null} value or if false {@code null} is + * considered less than a Non-{@code null} value + * @return a negative value if c1 < c2, zero if c1 = c2 + * and a positive value if c1 > c2 + * @see java.util.Comparator#compare(Object, Object) + */ + public static > int compare(final T c1, final T c2, final boolean nullGreater) { + if (c1 == c2) { + return 0; + } else if (c1 == null) { + return nullGreater ? 1 : -1; + } else if (c2 == null) { + return nullGreater ? -1 : 1; + } + return c1.compareTo(c2); + } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
+     *     public final static boolean MAGIC_FLAG = ObjectUtils.CONST(true);
+     * 
+ * + * 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., + * + *
+     *     public final static byte MAGIC_BYTE = ObjectUtils.CONST((byte) 127);
+     * 
+ * + * 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., + * + *
+     *     public final static char MAGIC_CHAR = ObjectUtils.CONST('a');
+     * 
+ * + * 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., + * + *
+     *     public final static double MAGIC_DOUBLE = ObjectUtils.CONST(1.0);
+     * 
+ * + * 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., + * + *
+     *     public final static float MAGIC_FLOAT = ObjectUtils.CONST(1.0f);
+     * 
+ * + * 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., + * + *
+     *     public final static int MAGIC_INT = ObjectUtils.CONST(123);
+     * 
+ * + * 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., + * + *
+     *     public final static long MAGIC_LONG = ObjectUtils.CONST(123L);
+     * 
+ * + * 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., + * + *
+     *     public final static short MAGIC_SHORT = ObjectUtils.CONST((short) 123);
+     * 
+ * + * 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., + * + *
+     *     public final static String MAGIC_STRING = ObjectUtils.CONST("abc");
+     * 
+ * + * 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 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 CONST(final T v) { + return v; + } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
+     *     public final static byte MAGIC_BYTE = ObjectUtils.CONST_BYTE(127);
+     * 
+ * + * 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., + * + *
+     *     public final static short MAGIC_SHORT = ObjectUtils.CONST_SHORT(127);
+     * 
+ * + * 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}.

+ * + *
+     * ObjectUtils.defaultIfNull(null, null)      = null
+     * ObjectUtils.defaultIfNull(null, "")        = ""
+     * ObjectUtils.defaultIfNull(null, "zz")      = "zz"
+     * ObjectUtils.defaultIfNull("abc", *)        = "abc"
+     * ObjectUtils.defaultIfNull(Boolean.TRUE, *) = Boolean.TRUE
+     * 
+ * + * @param the type of the object + * @param object the {@code Object} to test, may be {@code null} + * @param defaultValue the default value to return, may be {@code null} + * @return {@code object} if it is not {@code null}, defaultValue otherwise + * TODO Rename to getIfNull in 4.0 + */ + public static 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}.

+ * + *
+     * ObjectUtils.equals(null, null)                  = true
+     * ObjectUtils.equals(null, "")                    = false
+     * ObjectUtils.equals("", null)                    = false
+     * ObjectUtils.equals("", "")                      = true
+     * ObjectUtils.equals(Boolean.TRUE, null)          = false
+     * ObjectUtils.equals(Boolean.TRUE, "true")        = false
+     * ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE)  = true
+     * ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
+     * 
+ * + * @param object1 the first object, may be {@code null} + * @param object2 the second object, may be {@code null} + * @return {@code true} if the values of both objects are the same + * @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) { + if (object1 == object2) { + return true; + } + if (object1 == null || object2 == null) { + return false; + } + return object1.equals(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.

+ * + *
+     * ObjectUtils.firstNonNull(null, null)      = null
+     * ObjectUtils.firstNonNull(null, "")        = ""
+     * ObjectUtils.firstNonNull(null, null, "")  = ""
+     * ObjectUtils.firstNonNull(null, "zz")      = "zz"
+     * ObjectUtils.firstNonNull("abc", *)        = "abc"
+     * ObjectUtils.firstNonNull(null, "xyz", *)  = "xyz"
+     * ObjectUtils.firstNonNull(Boolean.TRUE, *) = Boolean.TRUE
+     * ObjectUtils.firstNonNull()                = null
+     * 
+ * + * @param the component type of the array + * @param values the values to test, may be {@code null} or empty + * @return the first value from {@code values} which is not {@code null}, + * or {@code null} if there are no non-null values + * @since 3.0 + */ + @SafeVarargs + public static T firstNonNull(final T... values) { + if (values != null) { + for (final T val : values) { + if (val != null) { + return val; + } + } + } + return null; + } + + /** + *

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.

+ * + *
+     * ObjectUtils.firstNonNullLazy(null, () -> null) = null
+     * ObjectUtils.firstNonNullLazy(() -> null, () -> "") = ""
+     * ObjectUtils.firstNonNullLazy(() -> "", () -> throw new IllegalStateException()) = ""
+     * ObjectUtils.firstNonNullLazy(() -> null, () -> "zz) = "zz"
+     * ObjectUtils.firstNonNullLazy() = null
+     * 
+ * + * @param 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 getFirstNonNull(final Supplier... suppliers) { + if (suppliers != null) { + for (final Supplier supplier : suppliers) { + if (supplier != null) { + final T value = supplier.get(); + if (value != null) { + return value; + } + } + } + } + return null; + } + + /** + *

+ * Returns the given {@code object} is it is non-null, otherwise returns the Supplier's {@link Supplier#get()} + * value. + *

+ * + *

+ * The caller responsible for thread-safety and exception handling of default value supplier. + *

+ * + *
+     * ObjectUtils.getIfNull(null, () -> null)     = null
+     * ObjectUtils.getIfNull(null, null)              = null
+     * ObjectUtils.getIfNull(null, () -> "")       = ""
+     * ObjectUtils.getIfNull(null, () -> "zz")     = "zz"
+     * ObjectUtils.getIfNull("abc", *)                = "abc"
+     * ObjectUtils.getIfNull(Boolean.TRUE, *)         = Boolean.TRUE
+     * 
+ * + * @param the type of the object + * @param object the {@code 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 getIfNull(final T object, final Supplier defaultSupplier) { + return object != null ? object : defaultSupplier == null ? null : defaultSupplier.get(); + } + + /** + *

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

+ * + *
+     * ObjectUtils.hashCode(null)   = 0
+     * ObjectUtils.hashCode(obj)    = obj.hashCode()
+     * 
+ * + * @param obj the object to obtain the hash code of, may be {@code null} + * @return the hash code of the object, or zero if null + * @since 2.1 + * @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) retained for performance, as hash code is often critical + return obj == null ? 0 : obj.hashCode(); + } + + /** + *

Gets the hash code for multiple objects.

+ * + *

This allows a hash code to be rapidly calculated for a number of objects. + * The hash code for a single object is the not same as {@link #hashCode(Object)}. + * The hash code for multiple objects is the same as that calculated by an + * {@code ArrayList} containing the specified objects.

+ * + *
+     * ObjectUtils.hashCodeMulti()                 = 1
+     * ObjectUtils.hashCodeMulti((Object[]) null)  = 1
+     * ObjectUtils.hashCodeMulti(a)                = 31 + a.hashCode()
+     * ObjectUtils.hashCodeMulti(a,b)              = (31 + a.hashCode()) * 31 + b.hashCode()
+     * ObjectUtils.hashCodeMulti(a,b,c)            = ((31 + a.hashCode()) * 31 + b.hashCode()) * 31 + c.hashCode()
+     * 
+ * + * @param objects the objects to obtain the hash code of, may be {@code null} + * @return the hash code of the objects, or zero if null + * @since 3.0 + * @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 = hashCode(object); + hash = hash * 31 + tmpHash; + } + } + return hash; + } + + /** + *

Appends the toString that would be produced by {@code Object} + * if a class did not override toString itself. {@code null} + * will throw a NullPointerException for either of the two parameters.

+ * + *
+     * ObjectUtils.identityToString(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")
+     * 
+ * + * @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 { + Validate.notNull(object, "object"); + appendable.append(object.getClass().getName()) + .append(AT_SIGN) + .append(Integer.toHexString(System.identityHashCode(object))); + } + + // Identity ToString + //----------------------------------------------------------------------- + /** + *

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

+ * + *
+     * ObjectUtils.identityToString(null)         = null
+     * ObjectUtils.identityToString("")           = "java.lang.String@1e23"
+     * ObjectUtils.identityToString(Boolean.TRUE) = "java.lang.Boolean@7fa"
+     * 
+ * + * @param object the object to create a toString for, may be + * {@code null} + * @return the default toString text, or {@code null} if + * {@code null} passed in + */ + public static String identityToString(final Object object) { + if (object == null) { + return null; + } + final String name = object.getClass().getName(); + final String hexString = Integer.toHexString(System.identityHashCode(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 {@code Object} + * if a class did not override toString itself. {@code null} + * will throw a NullPointerException for either of the two parameters.

+ * + *
+     * 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")
+     * 
+ * + * @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) { + Validate.notNull(object, "object"); + final String name = object.getClass().getName(); + final String hexString = Integer.toHexString(System.identityHashCode(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 {@code Object} + * if a class did not override toString itself. {@code null} + * will throw a NullPointerException for either of the two parameters.

+ * + *
+     * ObjectUtils.identityToString(buf, "")            = buf.append("java.lang.String@1e23"
+     * ObjectUtils.identityToString(buf, Boolean.TRUE)  = buf.append("java.lang.Boolean@7fa"
+     * ObjectUtils.identityToString(buf, Boolean.TRUE)  = buf.append("java.lang.Boolean@7fa")
+     * 
+ * + * @param buffer the buffer to append to + * @param object the object to create a toString for + * @since 2.4 + */ + public static void identityToString(final StringBuffer buffer, final Object object) { + Validate.notNull(object, "object"); + final String name = object.getClass().getName(); + final String hexString = Integer.toHexString(System.identityHashCode(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 {@code Object} + * if a class did not override toString itself. {@code null} + * will throw a NullPointerException for either of the two parameters.

+ * + *
+     * 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")
+     * 
+ * + * @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) { + Validate.notNull(object, "object"); + final String name = object.getClass().getName(); + final String hexString = Integer.toHexString(System.identityHashCode(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); + */ + + + // Empty checks + //----------------------------------------------------------------------- + /** + *

Checks if an Object is empty or null.

+ * + * The following types are supported: + *
    + *
  • {@link CharSequence}: Considered empty if its length is zero.
  • + *
  • {@code Array}: Considered empty if its length is zero.
  • + *
  • {@link Collection}: Considered empty if it has zero elements.
  • + *
  • {@link Map}: Considered empty if it has zero key-value mappings.
  • + *
+ * + *
+     * 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
+     * 
+ * + * @param object the {@code 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 (object.getClass().isArray()) { + return Array.getLength(object) == 0; + } + if (object instanceof Collection) { + return ((Collection) object).isEmpty(); + } + if (object instanceof Map) { + return ((Map) object).isEmpty(); + } + return false; + } + + /** + *

Checks if an Object is not empty and not null.

+ * + * The following types are supported: + *
    + *
  • {@link CharSequence}: Considered empty if its length is zero.
  • + *
  • {@code Array}: Considered empty if its length is zero.
  • + *
  • {@link Collection}: Considered empty if it has zero elements.
  • + *
  • {@link Map}: Considered empty if it has zero key-value mappings.
  • + *
+ * + *
+     * 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
+     * 
+ * + * @param object the {@code 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.

+ * + * @param type of the values processed by this method + * @param values the set of comparable values, may be null + * @return + *
    + *
  • If any objects are non-null and unequal, the greater object. + *
  • If all objects are non-null and equal, the first. + *
  • If any of the comparables are null, the greater of the non-null objects. + *
  • If all the comparables are null, null is returned. + *
+ */ + @SafeVarargs + public static > 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 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 median(final Comparator comparator, final T... items) { + Validate.notEmpty(items, "null/empty items"); + Validate.noNullElements(items); + Validate.notNull(comparator, "comparator"); + final TreeSet sort = new TreeSet<>(comparator); + 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; + } + + /** + * Find the "best guess" middle value among comparables. If there is an even + * number of total values, the lower of the two middle values will be returned. + * @param type of values processed by this method + * @param items to compare + * @return T at middle position + * @throws NullPointerException if items is {@code null} + * @throws IllegalArgumentException if items is empty or contains {@code null} values + * @since 3.0.1 + */ + @SafeVarargs + public static > T median(final T... items) { + Validate.notEmpty(items); + Validate.noNullElements(items); + final TreeSet 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; + } + + // Comparable + //----------------------------------------------------------------------- + /** + *

Null safe comparison of Comparables.

+ * + * @param type of the values processed by this method + * @param values the set of comparable values, may be null + * @return + *
    + *
  • If any objects are non-null and unequal, the lesser object. + *
  • If all objects are non-null and equal, the first. + *
  • If any of the comparables are null, the lesser of the non-null objects. + *
  • If all the comparables are null, null is returned. + *
+ */ + @SafeVarargs + public static > 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; + } + + + // Mode + //----------------------------------------------------------------------- + /** + * Find the most frequently occurring item. + * + * @param type of values processed by this method + * @param items to check + * @return most populous T, {@code null} if non-unique or no items supplied + * @since 3.0.1 + */ + @SafeVarargs + public static T mode(final T... items) { + if (ArrayUtils.isNotEmpty(items)) { + final HashMap 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 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}.

+ * + *
+     * ObjectUtils.notEqual(null, null)                  = false
+     * ObjectUtils.notEqual(null, "")                    = true
+     * ObjectUtils.notEqual("", null)                    = true
+     * ObjectUtils.notEqual("", "")                      = false
+     * ObjectUtils.notEqual(Boolean.TRUE, null)          = true
+     * ObjectUtils.notEqual(Boolean.TRUE, "true")        = true
+     * ObjectUtils.notEqual(Boolean.TRUE, Boolean.TRUE)  = false
+     * ObjectUtils.notEqual(Boolean.TRUE, Boolean.FALSE) = true
+     * 
+ * + * @param object1 the first object, may be {@code null} + * @param object2 the second object, may be {@code null} + * @return {@code false} if the values of both objects are the same + */ + public static boolean notEqual(final Object object1, final Object object2) { + return !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: + * + *
+ * + *
+     * public Foo(Bar bar) {
+     *     this.bar = Objects.requireNonEmpty(bar);
+     * }
+     * 
+ * + *
+ * + * @param 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 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: + * + *
+ * + *
+     * public Foo(Bar bar) {
+     *     this.bar = Objects.requireNonEmpty(bar, "bar");
+     * }
+     * 
+ * + *
+ * + * @param 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 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; + } + + // ToString + //----------------------------------------------------------------------- + /** + *

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

+ * + *
+     * ObjectUtils.toString(null)         = ""
+     * ObjectUtils.toString("")           = ""
+     * ObjectUtils.toString("bat")        = "bat"
+     * ObjectUtils.toString(Boolean.TRUE) = "true"
+     * 
+ * + * @see StringUtils#defaultString(String) + * @see String#valueOf(Object) + * @param obj the Object to {@code toString}, may be null + * @return the passed in Object's toString, or {@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 {@code Object} returning + * a specified text if {@code null} input.

+ * + *
+     * ObjectUtils.toString(null, null)           = null
+     * ObjectUtils.toString(null, "null")         = "null"
+     * ObjectUtils.toString("", "null")           = ""
+     * ObjectUtils.toString("bat", "null")        = "bat"
+     * ObjectUtils.toString(Boolean.TRUE, "null") = "true"
+     * 
+ * + * @see StringUtils#defaultString(String,String) + * @see String#valueOf(Object) + * @param obj the Object to {@code toString}, may be null + * @param nullStr the String to return if {@code null} input, may be null + * @return the passed in Object's toString, or {@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 {@code Object} returning + * a specified text if {@code null} input.

+ * + *
+     * ObjectUtils.toString(obj, () -> expensive())
+     * 
+ *
+     * ObjectUtils.toString(null, () -> expensive())         = result of expensive()
+     * ObjectUtils.toString(null, () -> expensive())         = result of expensive()
+     * ObjectUtils.toString("", () -> expensive())           = ""
+     * ObjectUtils.toString("bat", () -> expensive())        = "bat"
+     * ObjectUtils.toString(Boolean.TRUE, () -> expensive()) = "true"
+     * 
+ * + * @param obj the Object to {@code toString}, may be null + * @param supplier the Supplier of String used on {@code null} input, may be null + * @return the passed in Object's toString, or {@code nullStr} if {@code null} input + * @since 3.11 + */ + public static String toString(final Object obj, final Supplier supplier) { + return obj == null ? supplier == null ? null : supplier.get() : 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 interrupted status 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)); + } + + /** + *

{@code ObjectUtils} instances should NOT be constructed in + * standard programming. Instead, the static methods on the class should + * be used, such as {@code ObjectUtils.defaultIfNull("a","b");}.

+ * + *

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

+ */ + public ObjectUtils() { + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/RandomStringUtils.java b/after/src/main/java/org/apache/commons/lang3/RandomStringUtils.java new file mode 100644 index 0000000..23d2161 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/RandomStringUtils.java @@ -0,0 +1,477 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + *

Generates random {@code String}s.

+ * + *

Caveat: Instances of {@link Random}, upon which the implementation of this + * class relies, are not cryptographically secure.

+ * + *

RandomStringUtils is intended for simple use cases. For more advanced + * use cases consider using Apache Commons Text's + * + * RandomStringGenerator instead.

+ * + *

The Apache Commons project provides + * Commons RNG dedicated to pseudo-random number generation, that may be + * a better choice for applications with more stringent requirements + * (performance and/or correctness).

+ * + *

Note that private high surrogate 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.

+ * + *

#ThreadSafe#

+ * @since 1.0 + */ +public class RandomStringUtils { + + /** + *

Random object used by random method. This has to be not local + * to the random method so as to not return the same value in the + * same millisecond.

+ */ + private static final Random RANDOM = new Random(); + + /** + *

{@code RandomStringUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * {@code RandomStringUtils.random(5);}.

+ * + *

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

+ */ + public RandomStringUtils() { + } + + // Random + //----------------------------------------------------------------------- + /** + *

Creates a random string whose length is the number of characters + * specified.

+ * + *

Characters will be chosen from the set of all characters.

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

+ * + *

Characters will be chosen from the set of characters whose + * ASCII value is between {@code 32} and {@code 126} (inclusive).

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

+ * + *

Characters will be chosen from the set of characters whose + * ASCII value is between {@code 32} and {@code 126} (inclusive).

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

+ * + *

Characters will be chosen from the set of Latin alphabetic + * characters (a-z, A-Z).

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

+ * + *

Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z).

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

+ * + *

Characters will be chosen from the set of Latin alphabetic + * characters (a-z, A-Z) and the digits 0-9.

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

+ * + *

Characters will be chosen from the set of Latin alphabetic + * characters (a-z, A-Z) and the digits 0-9.

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

+ * + *

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

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

+ * + *

Characters will be chosen from the set of \p{Graph} characters.

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

+ * + *

Characters will be chosen from the set of numeric + * characters.

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

+ * + *

Characters will be chosen from the set of \p{Digit} characters.

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

+ * + *

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

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

+ * + *

Characters will be chosen from the set of \p{Print} characters.

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

Creates a random string whose length is the number of characters + * specified.

+ * + *

Characters will be chosen from the set of alpha-numeric + * characters as indicated by the arguments.

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

+ * + *

Characters will be chosen from the set of alpha-numeric + * characters as indicated by the arguments.

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

+ * + *

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.

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

+ * + *

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

If set is not {@code null}, characters between start and + * end are chosen.

+ * + *

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.

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

+ * + *

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.

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

+ * + *

Characters will be chosen from the set of characters specified.

+ * + * @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} < 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); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/RandomUtils.java b/after/src/main/java/org/apache/commons/lang3/RandomUtils.java new file mode 100644 index 0000000..6f963d1 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/RandomUtils.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 java.util.Random; + +/** + *

Utility library that supplements the standard {@link Random} class.

+ * + *

Caveat: Instances of {@link Random} are not cryptographically secure.

+ * + *

Please note that the Apache Commons project provides a component + * dedicated to pseudo-random number generation, namely + * Commons RNG, that may be + * a better choice for applications with more stringent requirements + * (performance and/or correctness).

+ * + * @since 3.3 + */ +public class RandomUtils { + + /** + * Random object used by random method. This has to be not local to the + * random method so as to not return the same value in the same millisecond. + */ + private static final Random RANDOM = new Random(); + + /** + *

+ * {@code RandomUtils} instances should NOT be constructed in standard + * programming. Instead, the class should be used as + * {@code RandomUtils.nextBytes(5);}. + *

+ * + *

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

+ */ + public RandomUtils() { + } + + /** + *

+ * Returns a random boolean value + *

+ * + * @return the random boolean + * @since 3.5 + */ + public static boolean nextBoolean() { + return RANDOM.nextBoolean(); + } + + /** + *

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

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

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

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

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

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

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

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

Returns 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); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/Range.java b/after/src/main/java/org/apache/commons/lang3/Range.java new file mode 100644 index 0000000..5f8889f --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/Range.java @@ -0,0 +1,512 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + *

An immutable range of objects from a minimum to maximum point inclusive.

+ * + *

The objects need to either be implementations of {@code Comparable} + * or you need to supply a {@code Comparator}.

+ * + *

#ThreadSafe# if the objects and comparator are thread-safe

+ * + * @param The type of range values. + * @since 3.0 + */ +public final class Range 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; + /** + *

Obtains a range with the specified minimum and maximum values (both inclusive).

+ * + *

The range uses the natural ordering of the elements to determine where + * values lie in the range.

+ * + *

The arguments may be passed in the order (min,max) or (max,min). + * The getMinimum and getMaximum methods will return the correct values.

+ * + * @param 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 IllegalArgumentException if either element is null + * @throws ClassCastException if the elements are not {@code Comparable} + */ + public static > Range between(final T fromInclusive, final T toInclusive) { + return between(fromInclusive, toInclusive, null); + } + + /** + *

Obtains a range with the specified minimum and maximum values (both inclusive).

+ * + *

The range uses the specified {@code Comparator} to determine where + * values lie in the range.

+ * + *

The arguments may be passed in the order (min,max) or (max,min). + * The getMinimum and getMaximum methods will return the correct values.

+ * + * @param 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 IllegalArgumentException if either element is null + * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable} + */ + public static Range between(final T fromInclusive, final T toInclusive, final Comparator comparator) { + return new Range<>(fromInclusive, toInclusive, comparator); + } + + /** + *

Obtains a range using the specified element as both the minimum + * and maximum in this range.

+ * + *

The range uses the natural ordering of the elements to determine where + * values lie in the range.

+ * + * @param 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 IllegalArgumentException if the element is null + * @throws ClassCastException if the element is not {@code Comparable} + */ + public static > Range is(final T element) { + return between(element, element, null); + } + + /** + *

Obtains a range using the specified element as both the minimum + * and maximum in this range.

+ * + *

The range uses the specified {@code Comparator} to determine where + * values lie in the range.

+ * + * @param 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 IllegalArgumentException if the element is null + * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable} + */ + public static Range is(final T element, final Comparator comparator) { + return between(element, element, comparator); + } + + /** + * The ordering scheme used in this range. + */ + private final Comparator 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 + */ + @SuppressWarnings("unchecked") + private Range(final T element1, final T element2, final Comparator comp) { + if (element1 == null || element2 == null) { + throw new IllegalArgumentException("Elements in a range must not be null: element1=" + + element1 + ", 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.

+ * + *

This method may fail if the ranges have two different comparators or element types.

+ * + * @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 otherRange) { + if (otherRange == null) { + return false; + } + return contains(otherRange.minimum) + && contains(otherRange.maximum); + } + + /** + *

Checks where the specified element occurs relative to this range.

+ * + *

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.

+ * + * @param element the element to check for, not null + * @return -1, 0 or +1 depending on the element's location relative to the range + */ + public int elementCompareTo(final T element) { + // Comparable API says throw NPE on null + Validate.notNull(element, "element"); + if (isAfter(element)) { + return -1; + } else if (isBefore(element)) { + return 1; + } else { + return 0; + } + } + + // Element tests + //-------------------------------------------------------------------- + + /** + *

Compares this range to another object to test if they are equal.

. + * + *

To be equal, the minimum and maximum values must be equal, which + * ignores any differences in the comparator.

+ * + * @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; + } else if (obj == null || obj.getClass() != getClass()) { + return false; + } else { + @SuppressWarnings("unchecked") // OK because we checked the class above + final + Range range = (Range) obj; + return minimum.equals(range.minimum) && + maximum.equals(range.maximum); + } + } + + /** + *

Gets the comparator being used to determine if objects are within the range.

+ * + *

Natural ordering uses an internal comparator implementation, thus this + * method never returns null. See {@link #isNaturalOrdering()}.

+ * + * @return the comparator being used, not null + */ + public Comparator 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 intersectionWith(final Range 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 between(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.

+ * + *

This method may fail if the ranges have two different comparators or element types.

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

+ * + *

This method may fail if the ranges have two different comparators or element types.

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

+ * + *

Natural ordering uses an internal comparator implementation, thus this + * method is the only way to check if a null comparator was specified.

+ * + * @return true if using natural ordering + */ + public boolean isNaturalOrdering() { + return comparator == ComparableComparator.INSTANCE; + } + + /** + *

Checks whether this range is overlapped by the specified range.

+ * + *

Two ranges overlap if there is at least one element in common.

+ * + *

This method may fail if the ranges have two different comparators or element types.

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

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

+ *
+     * Range<Integer> range = Range.between(16, 64);
+     * range.fit(-9) -->  16
+     * range.fit(0)  -->  16
+     * range.fit(15) -->  16
+     * range.fit(16) -->  16
+     * range.fit(17) -->  17
+     * ...
+     * range.fit(63) -->  63
+     * range.fit(64) -->  64
+     * range.fit(99) -->  64
+     * 
+ * @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 + * @since 3.10 + */ + public T fit(final T element) { + // Comparable API says throw NPE on null + Validate.notNull(element, "element"); + if (isAfter(element)) { + return minimum; + } else if (isBefore(element)) { + return maximum; + } else { + return element; + } + } + + /** + *

Gets the range as a {@code String}.

+ * + *

The format of the String is '[min..max]'.

+ * + * @return the {@code String} representation of this range + */ + @Override + public String toString() { + if (toString == null) { + toString = "[" + minimum + ".." + maximum + "]"; + } + return toString; + } + + /** + *

Formats the receiver using the given format.

+ * + *

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

+ * + * @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/after/src/main/java/org/apache/commons/lang3/RegExUtils.java b/after/src/main/java/org/apache/commons/lang3/RegExUtils.java new file mode 100644 index 0000000..53ba4ff --- /dev/null +++ b/after/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: + *
    + *
  • {@code pattern.matcher(text).replaceAll(StringUtils.EMPTY)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * 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<__>\n<__>B", Pattern.compile("<.*>"))      = "A\nB"
+     * StringUtils.removeAll("A<__>\n<__>B", Pattern.compile("(?s)<.*>"))  = "AB"
+     * StringUtils.removeAll("A<__>\n<__>B", Pattern.compile("<.*>", Pattern.DOTALL))  = "AB"
+     * StringUtils.removeAll("ABCabc123abc", Pattern.compile("[a-z]"))     = "ABC123"
+     * 
+ * + * @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: + *
    + *
  • {@code text.replaceAll(regex, StringUtils.EMPTY)}
  • + *
  • {@code Pattern.compile(regex).matcher(text).replaceAll(StringUtils.EMPTY)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *

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.

+ * + *
+     * 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<__>\n<__>B", "<.*>")      = "A\nB"
+     * StringUtils.removeAll("A<__>\n<__>B", "(?s)<.*>")  = "AB"
+     * StringUtils.removeAll("ABCabc123abc", "[a-z]")     = "ABC123"
+     * 
+ * + * @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: + *
    + *
  • {@code pattern.matcher(text).replaceFirst(StringUtils.EMPTY)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * 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<__>\n<__>B", Pattern.compile("<.*>"))      = "A\n<__>B"
+     * StringUtils.removeFirst("A<__>\n<__>B", Pattern.compile("(?s)<.*>"))  = "AB"
+     * StringUtils.removeFirst("ABCabc123", Pattern.compile("[a-z]"))          = "ABCbc123"
+     * StringUtils.removeFirst("ABCabc123abc", Pattern.compile("[a-z]+"))      = "ABC123abc"
+     * 
+ * + * @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: + *
    + *
  • {@code text.replaceFirst(regex, StringUtils.EMPTY)}
  • + *
  • {@code Pattern.compile(regex).matcher(text).replaceFirst(StringUtils.EMPTY)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *

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.

+ * + *
+     * 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<__>\n<__>B", "<.*>")      = "A\n<__>B"
+     * StringUtils.removeFirst("A<__>\n<__>B", "(?s)<.*>")  = "AB"
+     * StringUtils.removeFirst("ABCabc123", "[a-z]")          = "ABCbc123"
+     * StringUtils.removeFirst("ABCabc123abc", "[a-z]+")      = "ABC123abc"
+     * 
+ * + * @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: + *
    + *
  • {@code text.replaceAll("(?s)" + regex, StringUtils.EMPTY)}
  • + *
  • {@code Pattern.compile(regex, Pattern.DOTALL).matcher(text).replaceAll(StringUtils.EMPTY)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * StringUtils.removePattern(null, *)       = null
+     * StringUtils.removePattern("any", (String) null)   = "any"
+     * StringUtils.removePattern("A<__>\n<__>B", "<.*>")  = "AB"
+     * StringUtils.removePattern("ABCabc123", "[a-z]")    = "ABC123"
+     * 
+ * + * @param text + * the source string + * @param regex + * the regular expression to which this string is to be matched + * @return The resulting {@code 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: + *
    + *
  • {@code pattern.matcher(text).replaceAll(replacement)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * 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("<__>\n<__>", Pattern.compile("<.*>"), "z")                 = "z\nz"
+     * StringUtils.replaceAll("<__>\n<__>", Pattern.compile("<.*>", Pattern.DOTALL), "z") = "z"
+     * StringUtils.replaceAll("<__>\n<__>", Pattern.compile("(?s)<.*>"), "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"
+     * 
+ * + * @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: + *
    + *
  • {@code text.replaceAll(regex, replacement)}
  • + *
  • {@code Pattern.compile(regex).matcher(text).replaceAll(replacement)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *

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.

+ * + *
+     * 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("<__>\n<__>", "<.*>", "z")      = "z\nz"
+     * StringUtils.replaceAll("<__>\n<__>", "(?s)<.*>", "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"
+     * 
+ * + * @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: + *
    + *
  • {@code pattern.matcher(text).replaceFirst(replacement)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * 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("<__>\n<__>", Pattern.compile("<.*>"), "z")      = "z\n<__>"
+     * StringUtils.replaceFirst("<__>\n<__>", Pattern.compile("(?s)<.*>"), "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"
+     * 
+ * + * @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: + *
    + *
  • {@code text.replaceFirst(regex, replacement)}
  • + *
  • {@code Pattern.compile(regex).matcher(text).replaceFirst(replacement)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *

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.

+ * + *
+     * 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("<__>\n<__>", "<.*>", "z")      = "z\n<__>"
+     * StringUtils.replaceFirst("<__>\n<__>", "(?s)<.*>", "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"
+     * 
+ * + * @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: + *
    + *
  • {@code text.replaceAll("(?s)" + regex, replacement)}
  • + *
  • {@code Pattern.compile(regex, Pattern.DOTALL).matcher(text).replaceAll(replacement)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * 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("<__>\n<__>", "<.*>", "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"
+     * 
+ * + * @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 {@code 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/after/src/main/java/org/apache/commons/lang3/SerializationException.java b/after/src/main/java/org/apache/commons/lang3/SerializationException.java new file mode 100644 index 0000000..af4e67a --- /dev/null +++ b/after/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.

+ * + *

The original error is wrapped within this one.

+ * + *

#NotThreadSafe# because Throwable is not thread-safe

+ * @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 {@code SerializationException} without specified + * detail message.

+ */ + public SerializationException() { + } + + /** + *

Constructs a new {@code SerializationException} with specified + * detail message.

+ * + * @param msg The error message. + */ + public SerializationException(final String msg) { + super(msg); + } + + /** + *

Constructs a new {@code SerializationException} with specified + * nested {@code Throwable}.

+ * + * @param cause The {@code Exception} or {@code Error} + * that caused this exception to be thrown. + */ + public SerializationException(final Throwable cause) { + super(cause); + } + + /** + *

Constructs a new {@code SerializationException} with specified + * detail message and nested {@code Throwable}.

+ * + * @param msg The error message. + * @param cause The {@code Exception} or {@code Error} + * that caused this exception to be thrown. + */ + public SerializationException(final String msg, final Throwable cause) { + super(msg, cause); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/SerializationUtils.java b/after/src/main/java/org/apache/commons/lang3/SerializationUtils.java new file mode 100644 index 0000000..947460d --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/SerializationUtils.java @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + *

Assists with the serialization process and performs additional functionality based + * on serialization.

+ * + *
    + *
  • Deep clone using serialization + *
  • Serialize managing finally and IOException + *
  • Deserialize managing finally and IOException + *
+ * + *

This class throws exceptions for invalid {@code null} inputs. + * Each method documents its behavior in more detail.

+ * + *

#ThreadSafe#

+ * @since 1.0 + */ +public class SerializationUtils { + + /** + *

Custom specialization of the standard JDK {@link java.io.ObjectInputStream} + * that uses a custom {@code ClassLoader} to resolve a class. + * If the specified {@code 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 + * {@code ClassLoader} the particular class that encapsulates + * serialization/deserialization lives.

+ * + *

For more in-depth information about the problem for which this + * class here is a workaround, see the JIRA issue LANG-626.

+ */ + static class ClassLoaderAwareObjectInputStream extends ObjectInputStream { + private static final Map> 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 {@code 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 {@code ClassLoader} or the {@code ClassLoader} + * of the current {@code Thread} to resolve the class. + * @param desc An instance of class {@code ObjectStreamClass}. + * @return A {@code 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 {@code Object} using serialization.

+ * + *

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 {@code Serializable}.

+ * + * @param the type of the object involved + * @param object the {@code Serializable} object to clone + * @return the cloned object + * @throws SerializationException (runtime) if the serialization fails + */ + public static T clone(final T object) { + if (object == null) { + return null; + } + final byte[] objectData = serialize(object); + final ByteArrayInputStream bais = new ByteArrayInputStream(objectData); + + try (ClassLoaderAwareObjectInputStream in = new ClassLoaderAwareObjectInputStream(bais, + object.getClass().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 + */ + @SuppressWarnings("unchecked") // see above + final T readObject = (T) in.readObject(); + return readObject; + + } catch (final ClassNotFoundException ex) { + throw new SerializationException("ClassNotFoundException while reading cloned object data", ex); + } catch (final IOException ex) { + throw new SerializationException("IOException while reading or closing cloned object data", ex); + } + } + + /** + *

+ * Deserializes a single {@code Object} from an array of bytes. + *

+ * + *

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

+ * + * @param 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 deserialize(final byte[] objectData) { + Validate.notNull(objectData, "objectData"); + return deserialize(new ByteArrayInputStream(objectData)); + } + + /** + *

+ * Deserializes an {@code Object} from the specified stream. + *

+ * + *

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

+ * + *

+ * The stream passed in is not buffered internally within this method. This is the responsibility of your + * application if desired. + *

+ * + *

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

+ * + * @param 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 deserialize(final InputStream inputStream) { + Validate.notNull(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 + * 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 roundtrip(final T obj) { + return (T) deserialize(serialize(obj)); + } + + /** + *

Serializes an {@code 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 {@code Object} to the specified stream.

+ * + *

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.

+ * + *

The stream passed in is not buffered internally within this method. + * This is the responsibility of your application if desired.

+ * + * @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) { + Validate.notNull(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)}.

+ * + *

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

+ * @since 2.0 + */ + public SerializationUtils() { + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/Streams.java b/after/src/main/java/org/apache/commons/lang3/Streams.java new file mode 100644 index 0000000..258b9a2 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/Streams.java @@ -0,0 +1,507 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3; + +import java.lang.reflect.Array; +import java.util.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 + *
+ *     Consumer<java.lang.reflect.Method> consumer = m -> {
+ *         try {
+ *             m.invoke(o, args);
+ *         } catch (Throwable t) {
+ *             throw Functions.rethrow(t);
+ *         }
+ *    };
+ *    stream.forEach(consumer);
+ * 
+ * Using a {@link FailableStream}, this can be rewritten as follows: + *
+ *     Streams.failable(stream).forEach((m) -> m.invoke(o, args));
+ * 
+ * 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 The streams element type. + * @deprecated Use {@link org.apache.commons.lang3.stream.Streams.FailableStream}. + */ + @Deprecated + public static class FailableStream { + + private Stream stream; + private boolean terminated; + + /** + * Constructs a new instance with the given {@code stream}. + * @param stream The stream. + */ + public FailableStream(final Stream stream) { + this.stream = stream; + } + + protected void assertNotTerminated() { + if (terminated) { + throw new IllegalStateException("This stream is already terminated."); + } + } + + protected void makeTerminated() { + assertNotTerminated(); + terminated = true; + } + + /** + * Returns a FailableStream consisting of the elements of this stream that match + * the given FailablePredicate. + * + *

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

This is a terminal operation. + * + *

The behavior of this operation is explicitly nondeterministic. + * For parallel stream pipelines, this operation does not + * guarantee to respect the encounter order of the stream, as doing so + * would sacrifice the benefit of parallelism. For any given element, the + * action may be performed at whatever time and in whatever thread the + * library chooses. If the action accesses shared state, it is + * responsible for providing the required synchronization. + * + * @param action a non-interfering action to perform on the elements + */ + public void forEach(final FailableConsumer action) { + makeTerminated(); + stream().forEach(Functions.asConsumer(action)); + } + + /** + * Performs a mutable reduction operation on the elements of this stream using a + * {@code Collector}. A {@code 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. + * + *

If the underlying stream is parallel, and the {@code 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.) + * + *

This is a terminal operation. + * + *

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 {@code ArrayList}), no + * additional synchronization is needed for a parallel reduction. + * + * Note + * The following will accumulate strings into an ArrayList: + *

{@code
+         *     List asList = stringStream.collect(Collectors.toList());
+         * }
+ * + *

The following will classify {@code Person} objects by city: + *

{@code
+         *     Map> peopleByCity
+         *         = personStream.collect(Collectors.groupingBy(Person::getCity));
+         * }
+ * + *

The following will classify {@code Person} objects by state and city, + * cascading two {@code Collector}s together: + *

{@code
+         *     Map>> peopleByStateAndCity
+         *         = personStream.collect(Collectors.groupingBy(Person::getState,
+         *                                                      Collectors.groupingBy(Person::getCity)));
+         * }
+ * + * @param the type of the result + * @param the intermediate accumulation type of the {@code Collector} + * @param collector the {@code Collector} describing the reduction + * @return the result of the reduction + * @see #collect(Supplier, BiConsumer, BiConsumer) + * @see Collectors + */ + public R collect(final Collector 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 {@code ArrayList}, and elements are incorporated by updating + * the state of the result rather than by replacing the result. This produces a result equivalent to: + *
{@code
+         *     R result = supplier.get();
+         *     for (T element : this stream)
+         *         accumulator.accept(result, element);
+         *     return result;
+         * }
+ * + *

Like {@link #reduce(Object, BinaryOperator)}, {@code collect} operations + * can be parallelized without requiring additional synchronization. + * + *

This is a terminal operation. + * + * 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 {@code ArrayList}: + *

{@code
+         *     List asList = stringStream.collect(ArrayList::new, ArrayList::add,
+         *                                                ArrayList::addAll);
+         * }
+ * + *

The following will take a stream of strings and concatenates them into a + * single string: + *

{@code
+         *     String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,
+         *                                          StringBuilder::append)
+         *                                 .toString();
+         * }
+ * + * @param type of the result + * @param
Type of the accumulator. + * @param pupplier 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 R collect(final Supplier pupplier, final BiConsumer accumulator, final BiConsumer combiner) { + makeTerminated(); + return stream().collect(pupplier, 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: + *
{@code
+         *     T result = identity;
+         *     for (T element : this stream)
+         *         result = accumulator.apply(result, element)
+         *     return result;
+         * }
+ * + * but is not constrained to execute sequentially. + * + *

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

This is a terminal operation. + * + * Note Sum, min, max, average, and string concatenation are all special + * cases of reduction. Summing a stream of numbers can be expressed as: + * + *

{@code
+         *     Integer sum = integers.reduce(0, (a, b) -> a+b);
+         * }
+ * + * or: + * + *
{@code
+         *     Integer sum = integers.reduce(0, Integer::sum);
+         * }
+ * + *

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

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

This is a short-circuiting terminal operation. + * + * Note + * This method evaluates the universal quantification of the + * predicate over the elements of the stream (for all x P(x)). If the + * stream is empty, the quantification is said to be vacuously + * satisfied and is always {@code true} (regardless of P(x)). + * + * @param predicate A non-interfering, stateless predicate to apply to + * elements of this stream + * @return {@code true} If either all elements of the stream match the + * provided predicate or the stream is empty, otherwise {@code false}. + */ + public boolean allMatch(final FailablePredicate predicate) { + assertNotTerminated(); + return stream().allMatch(Functions.asPredicate(predicate)); + } + + /** + * Returns whether any elements of this stream match the provided + * predicate. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the stream is empty then + * {@code false} is returned and the predicate is not evaluated. + * + *

This is a short-circuiting terminal operation. + * + * Note + * This method evaluates the existential quantification of the + * predicate over the elements of the stream (for some x P(x)). + * + * @param predicate A non-interfering, stateless predicate to apply to + * elements of this stream + * @return {@code true} if any elements of the stream match the provided + * predicate, otherwise {@code false} + */ + public boolean anyMatch(final FailablePredicate predicate) { + assertNotTerminated(); + return stream().anyMatch(Functions.asPredicate(predicate)); + } + } + + /** + * Converts the given {@link Stream stream} into a {@link FailableStream}. + * This is basically a simplified, reduced version of the {@link Stream} + * class, with the same underlying element stream, except that failable + * objects, like {@link FailablePredicate}, {@link FailableFunction}, or + * {@link FailableConsumer} may be applied, instead of + * {@link Predicate}, {@link Function}, or {@link Consumer}. The idea is + * to rewrite a code snippet like this: + *

+     *     final List<O> list;
+     *     final Method m;
+     *     final Function<O,String> mapper = (o) -> {
+     *         try {
+     *             return (String) m.invoke(o);
+     *         } catch (Throwable t) {
+     *             throw Functions.rethrow(t);
+     *         }
+     *     };
+     *     final List<String> strList = list.stream()
+     *         .map(mapper).collect(Collectors.toList());
+     *  
+ * as follows: + *
+     *     final List<O> list;
+     *     final Method m;
+     *     final List<String> strList = Functions.stream(list.stream())
+     *         .map((o) -> (String) m.invoke(o)).collect(Collectors.toList());
+     *  
+ * While the second version may not be quite as + * efficient (because it depends on the creation of additional, + * intermediate objects, of type FailableStream), it is much more + * concise, and readable, and meets the spirit of Lambdas better + * than the first version. + * @param The streams element type. + * @param stream The stream, which is being converted. + * @return The {@link FailableStream}, which has been created by + * converting the stream. + */ + public static FailableStream stream(final Stream stream) { + return new FailableStream<>(stream); + } + + /** + * 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: + *
+     *     final List<O> list;
+     *     final Method m;
+     *     final Function<O,String> mapper = (o) -> {
+     *         try {
+     *             return (String) m.invoke(o);
+     *         } catch (Throwable t) {
+     *             throw Functions.rethrow(t);
+     *         }
+     *     };
+     *     final List<String> strList = list.stream()
+     *         .map(mapper).collect(Collectors.toList());
+     *  
+ * as follows: + *
+     *     final List<O> list;
+     *     final Method m;
+     *     final List<String> strList = Functions.stream(list.stream())
+     *         .map((o) -> (String) m.invoke(o)).collect(Collectors.toList());
+     *  
+ * While the second version may not be quite as + * efficient (because it depends on the creation of additional, + * intermediate objects, of type FailableStream), it is much more + * concise, and readable, and meets the spirit of Lambdas better + * than the first version. + * @param The streams element type. + * @param stream The stream, which is being converted. + * @return The {@link FailableStream}, which has been created by + * converting the stream. + */ + public static FailableStream stream(final Collection stream) { + return stream(stream.stream()); + } + + /** + * A Collector type for arrays. + * + * @param The array type. + * @deprecated Use {@link org.apache.commons.lang3.stream.Streams.ArrayCollector}. + */ + @Deprecated + public static class ArrayCollector implements Collector, O[]> { + private static final Set characteristics = Collections.emptySet(); + private final Class elementType; + + /** + * Constructs a new instance for the given element type. + * + * @param elementType The element type. + */ + public ArrayCollector(final Class elementType) { + this.elementType = elementType; + } + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer, O> accumulator() { + return List::add; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> { + left.addAll(right); + return left; + }; + } + + @Override + public Function, O[]> finisher() { + return list -> { + @SuppressWarnings("unchecked") + final O[] array = (O[]) Array.newInstance(elementType, list.size()); + return list.toArray(array); + }; + } + + @Override + public Set characteristics() { + return characteristics; + } + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a + * new array. + * + * @param pElementType Type of an element in the array. + * @param the type of the input elements + * @return a {@code Collector} which collects all the input elements into an + * array, in encounter order + */ + public static Collector toArray(final Class pElementType) { + return new ArrayCollector<>(pElementType); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java b/after/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java new file mode 100644 index 0000000..d2a8f5d --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java @@ -0,0 +1,805 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 {@code String}s for + * Java, Java Script, HTML and XML.

+ * + *

#ThreadSafe#

+ * @since 2.0 + * @deprecated as of 3.6, use commons-text + *
+ * StringEscapeUtils 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", " " }, + { "\u000c", " " }, + { "\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 */ + + /** + *

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

+ * + *

Instead, the class should be used as:

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

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

+ */ + public StringEscapeUtils() { + } + + // Java and JavaScript + //-------------------------------------------------------------------------- + /** + *

Escapes the characters in a {@code String} using Java String rules.

+ * + *

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

+ * + *

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

+ * + *

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

+ * + *

Example:

+ *
+     * input string: He didn't say, "Stop!"
+     * output string: He didn't say, \"Stop!\"
+     * 
+ * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + */ + public static final String escapeJava(final String input) { + return ESCAPE_JAVA.translate(input); + } + + /** + *

Escapes the characters in a {@code String} using EcmaScript String rules.

+ *

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

+ * + *

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

+ * + *

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

+ * + *

Note that EcmaScript is best known by the JavaScript and ActionScript dialects.

+ * + *

Example:

+ *
+     * input string: He didn't say, "Stop!"
+     * output string: He didn\'t say, \"Stop!\"
+     * 
+ * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + * + * @since 3.0 + */ + public static final String escapeEcmaScript(final String input) { + return ESCAPE_ECMASCRIPT.translate(input); + } + + /** + *

Escapes the characters in a {@code String} using Json String rules.

+ *

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

+ * + *

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

+ * + *

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

+ * + *

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

+ * + *

Example:

+ *
+     * input string: He didn't say, "Stop!"
+     * output string: He didn't say, \"Stop!\"
+     * 
+ * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + * + * @since 3.2 + */ + public static final String escapeJson(final String input) { + return ESCAPE_JSON.translate(input); + } + + /** + *

Unescapes any Java literals found in the {@code 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 {@code String} to unescape, may be null + * @return a new unescaped {@code 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 {@code String}.

+ * + *

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

+ * + * @see #unescapeJava(String) + * @param input the {@code String} to unescape, may be null + * @return A new unescaped {@code 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 {@code String}.

+ * + *

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

+ * + * @see #unescapeJava(String) + * @param input the {@code String} to unescape, may be null + * @return A new unescaped {@code String}, {@code null} if null string input + * + * @since 3.2 + */ + public static final String unescapeJson(final String input) { + return UNESCAPE_JSON.translate(input); + } + + // HTML and XML + //-------------------------------------------------------------------------- + /** + *

Escapes the characters in a {@code String} using HTML entities.

+ * + *

+ * For example: + *

+ *

{@code "bread" & "butter"}

+ * becomes: + *

+ * {@code &quot;bread&quot; &amp; &quot;butter&quot;}. + *

+ * + *

Supports all known HTML 4.0 entities, including funky accents. + * Note that the commonly used apostrophe escape character (&apos;) + * is not a legal entity and so is not supported).

+ * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * + * @see ISO Entities + * @see HTML 3.2 Character Entities for ISO Latin-1 + * @see HTML 4.0 Character entity references + * @see HTML 4.01 Character References + * @see HTML 4.01 Code positions + * + * @since 3.0 + */ + public static final String escapeHtml4(final String input) { + return ESCAPE_HTML4.translate(input); + } + + /** + *

Escapes the characters in a {@code String} using HTML entities.

+ *

Supports only the HTML 3.0 entities.

+ * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code 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.

+ * + *

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

+ * + *

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

+ * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code 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 {@code String} to unescape, may be null + * @return a new unescaped {@code 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 {@code String} using XML entities.

+ * + *

For example: {@code "bread" & "butter"} => + * {@code "bread" & "butter"}. + *

+ * + *

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

+ * + *

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) );}

+ * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * @see #unescapeXml(java.lang.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 {@code String} using XML entities.

+ * + *

For example: {@code "bread" & "butter"} => + * {@code "bread" & "butter"}. + *

+ * + *

Note that XML 1.0 is a text-only format: it cannot represent control + * characters or unpaired Unicode surrogate codepoints, even after escaping. + * {@code escapeXml10} will remove characters that do not fit in the + * following ranges:

+ * + *

{@code #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]}

+ * + *

Though not strictly necessary, {@code escapeXml10} will escape + * characters in the following ranges:

+ * + *

{@code [#x7F-#x84] | [#x86-#x9F]}

+ * + *

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

+ * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * @see #unescapeXml(java.lang.String) + * @since 3.3 + */ + public static String escapeXml10(final String input) { + return ESCAPE_XML10.translate(input); + } + + /** + *

Escapes the characters in a {@code String} using XML entities.

+ * + *

For example: {@code "bread" & "butter"} => + * {@code "bread" & "butter"}. + *

+ * + *

XML 1.1 can represent certain control characters, but it cannot represent + * the null byte or unpaired Unicode surrogate codepoints, even after escaping. + * {@code escapeXml11} will remove characters that do not fit in the following + * ranges:

+ * + *

{@code [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]}

+ * + *

{@code escapeXml11} will escape characters in the following ranges:

+ * + *

{@code [#x1-#x8] | [#xB-#xC] | [#xE-#x1F] | [#x7F-#x84] | [#x86-#x9F]}

+ * + *

The returned string can be inserted into a valid XML 1.1 document. Do not + * use it for XML 1.0 documents.

+ * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * @see #unescapeXml(java.lang.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.

+ * + *

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

+ * + *

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

+ * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code 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 {@code String} value for a CSV column enclosed in double quotes, + * if required.

+ * + *

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

+ * + *

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

+ * + *

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

+ * + * see Wikipedia and + * RFC 4180. + * + * @param input the input CSV column String, may be null + * @return the input String, enclosed in double quotes if the value contains a comma, + * newline or double quote, {@code null} if null string input + * @since 2.4 + */ + public static final String escapeCsv(final String input) { + return ESCAPE_CSV.translate(input); + } + + /** + *

Returns a {@code String} value for an unescaped CSV column.

+ * + *

If the value is enclosed in double quotes, and contains a comma, newline + * or double quote, then quotes are removed. + *

+ * + *

Any double quote escaped characters (a pair of double quotes) are unescaped + * to just one double quote.

+ * + *

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.

+ * + * see Wikipedia and + * RFC 4180. + * + * @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/after/src/main/java/org/apache/commons/lang3/StringUtils.java b/after/src/main/java/org/apache/commons/lang3/StringUtils.java new file mode 100644 index 0000000..284c202 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -0,0 +1,9650 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.StringJoiner; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.function.ToBooleanBiFunction; + +/** + *

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

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

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

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

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

+ * + *

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

+ * + *

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

+ * + *

#ThreadSafe#

+ * @see java.lang.String + * @since 1.0 + */ +//@Immutable +public class StringUtils { + + private static final int STRING_BUILDER_SIZE = 256; + + // 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 JLF: Escape Sequences + * for Character and String Literals + * @since 3.2 + */ + public static final String LF = "\n"; + + /** + * A String for carriage return CR ("\r"). + * + * @see JLF: Escape Sequences + * for Character and String Literals + * @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..."

+ * + *

Specifically:

+ *
    + *
  • If the number of characters in {@code str} is less than or equal to + * {@code maxWidth}, return {@code str}.
  • + *
  • Else abbreviate it to {@code (substring(str, 0, max-3) + "...")}.
  • + *
  • If {@code maxWidth} is less than {@code 4}, throw an + * {@code IllegalArgumentException}.
  • + *
  • In no case will it return a String of length greater than + * {@code maxWidth}.
  • + *
+ * + *
+     * StringUtils.abbreviate(null, *)      = null
+     * StringUtils.abbreviate("", 4)        = ""
+     * StringUtils.abbreviate("abcdefg", 6) = "abc..."
+     * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
+     * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
+     * StringUtils.abbreviate("abcdefg", 4) = "a..."
+     * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
+     * 
+ * + * @param str the String to check, may be null + * @param maxWidth maximum length of result String, must be at least 4 + * @return abbreviated String, {@code null} if null String input + * @throws IllegalArgumentException if the width is too small + * @since 2.0 + */ + public static String abbreviate(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..."

+ * + *

Works like {@code abbreviate(String, int)}, but allows you to specify + * a "left edge" offset. Note that this left edge is not necessarily going to + * be the leftmost character in the result, or the first character following the + * ellipses, but it will appear somewhere in the result. + * + *

In no case will it return a String of length greater than + * {@code maxWidth}.

+ * + *
+     * StringUtils.abbreviate(null, *, *)                = null
+     * StringUtils.abbreviate("", 0, 4)                  = ""
+     * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
+     * StringUtils.abbreviate("abcdefghijklmno", 0, 10)  = "abcdefg..."
+     * StringUtils.abbreviate("abcdefghijklmno", 1, 10)  = "abcdefg..."
+     * StringUtils.abbreviate("abcdefghijklmno", 4, 10)  = "abcdefg..."
+     * StringUtils.abbreviate("abcdefghijklmno", 5, 10)  = "...fghi..."
+     * StringUtils.abbreviate("abcdefghijklmno", 6, 10)  = "...ghij..."
+     * StringUtils.abbreviate("abcdefghijklmno", 8, 10)  = "...ijklmno"
+     * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
+     * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
+     * StringUtils.abbreviate("abcdefghij", 0, 3)        = IllegalArgumentException
+     * StringUtils.abbreviate("abcdefghij", 5, 6)        = IllegalArgumentException
+     * 
+ * + * @param str the String to check, may be null + * @param offset left edge of source String + * @param maxWidth maximum length of result String, must be at least 4 + * @return abbreviated String, {@code null} if null String input + * @throws IllegalArgumentException if the width is too small + * @since 2.0 + */ + public static String abbreviate(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.

+ * + *

Specifically:

+ *
    + *
  • If the number of characters in {@code str} is less than or equal to + * {@code maxWidth}, return {@code str}.
  • + *
  • Else abbreviate it to {@code (substring(str, 0, max-abbrevMarker.length) + abbrevMarker)}.
  • + *
  • If {@code maxWidth} is less than {@code abbrevMarker.length + 1}, throw an + * {@code IllegalArgumentException}.
  • + *
  • In no case will it return a String of length greater than + * {@code maxWidth}.
  • + *
+ * + *
+     * 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
+     * 
+ * + * @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.

+ * + *

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

In no case will it return a String of length greater than {@code maxWidth}.

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

+ * + *

This abbreviation only occurs if the following criteria is met:

+ *
    + *
  • Neither the String for abbreviation nor the replacement String are null or empty
  • + *
  • The length to truncate to is less than the length of the supplied String
  • + *
  • The length to truncate to is greater than 0
  • + *
  • The abbreviated String will have enough room for the length supplied replacement String + * and the first and last characters of the supplied String for abbreviation
  • + *
+ *

Otherwise, the returned String will be the same as the supplied String for abbreviation. + *

+ * + *
+     * StringUtils.abbreviateMiddle(null, null, 0)      = null
+     * StringUtils.abbreviateMiddle("abc", null, 0)      = "abc"
+     * StringUtils.abbreviateMiddle("abc", ".", 0)      = "abc"
+     * StringUtils.abbreviateMiddle("abc", ".", 3)      = "abc"
+     * StringUtils.abbreviateMiddle("abcdef", ".", 4)     = "ab.f"
+     * 
+ * + * @param str the String to abbreviate, may be null + * @param middle the String to replace the middle characters with, may be null + * @param length the length to abbreviate {@code str} to. + * @return the abbreviated String if the above criteria is met, or the original String supplied for abbreviation. + * @since 2.5 + */ + public static String abbreviateMiddle(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. + * + *
+     * 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"
+     * 
+ *

With additional suffixes,

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

With additional suffixes,

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

+ * + *

For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#capitalize(String)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.capitalize(null)  = null
+     * StringUtils.capitalize("")    = ""
+     * StringUtils.capitalize("cat") = "Cat"
+     * StringUtils.capitalize("cAt") = "CAt"
+     * StringUtils.capitalize("'cat'") = "'cat'"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return the capitalized String, {@code null} if null String input + * @see org.apache.commons.lang3.text.WordUtils#capitalize(String) + * @see #uncapitalize(String) + * @since 2.0 + */ + public static String capitalize(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 codepoint + 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 (' ').

+ * + *

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.

+ * + *

Equivalent to {@code center(str, size, " ")}.

+ * + *
+     * StringUtils.center(null, *)   = null
+     * StringUtils.center("", 4)     = "    "
+     * StringUtils.center("ab", -1)  = "ab"
+     * StringUtils.center("ab", 4)   = " ab "
+     * StringUtils.center("abcd", 2) = "abcd"
+     * StringUtils.center("a", 4)    = " a  "
+     * 
+ * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @return centered String, {@code null} if null String input + */ + public static String center(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.

+ * + *

If the size is less than the String length, the String is returned. + * A {@code null} String returns {@code null}. + * A negative size is treated as zero.

+ * + *
+     * StringUtils.center(null, *, *)     = null
+     * StringUtils.center("", 4, ' ')     = "    "
+     * StringUtils.center("ab", -1, ' ')  = "ab"
+     * StringUtils.center("ab", 4, ' ')   = " ab "
+     * StringUtils.center("abcd", 2, ' ') = "abcd"
+     * StringUtils.center("a", 4, ' ')    = " a  "
+     * StringUtils.center("a", 4, 'y')    = "yayy"
+     * 
+ * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @param padChar the character to pad the new String with + * @return centered String, {@code null} if null String input + * @since 2.0 + */ + public static String center(String str, 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.

+ * + *

If the size is less than the String length, the String is returned. + * A {@code null} String returns {@code null}. + * A negative size is treated as zero.

+ * + *
+     * StringUtils.center(null, *, *)     = null
+     * StringUtils.center("", 4, " ")     = "    "
+     * StringUtils.center("ab", -1, " ")  = "ab"
+     * StringUtils.center("ab", 4, " ")   = " ab "
+     * StringUtils.center("abcd", 2, " ") = "abcd"
+     * StringUtils.center("a", 4, " ")    = " a  "
+     * StringUtils.center("a", 4, "yz")   = "yayz"
+     * StringUtils.center("abc", 7, null) = "  abc  "
+     * StringUtils.center("abc", 7, "")   = "  abc  "
+     * 
+ * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @param padStr the String to pad the new String with, must not be null or empty + * @return centered String, {@code null} if null String input + * @throws IllegalArgumentException if padStr is {@code null} or empty + */ + public static String center(String str, 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 "{@code \n}", + * "{@code \r}", or "{@code \r\n}".

+ * + *

NOTE: This method changed in 2.0. + * It now more closely matches Perl chomp.

+ * + *
+     * StringUtils.chomp(null)          = null
+     * StringUtils.chomp("")            = ""
+     * StringUtils.chomp("abc \r")      = "abc "
+     * StringUtils.chomp("abc\n")       = "abc"
+     * StringUtils.chomp("abc\r\n")     = "abc"
+     * StringUtils.chomp("abc\r\n\r\n") = "abc\r\n"
+     * StringUtils.chomp("abc\n\r")     = "abc\n"
+     * StringUtils.chomp("abc\n\rabc")  = "abc\n\rabc"
+     * StringUtils.chomp("\r")          = ""
+     * StringUtils.chomp("\n")          = ""
+     * StringUtils.chomp("\r\n")        = ""
+     * 
+ * + * @param str the String to chomp a newline from, may be null + * @return String without newline, {@code null} if null String input + */ + public static String chomp(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.

+ * + *

NOTE: This method changed in version 2.0. + * It now more closely matches Perl chomp. + * For the previous behavior, use {@link #substringBeforeLast(String, String)}. + * This method uses {@link String#endsWith(String)}.

+ * + *
+     * StringUtils.chomp(null, *)         = null
+     * StringUtils.chomp("", *)           = ""
+     * StringUtils.chomp("foobar", "bar") = "foo"
+     * StringUtils.chomp("foobar", "baz") = "foobar"
+     * StringUtils.chomp("foo", "foo")    = ""
+     * StringUtils.chomp("foo ", "foo")   = "foo "
+     * StringUtils.chomp(" foo", "foo")   = " "
+     * StringUtils.chomp("foo", "foooo")  = "foo"
+     * StringUtils.chomp("foo", "")       = "foo"
+     * StringUtils.chomp("foo", null)     = "foo"
+     * 
+ * + * @param str the String to chomp from, may be null + * @param separator separator String, may be null + * @return String without trailing separator, {@code null} if null String input + * @deprecated This feature will be removed in Lang 4.0, 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.

+ * + *

If the String ends in {@code \r\n}, then remove both + * of them.

+ * + *
+     * StringUtils.chop(null)          = null
+     * StringUtils.chop("")            = ""
+     * StringUtils.chop("abc \r")      = "abc "
+     * StringUtils.chop("abc\n")       = "abc"
+     * StringUtils.chop("abc\r\n")     = "abc"
+     * StringUtils.chop("abc")         = "ab"
+     * StringUtils.chop("abc\nabc")    = "abc\nab"
+     * StringUtils.chop("a")           = ""
+     * StringUtils.chop("\r")          = ""
+     * StringUtils.chop("\n")          = ""
+     * StringUtils.chop("\r\n")        = ""
+     * 
+ * + * @param str the String to chop last character from, may be null + * @return String without last character, {@code null} if null String input + */ + public static String chop(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 :

+ *
    + *
  • {@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})
  • + *
  • {@code int < 0}, if {@code str1} is less than {@code str2}
  • + *
  • {@code int > 0}, if {@code str1} is greater than {@code str2}
  • + *
+ * + *

This is a {@code null} safe version of :

+ *
str1.compareTo(str2)
+ * + *

{@code null} value is considered less than non-{@code null} value. + * Two {@code null} references are considered equal.

+ * + *
+     * StringUtils.compare(null, null)   = 0
+     * StringUtils.compare(null , "a")   < 0
+     * StringUtils.compare("a", null)    > 0
+     * StringUtils.compare("abc", "abc") = 0
+     * StringUtils.compare("a", "b")     < 0
+     * StringUtils.compare("b", "a")     > 0
+     * StringUtils.compare("a", "B")     > 0
+     * StringUtils.compare("ab", "abc")  < 0
+     * 
+ * + * @see #compare(String, String, boolean) + * @see String#compareTo(String) + * @param str1 the String to compare from + * @param str2 the String to compare to + * @return < 0, 0, > 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 :

+ *
    + *
  • {@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})
  • + *
  • {@code int < 0}, if {@code str1} is less than {@code str2}
  • + *
  • {@code int > 0}, if {@code str1} is greater than {@code str2}
  • + *
+ * + *

This is a {@code null} safe version of :

+ *
str1.compareTo(str2)
+ * + *

{@code null} inputs are handled according to the {@code nullIsLess} parameter. + * Two {@code null} references are considered equal.

+ * + *
+     * StringUtils.compare(null, null, *)     = 0
+     * StringUtils.compare(null , "a", true)  < 0
+     * StringUtils.compare(null , "a", false) > 0
+     * StringUtils.compare("a", null, true)   > 0
+     * StringUtils.compare("a", null, false)  < 0
+     * StringUtils.compare("abc", "abc", *)   = 0
+     * StringUtils.compare("a", "b", *)       < 0
+     * StringUtils.compare("b", "a", *)       > 0
+     * StringUtils.compare("a", "B", *)       > 0
+     * StringUtils.compare("ab", "abc", *)    < 0
+     * 
+ * + * @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 < 0, 0, > 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 :

+ *
    + *
  • {@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})
  • + *
  • {@code int < 0}, if {@code str1} is less than {@code str2}
  • + *
  • {@code int > 0}, if {@code str1} is greater than {@code str2}
  • + *
+ * + *

This is a {@code null} safe version of :

+ *
str1.compareToIgnoreCase(str2)
+ * + *

{@code null} value is considered less than non-{@code null} value. + * Two {@code null} references are considered equal. + * Comparison is case insensitive.

+ * + *
+     * StringUtils.compareIgnoreCase(null, null)   = 0
+     * StringUtils.compareIgnoreCase(null , "a")   < 0
+     * StringUtils.compareIgnoreCase("a", null)    > 0
+     * StringUtils.compareIgnoreCase("abc", "abc") = 0
+     * StringUtils.compareIgnoreCase("abc", "ABC") = 0
+     * StringUtils.compareIgnoreCase("a", "b")     < 0
+     * StringUtils.compareIgnoreCase("b", "a")     > 0
+     * StringUtils.compareIgnoreCase("a", "B")     < 0
+     * StringUtils.compareIgnoreCase("A", "b")     < 0
+     * StringUtils.compareIgnoreCase("ab", "ABC")  < 0
+     * 
+ * + * @see #compareIgnoreCase(String, String, boolean) + * @see String#compareToIgnoreCase(String) + * @param str1 the String to compare from + * @param str2 the String to compare to + * @return < 0, 0, > 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 :

+ *
    + *
  • {@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})
  • + *
  • {@code int < 0}, if {@code str1} is less than {@code str2}
  • + *
  • {@code int > 0}, if {@code str1} is greater than {@code str2}
  • + *
+ * + *

This is a {@code null} safe version of :

+ *
str1.compareToIgnoreCase(str2)
+ * + *

{@code null} inputs are handled according to the {@code nullIsLess} parameter. + * Two {@code null} references are considered equal. + * Comparison is case insensitive.

+ * + *
+     * StringUtils.compareIgnoreCase(null, null, *)     = 0
+     * StringUtils.compareIgnoreCase(null , "a", true)  < 0
+     * StringUtils.compareIgnoreCase(null , "a", false) > 0
+     * StringUtils.compareIgnoreCase("a", null, true)   > 0
+     * StringUtils.compareIgnoreCase("a", null, false)  < 0
+     * StringUtils.compareIgnoreCase("abc", "abc", *)   = 0
+     * StringUtils.compareIgnoreCase("abc", "ABC", *)   = 0
+     * StringUtils.compareIgnoreCase("a", "b", *)       < 0
+     * StringUtils.compareIgnoreCase("b", "a", *)       > 0
+     * StringUtils.compareIgnoreCase("a", "B", *)       < 0
+     * StringUtils.compareIgnoreCase("A", "b", *)       < 0
+     * StringUtils.compareIgnoreCase("ab", "abc", *)    < 0
+     * 
+ * + * @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 < 0, 0, > 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.

+ * + *

A {@code null} CharSequence will return {@code false}.

+ * + *
+     * StringUtils.contains(null, *)     = false
+     * StringUtils.contains(*, null)     = false
+     * StringUtils.contains("", "")      = true
+     * StringUtils.contains("abc", "")   = true
+     * StringUtils.contains("abc", "a")  = true
+     * StringUtils.contains("abc", "z")  = false
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @return true if the CharSequence contains the search CharSequence, + * false if not or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from contains(String, String) to contains(CharSequence, CharSequence) + */ + public static boolean contains(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.

+ * + *

A {@code null} or empty ("") CharSequence will return {@code false}.

+ * + *
+     * StringUtils.contains(null, *)    = false
+     * StringUtils.contains("", *)      = false
+     * StringUtils.contains("abc", 'a') = true
+     * StringUtils.contains("abc", 'z') = false
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @return true if the CharSequence contains the search character, + * false if not or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from contains(String, int) to contains(CharSequence, int) + */ + public static boolean contains(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.

+ * + *

A {@code null} CharSequence will return {@code false}. + * A {@code null} or zero length search array will return {@code false}.

+ * + *
+     * StringUtils.containsAny(null, *)                  = false
+     * StringUtils.containsAny("", *)                    = false
+     * StringUtils.containsAny(*, null)                  = false
+     * StringUtils.containsAny(*, [])                    = false
+     * StringUtils.containsAny("zzabyycdxx", ['z', 'a']) = true
+     * StringUtils.containsAny("zzabyycdxx", ['b', 'y']) = true
+     * StringUtils.containsAny("zzabyycdxx", ['z', 'y']) = true
+     * StringUtils.containsAny("aba", ['z'])             = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the {@code true} if any of the chars are found, + * {@code false} if no match or null input + * @since 2.4 + * @since 3.0 Changed signature from containsAny(String, char[]) to containsAny(CharSequence, char...) + */ + public static boolean containsAny(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)) { + if (j == searchLast) { + // missing low surrogate, fine, like String.indexOf(String) + return true; + } + if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) { + return true; + } + } else { + // ch is in the Basic Multilingual Plane + return true; + } + } + } + } + return false; + } + + /** + *

+ * Checks if the CharSequence contains any character in the given set of characters. + *

+ * + *

+ * A {@code null} CharSequence will return {@code false}. A {@code null} search CharSequence will return + * {@code false}. + *

+ * + *
+     * StringUtils.containsAny(null, *)               = false
+     * StringUtils.containsAny("", *)                 = false
+     * StringUtils.containsAny(*, null)               = false
+     * StringUtils.containsAny(*, "")                 = false
+     * StringUtils.containsAny("zzabyycdxx", "za")    = true
+     * StringUtils.containsAny("zzabyycdxx", "by")    = true
+     * StringUtils.containsAny("zzabyycdxx", "zy")    = true
+     * StringUtils.containsAny("zzabyycdxx", "\tx")   = true
+     * StringUtils.containsAny("zzabyycdxx", "$.#yF") = true
+     * StringUtils.containsAny("aba", "z")            = false
+     * 
+ * + * @param cs + * the CharSequence to check, may be null + * @param searchChars + * the chars to search for, may be null + * @return the {@code true} if any of the chars are found, {@code false} if no match or null input + * @since 2.4 + * @since 3.0 Changed signature from containsAny(String, String) to containsAny(CharSequence, CharSequence) + */ + public static boolean containsAny(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. + *

+ * + *

+ * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will + * return {@code false}. + *

+ * + *
+     * StringUtils.containsAny(null, *)            = false
+     * StringUtils.containsAny("", *)              = false
+     * StringUtils.containsAny(*, null)            = false
+     * StringUtils.containsAny(*, [])              = false
+     * StringUtils.containsAny("abcd", "ab", null) = true
+     * StringUtils.containsAny("abcd", "ab", "cd") = true
+     * StringUtils.containsAny("abc", "d", "abc")  = true
+     * 
+ * + * + * @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. + *

+ * + *

+ * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will + * return {@code false}. + *

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

+ * + *

+ * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will + * return {@code false}. + *

+ * + *
+     * StringUtils.containsAny(null, *)            = false
+     * StringUtils.containsAny("", *)              = false
+     * StringUtils.containsAny(*, null)            = false
+     * StringUtils.containsAny(*, [])              = false
+     * StringUtils.containsAny("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
+     * 
+ * + * + * @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)}. + * + *

A {@code null} CharSequence will return {@code false}.

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

+ * + *

A {@code null} CharSequence will return {@code true}. + * A {@code null} invalid character array will return {@code true}. + * An empty CharSequence (length()=0) always returns true.

+ * + *
+     * StringUtils.containsNone(null, *)       = true
+     * StringUtils.containsNone(*, null)       = true
+     * StringUtils.containsNone("", *)         = true
+     * StringUtils.containsNone("ab", '')      = true
+     * StringUtils.containsNone("abab", 'xyz') = true
+     * StringUtils.containsNone("ab1", 'xyz')  = true
+     * StringUtils.containsNone("abz", 'xyz')  = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param searchChars an array of invalid chars, may be null + * @return true if it contains none of the invalid chars, or is null + * @since 2.0 + * @since 3.0 Changed signature from containsNone(String, char[]) to containsNone(CharSequence, char...) + */ + public static boolean containsNone(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)) { + if (j == searchLast) { + // missing low surrogate, fine, like String.indexOf(String) + return false; + } + if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) { + return false; + } + } else { + // ch is in the Basic Multilingual Plane + return false; + } + } + } + } + return true; + } + + /** + *

Checks that the CharSequence does not contain certain characters.

+ * + *

A {@code null} CharSequence will return {@code true}. + * A {@code null} invalid character array will return {@code true}. + * An empty String ("") always returns true.

+ * + *
+     * StringUtils.containsNone(null, *)       = true
+     * StringUtils.containsNone(*, null)       = true
+     * StringUtils.containsNone("", *)         = true
+     * StringUtils.containsNone("ab", "")      = true
+     * StringUtils.containsNone("abab", "xyz") = true
+     * StringUtils.containsNone("ab1", "xyz")  = true
+     * StringUtils.containsNone("abz", "xyz")  = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param invalidChars a String of invalid chars, may be null + * @return true if it contains none of the invalid chars, or is null + * @since 2.0 + * @since 3.0 Changed signature from containsNone(String, String) to containsNone(CharSequence, String) + */ + public static boolean containsNone(final CharSequence cs, final String invalidChars) { + if (invalidChars == null) { + return true; + } + return containsNone(cs, invalidChars.toCharArray()); + } + + /** + *

Checks if the CharSequence contains only certain characters.

+ * + *

A {@code null} CharSequence will return {@code false}. + * A {@code null} valid character array will return {@code false}. + * An empty CharSequence (length()=0) always returns {@code true}.

+ * + *
+     * StringUtils.containsOnly(null, *)       = false
+     * StringUtils.containsOnly(*, null)       = false
+     * StringUtils.containsOnly("", *)         = true
+     * StringUtils.containsOnly("ab", '')      = false
+     * StringUtils.containsOnly("abab", 'abc') = true
+     * StringUtils.containsOnly("ab1", 'abc')  = false
+     * StringUtils.containsOnly("abz", 'abc')  = false
+     * 
+ * + * @param cs the String to check, may be null + * @param valid an array of valid chars, may be null + * @return true if it only contains valid chars and is non-null + * @since 3.0 Changed signature from containsOnly(String, char[]) to containsOnly(CharSequence, char...) + */ + public static boolean containsOnly(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.

+ * + *

A {@code null} CharSequence will return {@code false}. + * A {@code null} valid character String will return {@code false}. + * An empty String (length()=0) always returns {@code true}.

+ * + *
+     * StringUtils.containsOnly(null, *)       = false
+     * StringUtils.containsOnly(*, null)       = false
+     * StringUtils.containsOnly("", *)         = true
+     * StringUtils.containsOnly("ab", "")      = false
+     * StringUtils.containsOnly("abab", "abc") = true
+     * StringUtils.containsOnly("ab1", "abc")  = false
+     * StringUtils.containsOnly("abz", "abc")  = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param validChars a String of valid chars, may be null + * @return true if it only contains valid chars and is non-null + * @since 2.0 + * @since 3.0 Changed signature from containsOnly(String, String) to containsOnly(CharSequence, String) + */ + public static boolean containsOnly(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.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

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

+ * + *

A {@code null} or empty ("") String input returns {@code 0}.

+ * + *
+     * 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
+     * 
+ * + * @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 look ups 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.

+ * + *

A {@code null} or empty ("") String input returns {@code 0}.

+ * + *
+     * StringUtils.countMatches(null, *)       = 0
+     * StringUtils.countMatches("", *)         = 0
+     * StringUtils.countMatches("abba", null)  = 0
+     * StringUtils.countMatches("abba", "")    = 0
+     * StringUtils.countMatches("abba", "a")   = 2
+     * StringUtils.countMatches("abba", "ab")  = 1
+     * StringUtils.countMatches("abba", "xxx") = 0
+     * StringUtils.countMatches("ababa", "aba") = 1
+     * 
+ * + * @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}.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.defaultIfBlank(null, "NULL")  = "NULL"
+     * StringUtils.defaultIfBlank("", "NULL")    = "NULL"
+     * StringUtils.defaultIfBlank(" ", "NULL")   = "NULL"
+     * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
+     * StringUtils.defaultIfBlank("", null)      = null
+     * 
+ * @param the specific kind of CharSequence + * @param str the CharSequence to check, may be null + * @param defaultStr the default CharSequence to return + * if the input is whitespace, empty ("") or {@code null}, may be null + * @return the passed in CharSequence, or the default + * @see StringUtils#defaultString(String, String) + */ + public static T defaultIfBlank(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}.

+ * + *
+     * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
+     * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
+     * StringUtils.defaultIfEmpty(" ", "NULL")   = " "
+     * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
+     * StringUtils.defaultIfEmpty("", null)      = null
+     * 
+ * @param the specific kind of CharSequence + * @param str the CharSequence to check, may be null + * @param defaultStr the default CharSequence to return + * if the input is empty ("") or {@code null}, may be null + * @return the passed in CharSequence, or the default + * @see StringUtils#defaultString(String, String) + */ + public static T defaultIfEmpty(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 ("").

+ * + *
+     * StringUtils.defaultString(null)  = ""
+     * StringUtils.defaultString("")    = ""
+     * StringUtils.defaultString("bat") = "bat"
+     * 
+ * + * @see ObjectUtils#toString(Object) + * @see String#valueOf(Object) + * @param str the String to check, may be null + * @return the passed in String, or the empty String if it + * was {@code null} + */ + public static String defaultString(final String str) { + return defaultString(str, EMPTY); + } + + /** + *

Returns either the passed in String, or if the String is + * {@code null}, the value of {@code defaultStr}.

+ * + *
+     * StringUtils.defaultString(null, "NULL")  = "NULL"
+     * StringUtils.defaultString("", "NULL")    = ""
+     * StringUtils.defaultString("bat", "NULL") = "bat"
+     * 
+ * + * @see ObjectUtils#toString(Object,String) + * @see String#valueOf(Object) + * @param str the String to check, may be null + * @param defaultStr the default String to return + * if the input is {@code null}, may be null + * @return the passed in String, or the default if it was {@code null} + */ + public static String defaultString(final String str, final String defaultStr) { + return str == null ? defaultStr : str; + } + + /** + *

Deletes all whitespaces from a String as defined by + * {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.deleteWhitespace(null)         = null
+     * StringUtils.deleteWhitespace("")           = ""
+     * StringUtils.deleteWhitespace("abc")        = "abc"
+     * StringUtils.deleteWhitespace("   ab  c  ") = "abc"
+     * 
+ * + * @param str the String to delete whitespace from, may be null + * @return the String without whitespaces, {@code null} if null String input + */ + public static String deleteWhitespace(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".

+ * + *

For example, + * {@code difference("i am a machine", "i am a robot") -> "robot"}.

+ * + *
+     * StringUtils.difference(null, null) = null
+     * StringUtils.difference("", "") = ""
+     * StringUtils.difference("", "abc") = "abc"
+     * StringUtils.difference("abc", "") = ""
+     * StringUtils.difference("abc", "abc") = ""
+     * StringUtils.difference("abc", "ab") = ""
+     * StringUtils.difference("ab", "abxyz") = "xyz"
+     * StringUtils.difference("abcde", "abxyz") = "xyz"
+     * StringUtils.difference("abcde", "xyz") = "xyz"
+     * 
+ * + * @param str1 the first String, may be null + * @param str2 the second String, may be null + * @return the portion of str2 where it differs from str1; returns the + * empty String if they are equal + * @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.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

+ * + *
+     * StringUtils.endsWith(null, null)      = true
+     * StringUtils.endsWith(null, "def")     = false
+     * StringUtils.endsWith("abcdef", null)  = false
+     * StringUtils.endsWith("abcdef", "def") = true
+     * StringUtils.endsWith("ABCDEF", "def") = false
+     * StringUtils.endsWith("ABCDEF", "cde") = false
+     * StringUtils.endsWith("ABCDEF", "")    = true
+     * 
+ * + * @see java.lang.String#endsWith(String) + * @param str the CharSequence to check, may be null + * @param suffix the suffix to find, may be null + * @return {@code true} if the CharSequence ends with the suffix, case sensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from endsWith(String, String) to endsWith(CharSequence, CharSequence) + */ + public static boolean endsWith(final CharSequence str, final CharSequence suffix) { + return endsWith(str, suffix, false); + } + + /** + *

Check if a CharSequence ends with a specified suffix (optionally case insensitive).

+ * + * @see java.lang.String#endsWith(String) + * @param str the CharSequence to check, may be null + * @param suffix the suffix to find, may be null + * @param ignoreCase indicates whether the compare should ignore case + * (case insensitive) or not. + * @return {@code true} if the CharSequence starts with the prefix or + * both {@code null} + */ + private static boolean endsWith(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.

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

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case insensitive.

+ * + *
+     * StringUtils.endsWithIgnoreCase(null, null)      = true
+     * StringUtils.endsWithIgnoreCase(null, "def")     = false
+     * StringUtils.endsWithIgnoreCase("abcdef", null)  = false
+     * StringUtils.endsWithIgnoreCase("abcdef", "def") = true
+     * StringUtils.endsWithIgnoreCase("ABCDEF", "def") = true
+     * StringUtils.endsWithIgnoreCase("ABCDEF", "cde") = false
+     * 
+ * + * @see java.lang.String#endsWith(String) + * @param str the CharSequence to check, may be null + * @param suffix the suffix to find, may be null + * @return {@code true} if the CharSequence ends with the suffix, case insensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from endsWithIgnoreCase(String, String) to endsWithIgnoreCase(CharSequence, CharSequence) + */ + public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix) { + return endsWith(str, suffix, true); + } + + /** + *

Compares two CharSequences, returning {@code true} if they represent + * equal sequences of characters.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

+ * + *
+     * StringUtils.equals(null, null)   = true
+     * StringUtils.equals(null, "abc")  = false
+     * StringUtils.equals("abc", null)  = false
+     * StringUtils.equals("abc", "abc") = true
+     * StringUtils.equals("abc", "ABC") = false
+     * 
+ * + * @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}.

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

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

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered equal. The comparison is case insensitive.

+ * + *
+     * StringUtils.equalsIgnoreCase(null, null)   = true
+     * StringUtils.equalsIgnoreCase(null, "abc")  = false
+     * StringUtils.equalsIgnoreCase("abc", null)  = false
+     * StringUtils.equalsIgnoreCase("abc", "abc") = true
+     * StringUtils.equalsIgnoreCase("abc", "ABC") = true
+     * 
+ * + * @param 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.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

If all values are blank or the array is {@code null} + * or empty then {@code null} is returned.

+ * + *
+     * 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
+     * 
+ * + * @param 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 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.

+ * + *

If all values are empty or the array is {@code null} + * or empty then {@code null} is returned.

+ * + *
+     * 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
+     * 
+ * + * @param 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 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 {@code 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 {@code 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.

+ * + *

For example, + * {@code getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) -> "i am a "}

+ * + *
+     * StringUtils.getCommonPrefix(null) = ""
+     * StringUtils.getCommonPrefix(new String[] {}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"abc"}) = "abc"
+     * StringUtils.getCommonPrefix(new String[] {null, null}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"", ""}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"", null}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"abc", null, null}) = ""
+     * StringUtils.getCommonPrefix(new String[] {null, null, "abc"}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"", "abc"}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"abc", ""}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"abc", "abc"}) = "abc"
+     * StringUtils.getCommonPrefix(new String[] {"abc", "a"}) = "a"
+     * StringUtils.getCommonPrefix(new String[] {"ab", "abxyz"}) = "ab"
+     * StringUtils.getCommonPrefix(new String[] {"abcde", "abxyz"}) = "ab"
+     * StringUtils.getCommonPrefix(new String[] {"abcde", "xyz"}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"xyz", "abcde"}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) = "i am a "
+     * 
+ * + * @param strs array of String objects, entries may be null + * @return the initial sequence of characters that are common to all Strings + * in the array; empty String if the array is null, the elements are all null + * or if there is no common prefix. + * @since 2.4 + */ + public static String getCommonPrefix(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]; + } else if (smallestIndexOfDiff == 0) { + // there were no common initial characters + return EMPTY; + } else { + // we found a common initial character sequence + return strs[0].substring(0, smallestIndexOfDiff); + } + } + + /** + *

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.

+ * + *

An empty ("") String will be returned if no digits found in {@code str}.

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

+ * + *

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.

+ * + *
+     * 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
+     * 
+ * + * @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 commons-text + * + * FuzzyScore 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"); + } else 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}.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

Caller responsible for thread-safety and exception handling of default value supplier

+ * + *
+     * {@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
+     * }
+ * @param 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 getIfBlank(final T str, final Supplier defaultSupplier) { + return isBlank(str) ? defaultSupplier == null ? null : defaultSupplier.get() : str; + } + + /** + *

Returns either the passed in CharSequence, or if the CharSequence is + * empty or {@code null}, the value supplied by {@code defaultStrSupplier}.

+ * + *

Caller responsible for thread-safety and exception handling of default value supplier

+ * + *
+     * {@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
+     * }
+     * 
+ * @param 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 getIfEmpty(final T str, final Supplier defaultSupplier) { + return isEmpty(str) ? defaultSupplier == null ? null : defaultSupplier.get() : str; + } + + /** + *

Find the Jaro Winkler Distance which indicates the similarity score between two Strings.

+ * + *

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.

+ * + *

This implementation is based on the Jaro Winkler similarity algorithm + * from http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance.

+ * + *
+     * 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 & 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
+     * 
+ * + * @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 commons-text + * + * JaroWinklerDistance 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.

+ * + *

This is the number of changes needed to change one String into + * another, where each change is a single character modification (deletion, + * insertion or substitution).

+ * + *

The implementation uses a single-dimensional array of length s.length() + 1. See + * + * http://blog.softwx.net/2014/12/optimizing-levenshtein-algorithm-in-c.html for details.

+ * + *
+     * StringUtils.getLevenshteinDistance(null, *)             = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance(*, null)             = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance("", "")              = 0
+     * StringUtils.getLevenshteinDistance("", "a")             = 1
+     * StringUtils.getLevenshteinDistance("aaapppp", "")       = 7
+     * StringUtils.getLevenshteinDistance("frog", "fog")       = 1
+     * StringUtils.getLevenshteinDistance("fly", "ant")        = 3
+     * StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
+     * StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
+     * StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
+     * StringUtils.getLevenshteinDistance("hello", "hallo")    = 1
+     * 
+ * + * @param s the first String, must not be null + * @param t the second String, must not be null + * @return result distance + * @throws IllegalArgumentException if either String input {@code null} + * @since 3.0 Changed signature from getLevenshteinDistance(String, String) to + * getLevenshteinDistance(CharSequence, CharSequence) + * @deprecated as of 3.6, use commons-text + * + * LevenshteinDistance 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; + } else 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.

+ * + *

This is the number of changes needed to change one String into + * another, where each change is a single character modification (deletion, + * insertion or substitution).

+ * + *

This implementation follows from Algorithms on Strings, Trees and Sequences by Dan Gusfield + * and Chas Emerick's implementation of the Levenshtein distance algorithm from + * http://www.merriampark.com/ld.htm

+ * + *
+     * StringUtils.getLevenshteinDistance(null, *, *)             = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance(*, null, *)             = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance(*, *, -1)               = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance("", "", 0)              = 0
+     * StringUtils.getLevenshteinDistance("aaapppp", "", 8)       = 7
+     * StringUtils.getLevenshteinDistance("aaapppp", "", 7)       = 7
+     * StringUtils.getLevenshteinDistance("aaapppp", "", 6))      = -1
+     * StringUtils.getLevenshteinDistance("elephant", "hippo", 7) = 7
+     * StringUtils.getLevenshteinDistance("elephant", "hippo", 6) = -1
+     * StringUtils.getLevenshteinDistance("hippo", "elephant", 7) = 7
+     * StringUtils.getLevenshteinDistance("hippo", "elephant", 6) = -1
+     * 
+ * + * @param s the first String, must not be null + * @param t the second String, must not be null + * @param threshold the target threshold, must not be negative + * @return result distance, or {@code -1} if the distance would be greater than the threshold + * @throws IllegalArgumentException if either String input {@code null} or negative threshold + * @deprecated as of 3.6, use commons-text + * + * LevenshteinDistance 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; + } else if (m == 0) { + return n <= threshold ? n : -1; + } else 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[] _d; // 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 + _d = p; + p = d; + d = _d; + } + + // if p[n] is greater than the threshold, there's no guarantee on it being the correct + // distance + if (p[n] <= threshold) { + return p[n]; + } + return -1; + } + + /** + *

Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String, int)} if possible.

+ * + *

A {@code null} CharSequence will return {@code -1}.

+ * + *
+     * StringUtils.indexOf(null, *)          = -1
+     * StringUtils.indexOf(*, null)          = -1
+     * StringUtils.indexOf("", "")           = 0
+     * StringUtils.indexOf("", *)            = -1 (except when * = "")
+     * StringUtils.indexOf("aabaabaa", "a")  = 0
+     * StringUtils.indexOf("aabaabaa", "b")  = 2
+     * StringUtils.indexOf("aabaabaa", "ab") = 1
+     * StringUtils.indexOf("aabaabaa", "")   = 0
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @return the first index of the search CharSequence, + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from indexOf(String, String) to indexOf(CharSequence, CharSequence) + */ + public static int indexOf(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.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A negative start position is treated as zero. + * An empty ("") search CharSequence always matches. + * A start position greater than the string length only matches + * an empty search CharSequence.

+ * + *
+     * StringUtils.indexOf(null, *, *)          = -1
+     * StringUtils.indexOf(*, null, *)          = -1
+     * StringUtils.indexOf("", "", 0)           = 0
+     * StringUtils.indexOf("", *, 0)            = -1 (except when * = "")
+     * StringUtils.indexOf("aabaabaa", "a", 0)  = 0
+     * StringUtils.indexOf("aabaabaa", "b", 0)  = 2
+     * StringUtils.indexOf("aabaabaa", "ab", 0) = 1
+     * StringUtils.indexOf("aabaabaa", "b", 3)  = 5
+     * StringUtils.indexOf("aabaabaa", "b", 9)  = -1
+     * StringUtils.indexOf("aabaabaa", "b", -1) = 2
+     * StringUtils.indexOf("aabaabaa", "", 2)   = 2
+     * StringUtils.indexOf("abc", "", 9)        = 3
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @param startPos the start position, negative treated as zero + * @return the first index of the search CharSequence (always ≥ 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} {@code 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 k such that: + *
+     * this.charAt(k) == searchChar
+     * 
+ * is true. For other values of {@code searchChar}, it is the + * smallest value k such that: + *
+     * this.codePointAt(k) == searchChar
+     * 
+ * is true. In either case, if no such character occurs in {@code seq}, + * then {@code INDEX_NOT_FOUND (-1)} is returned. + * + *

Furthermore, a {@code null} or empty ("") CharSequence will + * return {@code INDEX_NOT_FOUND (-1)}.

+ * + *
+     * StringUtils.indexOf(null, *)         = -1
+     * StringUtils.indexOf("", *)           = -1
+     * StringUtils.indexOf("aabaabaa", 'a') = 0
+     * StringUtils.indexOf("aabaabaa", 'b') = 2
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @return the first index of the search character, + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from indexOf(String, int) to indexOf(CharSequence, int) + * @since 3.6 Updated {@link CharSequenceUtils} call to behave more like {@code 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. + *

+ * If a character with value {@code searchChar} occurs in the + * character sequence represented by the {@code seq} {@code 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 k such that: + *

+     * (this.charAt(k) == searchChar) && (k >= startPos)
+     * 
+ * is true. For other values of {@code searchChar}, it is the + * smallest value k such that: + *
+     * (this.codePointAt(k) == searchChar) && (k >= startPos)
+     * 
+ * is true. In either case, if no such character occurs in {@code seq} + * at or after position {@code startPos}, then + * {@code -1} is returned. + * + *

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

All indices are specified in {@code char} values + * (Unicode code units). + * + *

+     * StringUtils.indexOf(null, *, *)          = -1
+     * StringUtils.indexOf("", *, *)            = -1
+     * StringUtils.indexOf("aabaabaa", 'b', 0)  = 2
+     * StringUtils.indexOf("aabaabaa", 'b', 3)  = 5
+     * StringUtils.indexOf("aabaabaa", 'b', 9)  = -1
+     * StringUtils.indexOf("aabaabaa", 'b', -1) = 2
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @param startPos the start position, negative treated as zero + * @return the first index of the search character (always ≥ 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 {@code 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.

+ * + *

A {@code null} String will return {@code -1}. + * A {@code null} or zero length search array will return {@code -1}.

+ * + *
+     * StringUtils.indexOfAny(null, *)                  = -1
+     * StringUtils.indexOfAny("", *)                    = -1
+     * StringUtils.indexOfAny(*, null)                  = -1
+     * StringUtils.indexOfAny(*, [])                    = -1
+     * StringUtils.indexOfAny("zzabyycdxx", ['z', 'a']) = 0
+     * StringUtils.indexOfAny("zzabyycdxx", ['b', 'y']) = 3
+     * StringUtils.indexOfAny("aba", ['z'])             = -1
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the index of any of the chars, -1 if no match or null input + * @since 2.0 + * @since 3.0 Changed signature from indexOfAny(String, char[]) to indexOfAny(CharSequence, char...) + */ + public static int indexOfAny(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)) { + // ch is a supplementary character + if (searchChars[j + 1] == cs.charAt(i + 1)) { + return i; + } + } else { + return i; + } + } + } + } + return INDEX_NOT_FOUND; + } + + /** + *

Find the first index of any of a set of potential substrings.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A {@code null} or zero length search array will return {@code -1}. + * A {@code null} search array entry will be ignored, but a search + * array containing "" will return {@code 0} if {@code str} is not + * null. This method uses {@link String#indexOf(String)} if possible.

+ * + *
+     * StringUtils.indexOfAny(null, *)                      = -1
+     * StringUtils.indexOfAny(*, null)                      = -1
+     * StringUtils.indexOfAny(*, [])                        = -1
+     * StringUtils.indexOfAny("zzabyycdxx", ["ab", "cd"])   = 2
+     * StringUtils.indexOfAny("zzabyycdxx", ["cd", "ab"])   = 2
+     * StringUtils.indexOfAny("zzabyycdxx", ["mn", "op"])   = -1
+     * StringUtils.indexOfAny("zzabyycdxx", ["zab", "aby"]) = 1
+     * StringUtils.indexOfAny("zzabyycdxx", [""])           = 0
+     * StringUtils.indexOfAny("", [""])                     = 0
+     * StringUtils.indexOfAny("", ["a"])                    = -1
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStrs the CharSequences to search for, may be null + * @return the first index of any of the searchStrs in str, -1 if no match + * @since 3.0 Changed signature from indexOfAny(String, String[]) to indexOfAny(CharSequence, CharSequence...) + */ + public static int indexOfAny(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 = 0; + 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.

+ * + *

A {@code null} String will return {@code -1}. + * A {@code null} search string will return {@code -1}.

+ * + *
+     * StringUtils.indexOfAny(null, *)            = -1
+     * StringUtils.indexOfAny("", *)              = -1
+     * StringUtils.indexOfAny(*, null)            = -1
+     * StringUtils.indexOfAny(*, "")              = -1
+     * StringUtils.indexOfAny("zzabyycdxx", "za") = 0
+     * StringUtils.indexOfAny("zzabyycdxx", "by") = 3
+     * StringUtils.indexOfAny("aba", "z")         = -1
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the index of any of the chars, -1 if no match or null input + * @since 2.0 + * @since 3.0 Changed signature from indexOfAny(String, String) to indexOfAny(CharSequence, String) + */ + public static int indexOfAny(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.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A {@code null} or zero length search array will return {@code -1}.

+ * + *
+     * StringUtils.indexOfAnyBut(null, *)                              = -1
+     * StringUtils.indexOfAnyBut("", *)                                = -1
+     * StringUtils.indexOfAnyBut(*, null)                              = -1
+     * StringUtils.indexOfAnyBut(*, [])                                = -1
+     * StringUtils.indexOfAnyBut("zzabyycdxx", new char[] {'z', 'a'} ) = 3
+     * StringUtils.indexOfAnyBut("aba", new char[] {'z'} )             = 0
+     * StringUtils.indexOfAnyBut("aba", new char[] {'a', 'b'} )        = -1
+
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the index of any of the chars, -1 if no match or null input + * @since 2.0 + * @since 3.0 Changed signature from indexOfAnyBut(String, char[]) to indexOfAnyBut(CharSequence, char...) + */ + public static int indexOfAnyBut(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)) { + if (searchChars[j + 1] == cs.charAt(i + 1)) { + continue outer; + } + } else { + continue outer; + } + } + } + return i; + } + return INDEX_NOT_FOUND; + } + + /** + *

Search a CharSequence to find the first index of any + * character not in the given set of characters.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A {@code null} or empty search string will return {@code -1}.

+ * + *
+     * StringUtils.indexOfAnyBut(null, *)            = -1
+     * StringUtils.indexOfAnyBut("", *)              = -1
+     * StringUtils.indexOfAnyBut(*, null)            = -1
+     * StringUtils.indexOfAnyBut(*, "")              = -1
+     * StringUtils.indexOfAnyBut("zzabyycdxx", "za") = 3
+     * StringUtils.indexOfAnyBut("zzabyycdxx", "")   = -1
+     * StringUtils.indexOfAnyBut("aba", "ab")        = -1
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the index of any of the chars, -1 if no match or null input + * @since 2.0 + * @since 3.0 Changed signature from indexOfAnyBut(String, String) to indexOfAnyBut(CharSequence, CharSequence) + */ + public static int indexOfAnyBut(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.

+ * + *

For example, + * {@code indexOfDifference(new String[] {"i am a machine", "i am a robot"}) -> 7}

+ * + *
+     * StringUtils.indexOfDifference(null) = -1
+     * StringUtils.indexOfDifference(new String[] {}) = -1
+     * StringUtils.indexOfDifference(new String[] {"abc"}) = -1
+     * StringUtils.indexOfDifference(new String[] {null, null}) = -1
+     * StringUtils.indexOfDifference(new String[] {"", ""}) = -1
+     * StringUtils.indexOfDifference(new String[] {"", null}) = 0
+     * StringUtils.indexOfDifference(new String[] {"abc", null, null}) = 0
+     * StringUtils.indexOfDifference(new String[] {null, null, "abc"}) = 0
+     * StringUtils.indexOfDifference(new String[] {"", "abc"}) = 0
+     * StringUtils.indexOfDifference(new String[] {"abc", ""}) = 0
+     * StringUtils.indexOfDifference(new String[] {"abc", "abc"}) = -1
+     * StringUtils.indexOfDifference(new String[] {"abc", "a"}) = 1
+     * StringUtils.indexOfDifference(new String[] {"ab", "abxyz"}) = 2
+     * StringUtils.indexOfDifference(new String[] {"abcde", "abxyz"}) = 2
+     * StringUtils.indexOfDifference(new String[] {"abcde", "xyz"}) = 0
+     * StringUtils.indexOfDifference(new String[] {"xyz", "abcde"}) = 0
+     * StringUtils.indexOfDifference(new String[] {"i am a machine", "i am a robot"}) = 7
+     * 
+ * + * @param css array of CharSequences, entries may be null + * @return the index where the strings begin to differ; -1 if they are all equal + * @since 2.4 + * @since 3.0 Changed signature from indexOfDifference(String...) to indexOfDifference(CharSequence...) + */ + public static int indexOfDifference(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.

+ * + *

For example, + * {@code indexOfDifference("i am a machine", "i am a robot") -> 7}

+ * + *
+     * StringUtils.indexOfDifference(null, null) = -1
+     * StringUtils.indexOfDifference("", "") = -1
+     * StringUtils.indexOfDifference("", "abc") = 0
+     * StringUtils.indexOfDifference("abc", "") = 0
+     * StringUtils.indexOfDifference("abc", "abc") = -1
+     * StringUtils.indexOfDifference("ab", "abxyz") = 2
+     * StringUtils.indexOfDifference("abcde", "abxyz") = 2
+     * StringUtils.indexOfDifference("abcde", "xyz") = 0
+     * 
+ * + * @param cs1 the first CharSequence, may be null + * @param cs2 the second CharSequence, may be null + * @return the index where cs1 and cs2 begin to differ; -1 if they are equal + * @since 2.0 + * @since 3.0 Changed signature from indexOfDifference(String, String) to + * indexOfDifference(CharSequence, CharSequence) + */ + public static int indexOfDifference(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.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A negative start position is treated as zero. + * An empty ("") search CharSequence always matches. + * A start position greater than the string length only matches + * an empty search CharSequence.

+ * + *
+     * StringUtils.indexOfIgnoreCase(null, *)          = -1
+     * StringUtils.indexOfIgnoreCase(*, null)          = -1
+     * StringUtils.indexOfIgnoreCase("", "")           = 0
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "a")  = 0
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "b")  = 2
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "ab") = 1
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @return the first index of the search CharSequence, + * -1 if no match or {@code null} string input + * @since 2.5 + * @since 3.0 Changed signature from indexOfIgnoreCase(String, String) to indexOfIgnoreCase(CharSequence, CharSequence) + */ + public static int indexOfIgnoreCase(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.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A negative start position is treated as zero. + * An empty ("") search CharSequence always matches. + * A start position greater than the string length only matches + * an empty search CharSequence.

+ * + *
+     * StringUtils.indexOfIgnoreCase(null, *, *)          = -1
+     * StringUtils.indexOfIgnoreCase(*, null, *)          = -1
+     * StringUtils.indexOfIgnoreCase("", "", 0)           = 0
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+     * StringUtils.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
+     * StringUtils.indexOfIgnoreCase("abc", "", 9)        = -1
+     * 
+ * + * @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 ≥ 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.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

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

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

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *
+     * StringUtils.isAllLowerCase(null)   = false
+     * StringUtils.isAllLowerCase("")     = false
+     * StringUtils.isAllLowerCase("  ")   = false
+     * StringUtils.isAllLowerCase("abc")  = true
+     * StringUtils.isAllLowerCase("abC")  = false
+     * StringUtils.isAllLowerCase("ab c") = false
+     * StringUtils.isAllLowerCase("ab1c") = false
+     * StringUtils.isAllLowerCase("ab/c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains lowercase characters, and is non-null + * @since 2.5 + * @since 3.0 Changed signature from isAllLowerCase(String) to isAllLowerCase(CharSequence) + */ + public static boolean isAllLowerCase(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.

+ * + *

{@code null} will return {@code false}. + * An empty String (length()=0) will return {@code false}.

+ * + *
+     * StringUtils.isAllUpperCase(null)   = false
+     * StringUtils.isAllUpperCase("")     = false
+     * StringUtils.isAllUpperCase("  ")   = false
+     * StringUtils.isAllUpperCase("ABC")  = true
+     * StringUtils.isAllUpperCase("aBC")  = false
+     * StringUtils.isAllUpperCase("A C")  = false
+     * StringUtils.isAllUpperCase("A1C")  = false
+     * StringUtils.isAllUpperCase("A/C")  = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains uppercase characters, and is non-null + * @since 2.5 + * @since 3.0 Changed signature from isAllUpperCase(String) to isAllUpperCase(CharSequence) + */ + public static boolean isAllUpperCase(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.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *
+     * StringUtils.isAlpha(null)   = false
+     * StringUtils.isAlpha("")     = false
+     * StringUtils.isAlpha("  ")   = false
+     * StringUtils.isAlpha("abc")  = true
+     * StringUtils.isAlpha("ab2c") = false
+     * StringUtils.isAlpha("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters, and is non-null + * @since 3.0 Changed signature from isAlpha(String) to isAlpha(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isAlpha(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.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *
+     * StringUtils.isAlphanumeric(null)   = false
+     * StringUtils.isAlphanumeric("")     = false
+     * StringUtils.isAlphanumeric("  ")   = false
+     * StringUtils.isAlphanumeric("abc")  = true
+     * StringUtils.isAlphanumeric("ab c") = false
+     * StringUtils.isAlphanumeric("ab2c") = true
+     * StringUtils.isAlphanumeric("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters or digits, + * and is non-null + * @since 3.0 Changed signature from isAlphanumeric(String) to isAlphanumeric(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isAlphanumeric(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 ' '}).

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * StringUtils.isAlphanumericSpace(null)   = false
+     * StringUtils.isAlphanumericSpace("")     = true
+     * StringUtils.isAlphanumericSpace("  ")   = true
+     * StringUtils.isAlphanumericSpace("abc")  = true
+     * StringUtils.isAlphanumericSpace("ab c") = true
+     * StringUtils.isAlphanumericSpace("ab2c") = true
+     * StringUtils.isAlphanumericSpace("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters, digits or space, + * and is non-null + * @since 3.0 Changed signature from isAlphanumericSpace(String) to isAlphanumericSpace(CharSequence) + */ + public static boolean isAlphanumericSpace(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 (' ').

+ * + *

{@code null} will return {@code false} + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * StringUtils.isAlphaSpace(null)   = false
+     * StringUtils.isAlphaSpace("")     = true
+     * StringUtils.isAlphaSpace("  ")   = true
+     * StringUtils.isAlphaSpace("abc")  = true
+     * StringUtils.isAlphaSpace("ab c") = true
+     * StringUtils.isAlphaSpace("ab2c") = false
+     * StringUtils.isAlphaSpace("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters and space, + * and is non-null + * @since 3.0 Changed signature from isAlphaSpace(String) to isAlphaSpace(CharSequence) + */ + public static boolean isAlphaSpace(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.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

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

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

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * StringUtils.isAsciiPrintable(null)     = false
+     * StringUtils.isAsciiPrintable("")       = true
+     * StringUtils.isAsciiPrintable(" ")      = true
+     * StringUtils.isAsciiPrintable("Ceki")   = true
+     * StringUtils.isAsciiPrintable("ab2c")   = true
+     * StringUtils.isAsciiPrintable("!ab-c~") = true
+     * StringUtils.isAsciiPrintable("\u0020") = true
+     * StringUtils.isAsciiPrintable("\u0021") = true
+     * StringUtils.isAsciiPrintable("\u007e") = true
+     * StringUtils.isAsciiPrintable("\u007f") = false
+     * StringUtils.isAsciiPrintable("Ceki G\u00fclc\u00fc") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if every character is in the range + * 32 thru 126 + * @since 2.1 + * @since 3.0 Changed signature from isAsciiPrintable(String) to isAsciiPrintable(CharSequence) + */ + public static boolean isAsciiPrintable(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.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.isBlank(null)      = true
+     * StringUtils.isBlank("")        = true
+     * StringUtils.isBlank(" ")       = true
+     * StringUtils.isBlank("bob")     = false
+     * StringUtils.isBlank("  bob  ") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is null, empty or whitespace 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.

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

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

+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is empty or null + * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence) + */ + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + /** + *

Checks if the CharSequence contains mixed casing of both uppercase and lowercase characters.

+ * + *

{@code null} will return {@code false}. An empty CharSequence ({@code length()=0}) will return + * {@code false}.

+ * + *
+     * StringUtils.isMixedCase(null)    = 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
+     * 
+ * + * @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; + } else 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.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

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

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

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.isNotBlank(null)      = false
+     * StringUtils.isNotBlank("")        = false
+     * StringUtils.isNotBlank(" ")       = false
+     * StringUtils.isNotBlank("bob")     = true
+     * StringUtils.isNotBlank("  bob  ") = true
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is + * not empty and not null and not whitespace 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.

+ * + *
+     * StringUtils.isNotEmpty(null)      = false
+     * StringUtils.isNotEmpty("")        = false
+     * StringUtils.isNotEmpty(" ")       = true
+     * StringUtils.isNotEmpty("bob")     = true
+     * StringUtils.isNotEmpty("  bob  ") = true
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is not empty and not null + * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence) + */ + public static boolean isNotEmpty(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.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *

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.

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

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * StringUtils.isNumericSpace(null)   = false
+     * StringUtils.isNumericSpace("")     = true
+     * StringUtils.isNumericSpace("  ")   = true
+     * StringUtils.isNumericSpace("123")  = true
+     * StringUtils.isNumericSpace("12 3") = true
+     * StringUtils.isNumericSpace("\u0967\u0968\u0969")  = true
+     * StringUtils.isNumericSpace("\u0967\u0968 \u0969")  = true
+     * StringUtils.isNumericSpace("ab2c") = false
+     * StringUtils.isNumericSpace("12-3") = false
+     * StringUtils.isNumericSpace("12.3") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains digits or space, + * and is non-null + * @since 3.0 Changed signature from isNumericSpace(String) to isNumericSpace(CharSequence) + */ + public static boolean isNumericSpace(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.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * StringUtils.isWhitespace(null)   = false
+     * StringUtils.isWhitespace("")     = true
+     * StringUtils.isWhitespace("  ")   = true
+     * StringUtils.isWhitespace("abc")  = false
+     * StringUtils.isWhitespace("ab2c") = false
+     * StringUtils.isWhitespace("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains whitespace, and is non-null + * @since 2.0 + * @since 3.0 Changed signature from isWhitespace(String) to isWhitespace(CharSequence) + */ + public static boolean isWhitespace(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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)              = null
+     * StringUtils.join([], *)                = ""
+     * StringUtils.join([null], *)            = ""
+     * StringUtils.join([false, false], ';')  = "false;false"
+     * 
+ * + * @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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)                   = null
+     * StringUtils.join([], *)                     = ""
+     * StringUtils.join([null], *)                 = ""
+     * StringUtils.join([true, false, true], ';')  = "true;false;true"
+     * 
+ * + * @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 StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(String.valueOf(array[i])); + } + return joiner.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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 StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(String.valueOf(array[i])); + } + return joiner.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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 StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(String.valueOf(array[i])); + } + return joiner.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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 StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(String.valueOf(array[i])); + } + return joiner.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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 StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(String.valueOf(array[i])); + } + return joiner.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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 StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(String.valueOf(array[i])); + } + return joiner.toString(); + } + + /** + *

Joins the elements of the provided {@code Iterable} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. Null objects or empty + * strings within the iteration are represented by empty strings.

+ * + *

See the examples here: {@link #join(Object[],char)}.

+ * + * @param iterable the {@code Iterable} providing the values to join together, may be null + * @param separator the separator character to use + * @return the joined String, {@code null} if null iterator input + * @since 2.3 + */ + public static String join(final Iterable iterable, final char separator) { + if (iterable == null) { + return null; + } + return join(iterable.iterator(), separator); + } + + /** + *

Joins the elements of the provided {@code Iterable} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String ("").

+ * + *

See the examples here: {@link #join(Object[],String)}.

+ * + * @param iterable the {@code Iterable} providing the values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null iterator input + * @since 2.3 + */ + public static String join(final Iterable iterable, final String separator) { + if (iterable == null) { + return null; + } + return join(iterable.iterator(), separator); + } + + /** + *

Joins the elements of the provided {@code Iterator} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. Null objects or empty + * strings within the iteration are represented by empty strings.

+ * + *

See the examples here: {@link #join(Object[],char)}.

+ * + * @param iterator the {@code Iterator} of values to join together, may be null + * @param separator the separator character to use + * @return the joined String, {@code null} if null iterator input + * @since 2.0 + */ + public static String join(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; + } + final Object first = iterator.next(); + if (!iterator.hasNext()) { + return toStringOrEmpty(first); + } + + // two or more elements + final StringBuilder buf = new StringBuilder(STRING_BUILDER_SIZE); // Java default is 16, probably too small + if (first != null) { + buf.append(first); + } + + while (iterator.hasNext()) { + buf.append(separator); + final Object obj = iterator.next(); + if (obj != null) { + buf.append(obj); + } + } + + return buf.toString(); + } + + /** + *

Joins the elements of the provided {@code Iterator} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String ("").

+ * + *

See the examples here: {@link #join(Object[],String)}.

+ * + * @param iterator the {@code Iterator} of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null iterator input + */ + public static String join(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; + } + final Object first = iterator.next(); + if (!iterator.hasNext()) { + return Objects.toString(first, ""); + } + + // two or more elements + final StringBuilder buf = new StringBuilder(STRING_BUILDER_SIZE); // Java default is 16, probably too small + if (first != null) { + buf.append(first); + } + + while (iterator.hasNext()) { + if (separator != null) { + buf.append(separator); + } + final Object obj = iterator.next(); + if (obj != null) { + buf.append(obj); + } + } + return buf.toString(); + } + + /** + *

Joins the elements of the provided {@code List} into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
+     * StringUtils.join(["a", "b", "c"], null) = "abc"
+     * StringUtils.join([null, "", "a"], ';')  = ";;a"
+     * 
+ * + * @param list the {@code 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 {@code List} into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
+     * StringUtils.join(["a", "b", "c"], null) = "abc"
+     * StringUtils.join([null, "", "a"], ';')  = ";;a"
+     * 
+ * + * @param list the {@code 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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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 StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(String.valueOf(array[i])); + } + return joiner.toString(); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
+     * StringUtils.join(["a", "b", "c"], null) = "abc"
+     * StringUtils.join([null, "", "a"], ';')  = ";;a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param 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.

+ * + *

No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
+     * StringUtils.join(["a", "b", "c"], null) = "abc"
+     * StringUtils.join([null, "", "a"], ';')  = ";;a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param 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) { + if (array == null) { + return null; + } + if (endIndex - startIndex <= 0) { + return EMPTY; + } + final StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(toStringOrEmpty(array[i])); + } + return joiner.toString(); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String (""). + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)                = null
+     * StringUtils.join([], *)                  = ""
+     * StringUtils.join([null], *)              = ""
+     * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
+     * StringUtils.join(["a", "b", "c"], null)  = "abc"
+     * StringUtils.join(["a", "b", "c"], "")    = "abc"
+     * StringUtils.join([null, "", "a"], ',')   = ",,a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param 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) { + 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.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String (""). + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *, *, *)                = null
+     * StringUtils.join([], *, *, *)                  = ""
+     * StringUtils.join([null], *, *, *)              = ""
+     * StringUtils.join(["a", "b", "c"], "--", 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"
+     * 
+ * + * @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
+ * {@code startIndex < 0} or
+ * {@code startIndex >= array.length()} or
+ * {@code endIndex < 0} or
+ * {@code endIndex > array.length()} + */ + public static String join(final Object[] array, final String delimiter, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + if (endIndex - startIndex <= 0) { + return EMPTY; + } + final StringJoiner joiner = new StringJoiner(toStringOrEmpty(delimiter)); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(toStringOrEmpty(array[i])); + } + return joiner.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @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 StringJoiner joiner = newStringJoiner(delimiter); + for (int i = startIndex; i < endIndex; i++) { + joiner.add(String.valueOf(array[i])); + } + return joiner.toString(); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No separator is added to the joined String. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null)            = null
+     * StringUtils.join([])              = ""
+     * StringUtils.join([null])          = ""
+     * StringUtils.join(["a", "b", "c"]) = "abc"
+     * StringUtils.join([null, "", "a"]) = "a"
+     * 
+ * + * @param the specific type of values to join together + * @param elements the values to join together, may be null + * @return the joined String, {@code null} if null array input + * @since 2.0 + * @since 3.0 Changed signature to use varargs + */ + @SafeVarargs + public static String join(final T... elements) { + return join(elements, null); + } + + /** + *

Joins the elements of the provided varargs into a + * single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. + * {@code null} elements and separator are treated as empty Strings ("").

+ * + *
+     * 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"
+     * 
+ * + * @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 java.lang.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.

+ * + *

A {@code null} CharSequence will return {@code -1}.

+ * + *
+     * StringUtils.lastIndexOf(null, *)          = -1
+     * StringUtils.lastIndexOf(*, null)          = -1
+     * StringUtils.lastIndexOf("", "")           = 0
+     * StringUtils.lastIndexOf("aabaabaa", "a")  = 7
+     * StringUtils.lastIndexOf("aabaabaa", "b")  = 5
+     * StringUtils.lastIndexOf("aabaabaa", "ab") = 4
+     * StringUtils.lastIndexOf("aabaabaa", "")   = 8
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @return the last index of the search String, + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from lastIndexOf(String, String) to lastIndexOf(CharSequence, CharSequence) + */ + public static int lastIndexOf(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.

+ * + *

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

+ * + *
+     * 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
+     * 
+ * + * @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 ≤ 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 k such that: + *
+     * this.charAt(k) == searchChar
+     * 
+ * is true. For other values of {@code searchChar}, it is the + * largest value k such that: + *
+     * this.codePointAt(k) == searchChar
+     * 
+ * is true. In either case, if no such character occurs in this + * string, then {@code -1} is returned. Furthermore, a {@code null} or empty ("") + * {@code CharSequence} will return {@code -1}. The + * {@code seq} {@code CharSequence} object is searched backwards + * starting at the last character. + * + *
+     * StringUtils.lastIndexOf(null, *)         = -1
+     * StringUtils.lastIndexOf("", *)           = -1
+     * StringUtils.lastIndexOf("aabaabaa", 'a') = 7
+     * StringUtils.lastIndexOf("aabaabaa", 'b') = 5
+     * 
+ * + * @param seq the {@code 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 {@code 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 k such that: + *
+     * (this.charAt(k) == searchChar) && (k <= startPos)
+     * 
+ * is true. For other values of {@code searchChar}, it is the + * largest value k such that: + *
+     * (this.codePointAt(k) == searchChar) && (k <= startPos)
+     * 
+ * 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 ("") + * {@code 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. + * + *

All indices are specified in {@code char} values + * (Unicode code units). + * + *

+     * StringUtils.lastIndexOf(null, *, *)          = -1
+     * StringUtils.lastIndexOf("", *,  *)           = -1
+     * StringUtils.lastIndexOf("aabaabaa", 'b', 8)  = 5
+     * StringUtils.lastIndexOf("aabaabaa", 'b', 4)  = 2
+     * StringUtils.lastIndexOf("aabaabaa", 'b', 0)  = -1
+     * StringUtils.lastIndexOf("aabaabaa", 'b', 9)  = 5
+     * StringUtils.lastIndexOf("aabaabaa", 'b', -1) = -1
+     * StringUtils.lastIndexOf("aabaabaa", 'a', 0)  = 0
+     * 
+ * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @param startPos the start position + * @return the last index of the search character (always ≤ 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.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A {@code null} search array will return {@code -1}. + * A {@code null} or zero length search array entry will be ignored, + * but a search array containing "" will return the length of {@code str} + * if {@code str} is not null. This method uses {@link String#indexOf(String)} if possible

+ * + *
+     * StringUtils.lastIndexOfAny(null, *)                    = -1
+     * StringUtils.lastIndexOfAny(*, null)                    = -1
+     * StringUtils.lastIndexOfAny(*, [])                      = -1
+     * StringUtils.lastIndexOfAny(*, [null])                  = -1
+     * StringUtils.lastIndexOfAny("zzabyycdxx", ["ab", "cd"]) = 6
+     * StringUtils.lastIndexOfAny("zzabyycdxx", ["cd", "ab"]) = 6
+     * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn", "op"]) = -1
+     * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn", "op"]) = -1
+     * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn", ""])   = 10
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStrs the CharSequences to search for, may be null + * @return the last index of any of the CharSequences, -1 if no match + * @since 3.0 Changed signature from lastIndexOfAny(String, String[]) to lastIndexOfAny(CharSequence, CharSequence) + */ + public static int lastIndexOfAny(final CharSequence str, final CharSequence... searchStrs) { + if (str == null || searchStrs == null) { + return INDEX_NOT_FOUND; + } + int ret = INDEX_NOT_FOUND; + int tmp = 0; + 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.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A negative start position returns {@code -1}. + * An empty ("") search CharSequence always matches unless the start position is negative. + * A start position greater than the string length searches the whole string.

+ * + *
+     * StringUtils.lastIndexOfIgnoreCase(null, *)          = -1
+     * StringUtils.lastIndexOfIgnoreCase(*, null)          = -1
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A")  = 7
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B")  = 5
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB") = 4
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @return the first index of the search CharSequence, + * -1 if no match or {@code null} string input + * @since 2.5 + * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String) to lastIndexOfIgnoreCase(CharSequence, CharSequence) + */ + public static int lastIndexOfIgnoreCase(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.

+ * + *

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

+ * + *
+     * StringUtils.lastIndexOfIgnoreCase(null, *, *)          = -1
+     * StringUtils.lastIndexOfIgnoreCase(*, null, *)          = -1
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 8)  = 7
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 8)  = 5
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB", 8) = 4
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 9)  = 5
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", -1) = -1
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 0)  = 0
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 0)  = -1
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param startPos the start position + * @return the last index of the search CharSequence (always ≤ 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)}.

+ * + *

A {@code null} String will return {@code -1}.

+ * + *
+     * StringUtils.lastOrdinalIndexOf(null, *, *)          = -1
+     * StringUtils.lastOrdinalIndexOf(*, null, *)          = -1
+     * StringUtils.lastOrdinalIndexOf("", "", *)           = 0
+     * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 1)  = 7
+     * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 2)  = 6
+     * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 1)  = 5
+     * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 2)  = 2
+     * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 1) = 4
+     * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 2) = 1
+     * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 1)   = 8
+     * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 2)   = 8
+     * 
+ * + *

Note that 'tail(CharSequence str, int n)' may be implemented as:

+ * + *
+     *   str.substring(lastOrdinalIndexOf(str, "\n", n) + 1)
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param ordinal the n-th last {@code searchStr} to find + * @return the n-th last index of the search CharSequence, + * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input + * @since 2.5 + * @since 3.0 Changed signature from lastOrdinalIndexOf(String, String, int) to lastOrdinalIndexOf(CharSequence, CharSequence, int) + */ + public static int lastOrdinalIndexOf(final CharSequence str, final CharSequence searchStr, final int ordinal) { + return ordinalIndexOf(str, searchStr, ordinal, true); + } + + /** + *

Gets the leftmost {@code len} characters of a String.

+ * + *

If {@code len} characters are not available, or the + * String is {@code null}, the String will be returned without + * an exception. An empty String is returned if len is negative.

+ * + *
+     * StringUtils.left(null, *)    = null
+     * StringUtils.left(*, -ve)     = ""
+     * StringUtils.left("", *)      = ""
+     * StringUtils.left("abc", 0)   = ""
+     * StringUtils.left("abc", 2)   = "ab"
+     * StringUtils.left("abc", 4)   = "abc"
+     * 
+ * + * @param str the String to get the leftmost characters from, may be null + * @param len the length of the required String + * @return the leftmost characters, {@code null} if null String input + */ + public static String left(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 (' ').

+ * + *

The String is padded to the size of {@code size}.

+ * + *
+     * StringUtils.leftPad(null, *)   = null
+     * StringUtils.leftPad("", 3)     = "   "
+     * StringUtils.leftPad("bat", 3)  = "bat"
+     * StringUtils.leftPad("bat", 5)  = "  bat"
+     * StringUtils.leftPad("bat", 1)  = "bat"
+     * StringUtils.leftPad("bat", -1) = "bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String leftPad(final String str, final int size) { + return leftPad(str, size, ' '); + } + + /** + *

Left pad a String with a specified character.

+ * + *

Pad to a size of {@code size}.

+ * + *
+     * StringUtils.leftPad(null, *, *)     = null
+     * StringUtils.leftPad("", 3, 'z')     = "zzz"
+     * StringUtils.leftPad("bat", 3, 'z')  = "bat"
+     * StringUtils.leftPad("bat", 5, 'z')  = "zzbat"
+     * StringUtils.leftPad("bat", 1, 'z')  = "bat"
+     * StringUtils.leftPad("bat", -1, 'z') = "bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padChar the character to pad with + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + * @since 2.0 + */ + public static String leftPad(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.

+ * + *

Pad to a size of {@code size}.

+ * + *
+     * StringUtils.leftPad(null, *, *)      = null
+     * StringUtils.leftPad("", 3, "z")      = "zzz"
+     * StringUtils.leftPad("bat", 3, "yz")  = "bat"
+     * StringUtils.leftPad("bat", 5, "yz")  = "yzbat"
+     * StringUtils.leftPad("bat", 8, "yz")  = "yzyzybat"
+     * StringUtils.leftPad("bat", 1, "yz")  = "bat"
+     * StringUtils.leftPad("bat", -1, "yz") = "bat"
+     * StringUtils.leftPad("bat", 5, null)  = "  bat"
+     * StringUtils.leftPad("bat", 5, "")    = "  bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padStr the String to pad with, null or empty treated as single space + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String leftPad(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); + } else if (pads < padLen) { + return padStr.substring(0, pads).concat(str); + } else { + 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()}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.lowerCase(null)  = null
+     * StringUtils.lowerCase("")    = ""
+     * StringUtils.lowerCase("aBc") = "abc"
+     * 
+ * + *

Note: As described in the documentation for {@link String#toLowerCase()}, + * the result of this method is affected by the current locale. + * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)} + * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).

+ * + * @param str the String to lower case, may be null + * @return the lower cased String, {@code null} if null String input + */ + public static String lowerCase(final String str) { + if (str == null) { + return null; + } + return str.toLowerCase(); + } + + /** + *

Converts a String to lower case as per {@link String#toLowerCase(Locale)}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.lowerCase(null, Locale.ENGLISH)  = null
+     * StringUtils.lowerCase("", Locale.ENGLISH)    = ""
+     * StringUtils.lowerCase("aBc", Locale.ENGLISH) = "abc"
+     * 
+ * + * @param str the String to lower case, may be null + * @param locale the locale that defines the case transformation rules, must not be null + * @return the lower cased String, {@code null} if null String input + * @since 2.5 + */ + public static String lowerCase(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)) { + prefix++; + } else { + break; + } + } + return new int[] { matches, transpositions / 2, prefix, max.length() }; + } + + /** + *

Gets {@code len} characters from the middle of a String.

+ * + *

If {@code len} characters are not available, the remainder + * of the String will be returned without an exception. If the + * String is {@code null}, {@code null} will be returned. + * An empty String is returned if len is negative or exceeds the + * length of {@code str}.

+ * + *
+     * StringUtils.mid(null, *, *)    = null
+     * StringUtils.mid(*, *, -ve)     = ""
+     * StringUtils.mid("", 0, *)      = ""
+     * StringUtils.mid("abc", 0, 2)   = "ab"
+     * StringUtils.mid("abc", 0, 4)   = "abc"
+     * StringUtils.mid("abc", 2, 4)   = "c"
+     * StringUtils.mid("abc", 4, 2)   = ""
+     * StringUtils.mid("abc", -2, 2)  = "ab"
+     * 
+ * + * @param str the String to get the characters from, may be null + * @param pos the position to start from, negative treated as zero + * @param len the length of the required String + * @return the middle characters, {@code null} if null String input + */ + public static String mid(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); + } + + private static StringJoiner newStringJoiner(final char delimiter) { + return new StringJoiner(String.valueOf(delimiter)); + } + + /** + *

+ * Similar to http://www.w3.org/TR/xpath/#function-normalize + * -space + *

+ *

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

+ * In XML Whitespace characters are the same as those allowed by the S production, which is S ::= (#x20 | #x9 | #xD | #xA)+ + *

+ * Java's regexp pattern \s defines whitespace as [ \t\n\x0B\f\r] + * + *

For reference:

+ *
    + *
  • \x0B = vertical tab
  • + *
  • \f = #xC = form feed
  • + *
  • #x20 = space
  • + *
  • #x9 = \t
  • + *
  • #xA = \n
  • + *
  • #xD = \r
  • + *
+ * + *

+ * The difference is that Java's whitespace includes vertical tab and form feed, which this functional will also + * normalize. Additionally {@code {@link #trim(String)}} removes control characters (char <= 32) from both + * ends of this String. + *

+ * + * @see Pattern + * @see #trim(String) + * @see http://www.w3.org/TR/xpath/#function-normalize-space + * @param str the source String to normalize whitespaces from, may be null + * @return the modified string with whitespace normalized, {@code null} if null String input + * + * @since 3.0 + */ + public static String normalizeSpace(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.

+ *

Note: 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.

+ *

A {@code null} CharSequence will return {@code -1}.

+ * + *
+     * StringUtils.ordinalIndexOf(null, *, *)          = -1
+     * StringUtils.ordinalIndexOf(*, null, *)          = -1
+     * StringUtils.ordinalIndexOf("", "", *)           = 0
+     * StringUtils.ordinalIndexOf("aabaabaa", "a", 1)  = 0
+     * StringUtils.ordinalIndexOf("aabaabaa", "a", 2)  = 1
+     * StringUtils.ordinalIndexOf("aabaabaa", "b", 1)  = 2
+     * StringUtils.ordinalIndexOf("aabaabaa", "b", 2)  = 5
+     * StringUtils.ordinalIndexOf("aabaabaa", "ab", 1) = 1
+     * StringUtils.ordinalIndexOf("aabaabaa", "ab", 2) = 4
+     * StringUtils.ordinalIndexOf("aabaabaa", "", 1)   = 0
+     * StringUtils.ordinalIndexOf("aabaabaa", "", 2)   = 0
+     * 
+ * + *

Matches may overlap:

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

Note that 'head(CharSequence str, int n)' may be implemented as:

+ * + *
+     *   str.substring(0, lastOrdinalIndexOf(str, "\n", n))
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param ordinal the n-th {@code searchStr} to find + * @return the n-th index of the search CharSequence, + * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input + * @since 2.1 + * @since 3.0 Changed signature from ordinalIndexOf(String, String, int) to ordinalIndexOf(CharSequence, CharSequence, int) + */ + public static int ordinalIndexOf(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.

+ *

Note that matches may overlap

+ * + *

A {@code null} CharSequence will return {@code -1}.

+ * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param ordinal the n-th {@code searchStr} to find, 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 thru 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.

+ * + *

A {@code null} string input returns {@code null}. + * A negative index is treated as zero. + * An index greater than the string length is treated as the string length. + * The start index is always the smaller of the two indices.

+ * + *
+     * StringUtils.overlay(null, *, *, *)            = null
+     * StringUtils.overlay("", "abc", 0, 0)          = "abc"
+     * StringUtils.overlay("abcdef", null, 2, 4)     = "abef"
+     * StringUtils.overlay("abcdef", "", 2, 4)       = "abef"
+     * StringUtils.overlay("abcdef", "", 4, 2)       = "abef"
+     * StringUtils.overlay("abcdef", "zzzz", 2, 4)   = "abzzzzef"
+     * StringUtils.overlay("abcdef", "zzzz", 4, 2)   = "abzzzzef"
+     * StringUtils.overlay("abcdef", "zzzz", -1, 4)  = "zzzzef"
+     * StringUtils.overlay("abcdef", "zzzz", 2, 8)   = "abzzzz"
+     * StringUtils.overlay("abcdef", "zzzz", -2, -3) = "zzzzabcdef"
+     * StringUtils.overlay("abcdef", "zzzz", 8, 10)  = "abcdefzzzz"
+     * 
+ * + * @param str the String to do overlaying in, may be null + * @param overlay the String to overlay, may be null + * @param start the position to start overlaying at + * @param end the position to stop overlaying before + * @return overlayed String, {@code null} if null String input + * @since 2.0 + */ + public static String overlay(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. + * + *
+     * 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"
+     * 
+ *

With additional prefixes,

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

With additional prefixes,

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

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string.

+ * + *
+     * StringUtils.remove(null, *)       = null
+     * StringUtils.remove("", *)         = ""
+     * StringUtils.remove("queued", 'u') = "qeed"
+     * StringUtils.remove("queued", 'z') = "queued"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the char to search for and remove, may be null + * @return the substring with the char removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String remove(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.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} remove string will return the source string. + * An empty ("") remove string will return the source string.

+ * + *
+     * StringUtils.remove(null, *)        = null
+     * StringUtils.remove("", *)          = ""
+     * StringUtils.remove(*, null)        = *
+     * StringUtils.remove(*, "")          = *
+     * StringUtils.remove("queued", "ue") = "qd"
+     * StringUtils.remove("queued", "zz") = "queued"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String remove(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: + *
    + *
  • {@code text.replaceAll(regex, StringUtils.EMPTY)}
  • + *
  • {@code Pattern.compile(regex).matcher(text).replaceAll(StringUtils.EMPTY)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *

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.

+ * + *
+     * 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<__>\n<__>B", "<.*>")      = "A\nB"
+     * StringUtils.removeAll("A<__>\n<__>B", "(?s)<.*>")  = "AB"
+     * StringUtils.removeAll("ABCabc123abc", "[a-z]")     = "ABC123"
+     * 
+ * + * @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.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

+ * + *
+     * StringUtils.removeEnd(null, *)      = null
+     * StringUtils.removeEnd("", *)        = ""
+     * StringUtils.removeEnd(*, null)      = *
+     * StringUtils.removeEnd("www.domain.com", ".com.")  = "www.domain.com"
+     * StringUtils.removeEnd("www.domain.com", ".com")   = "www.domain"
+     * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com"
+     * StringUtils.removeEnd("abc", "")    = "abc"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String removeEnd(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.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

+ * + *
+     * StringUtils.removeEndIgnoreCase(null, *)      = null
+     * StringUtils.removeEndIgnoreCase("", *)        = ""
+     * StringUtils.removeEndIgnoreCase(*, null)      = *
+     * StringUtils.removeEndIgnoreCase("www.domain.com", ".com.")  = "www.domain.com"
+     * StringUtils.removeEndIgnoreCase("www.domain.com", ".com")   = "www.domain"
+     * StringUtils.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com"
+     * StringUtils.removeEndIgnoreCase("abc", "")    = "abc"
+     * StringUtils.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain")
+     * StringUtils.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain")
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the String to search for (case insensitive) and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.4 + */ + public static String removeEndIgnoreCase(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: + *
    + *
  • {@code text.replaceFirst(regex, StringUtils.EMPTY)}
  • + *
  • {@code Pattern.compile(regex).matcher(text).replaceFirst(StringUtils.EMPTY)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *

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.

+ * + *
+     * 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<__>\n<__>B", "<.*>")      = "A\n<__>B"
+     * StringUtils.removeFirst("A<__>\n<__>B", "(?s)<.*>")  = "AB"
+     * StringUtils.removeFirst("ABCabc123", "[a-z]")          = "ABCbc123"
+     * StringUtils.removeFirst("ABCabc123abc", "[a-z]+")      = "ABC123abc"
+     * 
+ * + * @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. + *

+ * + *

+ * A {@code null} source string will return {@code null}. An empty ("") + * source string will return the empty string. A {@code null} remove string + * will return the source string. An empty ("") remove string will return + * the source string. + *

+ * + *
+     * StringUtils.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"
+     * 
+ * + * @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: + *
    + *
  • {@code source.replaceAll("(?s)" + regex, StringUtils.EMPTY)}
  • + *
  • {@code Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(StringUtils.EMPTY)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * StringUtils.removePattern(null, *)       = null
+     * StringUtils.removePattern("any", (String) null)   = "any"
+     * StringUtils.removePattern("A<__>\n<__>B", "<.*>")  = "AB"
+     * StringUtils.removePattern("ABCabc123", "[a-z]")    = "ABC123"
+     * 
+ * + * @param source + * the source string + * @param regex + * the regular expression to which this string is to be matched + * @return The resulting {@code 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 substring only if it is at the beginning of a source string, + * otherwise returns the source string.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

+ * + *
+     * StringUtils.removeStart(null, *)      = null
+     * StringUtils.removeStart("", *)        = ""
+     * StringUtils.removeStart(*, null)      = *
+     * StringUtils.removeStart("www.domain.com", "www.")   = "domain.com"
+     * StringUtils.removeStart("domain.com", "www.")       = "domain.com"
+     * StringUtils.removeStart("www.domain.com", "domain") = "www.domain.com"
+     * StringUtils.removeStart("abc", "")    = "abc"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String removeStart(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.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

+ * + *
+     * StringUtils.removeStartIgnoreCase(null, *)      = null
+     * StringUtils.removeStartIgnoreCase("", *)        = ""
+     * StringUtils.removeStartIgnoreCase(*, null)      = *
+     * StringUtils.removeStartIgnoreCase("www.domain.com", "www.")   = "domain.com"
+     * StringUtils.removeStartIgnoreCase("www.domain.com", "WWW.")   = "domain.com"
+     * StringUtils.removeStartIgnoreCase("domain.com", "www.")       = "domain.com"
+     * StringUtils.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com"
+     * StringUtils.removeStartIgnoreCase("abc", "")    = "abc"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the String to search for (case insensitive) and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.4 + */ + public static String removeStartIgnoreCase(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.

+ * + *
+     * StringUtils.repeat('e', 0)  = ""
+     * StringUtils.repeat('e', 3)  = "eee"
+     * StringUtils.repeat('e', -2) = ""
+     * 
+ * + *

Note: this method does not support padding with + * Unicode Supplementary Characters + * as they require a pair of {@code char}s to be represented. + * If you are needing to support full I18N of your applications + * consider using {@link #repeat(String, int)} instead. + *

+ * + * @param ch character to repeat + * @param repeat number of times to repeat char, negative treated as zero + * @return String with repeated character + * @see #repeat(String, int) + */ + public static String repeat(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.

+ * + *
+     * StringUtils.repeat(null, 2) = null
+     * StringUtils.repeat("", 0)   = ""
+     * StringUtils.repeat("", 2)   = ""
+     * StringUtils.repeat("a", 3)  = "aaa"
+     * StringUtils.repeat("ab", 2) = "abab"
+     * StringUtils.repeat("a", -2) = ""
+     * 
+ * + * @param str the String to repeat, may be null + * @param repeat number of times to repeat str, negative treated as zero + * @return a new String consisting of the original String repeated, + * {@code null} if null String input + */ + public static String repeat(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.

+ * + *
+     * StringUtils.repeat(null, null, 2) = null
+     * StringUtils.repeat(null, "x", 2)  = null
+     * StringUtils.repeat("", null, 0)   = ""
+     * StringUtils.repeat("", "", 2)     = ""
+     * StringUtils.repeat("", "x", 3)    = "xxx"
+     * StringUtils.repeat("?", ", ", 3)  = "?, ?, ?"
+     * 
+ * + * @param str the String to repeat, may be null + * @param separator the String to inject, may be null + * @param repeat number of times to repeat str, negative treated as zero + * @return a new String consisting of the original String repeated, + * {@code null} if null String input + * @since 2.5 + */ + public static String repeat(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.

+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * StringUtils.replace(null, *, *)        = null
+     * StringUtils.replace("", *, *)          = ""
+     * StringUtils.replace("any", null, *)    = "any"
+     * StringUtils.replace("any", *, null)    = "any"
+     * StringUtils.replace("any", "", *)      = "any"
+     * StringUtils.replace("aba", "a", null)  = "aba"
+     * StringUtils.replace("aba", "a", "")    = "b"
+     * StringUtils.replace("aba", "a", "z")   = "zbz"
+     * 
+ * + * @see #replace(String text, String searchString, String replacement, int max) + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace it with, may be null + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replace(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.

+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * StringUtils.replace(null, *, *, *)         = null
+     * StringUtils.replace("", *, *, *)           = ""
+     * StringUtils.replace("any", null, *, *)     = "any"
+     * StringUtils.replace("any", *, null, *)     = "any"
+     * StringUtils.replace("any", "", *, *)       = "any"
+     * StringUtils.replace("any", *, *, 0)        = "any"
+     * StringUtils.replace("abaa", "a", null, -1) = "abaa"
+     * StringUtils.replace("abaa", "a", "", -1)   = "b"
+     * StringUtils.replace("abaa", "a", "z", 0)   = "abaa"
+     * StringUtils.replace("abaa", "a", "z", 1)   = "zbaa"
+     * StringUtils.replace("abaa", "a", "z", 2)   = "zbza"
+     * StringUtils.replace("abaa", "a", "z", -1)  = "zbzz"
+     * 
+ * + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace it with, may be null + * @param max maximum number of values to replace, or {@code -1} if no maximum + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replace(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.

+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * 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"
+     * 
+ * + * @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: + *
    + *
  • {@code text.replaceAll(regex, replacement)}
  • + *
  • {@code Pattern.compile(regex).matcher(text).replaceAll(replacement)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *

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.

+ * + *
+     * 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("<__>\n<__>", "<.*>", "z")      = "z\nz"
+     * StringUtils.replaceAll("<__>\n<__>", "(?s)<.*>", "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"
+     * 
+ * + * @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)}.

+ * + *

A {@code null} string input returns {@code null}. + * An empty ("") string input returns an empty string.

+ * + *
+     * StringUtils.replaceChars(null, *, *)        = null
+     * StringUtils.replaceChars("", *, *)          = ""
+     * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
+     * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
+     * 
+ * + * @param str String to replace characters in, may be null + * @param searchChar the character to search for, may be null + * @param replaceChar the character to replace, may be null + * @return modified String, {@code null} if null string input + * @since 2.0 + */ + public static String replaceChars(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.

+ * + *

For example:
+ * {@code replaceChars("hello", "ho", "jy") = jelly}.

+ * + *

A {@code null} string input returns {@code null}. + * An empty ("") string input returns an empty string. + * A null or empty set of search characters returns the input string.

+ * + *

The length of the search characters should normally equal the length + * of the replace characters. + * If the search characters is longer, then the extra search characters + * are deleted. + * If the search characters is shorter, then the extra replace characters + * are ignored.

+ * + *
+     * StringUtils.replaceChars(null, *, *)           = null
+     * StringUtils.replaceChars("", *, *)             = ""
+     * StringUtils.replaceChars("abc", null, *)       = "abc"
+     * StringUtils.replaceChars("abc", "", *)         = "abc"
+     * StringUtils.replaceChars("abc", "b", null)     = "ac"
+     * StringUtils.replaceChars("abc", "b", "")       = "ac"
+     * StringUtils.replaceChars("abcba", "bc", "yz")  = "ayzya"
+     * StringUtils.replaceChars("abcba", "bc", "y")   = "ayya"
+     * StringUtils.replaceChars("abcba", "bc", "yzx") = "ayzya"
+     * 
+ * + * @param str String to replace characters in, may be null + * @param searchChars a set of characters to search for, may be null + * @param replaceChars a set of characters to replace, may be null + * @return modified String, {@code null} if null string input + * @since 2.0 + */ + public static String replaceChars(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. + *

+ * + *

+ * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. This will not repeat. For repeating replaces, call the + * overloaded method. + *

+ * + *
+     *  StringUtils.replaceEach(null, *, *)        = null
+     *  StringUtils.replaceEach("", *, *)          = ""
+     *  StringUtils.replaceEach("aba", null, null) = "aba"
+     *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
+     *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
+     *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
+     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
+     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
+     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
+     *  (example of how it does not repeat)
+     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
+     * 
+ * + * @param text + * text to search and replace in, no-op if null + * @param searchList + * the Strings to search for, no-op if null + * @param replacementList + * the Strings to replace them with, no-op if null + * @return the text with any replacements processed, {@code null} if + * null String input + * @throws IllegalArgumentException + * if the lengths of the arrays are not the same (null is ok, + * and/or size 0) + * @since 2.4 + */ + public static String replaceEach(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[])} + *

+ * + *

+ * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. + *

+ * + *
+     *  StringUtils.replaceEach(null, *, *, *, *) = null
+     *  StringUtils.replaceEach("", *, *, *, *) = ""
+     *  StringUtils.replaceEach("aba", null, null, *, *) = "aba"
+     *  StringUtils.replaceEach("aba", new String[0], null, *, *) = "aba"
+     *  StringUtils.replaceEach("aba", null, new String[0], *, *) = "aba"
+     *  StringUtils.replaceEach("aba", new String[]{"a"}, null, *, *) = "aba"
+     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *, >=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
+     * 
+ * + * @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 searchSet = new HashSet<>(Arrays.asList(searchList)); + final Set 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 = -1; + + // index of replace array that will replace the search string found + // NOTE: logic duplicated below START + for (int i = 0; i < searchLength; i++) { + if (noMoreMatchesForReplIndex[i] || 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] || searchList[i] == null || + searchList[i].isEmpty() || 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. + *

+ * + *

+ * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. + *

+ * + *
+     *  StringUtils.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
+     * 
+ * + * @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) { + // timeToLive should be 0 if not used or nothing to replace, else it's + // the length of the replace array + final int timeToLive = searchList == null ? 0 : searchList.length; + return replaceEach(text, searchList, replacementList, true, timeToLive); + } + + /** + *

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: + *
    + *
  • {@code text.replaceFirst(regex, replacement)}
  • + *
  • {@code Pattern.compile(regex).matcher(text).replaceFirst(replacement)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *

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.

+ * + *
+     * 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("<__>\n<__>", "<.*>", "z")      = "z\n<__>"
+     * StringUtils.replaceFirst("<__>\n<__>", "(?s)<.*>", "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"
+     * 
+ * + * @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.

+ * + *

A {@code null} reference passed to this method is a no-op.

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

+ * + *

A {@code null} reference passed to this method is a no-op.

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

+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * StringUtils.replaceOnce(null, *, *)        = null
+     * StringUtils.replaceOnce("", *, *)          = ""
+     * StringUtils.replaceOnce("any", null, *)    = "any"
+     * StringUtils.replaceOnce("any", *, null)    = "any"
+     * StringUtils.replaceOnce("any", "", *)      = "any"
+     * StringUtils.replaceOnce("aba", "a", null)  = "aba"
+     * StringUtils.replaceOnce("aba", "a", "")    = "ba"
+     * StringUtils.replaceOnce("aba", "a", "z")   = "zba"
+     * 
+ * + * @see #replace(String text, String searchString, String replacement, int max) + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace with, may be null + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replaceOnce(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.

+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * 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"
+     * 
+ * + * @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: + *
    + *
  • {@code source.replaceAll("(?s)" + regex, replacement)}
  • + *
  • {@code Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement)}
  • + *
+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * 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("<__>\n<__>", "<.*>", "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"
+     * 
+ * + * @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 {@code 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()}.

+ * + *

A {@code null} String returns {@code null}.

+ * + *
+     * StringUtils.reverse(null)  = null
+     * StringUtils.reverse("")    = ""
+     * StringUtils.reverse("bat") = "tab"
+     * 
+ * + * @param str the String to reverse, may be null + * @return the reversed String, {@code null} if null String input + */ + public static String reverse(final String str) { + if (str == null) { + return null; + } + return new StringBuilder(str).reverse().toString(); + } + + /** + *

Reverses a String that is delimited by a specific character.

+ * + *

The Strings between the delimiters are not reversed. + * Thus java.lang.String becomes String.lang.java (if the delimiter + * is {@code '.'}).

+ * + *
+     * StringUtils.reverseDelimited(null, *)      = null
+     * StringUtils.reverseDelimited("", *)        = ""
+     * StringUtils.reverseDelimited("a.b.c", 'x') = "a.b.c"
+     * StringUtils.reverseDelimited("a.b.c", ".") = "c.b.a"
+     * 
+ * + * @param str the String to reverse, may be null + * @param separatorChar the separator character to use + * @return the reversed String, {@code null} if null String input + * @since 2.0 + */ + public static String reverseDelimited(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.

+ * + *

If {@code len} characters are not available, or the String + * is {@code null}, the String will be returned without an + * an exception. An empty String is returned if len is negative.

+ * + *
+     * StringUtils.right(null, *)    = null
+     * StringUtils.right(*, -ve)     = ""
+     * StringUtils.right("", *)      = ""
+     * StringUtils.right("abc", 0)   = ""
+     * StringUtils.right("abc", 2)   = "bc"
+     * StringUtils.right("abc", 4)   = "abc"
+     * 
+ * + * @param str the String to get the rightmost characters from, may be null + * @param len the length of the required String + * @return the rightmost characters, {@code null} if null String input + */ + public static String right(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 (' ').

+ * + *

The String is padded to the size of {@code size}.

+ * + *
+     * StringUtils.rightPad(null, *)   = null
+     * StringUtils.rightPad("", 3)     = "   "
+     * StringUtils.rightPad("bat", 3)  = "bat"
+     * StringUtils.rightPad("bat", 5)  = "bat  "
+     * StringUtils.rightPad("bat", 1)  = "bat"
+     * StringUtils.rightPad("bat", -1) = "bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String rightPad(final String str, final int size) { + return rightPad(str, size, ' '); + } + + /** + *

Right pad a String with a specified character.

+ * + *

The String is padded to the size of {@code size}.

+ * + *
+     * StringUtils.rightPad(null, *, *)     = null
+     * StringUtils.rightPad("", 3, 'z')     = "zzz"
+     * StringUtils.rightPad("bat", 3, 'z')  = "bat"
+     * StringUtils.rightPad("bat", 5, 'z')  = "batzz"
+     * StringUtils.rightPad("bat", 1, 'z')  = "bat"
+     * StringUtils.rightPad("bat", -1, 'z') = "bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padChar the character to pad with + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + * @since 2.0 + */ + public static String rightPad(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.

+ * + *

The String is padded to the size of {@code size}.

+ * + *
+     * StringUtils.rightPad(null, *, *)      = null
+     * StringUtils.rightPad("", 3, "z")      = "zzz"
+     * StringUtils.rightPad("bat", 3, "yz")  = "bat"
+     * StringUtils.rightPad("bat", 5, "yz")  = "batyz"
+     * StringUtils.rightPad("bat", 8, "yz")  = "batyzyzy"
+     * StringUtils.rightPad("bat", 1, "yz")  = "bat"
+     * StringUtils.rightPad("bat", -1, "yz") = "bat"
+     * StringUtils.rightPad("bat", 5, null)  = "bat  "
+     * StringUtils.rightPad("bat", 5, "")    = "bat  "
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padStr the String to pad with, null or empty treated as single space + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String rightPad(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); + } else if (pads < padLen) { + return str.concat(padStr.substring(0, pads)); + } else { + 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.

+ *
    + *
  • If {@code shift > 0}, right circular shift (ex : ABCDEF => FABCDE)
  • + *
  • If {@code shift < 0}, left circular shift (ex : ABCDEF => BCDEFA)
  • + *
+ * + *
+     * 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"
+     * 
+ * + * @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)}.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as one separator. + * For more control over the split use the StrTokenizer class.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.split(null)       = null
+     * StringUtils.split("")         = []
+     * StringUtils.split("abc def")  = ["abc", "def"]
+     * StringUtils.split("abc  def") = ["abc", "def"]
+     * StringUtils.split(" abc ")    = ["abc"]
+     * 
+ * + * @param str the String to parse, may be null + * @return an array of parsed Strings, {@code null} if null String input + */ + public static String[] split(final String str) { + return split(str, null, -1); + } + + /** + *

Splits the provided text into an array, separator specified. + * This is an alternative to using StringTokenizer.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as one separator. + * For more control over the split use the StrTokenizer class.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.split(null, *)         = null
+     * StringUtils.split("", *)           = []
+     * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
+     * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
+     * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
+     * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
+     * 
+ * + * @param str the String to parse, may be null + * @param separatorChar the character used as the delimiter + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.0 + */ + public static String[] split(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.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as one separator. + * For more control over the split use the StrTokenizer class.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

+ * + *
+     * StringUtils.split(null, *)         = null
+     * StringUtils.split("", *)           = []
+     * StringUtils.split("abc def", null) = ["abc", "def"]
+     * StringUtils.split("abc def", " ")  = ["abc", "def"]
+     * StringUtils.split("abc  def", " ") = ["abc", "def"]
+     * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+     * 
+ * + * @param str the String to parse, may be null + * @param separatorChars the characters used as the delimiters, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String input + */ + public static String[] split(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.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as one separator.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

+ * + *

If more than {@code max} delimited substrings are found, the last + * returned string includes all characters after the first {@code max - 1} + * returned strings (including separator characters).

+ * + *
+     * StringUtils.split(null, *, *)            = null
+     * StringUtils.split("", *, *)              = []
+     * StringUtils.split("ab 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"]
+     * 
+ * + * @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. + *

+     * StringUtils.splitByCharacterType(null)         = null
+     * StringUtils.splitByCharacterType("")           = []
+     * StringUtils.splitByCharacterType("ab de fg")   = ["ab", " ", "de", " ", "fg"]
+     * StringUtils.splitByCharacterType("ab   de fg") = ["ab", "   ", "de", " ", "fg"]
+     * StringUtils.splitByCharacterType("ab:cd:ef")   = ["ab", ":", "cd", ":", "ef"]
+     * StringUtils.splitByCharacterType("number5")    = ["number", "5"]
+     * StringUtils.splitByCharacterType("fooBar")     = ["foo", "B", "ar"]
+     * StringUtils.splitByCharacterType("foo200Bar")  = ["foo", "200", "B", "ar"]
+     * StringUtils.splitByCharacterType("ASFRules")   = ["ASFR", "ules"]
+     * 
+ * @param str the String to split, may be {@code null} + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.4 + */ + public static String[] splitByCharacterType(final String str) { + return splitByCharacterType(str, false); + } + + /** + *

Splits a String by Character type as returned by + * {@code java.lang.Character.getType(char)}. Groups of contiguous + * characters of the same type are returned as complete tokens, with the + * following exception: 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 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); + } + + /** + *

Splits a String by Character type as returned by + * {@code java.lang.Character.getType(char)}. Groups of contiguous + * characters of the same type are returned as complete tokens, with the + * following exception: the character of type + * {@code Character.UPPERCASE_LETTER}, if any, immediately + * preceding a token of type {@code Character.LOWERCASE_LETTER} + * will belong to the following token rather than to the preceding, if any, + * {@code Character.UPPERCASE_LETTER} token. + *

+     * StringUtils.splitByCharacterTypeCamelCase(null)         = null
+     * StringUtils.splitByCharacterTypeCamelCase("")           = []
+     * StringUtils.splitByCharacterTypeCamelCase("ab de fg")   = ["ab", " ", "de", " ", "fg"]
+     * StringUtils.splitByCharacterTypeCamelCase("ab   de fg") = ["ab", "   ", "de", " ", "fg"]
+     * StringUtils.splitByCharacterTypeCamelCase("ab:cd:ef")   = ["ab", ":", "cd", ":", "ef"]
+     * StringUtils.splitByCharacterTypeCamelCase("number5")    = ["number", "5"]
+     * StringUtils.splitByCharacterTypeCamelCase("fooBar")     = ["foo", "Bar"]
+     * StringUtils.splitByCharacterTypeCamelCase("foo200Bar")  = ["foo", "200", "Bar"]
+     * StringUtils.splitByCharacterTypeCamelCase("ASFRules")   = ["ASF", "Rules"]
+     * 
+ * @param str the String to split, may be {@code null} + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.4 + */ + public static String[] splitByCharacterTypeCamelCase(final String str) { + return splitByCharacterType(str, true); + } + + /** + *

Splits the provided text into an array, separator string specified.

+ * + *

The separator(s) will not be included in the returned String array. + * Adjacent separators are treated as one separator.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

+ * + *
+     * StringUtils.splitByWholeSeparator(null, *)               = null
+     * StringUtils.splitByWholeSeparator("", *)                 = []
+     * StringUtils.splitByWholeSeparator("ab de fg", null)      = ["ab", "de", "fg"]
+     * StringUtils.splitByWholeSeparator("ab   de fg", null)    = ["ab", "de", "fg"]
+     * StringUtils.splitByWholeSeparator("ab:cd:ef", ":")       = ["ab", "cd", "ef"]
+     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
+     * 
+ * + * @param str the String to parse, may be null + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String was input + */ + public static String[] splitByWholeSeparator(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.

+ * + *

The separator(s) will not be included in the returned String array. + * Adjacent separators are treated as one separator.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

+ * + *
+     * StringUtils.splitByWholeSeparator(null, *, *)               = null
+     * StringUtils.splitByWholeSeparator("", *, *)                 = []
+     * StringUtils.splitByWholeSeparator("ab de fg", null, 0)      = ["ab", "de", "fg"]
+     * StringUtils.splitByWholeSeparator("ab   de fg", null, 0)    = ["ab", "de", "fg"]
+     * StringUtils.splitByWholeSeparator("ab:cd:ef", ":", 2)       = ["ab", "cd:ef"]
+     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
+     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
+     * 
+ * + * @param str the String to parse, may be null + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @param max the maximum number of elements to include in the returned + * array. A zero or negative value implies no limit. + * @return an array of parsed Strings, {@code null} if null String was input + */ + public static String[] splitByWholeSeparator( 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.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

+ * + *
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens(null, *)               = null
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("", *)                 = []
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null)      = ["ab", "de", "fg"]
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab   de fg", null)    = ["ab", "", "", "de", "fg"]
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab:cd:ef", ":")       = ["ab", "cd", "ef"]
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
+     * 
+ * + * @param str the String to parse, may be null + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String was input + * @since 2.4 + */ + public static String[] splitByWholeSeparatorPreserveAllTokens(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.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

+ * + *
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens(null, *, *)               = null
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("", *, *)                 = []
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null, 0)      = ["ab", "de", "fg"]
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab   de fg", null, 0)    = ["ab", "", "", "de", "fg"]
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab:cd:ef", ":", 2)       = ["ab", "cd:ef"]
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
+     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
+     * 
+ * + * @param str the String to parse, may be null + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @param max the maximum number of elements to include in the returned + * array. A zero or negative value implies no limit. + * @return an array of parsed Strings, {@code null} if null String was input + * @since 2.4 + */ + public static String[] splitByWholeSeparatorPreserveAllTokens(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 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)}.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.splitPreserveAllTokens(null)       = null
+     * StringUtils.splitPreserveAllTokens("")         = []
+     * StringUtils.splitPreserveAllTokens("abc def")  = ["abc", "def"]
+     * StringUtils.splitPreserveAllTokens("abc  def") = ["abc", "", "def"]
+     * StringUtils.splitPreserveAllTokens(" abc ")    = ["", "abc", ""]
+     * 
+ * + * @param str the String to parse, may be {@code null} + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.1 + */ + public static String[] splitPreserveAllTokens(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.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.splitPreserveAllTokens(null, *)         = null
+     * StringUtils.splitPreserveAllTokens("", *)           = []
+     * StringUtils.splitPreserveAllTokens("a.b.c", '.')    = ["a", "b", "c"]
+     * StringUtils.splitPreserveAllTokens("a..b.c", '.')   = ["a", "", "b", "c"]
+     * StringUtils.splitPreserveAllTokens("a:b:c", '.')    = ["a:b:c"]
+     * StringUtils.splitPreserveAllTokens("a\tb\nc", null) = ["a", "b", "c"]
+     * StringUtils.splitPreserveAllTokens("a b c", ' ')    = ["a", "b", "c"]
+     * StringUtils.splitPreserveAllTokens("a b c ", ' ')   = ["a", "b", "c", ""]
+     * StringUtils.splitPreserveAllTokens("a b c  ", ' ')   = ["a", "b", "c", "", ""]
+     * StringUtils.splitPreserveAllTokens(" a b c", ' ')   = ["", a", "b", "c"]
+     * StringUtils.splitPreserveAllTokens("  a b c", ' ')  = ["", "", a", "b", "c"]
+     * StringUtils.splitPreserveAllTokens(" a b c ", ' ')  = ["", a", "b", "c", ""]
+     * 
+ * + * @param str the String to parse, may be {@code null} + * @param separatorChar the character used as the delimiter, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.1 + */ + public static String[] splitPreserveAllTokens(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.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

+ * + *
+     * StringUtils.splitPreserveAllTokens(null, *)           = null
+     * StringUtils.splitPreserveAllTokens("", *)             = []
+     * StringUtils.splitPreserveAllTokens("abc def", null)   = ["abc", "def"]
+     * StringUtils.splitPreserveAllTokens("abc def", " ")    = ["abc", "def"]
+     * StringUtils.splitPreserveAllTokens("abc  def", " ")   = ["abc", "", def"]
+     * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":")   = ["ab", "cd", "ef"]
+     * StringUtils.splitPreserveAllTokens("ab:cd:ef:", ":")  = ["ab", "cd", "ef", ""]
+     * StringUtils.splitPreserveAllTokens("ab:cd:ef::", ":") = ["ab", "cd", "ef", "", ""]
+     * StringUtils.splitPreserveAllTokens("ab::cd:ef", ":")  = ["ab", "", cd", "ef"]
+     * StringUtils.splitPreserveAllTokens(":cd:ef", ":")     = ["", cd", "ef"]
+     * StringUtils.splitPreserveAllTokens("::cd:ef", ":")    = ["", "", cd", "ef"]
+     * StringUtils.splitPreserveAllTokens(":cd:ef:", ":")    = ["", cd", "ef", ""]
+     * 
+ * + * @param str the String to parse, may be {@code null} + * @param separatorChars the characters used as the delimiters, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.1 + */ + public static String[] splitPreserveAllTokens(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.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * Adjacent separators are treated as one separator.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

+ * + *

If more than {@code max} delimited substrings are found, the last + * returned string includes all characters after the first {@code max - 1} + * returned strings (including separator characters).

+ * + *
+     * StringUtils.splitPreserveAllTokens(null, *, *)            = null
+     * StringUtils.splitPreserveAllTokens("", *, *)              = []
+     * StringUtils.splitPreserveAllTokens("ab de fg", null, 0)   = ["ab", "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"]
+     * 
+ * + * @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 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 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.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

+ * + *
+     * StringUtils.startsWith(null, null)      = true
+     * StringUtils.startsWith(null, "abc")     = false
+     * StringUtils.startsWith("abcdef", null)  = false
+     * StringUtils.startsWith("abcdef", "abc") = true
+     * StringUtils.startsWith("ABCDEF", "abc") = false
+     * 
+ * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @return {@code true} if the CharSequence starts with the prefix, case sensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from startsWith(String, String) to startsWith(CharSequence, CharSequence) + */ + public static boolean startsWith(final CharSequence str, final CharSequence prefix) { + return startsWith(str, prefix, false); + } + + /** + *

Check if a CharSequence starts with a specified prefix (optionally case insensitive).

+ * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @param ignoreCase indicates whether the compare should ignore case + * (case insensitive) or not. + * @return {@code true} if the CharSequence starts with the prefix or + * both {@code null} + */ + private static boolean startsWith(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.

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

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case insensitive.

+ * + *
+     * StringUtils.startsWithIgnoreCase(null, null)      = true
+     * StringUtils.startsWithIgnoreCase(null, "abc")     = false
+     * StringUtils.startsWithIgnoreCase("abcdef", null)  = false
+     * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
+     * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
+     * 
+ * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @return {@code true} if the CharSequence starts with the prefix, case insensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from startsWithIgnoreCase(String, String) to startsWithIgnoreCase(CharSequence, CharSequence) + */ + public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix) { + return startsWith(str, prefix, true); + } + + /** + *

Strips whitespace from the start and end of a String.

+ * + *

This is similar to {@link #trim(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.strip(null)     = null
+     * StringUtils.strip("")       = ""
+     * StringUtils.strip("   ")    = ""
+     * StringUtils.strip("abc")    = "abc"
+     * StringUtils.strip("  abc")  = "abc"
+     * StringUtils.strip("abc  ")  = "abc"
+     * StringUtils.strip(" abc ")  = "abc"
+     * StringUtils.strip(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to remove whitespace from, may be null + * @return the stripped String, {@code null} if null String input + */ + public static String strip(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.

+ * + *

A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}. + * Alternatively use {@link #strip(String)}.

+ * + *
+     * StringUtils.strip(null, *)          = null
+     * StringUtils.strip("", *)            = ""
+     * StringUtils.strip("abc", null)      = "abc"
+     * StringUtils.strip("  abc", null)    = "abc"
+     * StringUtils.strip("abc  ", null)    = "abc"
+     * StringUtils.strip(" abc ", null)    = "abc"
+     * StringUtils.strip("  abcyx", "xyz") = "  abc"
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String strip(String str, final String stripChars) { + str = stripStart(str, stripChars); + return stripEnd(str, stripChars); + } + + /** + *

Removes diacritics (~= accents) from a string. The case will not be altered.

+ *

For instance, 'à' will be replaced by 'a'.

+ *

Note that ligatures will be left as is.

+ * + *
+     * StringUtils.stripAccents(null)                = null
+     * StringUtils.stripAccents("")                  = ""
+     * StringUtils.stripAccents("control")           = "control"
+     * StringUtils.stripAccents("éclair")     = "eclair"
+     * 
+ * + * @param input String to be stripped + * @return input text with diacritics removed + * + * @since 3.0 + */ + // See also Lucene's ASCIIFoldingFilter (Lucene 2.9) that replaces accented characters by their unaccented equivalent (and uncommitted bug fix: https://issues.apache.org/jira/browse/LUCENE-1343?focusedCommentId=12858907&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_12858907). + public static String stripAccents(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)}.

+ * + *

A new array is returned each time, except for length zero. + * A {@code null} array will return {@code null}. + * An empty array will return itself. + * A {@code null} array entry will be ignored.

+ * + *
+     * StringUtils.stripAll(null)             = null
+     * StringUtils.stripAll([])               = []
+     * StringUtils.stripAll(["abc", "  abc"]) = ["abc", "abc"]
+     * StringUtils.stripAll(["abc  ", null])  = ["abc", null]
+     * 
+ * + * @param strs the array to remove whitespace from, may be null + * @return the stripped Strings, {@code null} if null array input + */ + public static String[] stripAll(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.

+ *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

A new array is returned each time, except for length zero. + * A {@code null} array will return {@code null}. + * An empty array will return itself. + * A {@code null} array entry will be ignored. + * A {@code null} stripChars will strip whitespace as defined by + * {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripAll(null, *)                = null
+     * StringUtils.stripAll([], *)                  = []
+     * StringUtils.stripAll(["abc", "  abc"], null) = ["abc", "abc"]
+     * StringUtils.stripAll(["abc  ", null], null)  = ["abc", null]
+     * StringUtils.stripAll(["abc  ", null], "yz")  = ["abc  ", null]
+     * StringUtils.stripAll(["yabcz", null], "yz")  = ["abc", null]
+     * 
+ * + * @param strs the array to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped Strings, {@code null} if null array input + */ + public static String[] stripAll(final String[] strs, final String stripChars) { + final int strsLen = ArrayUtils.getLength(strs); + if (strsLen == 0) { + return strs; + } + final String[] newArr = new String[strsLen]; + for (int i = 0; i < strsLen; i++) { + newArr[i] = strip(strs[i], stripChars); + } + return newArr; + } + + /** + *

Strips any of a set of characters from the end of a String.

+ * + *

A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripEnd(null, *)          = null
+     * StringUtils.stripEnd("", *)            = ""
+     * StringUtils.stripEnd("abc", "")        = "abc"
+     * StringUtils.stripEnd("abc", null)      = "abc"
+     * StringUtils.stripEnd("  abc", null)    = "  abc"
+     * StringUtils.stripEnd("abc  ", null)    = "abc"
+     * StringUtils.stripEnd(" abc ", null)    = " abc"
+     * StringUtils.stripEnd("  abcyx", "xyz") = "  abc"
+     * StringUtils.stripEnd("120.00", ".0")   = "12"
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the set of characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String stripEnd(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.

+ * + *

A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripStart(null, *)          = null
+     * StringUtils.stripStart("", *)            = ""
+     * StringUtils.stripStart("abc", "")        = "abc"
+     * StringUtils.stripStart("abc", null)      = "abc"
+     * StringUtils.stripStart("  abc", null)    = "abc"
+     * StringUtils.stripStart("abc  ", null)    = "abc  "
+     * StringUtils.stripStart(" abc ", null)    = "abc "
+     * StringUtils.stripStart("yxabc  ", "xyz") = "abc  "
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String stripStart(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.

+ * + *

This is similar to {@link #trimToEmpty(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripToEmpty(null)     = ""
+     * StringUtils.stripToEmpty("")       = ""
+     * StringUtils.stripToEmpty("   ")    = ""
+     * StringUtils.stripToEmpty("abc")    = "abc"
+     * StringUtils.stripToEmpty("  abc")  = "abc"
+     * StringUtils.stripToEmpty("abc  ")  = "abc"
+     * StringUtils.stripToEmpty(" abc ")  = "abc"
+     * StringUtils.stripToEmpty(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to be stripped, may be null + * @return the trimmed String, or an empty String if {@code null} input + * @since 2.0 + */ + public static String stripToEmpty(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.

+ * + *

This is similar to {@link #trimToNull(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripToNull(null)     = null
+     * StringUtils.stripToNull("")       = null
+     * StringUtils.stripToNull("   ")    = null
+     * StringUtils.stripToNull("abc")    = "abc"
+     * StringUtils.stripToNull("  abc")  = "abc"
+     * StringUtils.stripToNull("abc  ")  = "abc"
+     * StringUtils.stripToNull(" abc ")  = "abc"
+     * StringUtils.stripToNull(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to be stripped, may be null + * @return the stripped String, + * {@code null} if whitespace, empty or null String input + * @since 2.0 + */ + public static String stripToNull(String str) { + if (str == null) { + return null; + } + str = strip(str, null); + return str.isEmpty() ? null : str; // NOSONARLINT str cannot be null here + } + + /** + *

Gets a substring from the specified String avoiding exceptions.

+ * + *

A negative start position can be used to start {@code n} + * characters from the end of the String.

+ * + *

A {@code null} String will return {@code null}. + * An empty ("") String will return "".

+ * + *
+     * StringUtils.substring(null, *)   = null
+     * StringUtils.substring("", *)     = ""
+     * StringUtils.substring("abc", 0)  = "abc"
+     * StringUtils.substring("abc", 2)  = "c"
+     * StringUtils.substring("abc", 4)  = ""
+     * StringUtils.substring("abc", -2) = "bc"
+     * StringUtils.substring("abc", -4) = "abc"
+     * 
+ * + * @param str the String to get the substring from, may be null + * @param start the position to start from, negative means + * count back from the end of the String by this many characters + * @return substring from start position, {@code null} if null String input + */ + public static String substring(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.

+ * + *

A negative start position can be used to start/end {@code n} + * characters from the end of the String.

+ * + *

The returned substring starts with the character in the {@code start} + * position and ends before the {@code end} position. All position counting is + * zero-based -- i.e., to start at the beginning of the string use + * {@code start = 0}. Negative start and end positions can be used to + * specify offsets relative to the end of the String.

+ * + *

If {@code start} is not strictly to the left of {@code end}, "" + * is returned.

+ * + *
+     * StringUtils.substring(null, *, *)    = null
+     * StringUtils.substring("", * ,  *)    = "";
+     * StringUtils.substring("abc", 0, 2)   = "ab"
+     * StringUtils.substring("abc", 2, 0)   = ""
+     * StringUtils.substring("abc", 2, 4)   = "c"
+     * StringUtils.substring("abc", 4, 6)   = ""
+     * StringUtils.substring("abc", 2, 2)   = ""
+     * StringUtils.substring("abc", -2, -1) = "b"
+     * StringUtils.substring("abc", -4, 2)  = "ab"
+     * 
+ * + * @param str the String to get the substring from, may be null + * @param start the position to start from, negative means + * count back from the end of the String by this many characters + * @param end the position to end at (exclusive), negative means + * count back from the end of the String by this many characters + * @return substring from start position to end position, + * {@code null} if null String input + */ + public static String substring(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.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * + *

If nothing is found, the empty string is returned.

+ * + *
+     * 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"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the character 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.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * A {@code null} separator will return the empty string if the + * input string is not {@code null}.

+ * + *

If nothing is found, the empty string is returned.

+ * + *
+     * StringUtils.substringAfter(null, *)      = null
+     * StringUtils.substringAfter("", *)        = ""
+     * StringUtils.substringAfter(*, null)      = ""
+     * StringUtils.substringAfter("abc", "a")   = "bc"
+     * StringUtils.substringAfter("abcba", "b") = "cba"
+     * StringUtils.substringAfter("abc", "c")   = ""
+     * StringUtils.substringAfter("abc", "d")   = ""
+     * StringUtils.substringAfter("abc", "")    = "abc"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring after the first occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringAfter(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.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * + *

If nothing is found, the empty string is returned.

+ * + *
+     * 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')     = ""
+     * 
+ * + * @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 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.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * An empty or {@code null} separator will return the empty string if + * the input string is not {@code null}.

+ * + *

If nothing is found, the empty string is returned.

+ * + *
+     * StringUtils.substringAfterLast(null, *)      = null
+     * StringUtils.substringAfterLast("", *)        = ""
+     * StringUtils.substringAfterLast(*, "")        = ""
+     * StringUtils.substringAfterLast(*, null)      = ""
+     * StringUtils.substringAfterLast("abc", "a")   = "bc"
+     * StringUtils.substringAfterLast("abcba", "b") = "a"
+     * StringUtils.substringAfterLast("abc", "c")   = ""
+     * StringUtils.substringAfterLast("a", "a")     = ""
+     * StringUtils.substringAfterLast("a", "z")     = ""
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring after the last occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringAfterLast(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. + *

+ * + *

+ * A {@code null} string input will return {@code null}. An empty ("") string input will return the empty string. + *

+ * + *

+ * If nothing is found, the string input is returned. + *

+ * + *
+     * StringUtils.substringBefore(null, *)      = null
+     * StringUtils.substringBefore("", *)        = ""
+     * StringUtils.substringBefore("abc", 'a')   = ""
+     * StringUtils.substringBefore("abcba", 'b') = "a"
+     * StringUtils.substringBefore("abc", 'c')   = "ab"
+     * StringUtils.substringBefore("abc", 'd')   = "abc"
+     * 
+ * + * @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 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.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * A {@code null} separator will return the input string.

+ * + *

If nothing is found, the string input is returned.

+ * + *
+     * StringUtils.substringBefore(null, *)      = null
+     * StringUtils.substringBefore("", *)        = ""
+     * StringUtils.substringBefore("abc", "a")   = ""
+     * StringUtils.substringBefore("abcba", "b") = "a"
+     * StringUtils.substringBefore("abc", "c")   = "ab"
+     * StringUtils.substringBefore("abc", "d")   = "abc"
+     * StringUtils.substringBefore("abc", "")    = ""
+     * StringUtils.substringBefore("abc", null)  = "abc"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring before the first occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringBefore(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.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * An empty or {@code null} separator will return the input string.

+ * + *

If nothing is found, the string input is returned.

+ * + *
+     * StringUtils.substringBeforeLast(null, *)      = null
+     * StringUtils.substringBeforeLast("", *)        = ""
+     * StringUtils.substringBeforeLast("abcba", "b") = "abc"
+     * StringUtils.substringBeforeLast("abc", "c")   = "ab"
+     * StringUtils.substringBeforeLast("a", "a")     = ""
+     * StringUtils.substringBeforeLast("a", "z")     = "a"
+     * StringUtils.substringBeforeLast("a", null)    = "a"
+     * StringUtils.substringBeforeLast("a", "")      = "a"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring before the last occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringBeforeLast(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.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} tag returns {@code null}.

+ * + *
+     * StringUtils.substringBetween(null, *)            = null
+     * StringUtils.substringBetween("", "")             = ""
+     * StringUtils.substringBetween("", "tag")          = null
+     * StringUtils.substringBetween("tagabctag", null)  = null
+     * StringUtils.substringBetween("tagabctag", "")    = ""
+     * StringUtils.substringBetween("tagabctag", "tag") = "abc"
+     * 
+ * + * @param str the String containing the substring, may be null + * @param tag the String before and after the substring, may be null + * @return the substring, {@code null} if no match + * @since 2.0 + */ + public static String substringBetween(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.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} open/close returns {@code null} (no match). + * An empty ("") open and close returns an empty string.

+ * + *
+     * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
+     * StringUtils.substringBetween(null, *, *)          = null
+     * StringUtils.substringBetween(*, null, *)          = null
+     * StringUtils.substringBetween(*, *, null)          = null
+     * StringUtils.substringBetween("", "", "")          = ""
+     * StringUtils.substringBetween("", "", "]")         = null
+     * StringUtils.substringBetween("", "[", "]")        = null
+     * StringUtils.substringBetween("yabcz", "", "")     = ""
+     * StringUtils.substringBetween("yabcz", "y", "z")   = "abc"
+     * StringUtils.substringBetween("yabczyabcz", "y", "z")   = "abc"
+     * 
+ * + * @param str the String containing the substring, may be null + * @param open the String before the substring, may be null + * @param close the String after the substring, may be null + * @return the substring, {@code null} if no match + * @since 2.0 + */ + public static String substringBetween(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.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} open/close returns {@code null} (no match). + * An empty ("") open/close returns {@code null} (no match).

+ * + *
+     * StringUtils.substringsBetween("[a][b][c]", "[", "]") = ["a","b","c"]
+     * StringUtils.substringsBetween(null, *, *)            = null
+     * StringUtils.substringsBetween(*, null, *)            = null
+     * StringUtils.substringsBetween(*, *, null)            = null
+     * StringUtils.substringsBetween("", "[", "]")          = []
+     * 
+ * + * @param str the String containing the substrings, null returns null, empty returns empty + * @param open the String identifying the start of the substring, empty returns null + * @param close the String identifying the end of the substring, empty returns null + * @return a String Array of substrings, or {@code null} if no match + * @since 2.3 + */ + public static String[] substringsBetween(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 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.

+ * + *
    + *
  • Upper case character converts to Lower case
  • + *
  • Title case character converts to Lower case
  • + *
  • Lower case character converts to Upper case
  • + *
+ * + *

For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#swapCase(String)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.swapCase(null)                 = null
+     * StringUtils.swapCase("")                   = ""
+     * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+     * 
+ * + *

NOTE: This method changed in Lang version 2.0. + * It no longer performs a word based algorithm. + * If you only use ASCII, you will notice no change. + * That functionality is available in org.apache.commons.lang3.text.WordUtils.

+ * + * @param str the String to swap case, may be null + * @return the changed String, {@code null} if null String input + */ + public static String swapCase(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 {@code CharSequence} into an array of code points.

+ * + *

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.

+ * + *
+     * StringUtils.toCodePoints(null)   =  null
+     * StringUtils.toCodePoints("")     =  []  // empty array
+     * 
+ * + * @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 <= 32) from both + * ends of this String, handling {@code null} by returning + * {@code null}.

+ * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #strip(String)}.

+ * + *

To trim your choice of characters, use the + * {@link #strip(String, String)} methods.

+ * + *
+     * StringUtils.trim(null)          = null
+     * StringUtils.trim("")            = ""
+     * StringUtils.trim("     ")       = ""
+     * StringUtils.trim("abc")         = "abc"
+     * StringUtils.trim("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed string, {@code null} if null String input + */ + public static String trim(final String str) { + return str == null ? null : str.trim(); + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String returning an empty String ("") if the String + * is empty ("") after the trim or if it is {@code null}. + * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #stripToEmpty(String)}.

+ * + *
+     * StringUtils.trimToEmpty(null)          = ""
+     * StringUtils.trimToEmpty("")            = ""
+     * StringUtils.trimToEmpty("     ")       = ""
+     * StringUtils.trimToEmpty("abc")         = "abc"
+     * StringUtils.trimToEmpty("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, or an empty String if {@code null} input + * @since 2.0 + */ + public static String trimToEmpty(final String str) { + return str == null ? EMPTY : str.trim(); + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String returning {@code null} if the String is + * empty ("") after the trim or if it is {@code null}. + * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #stripToNull(String)}.

+ * + *
+     * StringUtils.trimToNull(null)          = null
+     * StringUtils.trimToNull("")            = null
+     * StringUtils.trimToNull("     ")       = null
+     * StringUtils.trimToNull("abc")         = "abc"
+     * StringUtils.trimToNull("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, + * {@code null} if only chars <= 32, empty or null String input + * @since 2.0 + */ + public static String trimToNull(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".

+ * + *

Specifically:

+ *
    + *
  • If {@code str} is less than {@code maxWidth} characters + * long, return it.
  • + *
  • Else truncate it to {@code substring(str, 0, maxWidth)}.
  • + *
  • If {@code maxWidth} is less than {@code 0}, throw an + * {@code IllegalArgumentException}.
  • + *
  • In no case will it return a String of length greater than + * {@code maxWidth}.
  • + *
+ * + *
+     * 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
+     * 
+ * + * @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".

+ * + *

Works like {@code truncate(String, int)}, but allows you to specify + * a "left edge" offset. + * + *

Specifically:

+ *
    + *
  • If {@code str} is less than {@code maxWidth} characters + * long, return it.
  • + *
  • Else truncate it to {@code substring(str, offset, maxWidth)}.
  • + *
  • If {@code maxWidth} is less than {@code 0}, throw an + * {@code IllegalArgumentException}.
  • + *
  • If {@code offset} is less than {@code 0}, throw an + * {@code IllegalArgumentException}.
  • + *
  • In no case will it return a String of length greater than + * {@code maxWidth}.
  • + *
+ * + *
+     * 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
+     * 
+ * + * @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.

+ * + *

For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#uncapitalize(String)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.uncapitalize(null)  = null
+     * StringUtils.uncapitalize("")    = ""
+     * StringUtils.uncapitalize("cat") = "cat"
+     * StringUtils.uncapitalize("Cat") = "cat"
+     * StringUtils.uncapitalize("CAT") = "cAT"
+     * 
+ * + * @param str the String to uncapitalize, may be null + * @return the uncapitalized String, {@code null} if null String input + * @see org.apache.commons.lang3.text.WordUtils#uncapitalize(String) + * @see #capitalize(String) + * @since 2.0 + */ + public static String uncapitalize(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 codepoint + 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. + *

+ * + *
+     * 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#"
+     * 
+ * + * @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 anther string. + *

+ * + *
+     * 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#"
+     * 
+ * + * @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)) { + final int startIndex = str.indexOf(wrapToken); + final int endIndex = str.lastIndexOf(wrapToken); + final int wrapLength = wrapToken.length(); + + if (startIndex != -1 && endIndex != -1) { + return str.substring(startIndex + wrapLength, endIndex); + } + } + + return str; + } + + /** + *

Converts a String to upper case as per {@link String#toUpperCase()}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.upperCase(null)  = null
+     * StringUtils.upperCase("")    = ""
+     * StringUtils.upperCase("aBc") = "ABC"
+     * 
+ * + *

Note: As described in the documentation for {@link String#toUpperCase()}, + * the result of this method is affected by the current locale. + * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)} + * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).

+ * + * @param str the String to upper case, may be null + * @return the upper cased String, {@code null} if null String input + */ + public static String upperCase(final String str) { + if (str == null) { + return null; + } + return str.toUpperCase(); + } + + /** + *

Converts a String to upper case as per {@link String#toUpperCase(Locale)}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.upperCase(null, Locale.ENGLISH)  = null
+     * StringUtils.upperCase("", Locale.ENGLISH)    = ""
+     * StringUtils.upperCase("aBc", Locale.ENGLISH) = "ABC"
+     * 
+ * + * @param str the String to upper case, may be null + * @param locale the locale that defines the case transformation rules, must not be null + * @return the upper cased String, {@code null} if null String input + * @since 2.5 + */ + public static String upperCase(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. + *

+ * + *
+     * StringUtils.wrap(null, *)        = null
+     * StringUtils.wrap("", *)          = ""
+     * StringUtils.wrap("ab", '\0')     = "ab"
+     * StringUtils.wrap("ab", 'x')      = "xabx"
+     * StringUtils.wrap("ab", '\'')     = "'ab'"
+     * StringUtils.wrap("\"ab\"", '\"') = "\"\"ab\"\""
+     * 
+ * + * @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. + *

+ * + *

+ * A {@code null} input String returns {@code null}. + *

+ * + *
+     * 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'\""
+     * 
+ * + * @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. + *

+ * + *

A new {@code String} will not be created if {@code str} is already wrapped.

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

+ * + *

A new {@code String} will not be created if {@code str} is already wrapped.

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

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

+ * + *

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

+ */ + public StringUtils() { + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/SystemUtils.java b/after/src/main/java/org/apache/commons/lang3/SystemUtils.java new file mode 100644 index 0000000..2a92a8d --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/SystemUtils.java @@ -0,0 +1,1945 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3; + +import java.io.File; + +/** + *

+ * Helpers for {@code java.lang.System}. + *

+ *

+ * If a system property cannot be read due to security restrictions, the corresponding field in this class will be set + * to {@code null} and a message will be written to {@code System.err}. + *

+ *

+ * #ThreadSafe# + *

+ * + * @since 1.0 + */ +public class SystemUtils { + + /** + * The prefix String for all Windows OS. + */ + private static final String OS_NAME_WINDOWS_PREFIX = "Windows"; + + // System property constants + // ----------------------------------------------------------------------- + // These MUST be declared first. Other constants depend on this. + + /** + * The System property key for the user home directory. + */ + private static final String USER_HOME_KEY = "user.home"; + + /** + * The System property key for the user name. + */ + private static final String USER_NAME_KEY = "user.name"; + + /** + * The System property key for the user directory. + */ + private static final String USER_DIR_KEY = "user.dir"; + + /** + * The System property key for the Java IO temporary directory. + */ + private static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir"; + + /** + * The System property key for the Java home directory. + */ + private static final String JAVA_HOME_KEY = "java.home"; + + /** + *

+ * The {@code awt.toolkit} System Property. + *

+ *

+ * Holds a class name, on Windows XP this is {@code sun.awt.windows.WToolkit}. + *

+ *

+ * On platforms without a GUI, this value is {@code null}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.1 + */ + public static final String AWT_TOOLKIT = getSystemProperty("awt.toolkit"); + + /** + *

+ * The {@code file.encoding} System Property. + *

+ *

+ * File encoding, such as {@code Cp1252}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.0 + * @since Java 1.2 + */ + public static final String FILE_ENCODING = getSystemProperty("file.encoding"); + + /** + *

+ * The {@code file.separator} System Property. + * The file separator is: + *

+ *
    + *
  • {@code "/"} on UNIX
  • + *
  • {@code "\"} on Windows.
  • + *
+ * + *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @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 = getSystemProperty("file.separator"); + + /** + *

+ * The {@code java.awt.fonts} System Property. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.1 + */ + public static final String JAVA_AWT_FONTS = getSystemProperty("java.awt.fonts"); + + /** + *

+ * The {@code java.awt.graphicsenv} System Property. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.1 + */ + public static final String JAVA_AWT_GRAPHICSENV = getSystemProperty("java.awt.graphicsenv"); + + /** + *

+ * The {@code java.awt.headless} System Property. The value of this property is the String {@code "true"} or + * {@code "false"}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @see #isJavaAwtHeadless() + * @since 2.1 + * @since Java 1.4 + */ + public static final String JAVA_AWT_HEADLESS = getSystemProperty("java.awt.headless"); + + /** + *

+ * The {@code java.awt.printerjob} System Property. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.1 + */ + public static final String JAVA_AWT_PRINTERJOB = getSystemProperty("java.awt.printerjob"); + + /** + *

+ * The {@code java.class.path} System Property. Java class path. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_CLASS_PATH = getSystemProperty("java.class.path"); + + /** + *

+ * The {@code java.class.version} System Property. Java class format version number. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_CLASS_VERSION = getSystemProperty("java.class.version"); + + /** + *

+ * The {@code java.compiler} System Property. Name of JIT compiler to use. First in JDK version 1.2. Not used in Sun + * JDKs after 1.2. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2. Not used in Sun versions after 1.2. + */ + public static final String JAVA_COMPILER = getSystemProperty("java.compiler"); + + /** + *

+ * The {@code java.endorsed.dirs} System Property. Path of endorsed directory or directories. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.4 + */ + public static final String JAVA_ENDORSED_DIRS = getSystemProperty("java.endorsed.dirs"); + + /** + *

+ * The {@code java.ext.dirs} System Property. Path of extension directory or directories. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.3 + */ + public static final String JAVA_EXT_DIRS = getSystemProperty("java.ext.dirs"); + + /** + *

+ * The {@code java.home} System Property. Java installation directory. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_HOME = getSystemProperty(JAVA_HOME_KEY); + + /** + *

+ * The {@code java.io.tmpdir} System Property. Default temp file path. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_IO_TMPDIR = getSystemProperty(JAVA_IO_TMPDIR_KEY); + + /** + *

+ * The {@code java.library.path} System Property. List of paths to search when loading libraries. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_LIBRARY_PATH = getSystemProperty("java.library.path"); + + /** + *

+ * The {@code java.runtime.name} System Property. Java Runtime Environment name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.0 + * @since Java 1.3 + */ + public static final String JAVA_RUNTIME_NAME = getSystemProperty("java.runtime.name"); + + /** + *

+ * The {@code java.runtime.version} System Property. Java Runtime Environment version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.0 + * @since Java 1.3 + */ + public static final String JAVA_RUNTIME_VERSION = getSystemProperty("java.runtime.version"); + + /** + *

+ * The {@code java.specification.name} System Property. Java Runtime Environment specification name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_SPECIFICATION_NAME = getSystemProperty("java.specification.name"); + + /** + *

+ * The {@code java.specification.vendor} System Property. Java Runtime Environment specification vendor. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_SPECIFICATION_VENDOR = getSystemProperty("java.specification.vendor"); + + /** + *

+ * The {@code java.specification.version} System Property. Java Runtime Environment specification version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.3 + */ + public static final String JAVA_SPECIFICATION_VERSION = getSystemProperty("java.specification.version"); + private static final JavaVersion JAVA_SPECIFICATION_VERSION_AS_ENUM = JavaVersion.get(JAVA_SPECIFICATION_VERSION); + + /** + *

+ * The {@code java.util.prefs.PreferencesFactory} System Property. A class name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.1 + * @since Java 1.4 + */ + public static final String JAVA_UTIL_PREFS_PREFERENCES_FACTORY = + getSystemProperty("java.util.prefs.PreferencesFactory"); + + /** + *

+ * The {@code java.vendor} System Property. Java vendor-specific string. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_VENDOR = getSystemProperty("java.vendor"); + + /** + *

+ * The {@code java.vendor.url} System Property. Java vendor URL. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_VENDOR_URL = getSystemProperty("java.vendor.url"); + + /** + *

+ * The {@code java.version} System Property. Java version number. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_VERSION = getSystemProperty("java.version"); + + /** + *

+ * The {@code java.vm.info} System Property. Java Virtual Machine implementation info. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.0 + * @since Java 1.2 + */ + public static final String JAVA_VM_INFO = getSystemProperty("java.vm.info"); + + /** + *

+ * The {@code java.vm.name} System Property. Java Virtual Machine implementation name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_NAME = getSystemProperty("java.vm.name"); + + /** + *

+ * The {@code java.vm.specification.name} System Property. Java Virtual Machine specification name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_NAME = getSystemProperty("java.vm.specification.name"); + + /** + *

+ * The {@code java.vm.specification.vendor} System Property. Java Virtual Machine specification vendor. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_VENDOR = getSystemProperty("java.vm.specification.vendor"); + + /** + *

+ * The {@code java.vm.specification.version} System Property. Java Virtual Machine specification version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_VERSION = getSystemProperty("java.vm.specification.version"); + + /** + *

+ * The {@code java.vm.vendor} System Property. Java Virtual Machine implementation vendor. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_VENDOR = getSystemProperty("java.vm.vendor"); + + /** + *

+ * The {@code java.vm.version} System Property. Java Virtual Machine implementation version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_VERSION = getSystemProperty("java.vm.version"); + + /** + *

+ * The {@code line.separator} System Property. Line separator ({@code "\n"} on UNIX). + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @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 = getSystemProperty("line.separator"); + + /** + *

+ * The {@code os.arch} System Property. Operating system architecture. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String OS_ARCH = getSystemProperty("os.arch"); + + /** + *

+ * The {@code os.name} System Property. Operating system name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String OS_NAME = getSystemProperty("os.name"); + + /** + *

+ * The {@code os.version} System Property. Operating system version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String OS_VERSION = getSystemProperty("os.version"); + + /** + *

+ * The {@code path.separator} System Property. Path separator ({@code ":"} on UNIX). + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @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 = getSystemProperty("path.separator"); + + /** + *

+ * The {@code user.country} or {@code user.region} System Property. User's country code, such as {@code GB}. First + * in Java version 1.2 as {@code user.region}. Renamed to {@code user.country} in 1.4 + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.0 + * @since Java 1.2 + */ + public static final String USER_COUNTRY = getSystemProperty("user.country") == null ? + getSystemProperty("user.region") : getSystemProperty("user.country"); + + /** + *

+ * The {@code user.dir} System Property. User's current working directory. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String USER_DIR = getSystemProperty(USER_DIR_KEY); + + /** + *

+ * The {@code user.home} System Property. User's home directory. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String USER_HOME = getSystemProperty(USER_HOME_KEY); + + /** + *

+ * The {@code user.language} System Property. User's language code, such as {@code "en"}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.0 + * @since Java 1.2 + */ + public static final String USER_LANGUAGE = getSystemProperty("user.language"); + + /** + *

+ * The {@code user.name} System Property. User's account name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since Java 1.1 + */ + public static final String USER_NAME = getSystemProperty(USER_NAME_KEY); + + /** + *

+ * The {@code user.timezone} System Property. For example: {@code "America/Los_Angeles"}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

+ * + * @since 2.1 + */ + public static final String USER_TIMEZONE = getSystemProperty("user.timezone"); + + // Java version checks + // ----------------------------------------------------------------------- + // These MUST be declared after those above as they depend on the + // values being set up + + /** + *

+ * Is {@code true} if this is Java version 1.1 (also 1.1.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_1 = getJavaVersionMatches("1.1"); + + /** + *

+ * Is {@code true} if this is Java version 1.2 (also 1.2.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_2 = getJavaVersionMatches("1.2"); + + /** + *

+ * Is {@code true} if this is Java version 1.3 (also 1.3.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_3 = getJavaVersionMatches("1.3"); + + /** + *

+ * Is {@code true} if this is Java version 1.4 (also 1.4.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_4 = getJavaVersionMatches("1.4"); + + /** + *

+ * Is {@code true} if this is Java version 1.5 (also 1.5.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_5 = getJavaVersionMatches("1.5"); + + /** + *

+ * Is {@code true} if this is Java version 1.6 (also 1.6.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_6 = getJavaVersionMatches("1.6"); + + /** + *

+ * Is {@code true} if this is Java version 1.7 (also 1.7.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ * + * @since 3.0 + */ + public static final boolean IS_JAVA_1_7 = getJavaVersionMatches("1.7"); + + /** + *

+ * Is {@code true} if this is Java version 1.8 (also 1.8.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ * + * @since 3.10 + */ + public static final boolean IS_JAVA_15 = getJavaVersionMatches("15"); + + // Operating system checks + // ----------------------------------------------------------------------- + // These MUST be declared after those above as they depend on the + // values being set up + // OS names from http://www.vamphq.com/os.html + // Selected ones included - please advise dev@commons.apache.org + // if you want another added or a mistake corrected + + /** + *

+ * Is {@code true} if this is AIX. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_AIX = getOsMatchesName("AIX"); + + /** + *

+ * Is {@code true} if this is HP-UX. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_HP_UX = getOsMatchesName("HP-UX"); + + /** + *

+ * Is {@code true} if this is IBM OS/400. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.3 + */ + public static final boolean IS_OS_400 = getOsMatchesName("OS/400"); + + /** + *

+ * Is {@code true} if this is Irix. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_IRIX = getOsMatchesName("Irix"); + + /** + *

+ * Is {@code true} if this is Linux. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_LINUX = getOsMatchesName("Linux") || getOsMatchesName("LINUX"); + + /** + *

+ * Is {@code true} if this is Mac. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_MAC = getOsMatchesName("Mac"); + + /** + *

+ * Is {@code true} if this is Mac. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_MAC_OSX = getOsMatchesName("Mac OS X"); + + /** + *

+ * Is {@code true} if this is Mac OS X Cheetah. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.1 + */ + public static final boolean IS_OS_FREE_BSD = getOsMatchesName("FreeBSD"); + + /** + *

+ * Is {@code true} if this is OpenBSD. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.1 + */ + public static final boolean IS_OS_OPEN_BSD = getOsMatchesName("OpenBSD"); + + /** + *

+ * Is {@code true} if this is NetBSD. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.1 + */ + public static final boolean IS_OS_NET_BSD = getOsMatchesName("NetBSD"); + + /** + *

+ * Is {@code true} if this is OS/2. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_OS2 = getOsMatchesName("OS/2"); + + /** + *

+ * Is {@code true} if this is Solaris. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_SOLARIS = getOsMatchesName("Solaris"); + + /** + *

+ * Is {@code true} if this is SunOS. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_SUN_OS = getOsMatchesName("SunOS"); + + /** + *

+ * Is {@code true} if this is a UNIX like system, as in any of AIX, HP-UX, Irix, Linux, MacOSX, Solaris or SUN OS. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.1 + */ + public static final boolean IS_OS_UNIX = IS_OS_AIX || IS_OS_HP_UX || IS_OS_IRIX || IS_OS_LINUX || IS_OS_MAC_OSX + || IS_OS_SOLARIS || IS_OS_SUN_OS || IS_OS_FREE_BSD || IS_OS_OPEN_BSD || IS_OS_NET_BSD; + + /** + *

+ * Is {@code true} if this is Windows. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS = getOsMatchesName(OS_NAME_WINDOWS_PREFIX); + + /** + *

+ * Is {@code true} if this is Windows 2000. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_2000 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 2000"); + + /** + *

+ * Is {@code true} if this is Windows 2003. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_NT = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " NT"); + + /** + *

+ * Is {@code true} if this is Windows XP. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_XP = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " XP"); + + // ----------------------------------------------------------------------- + /** + *

+ * Is {@code true} if this is Windows Vista. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.4 + */ + public static final boolean IS_OS_WINDOWS_VISTA = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " Vista"); + + /** + *

+ * Is {@code true} if this is Windows 7. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.0 + */ + public static final boolean IS_OS_WINDOWS_7 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 7"); + + /** + *

+ * Is {@code true} if this is Windows 8. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.5 + */ + public static final boolean IS_OS_WINDOWS_10 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 10"); + + /** + *

+ * Is {@code true} if this is z/OS. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

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

+ * Gets an environment variable, defaulting to {@code defaultValue} if the variable cannot be read. + *

+ *

+ * If a {@code SecurityException} is caught, the return value is {@code defaultValue} and a message is written to + * {@code System.err}. + *

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

+ * If you want to know what the network stack says is the host name, you should use {@code InetAddress.getLocalHost().getHostName()}. + *

+ * + * @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 Java home directory as a {@code File}. + *

+ * + * @return a directory + * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow + * access to the specified system property. + * @see System#getProperty(String) + * @since 2.1 + */ + public static File getJavaHome() { + return new File(System.getProperty(JAVA_HOME_KEY)); + } + + /** + *

+ * Gets the Java IO temporary directory as a {@code File}. + *

+ * + * @return a directory + * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow + * access to the specified system property. + * @see System#getProperty(String) + * @since 2.1 + */ + public static File getJavaIoTmpDir() { + return new File(System.getProperty(JAVA_IO_TMPDIR_KEY)); + } + + /** + *

+ * Decides if the Java version matches. + *

+ * + * @param versionPrefix the prefix for the java version + * @return true if matches, or false if not or can't determine + */ + private static boolean getJavaVersionMatches(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 a System property, defaulting to {@code null} if the property cannot be read. + *

+ *

+ * If a {@code SecurityException} is caught, the return value is {@code null} and a message is written to + * {@code System.err}. + *

+ * + * @param property the system property name + * @return the system property value or {@code null} if a security problem occurs + */ + private static String getSystemProperty(final String property) { + try { + return System.getProperty(property); + } catch (final SecurityException ex) { + // we are not allowed to look at this property + // System.err.println("Caught a SecurityException reading the system property '" + property + // + "'; the SystemUtils property value will default to null."); + return null; + } + } + + /** + *

+ * Gets the user directory as a {@code File}. + *

+ * + * @return a directory + * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow + * access to the specified system property. + * @see System#getProperty(String) + * @since 2.1 + */ + public static File getUserDir() { + return new File(System.getProperty(USER_DIR_KEY)); + } + + /** + *

+ * Gets the user home directory as a {@code File}. + *

+ * + * @return a directory + * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow + * access to the specified system property. + * @see System#getProperty(String) + * @since 2.1 + */ + public static File getUserHome() { + return new File(System.getProperty(USER_HOME_KEY)); + } + + /** + *

+ * Gets the 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 System#getProperty(String) + * @since 3.10 + */ + public static String getUserName() { + return System.getProperty(USER_NAME_KEY); + } + + /** + *

+ * 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 System#getProperty(String) + * @since 3.10 + */ + public static String getUserName(final String defaultValue) { + return System.getProperty(USER_NAME_KEY, 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. + *

+ *

+ * Example input: + *

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

+ *

+ * This method is package private instead of private to support unit test invocation. + *

+ * + * @param version the actual Java version + * @param versionPrefix the prefix for the expected Java version + * @return true if matches, or false if not or can't determine + */ + static boolean isJavaVersionMatch(final String version, final String versionPrefix) { + if (version == null) { + return false; + } + return version.startsWith(versionPrefix); + } + + /** + * Decides if the operating system matches. + *

+ * This method is package private instead of private to support unit test invocation. + *

+ * + * @param osName the actual OS name + * @param osVersion the actual OS version + * @param osNamePrefix the prefix for the expected OS name + * @param osVersionPrefix the prefix for the expected OS version + * @return true if matches, or false if not or can't determine + */ + static boolean isOSMatch(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. + *

+ * This method is package private instead of private to support unit test invocation. + *

+ * + * @param osName the actual OS name + * @param osNamePrefix the prefix for the expected OS name + * @return true if matches, or false if not or can't determine + */ + static boolean isOSNameMatch(final String osName, final String osNamePrefix) { + if (osName == null) { + return false; + } + return osName.startsWith(osNamePrefix); + } + + /** + * Decides if the operating system version matches. + *

+ * This method is package private instead of private to support unit test invocation. + *

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

+ *

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

+ */ + public SystemUtils() { + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/ThreadUtils.java b/after/src/main/java/org/apache/commons/lang3/ThreadUtils.java new file mode 100644 index 0000000..2a5f35d --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/ThreadUtils.java @@ -0,0 +1,469 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.time.DurationUtils; + +/** + *

+ * Helpers for {@code java.lang.Thread} and {@code java.lang.ThreadGroup}. + *

+ *

+ * #ThreadSafe# + *

+ * + * @see java.lang.Thread + * @see java.lang.ThreadGroup + * @since 3.5 + */ +public class ThreadUtils { + + /** + * A predicate implementation which always returns true. + */ + 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; + } + } + + /** + * A predicate implementation which matches a thread or threadgroup name. + */ + public static class NamePredicate implements ThreadPredicate, ThreadGroupPredicate { + + private final String name; + + /** + * Predicate constructor + * + * @param name thread or threadgroup name + * @throws IllegalArgumentException if the name is {@code null} + */ + public NamePredicate(final String name) { + Validate.notNull(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 threadgroups. + */ + // When breaking BC, replace this with Predicate + @FunctionalInterface + public interface ThreadGroupPredicate { + + /** + * Evaluates this predicate on the given threadgroup. + * @param threadGroup the threadgroup + * @return {@code true} if the threadGroup matches the predicate, otherwise {@code false} + */ + boolean test(ThreadGroup threadGroup); + } + + /** + * A predicate implementation which matches a thread id. + */ + 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. + */ + // When breaking BC, replace this with Predicate + @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. + */ + public static final AlwaysTruePredicate ALWAYS_TRUE_PREDICATE = new AlwaysTruePredicate(); + + /** + * 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) { + final Collection result = findThreads(new ThreadIdPredicate(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 IllegalArgumentException if the specified id is zero or negative or the 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 Thread findThreadById(final long threadId, final String threadGroupName) { + Validate.notNull(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 IllegalArgumentException if the specified id is zero or negative or the 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 Thread findThreadById(final long threadId, final ThreadGroup threadGroup) { + Validate.notNull(threadGroup, "threadGroup"); + final Thread thread = findThreadById(threadId); + if (thread != null && threadGroup.equals(thread.getThreadGroup())) { + return thread; + } + return null; + } + + /** + * Select all active threadgroups which match the given predicate and which is a subgroup of the given thread group (or one of its subgroups). + * + * @param group the thread group + * @param recurse if {@code true} then evaluate the predicate recursively on all threadgroups in all subgroups of the given group + * @param predicate the predicate + * @return An unmodifiable {@code Collection} of active threadgroups which match the given predicate and which is a subgroup of the given thread group + * @throws IllegalArgumentException 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 + */ + public static Collection findThreadGroups(final ThreadGroup group, final boolean recurse, final ThreadGroupPredicate predicate) { + Validate.notNull(group, "group"); + Validate.notNull(predicate, "predicate"); + + int count = group.activeGroupCount(); + ThreadGroup[] threadGroups; + do { + threadGroups = new ThreadGroup[count + (count / 2) + 1]; //slightly grow the array size + count = group.enumerate(threadGroups, recurse); + //return value of enumerate() must be strictly less than the array size according to javadoc + } while (count >= threadGroups.length); + + final List result = new ArrayList<>(count); + for (int i = 0; i < count; ++i) { + if (predicate.test(threadGroups[i])) { + result.add(threadGroups[i]); + } + } + return Collections.unmodifiableCollection(result); + } + + /** + * Select all active threadgroups which match the given predicate. + * + * @param predicate the predicate + * @return An unmodifiable {@code Collection} of active threadgroups matching the given predicate + * @throws IllegalArgumentException 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 + */ + public static Collection 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 IllegalArgumentException 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 findThreadGroupsByName(final String threadGroupName) { + return findThreadGroups(new NamePredicate(threadGroupName)); + } + + /** + * Select all active threads which match the given predicate and which belongs to the given thread group (or one of its subgroups). + * + * @param group 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 {@code Collection} of active threads which match the given predicate and which belongs to the given thread group + * @throws IllegalArgumentException 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 + */ + public static Collection findThreads(final ThreadGroup group, final boolean recurse, final ThreadPredicate predicate) { + Validate.notNull(group, "The group must not be null"); + Validate.notNull(predicate, "The predicate must not be null"); + + int count = group.activeCount(); + Thread[] threads; + do { + threads = new Thread[count + (count / 2) + 1]; //slightly grow the array size + count = group.enumerate(threads, recurse); + //return value of enumerate() must be strictly less than the array size according to javadoc + } while (count >= threads.length); + + final List result = new ArrayList<>(count); + for (int i = 0; i < count; ++i) { + if (predicate.test(threads[i])) { + result.add(threads[i]); + } + } + return Collections.unmodifiableCollection(result); + } + + /** + * Select all active threads which match the given predicate. + * + * @param predicate the predicate + * @return An unmodifiable {@code Collection} of active threads matching the given predicate + * + * @throws IllegalArgumentException 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 + */ + public static Collection 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 IllegalArgumentException 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 findThreadsByName(final String threadName) { + return findThreads(new NamePredicate(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 IllegalArgumentException 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 findThreadsByName(final String threadName, final String threadGroupName) { + Validate.notNull(threadName, "threadName"); + Validate.notNull(threadGroupName, "threadGroupName"); + + final Collection threadGroups = findThreadGroups(new NamePredicate(threadGroupName)); + + if (threadGroups.isEmpty()) { + return Collections.emptyList(); + } + + final Collection result = new ArrayList<>(); + final NamePredicate threadNamePredicate = new NamePredicate(threadName); + for (final ThreadGroup group : threadGroups) { + result.addAll(findThreads(group, false, threadNamePredicate)); + } + return Collections.unmodifiableCollection(result); + } + + /** + * 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 IllegalArgumentException 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 findThreadsByName(final String threadName, final ThreadGroup threadGroup) { + return findThreads(threadGroup, false, new NamePredicate(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 getAllThreadGroups() { + return findThreadGroups(ALWAYS_TRUE_PREDICATE); + } + + /** + * 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 getAllThreads() { + return findThreads(ALWAYS_TRUE_PREDICATE); + } + + /** + * Gets the system thread group (sometimes also referred as "root thread group"). + * + * @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.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); + } + + /** + * 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); + } + + /** + *

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

+ *

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

+ */ + public ThreadUtils() { + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/Validate.java b/after/src/main/java/org/apache/commons/lang3/Validate.java new file mode 100644 index 0000000..6a44d7f --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/Validate.java @@ -0,0 +1,1329 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.regex.Pattern; + +/** + *

This class assists in validating arguments. The validation methods are + * based along the following principles: + *

    + *
  • An invalid {@code null} argument causes a {@link NullPointerException}.
  • + *
  • A non-{@code null} argument causes an {@link IllegalArgumentException}.
  • + *
  • An invalid index into an array/collection/map/string causes an {@link IndexOutOfBoundsException}.
  • + *
+ * + *

All exceptions messages are + * format strings + * as defined by the Java platform. For example:

+ * + *
+ * Validate.isTrue(i > 0, "The value must be greater than zero: %d", i);
+ * Validate.notNull(surname, "The surname must not be %s", null);
+ * 
+ * + *

#ThreadSafe#

+ * @see java.lang.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() { + } + + // isTrue + //--------------------------------------------------------------------------------- + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);
+ * + *

For performance reasons, the long value is passed as a separate parameter and + * appended to the exception message only in the case of an error.

+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param value the value to append to the message when invalid + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, double) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression, final String message, final long value) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, Long.valueOf(value))); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
Validate.isTrue(d > 0.0, "The value must be greater than zero: %s", d);
+ * + *

For performance reasons, the double value is passed as a separate parameter and + * appended to the exception message only in the case of an error.

+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param value the value to append to the message when invalid + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression, final String message, final double value) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, Double.valueOf(value))); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
+     * Validate.isTrue(i >= min && i <= max, "The value must be between %d and %d", min, max);
+     * Validate.isTrue(myObject.isOk(), "The object is not okay");
+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, double) + */ + public static void isTrue(final boolean expression, final String message, final Object... values) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception. This method is useful when validating according + * to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
+     * Validate.isTrue(i > 0);
+     * Validate.isTrue(myObject.isOk());
+ * + *

The message of the exception is "The validated expression is + * false".

+ * + * @param expression the boolean expression to check + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, double) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression) { + if (!expression) { + throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); + } + } + + // notNull + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument is not {@code null}; + * otherwise throwing an exception. + * + *

Validate.notNull(myObject, "The object must not be null");
+ * + *

The message of the exception is "The validated object is + * null".

+ * + * @param the object type + * @param object the object to check + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + * @see #notNull(Object, String, Object...) + */ + public static T notNull(final T object) { + return notNull(object, DEFAULT_IS_NULL_EX_MESSAGE); + } + + /** + *

Validate that the specified argument is not {@code null}; + * otherwise throwing an exception with the specified message. + * + *

Validate.notNull(myObject, "The object must not be null");
+ * + * @param the object type + * @param object the object to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + * @see #notNull(Object) + */ + public static T notNull(final T object, final String message, final Object... values) { + return Objects.requireNonNull(object, () -> String.format(message, values)); + } + + // notEmpty array + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument array is neither {@code null} + * nor a length of zero (no elements); otherwise throwing an exception + * with the specified message. + * + *

Validate.notEmpty(myArray, "The array must not be empty");
+ * + * @param the array type + * @param array the array to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated array (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if the array is empty + * @see #notEmpty(Object[]) + */ + public static T[] notEmpty(final T[] array, final String message, final Object... values) { + Objects.requireNonNull(array, () -> String.format(message, values)); + if (array.length == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + return array; + } + + /** + *

Validate that the specified argument array is neither {@code null} + * nor a length of zero (no elements); otherwise throwing an exception. + * + *

Validate.notEmpty(myArray);
+ * + *

The message in the exception is "The validated array is + * empty". + * + * @param the array type + * @param array the array to check, validated not null by this method + * @return the validated array (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if the array is empty + * @see #notEmpty(Object[], String, Object...) + */ + public static T[] notEmpty(final T[] array) { + return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); + } + + // notEmpty collection + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument collection is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception + * with the specified message. + * + *

Validate.notEmpty(myCollection, "The collection must not be empty");
+ * + * @param the collection type + * @param collection the collection to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated collection (never {@code null} method for chaining) + * @throws NullPointerException if the collection is {@code null} + * @throws IllegalArgumentException if the collection is empty + * @see #notEmpty(Object[]) + */ + public static > T notEmpty(final T collection, final String message, final Object... values) { + Objects.requireNonNull(collection, () -> String.format(message, values)); + if (collection.isEmpty()) { + throw new IllegalArgumentException(String.format(message, values)); + } + return collection; + } + + /** + *

Validate that the specified argument collection is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception. + * + *

Validate.notEmpty(myCollection);
+ * + *

The message in the exception is "The validated collection is + * empty".

+ * + * @param the collection type + * @param collection the collection to check, validated not null by this method + * @return the validated collection (never {@code null} method for chaining) + * @throws NullPointerException if the collection is {@code null} + * @throws IllegalArgumentException if the collection is empty + * @see #notEmpty(Collection, String, Object...) + */ + public static > T notEmpty(final T collection) { + return notEmpty(collection, DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); + } + + // notEmpty map + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument map is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception + * with the specified message. + * + *

Validate.notEmpty(myMap, "The map must not be empty");
+ * + * @param the map type + * @param map the map to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated map (never {@code null} method for chaining) + * @throws NullPointerException if the map is {@code null} + * @throws IllegalArgumentException if the map is empty + * @see #notEmpty(Object[]) + */ + public static > T notEmpty(final T map, final String message, final Object... values) { + Objects.requireNonNull(map, () -> String.format(message, values)); + if (map.isEmpty()) { + throw new IllegalArgumentException(String.format(message, values)); + } + return map; + } + + /** + *

Validate that the specified argument map is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception. + * + *

Validate.notEmpty(myMap);
+ * + *

The message in the exception is "The validated map is + * empty".

+ * + * @param the map type + * @param map the map to check, validated not null by this method + * @return the validated map (never {@code null} method for chaining) + * @throws NullPointerException if the map is {@code null} + * @throws IllegalArgumentException if the map is empty + * @see #notEmpty(Map, String, Object...) + */ + public static > T notEmpty(final T map) { + return notEmpty(map, DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); + } + + // notEmpty string + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument character sequence is + * neither {@code null} nor a length of zero (no characters); + * otherwise throwing an exception with the specified message. + * + *

Validate.notEmpty(myString, "The string must not be empty");
+ * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated character sequence (never {@code null} method for chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IllegalArgumentException if the character sequence is empty + * @see #notEmpty(CharSequence) + */ + public static T notEmpty(final T chars, final String message, final Object... values) { + Objects.requireNonNull(chars, () -> String.format(message, values)); + if (chars.length() == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + return chars; + } + + /** + *

Validate that the specified argument character sequence is + * neither {@code null} nor a length of zero (no characters); + * otherwise throwing an exception with the specified message. + * + *

Validate.notEmpty(myString);
+ * + *

The message in the exception is "The validated + * character sequence is empty".

+ * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @return the validated character sequence (never {@code null} method for chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IllegalArgumentException if the character sequence is empty + * @see #notEmpty(CharSequence, String, Object...) + */ + public static T notEmpty(final T chars) { + return notEmpty(chars, DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); + } + + // notBlank string + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument character sequence is + * neither {@code null}, a length of zero (no characters), empty + * nor whitespace; otherwise throwing an exception with the specified + * message. + * + *

Validate.notBlank(myString, "The string must not be blank");
+ * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated character sequence (never {@code null} method for chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IllegalArgumentException if the character sequence is blank + * @see #notBlank(CharSequence) + * + * @since 3.0 + */ + public static T notBlank(final T chars, final String message, final Object... values) { + Objects.requireNonNull(chars, () -> String.format(message, values)); + if (StringUtils.isBlank(chars)) { + throw new IllegalArgumentException(String.format(message, values)); + } + return chars; + } + + /** + *

Validate that the specified argument character sequence is + * neither {@code null}, a length of zero (no characters), empty + * nor whitespace; otherwise throwing an exception. + * + *

Validate.notBlank(myString);
+ * + *

The message in the exception is "The validated character + * sequence is blank".

+ * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @return the validated character sequence (never {@code null} method for chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IllegalArgumentException if the character sequence is blank + * @see #notBlank(CharSequence, String, Object...) + * + * @since 3.0 + */ + public static T notBlank(final T chars) { + return notBlank(chars, DEFAULT_NOT_BLANK_EX_MESSAGE); + } + + // noNullElements array + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument array is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception with the specified message. + * + *

Validate.noNullElements(myArray, "The array contain null at position %d");
+ * + *

If the array is {@code null}, then the message in the exception + * is "The validated object is null".

+ * + *

If the array has a {@code null} element, then the iteration + * index of the invalid element is appended to the {@code values} + * argument.

+ * + * @param the array type + * @param array the array to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated array (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if an element is {@code null} + * @see #noNullElements(Object[]) + */ + public static T[] noNullElements(final T[] array, final String message, final Object... values) { + notNull(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(String.format(message, values2)); + } + } + return array; + } + + /** + *

Validate that the specified argument array is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception.

+ * + *
Validate.noNullElements(myArray);
+ * + *

If the array is {@code null}, then the message in the exception + * is "The validated object is null".

+ * + *

If the array has a {@code null} element, then the message in the + * exception is "The validated array contains null element at index: + * " followed by the index.

+ * + * @param the array type + * @param array the array to check, validated not null by this method + * @return the validated array (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if an element is {@code null} + * @see #noNullElements(Object[], String, Object...) + */ + public static T[] noNullElements(final T[] array) { + return noNullElements(array, DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE); + } + + // noNullElements iterable + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument iterable is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception with the specified message. + * + *

Validate.noNullElements(myCollection, "The collection contains null at position %d");
+ * + *

If the iterable is {@code null}, then the message in the exception + * is "The validated object is null".

+ * + *

If the iterable has a {@code null} element, then the iteration + * index of the invalid element is appended to the {@code values} + * argument.

+ * + * @param the iterable type + * @param iterable the iterable to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated iterable (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if an element is {@code null} + * @see #noNullElements(Iterable) + */ + public static > T noNullElements(final T iterable, final String message, final Object... values) { + notNull(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(String.format(message, values2)); + } + } + return iterable; + } + + /** + *

Validate that the specified argument iterable is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception. + * + *

Validate.noNullElements(myCollection);
+ * + *

If the iterable is {@code null}, then the message in the exception + * is "The validated object is null".

+ * + *

If the array has a {@code null} element, then the message in the + * exception is "The validated iterable contains null element at index: + * " followed by the index.

+ * + * @param the iterable type + * @param iterable the iterable to check, validated not null by this method + * @return the validated iterable (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if an element is {@code null} + * @see #noNullElements(Iterable, String, Object...) + */ + public static > T noNullElements(final T iterable) { + return noNullElements(iterable, DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE); + } + + // validIndex array + //--------------------------------------------------------------------------------- + + /** + *

Validates that the index is within the bounds of the argument + * array; otherwise throwing an exception with the specified message.

+ * + *
Validate.validIndex(myArray, 2, "The array index is invalid: ");
+ * + *

If the array is {@code null}, then the message of the exception + * is "The validated object is null".

+ * + * @param the array type + * @param array the array to check, validated not null by this method + * @param index the index to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated array (never {@code null} for method chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(Object[], int) + * + * @since 3.0 + */ + public static T[] validIndex(final T[] array, final int index, final String message, final Object... values) { + notNull(array); + if (index < 0 || index >= array.length) { + throw new IndexOutOfBoundsException(String.format(message, values)); + } + return array; + } + + /** + *

Validates that the index is within the bounds of the argument + * array; otherwise throwing an exception.

+ * + *
Validate.validIndex(myArray, 2);
+ * + *

If the array is {@code null}, then the message of the exception + * is "The validated object is null".

+ * + *

If the index is invalid, then the message of the exception is + * "The validated array index is invalid: " followed by the + * index.

+ * + * @param the array type + * @param array the array to check, validated not null by this method + * @param index the index to check + * @return the validated array (never {@code null} for method chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(Object[], int, String, Object...) + * + * @since 3.0 + */ + public static T[] validIndex(final T[] array, final int index) { + return validIndex(array, index, DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE, Integer.valueOf(index)); + } + + // validIndex collection + //--------------------------------------------------------------------------------- + + /** + *

Validates that the index is within the bounds of the argument + * collection; otherwise throwing an exception with the specified message.

+ * + *
Validate.validIndex(myCollection, 2, "The collection index is invalid: ");
+ * + *

If the collection is {@code null}, then the message of the + * exception is "The validated object is null".

+ * + * @param the collection type + * @param collection the collection to check, validated not null by this method + * @param index the index to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated collection (never {@code null} for chaining) + * @throws NullPointerException if the collection is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(Collection, int) + * + * @since 3.0 + */ + public static > T validIndex(final T collection, final int index, final String message, final Object... values) { + notNull(collection); + if (index < 0 || index >= collection.size()) { + throw new IndexOutOfBoundsException(String.format(message, values)); + } + return collection; + } + + /** + *

Validates that the index is within the bounds of the argument + * collection; otherwise throwing an exception.

+ * + *
Validate.validIndex(myCollection, 2);
+ * + *

If the index is invalid, then the message of the exception + * is "The validated collection index is invalid: " + * followed by the index.

+ * + * @param the collection type + * @param collection the collection to check, validated not null by this method + * @param index the index to check + * @return the validated collection (never {@code null} for method chaining) + * @throws NullPointerException if the collection is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(Collection, int, String, Object...) + * + * @since 3.0 + */ + public static > T validIndex(final T collection, final int index) { + return validIndex(collection, index, DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE, Integer.valueOf(index)); + } + + // validIndex string + //--------------------------------------------------------------------------------- + + /** + *

Validates that the index is within the bounds of the argument + * character sequence; otherwise throwing an exception with the + * specified message.

+ * + *
Validate.validIndex(myStr, 2, "The string index is invalid: ");
+ * + *

If the character sequence is {@code null}, then the message + * of the exception is "The validated object is null".

+ * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @param index the index to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated character sequence (never {@code null} for method chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(CharSequence, int) + * + * @since 3.0 + */ + public static T validIndex(final T chars, final int index, final String message, final Object... values) { + notNull(chars); + if (index < 0 || index >= chars.length()) { + throw new IndexOutOfBoundsException(String.format(message, values)); + } + return chars; + } + + /** + *

Validates that the index is within the bounds of the argument + * character sequence; otherwise throwing an exception.

+ * + *
Validate.validIndex(myStr, 2);
+ * + *

If the character sequence is {@code null}, then the message + * of the exception is "The validated object is + * null".

+ * + *

If the index is invalid, then the message of the exception + * is "The validated character sequence index is invalid: " + * followed by the index.

+ * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @param index the index to check + * @return the validated character sequence (never {@code null} for method chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(CharSequence, int, String, Object...) + * + * @since 3.0 + */ + public static T validIndex(final T chars, final int index) { + return validIndex(chars, index, DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE, Integer.valueOf(index)); + } + + // validState + //--------------------------------------------------------------------------------- + + /** + *

Validate that the stateful condition is {@code true}; otherwise + * throwing an exception. This method is useful when validating according + * to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
+     * Validate.validState(field > 0);
+     * Validate.validState(this.isOk());
+ * + *

The message of the exception is "The validated state is + * false".

+ * + * @param expression the boolean expression to check + * @throws IllegalStateException if expression is {@code false} + * @see #validState(boolean, String, Object...) + * + * @since 3.0 + */ + public static void validState(final boolean expression) { + if (!expression) { + throw new IllegalStateException(DEFAULT_VALID_STATE_EX_MESSAGE); + } + } + + /** + *

Validate that the stateful condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
Validate.validState(this.isOk(), "The state is not OK: %s", myObject);
+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalStateException if expression is {@code false} + * @see #validState(boolean) + * + * @since 3.0 + */ + public static void validState(final boolean expression, final String message, final Object... values) { + if (!expression) { + throw new IllegalStateException(String.format(message, values)); + } + } + + // matchesPattern + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument character sequence matches the specified regular + * expression pattern; otherwise throwing an exception.

+ * + *
Validate.matchesPattern("hi", "[a-z]*");
+ * + *

The syntax of the pattern is the one used in the {@link Pattern} class.

+ * + * @param input the character sequence to validate, not null + * @param pattern the regular expression pattern, not null + * @throws IllegalArgumentException if the character sequence does not match the pattern + * @see #matchesPattern(CharSequence, String, String, Object...) + * + * @since 3.0 + */ + public static void matchesPattern(final CharSequence input, final String pattern) { + // TODO when breaking BC, consider returning input + if (!Pattern.matches(pattern, input)) { + throw new IllegalArgumentException(String.format(DEFAULT_MATCHES_PATTERN_EX, input, pattern)); + } + } + + /** + *

Validate that the specified argument character sequence matches the specified regular + * expression pattern; otherwise throwing an exception with the specified message.

+ * + *
Validate.matchesPattern("hi", "[a-z]*", "%s does not match %s", "hi" "[a-z]*");
+ * + *

The syntax of the pattern is the one used in the {@link Pattern} class.

+ * + * @param input the character sequence to validate, not null + * @param pattern the regular expression pattern, not null + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if the character sequence does not match the pattern + * @see #matchesPattern(CharSequence, String) + * + * @since 3.0 + */ + public static void matchesPattern(final CharSequence input, final String pattern, final String message, final Object... values) { + // TODO when breaking BC, consider returning input + if (!Pattern.matches(pattern, input)) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + // notNaN + //--------------------------------------------------------------------------------- + + /** + *

Validates that the specified argument is not {@code NaN}; otherwise + * throwing an exception.

+ * + *
Validate.notNaN(myDouble);
+ * + *

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

+ * + * @param value the value to validate + * @throws IllegalArgumentException if the value is not a number + * @see #notNaN(double, java.lang.String, java.lang.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 {@code NaN}; otherwise + * throwing an exception with the specified message.

+ * + *
Validate.notNaN(myDouble, "The value must be a number");
+ * + * @param value the value to validate + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message + * @throws IllegalArgumentException if the value is not a number + * @see #notNaN(double) + * + * @since 3.5 + */ + public static void notNaN(final double value, final String message, final Object... values) { + if (Double.isNaN(value)) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + // finite + //--------------------------------------------------------------------------------- + + /** + *

Validates that the specified argument is not infinite or {@code NaN}; + * otherwise throwing an exception.

+ * + *
Validate.finite(myDouble);
+ * + *

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

+ * + * @param value the value to validate + * @throws IllegalArgumentException if the value is infinite or {@code NaN} + * @see #finite(double, java.lang.String, java.lang.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 {@code NaN}; + * otherwise throwing an exception with the specified message.

+ * + *
Validate.finite(myDouble, "The argument must contain a numeric value");
+ * + * @param value the value to validate + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message + * @throws IllegalArgumentException if the value is infinite or {@code 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(String.format(message, values)); + } + } + + // inclusiveBetween + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument object fall between the two + * inclusive values specified; otherwise, throws an exception.

+ * + *
Validate.inclusiveBetween(0, 2, 1);
+ * + * @param the type of the argument object + * @param start the inclusive start value, not null + * @param end the inclusive end value, not null + * @param value the object to validate, not null + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #inclusiveBetween(Object, Object, Comparable, String, Object...) + * + * @since 3.0 + */ + public static void inclusiveBetween(final T start, final T end, final Comparable value) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + *

Validate that the specified argument object fall between the two + * inclusive values specified; otherwise, throws an exception with the + * specified message.

+ * + *
Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");
+ * + * @param the type of the argument object + * @param start the inclusive start value, not null + * @param end the inclusive end value, not null + * @param value the object to validate, not null + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #inclusiveBetween(Object, Object, Comparable) + * + * @since 3.0 + */ + public static void inclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * inclusive values specified; otherwise, throws an exception. + * + *
Validate.inclusiveBetween(0, 2, 1);
+ * + * @param start the inclusive start value + * @param end the inclusive end value + * @param value the value to validate + * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) + * + * @since 3.3 + */ + @SuppressWarnings("boxing") + public static void 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. + * + *
Validate.inclusiveBetween(0, 2, 1, "Not in range");
+ * + * @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. + * + *
Validate.inclusiveBetween(0.1, 2.1, 1.1);
+ * + * @param start the inclusive start value + * @param end the inclusive end value + * @param value the value to validate + * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) + * + * @since 3.3 + */ + @SuppressWarnings("boxing") + public static void 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. + * + *
Validate.inclusiveBetween(0.1, 2.1, 1.1, "Not in range");
+ * + * @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); + } + } + + // exclusiveBetween + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument object fall between the two + * exclusive values specified; otherwise, throws an exception.

+ * + *
Validate.exclusiveBetween(0, 2, 1);
+ * + * @param the type of the argument object + * @param start the exclusive start value, not null + * @param end the exclusive end value, not null + * @param value the object to validate, not null + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #exclusiveBetween(Object, Object, Comparable, String, Object...) + * + * @since 3.0 + */ + public static void exclusiveBetween(final T start, final T end, final Comparable value) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + *

Validate that the specified argument object fall between the two + * exclusive values specified; otherwise, throws an exception with the + * specified message.

+ * + *
Validate.exclusiveBetween(0, 2, 1, "Not in boundaries");
+ * + * @param the type of the argument object + * @param start the exclusive start value, not null + * @param end the exclusive end value, not null + * @param value the object to validate, not null + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #exclusiveBetween(Object, Object, Comparable) + * + * @since 3.0 + */ + public static void exclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * exclusive values specified; otherwise, throws an exception. + * + *
Validate.exclusiveBetween(0, 2, 1);
+ * + * @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. + * + *
Validate.exclusiveBetween(0, 2, 1, "Not in range");
+ * + * @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. + * + *
Validate.exclusiveBetween(0.1, 2.1, 1.1);
+ * + * @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. + * + *
Validate.exclusiveBetween(0.1, 2.1, 1.1, "Not in range");
+ * + * @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); + } + } + + // isInstanceOf + //--------------------------------------------------------------------------------- + + /** + * Validates that the argument is an instance of the specified class, if not throws an exception. + * + *

This method is useful when validating according to an arbitrary class

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

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

+ * + * @param type the class the object must be validated against, not null + * @param obj the object to check, null throws an exception + * @throws IllegalArgumentException if argument is not of specified class + * @see #isInstanceOf(Class, Object, String, Object...) + * + * @since 3.0 + */ + 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(), + obj == null ? "null" : obj.getClass().getName())); + } + } + + /** + *

Validate that the argument is an instance of the specified class; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary class

+ * + *
Validate.isInstanceOf(OkClass.class, object, "Wrong class, object is of class %s",
+     *   object.getClass().getName());
+ * + * @param type the class the object must be validated against, not null + * @param obj the object to check, null throws an exception + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if argument is not of specified class + * @see #isInstanceOf(Class, Object) + * + * @since 3.0 + */ + public static void isInstanceOf(final Class type, final Object obj, final String message, final Object... values) { + // TODO when breaking BC, consider returning obj + if (!type.isInstance(obj)) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + // isAssignableFrom + //--------------------------------------------------------------------------------- + + /** + * Validates that the argument can be converted to the specified class, if not, throws an exception. + * + *

This method is useful when validating that there will be no casting errors.

+ * + *
Validate.isAssignableFrom(SuperClass.class, object.getClass());
+ * + *

The message format of the exception is "Cannot assign {type} to {superType}"

+ * + * @param superType the class 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 (!superType.isAssignableFrom(type)) { + throw new IllegalArgumentException(String.format(DEFAULT_IS_ASSIGNABLE_EX_MESSAGE, type == null ? "null" : type.getName(), + superType.getName())); + } + } + + /** + * Validates that the argument can be converted to the specified class, if not throws an exception. + * + *

This method is useful when validating if there will be no casting errors.

+ * + *
Validate.isAssignableFrom(SuperClass.class, object.getClass());
+ * + *

The message of the exception is "The validated object can not be converted to the" + * followed by the name of the class and "class"

+ * + * @param superType the class the class must be validated against, not null + * @param type the class to check, not null + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if argument can not be converted to the specified class + * @see #isAssignableFrom(Class, Class) + */ + public static void isAssignableFrom(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(String.format(message, values)); + } + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/arch/Processor.java b/after/src/main/java/org/apache/commons/lang3/arch/Processor.java new file mode 100644 index 0000000..d825af7 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/arch/Processor.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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: + *
    + *
  • 32-bit
  • + *
  • 64-bit
  • + *
  • Unknown
  • + *
+ */ + 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. + * + * @since 3.10 + */ + 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: + *
    + *
  • x86
  • + *
  • ia64
  • + *
  • PPC
  • + *
  • Unknown
  • + *
+ */ + public enum Type { + + /** + * Intel x86 series of instruction set architectures. + */ + X86, + + /** + * Intel Itanium 64-bit architecture. + */ + IA_64, + + /** + * Apple–IBM–Motorola PowerPC architecture. + */ + PPC, + + /** + * Unknown architecture. + */ + UNKNOWN + } + + 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; + } + + /** + * Returns 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; + } + + /** + * Returns the processor type as {@link Type} enum. + * The processor type defines, if the processor is for example + * a x86 or PPA. + * + * @return A {@link Type} enum. + */ + public Type getType() { + return type; + } + + /** + * Checks 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; + } + + /** + * Checks 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; + } + + /** + * Checks 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; + } + + /** + * Checks 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; + } + + /** + * Checks 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; + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/arch/package-info.java b/after/src/main/java/org/apache/commons/lang3/arch/package-info.java new file mode 100644 index 0000000..f256190 --- /dev/null +++ b/after/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/after/src/main/java/org/apache/commons/lang3/builder/Builder.java b/after/src/main/java/org/apache/commons/lang3/builder/Builder.java new file mode 100644 index 0000000..ad4edce --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/Builder.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +/** + *

+ * The Builder interface is designed to designate a class as a builder + * object in the Builder design pattern. Builders are capable of creating and + * configuring objects or results that normally take multiple steps to construct + * or are very complex to derive. + *

+ * + *

+ * The builder interface defines a single method, {@link #build()}, that + * classes must implement. The result of this method should be the final + * configured object or result after all building operations are performed. + *

+ * + *

+ * It is a recommended practice that the methods supplied to configure the + * object or result being built return a reference to {@code this} so that + * method calls can be chained together. + *

+ * + *

+ * Example Builder: + *


+ * class FontBuilder implements Builder<Font> {
+ *     private Font font;
+ *
+ *     public FontBuilder(String fontName) {
+ *         this.font = new Font(fontName, Font.PLAIN, 12);
+ *     }
+ *
+ *     public FontBuilder bold() {
+ *         this.font = this.font.deriveFont(Font.BOLD);
+ *         return this; // Reference returned so calls can be chained
+ *     }
+ *
+ *     public FontBuilder size(float pointSize) {
+ *         this.font = this.font.deriveFont(pointSize);
+ *         return this; // Reference returned so calls can be chained
+ *     }
+ *
+ *     // Other Font construction methods
+ *
+ *     public Font build() {
+ *         return this.font;
+ *     }
+ * }
+ * 
+ * + * Example Builder Usage: + *

+ * Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
+ *                                                              .size(14.0f)
+ *                                                              .build();
+ * 
+ * + * + * @param the type of object that the builder will construct or compute. + * + * @since 3.0 + */ +@FunctionalInterface +public interface Builder { + + /** + * Returns a reference to the object being constructed or result being + * calculated by the builder. + * + * @return the object constructed or result calculated by the builder. + */ + T build(); +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/CompareToBuilder.java b/after/src/main/java/org/apache/commons/lang3/builder/CompareToBuilder.java new file mode 100644 index 0000000..8a8580b --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/CompareToBuilder.java @@ -0,0 +1,1031 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + * Assists in implementing {@link java.lang.Comparable#compareTo(Object)} methods. + * + *

It is consistent with {@code equals(Object)} and + * {@code hashcode()} built with {@link EqualsBuilder} and + * {@link HashCodeBuilder}.

+ * + *

Two Objects that compare equal using {@code equals(Object)} should normally + * also compare equal using {@code compareTo(Object)}.

+ * + *

All relevant fields should be included in the calculation of the + * comparison. Derived fields may be ignored. The same fields, in the same + * order, should be used in both {@code compareTo(Object)} and + * {@code equals(Object)}.

+ * + *

To use this class write code as follows:

+ * + *
+ * public class MyClass {
+ *   String field1;
+ *   int field2;
+ *   boolean field3;
+ *
+ *   ...
+ *
+ *   public int compareTo(Object o) {
+ *     MyClass myClass = (MyClass) o;
+ *     return new CompareToBuilder()
+ *       .appendSuper(super.compareTo(o)
+ *       .append(this.field1, myClass.field1)
+ *       .append(this.field2, myClass.field2)
+ *       .append(this.field3, myClass.field3)
+ *       .toComparison();
+ *   }
+ * }
+ * 
+ * + *

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.

+ * + *

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.

+ * + *

A typical implementation of {@code compareTo(Object)} using + * {@code reflectionCompare} looks like:

+ + *
+ * public int compareTo(Object o) {
+ *   return CompareToBuilder.reflectionCompare(this, o);
+ * }
+ * 
+ * + *

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

+ * + * @see java.lang.Comparable + * @see java.lang.Object#equals(Object) + * @see java.lang.Object#hashCode() + * @see EqualsBuilder + * @see HashCodeBuilder + * @since 1.0 + */ +public class CompareToBuilder implements Builder { + + /** + * Current state of the comparison as appended fields are checked. + */ + private int comparison; + + /** + *

Constructor for CompareToBuilder.

+ * + *

Starts off assuming that the objects are equal. Multiple calls are + * then made to the various append methods, followed by a call to + * {@link #toComparison} to get the result.

+ */ + public CompareToBuilder() { + comparison = 0; + } + + //----------------------------------------------------------------------- + /** + *

Compares two {@code Object}s via reflection.

+ * + *

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.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • Transient members will be not be compared, as they are likely derived + * fields
  • + *
  • Superclass fields will be compared
  • + *
+ * + *

If both {@code lhs} and {@code rhs} are {@code null}, + * they are considered equal.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @return a negative integer, zero, or a positive integer as {@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 {@code Object}s via reflection.

+ * + *

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.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • If {@code compareTransients} is {@code true}, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
  • + *
  • Superclass fields will be compared
  • + *
+ * + *

If both {@code lhs} and {@code rhs} are {@code null}, + * they are considered equal.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @param compareTransients whether to compare transient fields + * @return a negative integer, zero, or a positive integer as {@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 {@code Object}s via reflection.

+ * + *

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.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • If {@code compareTransients} is {@code true}, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
  • + *
  • Superclass fields will be compared
  • + *
+ * + *

If both {@code lhs} and {@code rhs} are {@code null}, + * they are considered equal.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @param excludeFields Collection of String fields to exclude + * @return a negative integer, zero, or a positive integer as {@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 excludeFields) { + return reflectionCompare(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + /** + *

Compares two {@code Object}s via reflection.

+ * + *

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.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • If {@code compareTransients} is {@code true}, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
  • + *
  • Superclass fields will be compared
  • + *
+ * + *

If both {@code lhs} and {@code rhs} are {@code null}, + * they are considered equal.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @param excludeFields array of fields to exclude + * @return a negative integer, zero, or a positive integer as {@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 {@code Object}s via reflection.

+ * + *

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.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • If the {@code compareTransients} is {@code true}, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
  • + *
  • Compares superclass fields up to and including {@code reflectUpToClass}. + * If {@code reflectUpToClass} is {@code null}, compares all superclass fields.
  • + *
+ * + *

If both {@code lhs} and {@code rhs} are {@code null}, + * they are considered equal.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @param compareTransients whether to compare transient fields + * @param reflectUpToClass last superclass for which fields are compared + * @param excludeFields fields to exclude + * @return a negative integer, zero, or a positive integer as {@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 {@code Class} that defines fields to be compared + * @param builder {@code 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 {@code Object}s.

+ * + *
    + *
  1. Check if {@code lhs == rhs}
  2. + *
  3. Check if either {@code lhs} or {@code rhs} is {@code null}, + * a {@code null} object is less than a non-{@code null} object
  4. + *
  5. Check the object contents
  6. + *
+ * + *

{@code lhs} must either be an array or implement {@link Comparable}.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @return this - used to chain append calls + * @throws ClassCastException if {@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 {@code Object}s.

+ * + *
    + *
  1. Check if {@code lhs == rhs}
  2. + *
  3. Check if either {@code lhs} or {@code rhs} is {@code null}, + * a {@code null} object is less than a non-{@code null} object
  4. + *
  5. Check the object contents
  6. + *
+ * + *

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.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @param comparator {@code Comparator} used to compare the objects, + * {@code null} means treat lhs as {@code 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.getClass().isArray()) { + // 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 comparable = (Comparable) lhs; + comparison = comparable.compareTo(rhs); + } else { + @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc + final Comparator comparator2 = (Comparator) comparator; + comparison = comparator2.compare(lhs, rhs); + } + return this; + } + + private void appendArray(final Object lhs, final Object rhs, final Comparator comparator) { + // switch on type of array, to dispatch to the correct handler + // handles multi dimensional arrays + // throws a ClassCastException if rhs is not the correct array type + if (lhs instanceof long[]) { + append((long[]) lhs, (long[]) rhs); + } else if (lhs instanceof int[]) { + append((int[]) lhs, (int[]) rhs); + } else if (lhs instanceof short[]) { + append((short[]) lhs, (short[]) rhs); + } else if (lhs instanceof char[]) { + append((char[]) lhs, (char[]) rhs); + } else if (lhs instanceof byte[]) { + append((byte[]) lhs, (byte[]) rhs); + } else if (lhs instanceof double[]) { + append((double[]) lhs, (double[]) rhs); + } else if (lhs instanceof float[]) { + append((float[]) lhs, (float[]) rhs); + } else if (lhs instanceof boolean[]) { + append((boolean[]) lhs, (boolean[]) rhs); + } else { + // not an array of primitives + // throws a ClassCastException if rhs is not an array + append((Object[]) lhs, (Object[]) rhs, comparator); + } + } + + //------------------------------------------------------------------------- + /** + * 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.

+ * + *

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

+ * + *

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

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

+ * + *

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

+ * + *

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

+ * + * @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 {@code Object} arrays.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a short length array is less than a long length array
  6. + *
  7. Check array contents element by element using {@link #append(Object, Object, Comparator)}
  8. + *
+ * + *

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

+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + * @throws ClassCastException if {@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 {@code Object} arrays.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a short length array is less than a long length array
  6. + *
  7. Check array contents element by element using {@link #append(Object, Object, Comparator)}
  8. + *
+ * + *

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

+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @param comparator {@code Comparator} to use to compare the array elements, + * {@code null} means to treat {@code lhs} elements as {@code 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.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(long, long)}
  8. + *
+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(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.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(int, int)}
  8. + *
+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(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.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(short, short)}
  8. + *
+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(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.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(char, char)}
  8. + *
+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(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.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(byte, byte)}
  8. + *
+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(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.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(double, double)}
  8. + *
+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(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.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(float, float)}
  8. + *
+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(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.

+ * + *
    + *
  1. Check if arrays are the same using {@code ==}
  2. + *
  3. Check if for {@code null}, {@code null} is less than non-{@code null}
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(boolean, boolean)}
  8. + *
+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(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/after/src/main/java/org/apache/commons/lang3/builder/Diff.java b/after/src/main/java/org/apache/commons/lang3/builder/Diff.java new file mode 100644 index 0000000..7b062d0 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/Diff.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.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 {@code Diff} contains the differences between two {@link Diffable} class + * fields. + *

+ * + *

+ * Typically, {@code Diff}s are retrieved by using a {@link DiffBuilder} to + * produce a {@link DiffResult}, containing the differences between two objects. + *

+ * + * + * @param + * The type of object contained within this {@code Diff}. Differences + * between primitive objects are stored as their Object wrapper + * equivalent. + * @since 3.3 + */ +public abstract class Diff extends Pair { + + private static final long serialVersionUID = 1L; + + private final Type type; + private final String fieldName; + + /** + *

+ * Constructs a new {@code Diff} for the given field name. + *

+ * + * @param fieldName + * the name of the field + */ + protected Diff(final String fieldName) { + this.type = ObjectUtils.defaultIfNull( + TypeUtils.getTypeArguments(getClass(), Diff.class).get( + Diff.class.getTypeParameters()[0]), Object.class); + this.fieldName = fieldName; + } + + /** + *

+ * Returns the type of the field. + *

+ * + * @return the field type + */ + public final Type getType() { + return type; + } + + /** + *

+ * Returns the name of the field. + *

+ * + * @return the field name + */ + public final String getFieldName() { + return fieldName; + } + + /** + *

+ * Returns a {@code String} representation of the {@code Diff}, with the + * following format:

+ * + *
+     * [fieldname: left-value, right-value]
+     * 
+ * + * + * @return the string representation + */ + @Override + public final String toString() { + return String.format("[%s: %s, %s]", fieldName, getLeft(), getRight()); + } + + /** + *

+ * Throws {@code UnsupportedOperationException}. + *

+ * + * @param value + * ignored + * @return nothing + */ + @Override + public final T setValue(final T value) { + throw new UnsupportedOperationException("Cannot alter Diff object."); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java b/after/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java new file mode 100644 index 0000000..103f2e5 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java @@ -0,0 +1,983 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.Validate; + +/** + *

+ * Assists in implementing {@link Diffable#diff(Object)} methods. + *

+ * + *

+ * To use this class, write code as follows: + *

+ * + *
+ * public class Person implements Diffable<Person> {
+ *   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();
+ *   }
+ * }
+ * 
+ * + *

+ * The {@code ToStringStyle} passed to the constructor is embedded in the + * returned {@code DiffResult} and influences the style of the + * {@code DiffResult.toString()} method. This style choice can be overridden by + * calling {@link DiffResult#toString(ToStringStyle)}. + *

+ * + * @param type of the left and right object. + * @see Diffable + * @see Diff + * @see DiffResult + * @see ToStringStyle + * @since 3.3 + */ +public class DiffBuilder implements Builder> { + + private final List> 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. + *

+ * + *

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

+ * + * @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) { + + Validate.notNull(lhs, "lhs"); + Validate.notNull(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. + *

+ * + *

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

+ * + *

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

+ * + * @param lhs + * {@code this} object + * @param rhs + * the object to diff against + * @param style + * the style will use when outputting the objects, {@code null} + * uses the default + * @throws 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 append(final String fieldName, final boolean lhs, + final boolean rhs) { + validateFieldNameNotNull(fieldName); + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(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 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(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 append(final String fieldName, final byte lhs, + final byte rhs) { + validateFieldNameNotNull(fieldName); + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(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 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(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 append(final String fieldName, final char lhs, + final char rhs) { + validateFieldNameNotNull(fieldName); + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(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 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(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 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(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 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(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 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(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 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(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 append(final String fieldName, final int lhs, + final int rhs) { + validateFieldNameNotNull(fieldName); + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(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 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(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 append(final String fieldName, final long lhs, + final long rhs) { + validateFieldNameNotNull(fieldName); + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(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 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(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 append(final String fieldName, final short lhs, + final short rhs) { + validateFieldNameNotNull(fieldName); + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Short getLeft() { + return Short.valueOf(lhs); + } + + @Override + public Short getRight() { + return Short.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code short[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code short[]} + * @param rhs + * the right hand {@code short[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final short[] lhs, + final short[] rhs) { + validateFieldNameNotNull(fieldName); + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Short[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Short[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code Objects}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 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 (objectToTest.getClass().isArray()) { + 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(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 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(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 {@code DiffResult}. + *

+ * + *

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

+ * + *
+     * public class Person implements Diffable<Person> {
+     *   String name;
+     *   Address address; // implements Diffable<Address>
+     *
+     *   ...
+     *
+     *   public DiffResult diff(Person obj) {
+     *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
+     *       .append("name", this.name, obj.name)
+     *       .append("address", this.address.diff(obj.address))
+     *       .build();
+     *   }
+     * }
+     * 
+ * + * @param fieldName + * the field name + * @param diffResult + * the {@code DiffResult} to append + * @return this + * @throws NullPointerException if field name is {@code null} + * @since 3.5 + */ + public DiffBuilder append(final String fieldName, + final DiffResult diffResult) { + validateFieldNameNotNull(fieldName); + Validate.notNull(diffResult, "diffResult"); + if (objectsTriviallyEqual) { + return this; + } + + for (final Diff diff : diffResult.getDiffs()) { + append(fieldName + "." + diff.getFieldName(), + diff.getLeft(), diff.getRight()); + } + + return this; + } + + /** + *

+ * Builds a {@link DiffResult} based on the differences appended to this + * builder. + *

+ * + * @return a {@code DiffResult} containing the differences between the two + * objects. + */ + @Override + public DiffResult build() { + return new DiffResult<>(left, right, diffs, style); + } + + private void validateFieldNameNotNull(final String fieldName) { + Validate.notNull(fieldName, "fieldName"); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/DiffResult.java b/after/src/main/java/org/apache/commons/lang3/builder/DiffResult.java new file mode 100644 index 0000000..0a2361d --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/DiffResult.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.apache.commons.lang3.Validate; + +/** + *

+ * A {@code 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. + *

+ *

+ * Use a {@link DiffBuilder} to build a {@code DiffResult} comparing two objects. + *

+ * @param type of the left and right object. + * + * @since 3.3 + */ +public class DiffResult implements Iterable> { + + /** + *

+ * The {@code 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> 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> diffList, + final ToStringStyle style) { + Validate.notNull(lhs, "lhs"); + Validate.notNull(rhs, "rhs"); + Validate.notNull(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 {@code Diff}s. The list may be empty if + * there were no differences between the objects. + *

+ * + * @return an unmodifiable list of {@code Diff}s + */ + public List> getDiffs() { + return Collections.unmodifiableList(diffList); + } + + /** + *

+ * Returns the number of differences between the two objects. + *

+ * + * @return the number of differences + */ + public int getNumberOfDiffs() { + return diffList.size(); + } + + /** + *

+ * Returns the style used by the {@link #toString()} method. + *

+ * + * @return the style + */ + public ToStringStyle getToStringStyle() { + return style; + } + + /** + *

+ * Builds a {@code String} description of the differences contained within + * this {@code DiffResult}. A {@link ToStringBuilder} is used for each object + * and the style of the output is governed by the {@code ToStringStyle} + * passed to the constructor. + *

+ * + *

+ * 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: + *

+ * + *
+     * Person[name=John Doe,age=32] differs from Person[name=Joe Bloggs,age=26]
+     * 
+ * + *

+ * This indicates that the objects differ in name and age, but not in + * smoking status. + *

+ * + *

+ * To use a different {@code ToStringStyle} for an instance of this class, + * use {@link #toString(ToStringStyle)}. + *

+ * + * @return a {@code String} description of the differences. + */ + @Override + public String toString() { + return toString(style); + } + + /** + *

+ * Builds a {@code String} description of the differences contained within + * this {@code DiffResult}, using the supplied {@code ToStringStyle}. + *

+ * + * @param style + * the {@code ToStringStyle} to use when outputting the objects + * + * @return a {@code 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); + + for (final Diff diff : diffList) { + 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 {@code Diff} objects contained in this list. + *

+ * + * @return the iterator + */ + @Override + public Iterator> iterator() { + return diffList.iterator(); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/Diffable.java b/after/src/main/java/org/apache/commons/lang3/builder/Diffable.java new file mode 100644 index 0000000..71ffe31 --- /dev/null +++ b/after/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; + +/** + *

{@code 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()}.

+ * + *

The calculation of the differences is consistent with equals 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 {@code NullPointerException}.

+ * + *

+ * {@code 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: + *

+ *
+ * Assert.assertEquals(expected.diff(result), expected, result);
+ * 
+ * + * @param the type of objects that this object may be differentiated against + * @since 3.3 + */ +@FunctionalInterface +public interface Diffable { + + /** + *

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 diff(T obj); +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java b/after/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java new file mode 100644 index 0000000..41b6948 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java @@ -0,0 +1,1145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.

+ * + *

This class provides methods to build a good equals method for any + * class. It follows rules laid out in + * Effective Java + * , by Joshua Bloch. In particular the rule for comparing {@code doubles}, + * {@code floats}, and arrays can be tricky. Also, making sure that + * {@code equals()} and {@code hashCode()} are consistent can be + * difficult.

+ * + *

Two Objects that compare as equals must generate the same hash code, + * but two Objects with the same hash code do not have to be equal.

+ * + *

All relevant fields should be included in the calculation of equals. + * Derived fields may be ignored. In particular, any field used in + * generating a hash code must be used in the equals method, and vice + * versa.

+ * + *

Typical use for the code is as follows:

+ *
+ * public boolean equals(Object obj) {
+ *   if (obj == null) { return false; }
+ *   if (obj == this) { return true; }
+ *   if (obj.getClass() != getClass()) {
+ *     return false;
+ *   }
+ *   MyClass rhs = (MyClass) obj;
+ *   return new EqualsBuilder()
+ *                 .appendSuper(super.equals(obj))
+ *                 .append(field1, rhs.field1)
+ *                 .append(field2, rhs.field2)
+ *                 .append(field3, rhs.field3)
+ *                 .isEquals();
+ *  }
+ * 
+ * + *

Alternatively, there is a method that uses reflection to determine + * the fields to test. Because these fields are usually private, the method, + * {@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()}.

+ * + *

A typical invocation for this method would look like:

+ *
+ * public boolean equals(Object obj) {
+ *   return EqualsBuilder.reflectionEquals(this, obj);
+ * }
+ * 
+ * + *

The {@link EqualsExclude} annotation can be used to exclude fields from being + * used by the {@code reflectionEquals} methods.

+ * + * @since 1.0 + */ +public class EqualsBuilder implements Builder { + + /** + *

+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. + *

+ * + * @since 3.0 + */ + private static final ThreadLocal>> REGISTRY = new ThreadLocal<>(); + + /* + * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode() + * we are in the process of calculating. + * + * So we generate a one-to-one mapping from the original object to a new object. + * + * Now HashSet uses equals() to determine if two elements with the same 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> 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 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> registry = getRegistry(); + final Pair pair = getRegisterPair(lhs, rhs); + final Pair 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> registry = getRegistry(); + if (registry == null) { + registry = new HashSet<>(); + REGISTRY.set(registry); + } + final Pair pair = getRegisterPair(lhs, rhs); + registry.add(pair); + } + + /** + *

+ * Unregisters the given object pair. + *

+ * + *

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

Constructor for EqualsBuilder.

+ * + *

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

+ * @see Object#equals(Object) + */ + public EqualsBuilder() { + // set up default classes to bypass reflection for + bypassReflectionClasses = new ArrayList<>(); + 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 {@code Class}es whose instances should be compared by calling their {@code equals} + * although being in recursive mode. So the fields of theses classes will not be compared recursively by reflection.

+ * + *

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

+ * @param bypassReflectionClasses classes to bypass reflection test + * @return EqualsBuilder - used to chain calls. + * @see #setTestRecursive(boolean) + * @since 3.8 + */ + public EqualsBuilder setBypassReflectionClasses(final List> 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 {@code Object}s + * are equal.

+ * + *

It uses {@code AccessibleObject.setAccessible} to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

Transient members will be not be tested, as they are likely derived + * fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs {@code this} object + * @param rhs the other object + * @param excludeFields Collection of String field names to exclude from testing + * @return {@code true} if the two Objects have tested equals. + * + * @see EqualsExclude + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { + return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + /** + *

This method uses reflection to determine if the two {@code Object}s + * are equal.

+ * + *

It uses {@code AccessibleObject.setAccessible} to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

Transient members will be not be tested, as they are likely derived + * fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs {@code this} object + * @param rhs the other object + * @param excludeFields array of field names to exclude from testing + * @return {@code true} if the two Objects have tested equals. + * + * @see EqualsExclude + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { + return reflectionEquals(lhs, rhs, false, null, excludeFields); + } + + /** + *

This method uses reflection to determine if the two {@code Object}s + * are equal.

+ * + *

It uses {@code AccessibleObject.setAccessible} to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

If the TestTransients parameter is set to {@code true}, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the {@code Object}.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @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 {@code Object}s + * are equal.

+ * + *

It uses {@code AccessibleObject.setAccessible} to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

If the testTransients parameter is set to {@code true}, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the {@code Object}.

+ * + *

Static fields will not be included. Superclass fields will be appended + * up to and including the specified superclass. A null superclass is treated + * as java.lang.Object.

+ * + * @param lhs {@code this} object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), + * may be {@code null} + * @param excludeFields array of field names to exclude from testing + * @return {@code true} if the two Objects have tested equals. + * + * @see EqualsExclude + * @since 2.0 + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, + final String... excludeFields) { + return reflectionEquals(lhs, rhs, testTransients, reflectUpToClass, false, excludeFields); + } + + /** + *

This method uses reflection to determine if the two {@code Object}s + * are equal.

+ * + *

It uses {@code AccessibleObject.setAccessible} to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

If the testTransients parameter is set to {@code true}, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the {@code Object}.

+ * + *

Static fields will not be included. Superclass fields will be appended + * up to and including the specified superclass. A null superclass is treated + * as java.lang.Object.

+ * + *

If the testRecursive parameter is set to {@code true}, non primitive + * (and non primitive wrapper) field types will be compared by + * {@code 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.

+ * + *

It uses {@code AccessibleObject.setAccessible} to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

If the testTransients 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 {@code Object}.

+ * + *

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.

+ * + *

Field names listed in field {@code excludeFields} will be ignored.

+ * + *

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.

+ * + * @param lhs the left hand object + * @param rhs the left 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 {@code 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 {@code 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 multi dimensional 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.

+ * + *

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

+ * + *

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

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

+ * + *

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

+ * + *

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

+ * + * @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 {@code Object} arrays.

+ * + *

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

+ * + *

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

+ * + * @param lhs the left hand {@code Object[]} + * @param rhs the right hand {@code Object[]} + * @return 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.

+ * + *

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

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

+ * + *

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

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

+ * + *

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

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

+ * + *

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

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

+ * + *

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

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

+ * + *

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

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

+ * + *

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

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

+ * + *

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

+ * + * @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/after/src/main/java/org/apache/commons/lang3/builder/EqualsExclude.java b/after/src/main/java/org/apache/commons/lang3/builder/EqualsExclude.java new file mode 100755 index 0000000..b4ede8f --- /dev/null +++ b/after/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/after/src/main/java/org/apache/commons/lang3/builder/HashCodeBuilder.java b/after/src/main/java/org/apache/commons/lang3/builder/HashCodeBuilder.java new file mode 100644 index 0000000..b1cc73d --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/HashCodeBuilder.java @@ -0,0 +1,994 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Set; + +import org.apache.commons.lang3.ArraySorter; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.Validate; + +/** + *

+ * Assists in implementing {@link Object#hashCode()} methods. + *

+ * + *

+ * This class enables a good {@code hashCode} method to be built for any class. It follows the rules laid out in + * the book Effective Java by Joshua Bloch. Writing a + * good {@code hashCode} method is actually quite difficult. This class aims to simplify the process. + *

+ * + *

+ * The following is the approach taken. When appending a data field, the current total is multiplied by the + * multiplier then a relevant value + * for that data type is added. For example, if the current hashCode is 17, and the multiplier is 37, then + * appending the integer 45 will create a hash code of 674, namely 17 * 37 + 45. + *

+ * + *

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

+ * + *

+ * To use this class write code as follows: + *

+ * + *
+ * public class Person {
+ *   String name;
+ *   int age;
+ *   boolean smoker;
+ *   ...
+ *
+ *   public int hashCode() {
+ *     // you pick a hard-coded, randomly chosen, non-zero, odd number
+ *     // ideally different for each class
+ *     return new HashCodeBuilder(17, 37).
+ *       append(name).
+ *       append(age).
+ *       append(smoker).
+ *       toHashCode();
+ *   }
+ * }
+ * 
+ * + *

+ * If required, the superclass {@code hashCode()} can be added using {@link #appendSuper}. + *

+ * + *

+ * Alternatively, there is a method that uses reflection to determine the fields to test. Because these fields are + * usually private, the method, {@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. + *

+ * + *

+ * A typical invocation for this method would look like: + *

+ * + *
+ * public int hashCode() {
+ *   return HashCodeBuilder.reflectionHashCode(this);
+ * }
+ * 
+ * + *

The {@link HashCodeExclude} annotation can be used to exclude fields from being + * used by the {@code reflectionHashCode} methods.

+ * + * @since 1.0 + */ +public class HashCodeBuilder implements Builder { + /** + * 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> 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 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 registry = getRegistry(); + return registry != null && registry.contains(new IDKey(value)); + } + + /** + *

+ * Appends the fields and values defined by the given object of the given {@code 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}. + *

+ * + *

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

+ * + *

+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * {@code Object}. + *

+ * + *

+ * Static fields will not be tested. Superclass fields will be included. + *

+ * + *

+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class, + * however this is not vital. Prime numbers are preferred, especially for the multiplier. + *

+ * + * @param initialNonZeroOddNumber + * a non-zero, odd number used as the initial value. 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}. + *

+ * + *

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

+ * + *

+ * 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 {@code Object}. + *

+ * + *

+ * Static fields will not be tested. Superclass fields will be included. + *

+ * + *

+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class, + * however this is not vital. Prime numbers are preferred, especially for the multiplier. + *

+ * + * @param initialNonZeroOddNumber + * a non-zero, odd number used as the initial value. 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}. + *

+ * + *

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

+ * + *

+ * 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 {@code Object}. + *

+ * + *

+ * Static fields will not be included. Superclass fields will be included up to and including the specified + * superclass. A null superclass is treated as java.lang.Object. + *

+ * + *

+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class, + * however this is not vital. Prime numbers are preferred, especially for the multiplier. + *

+ * + * @param + * the type of the object involved + * @param initialNonZeroOddNumber + * a non-zero, odd number used as the initial value. 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 int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final T object, + final boolean testTransients, final Class reflectUpToClass, final String... excludeFields) { + Validate.notNull(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}. + *

+ * + *

+ * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

+ * + *

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

+ * + *

+ * 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 {@code Object}. + *

+ * + *

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

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

+ * + *

+ * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

+ * + *

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

+ * + *

+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * {@code Object}. + *

+ * + *

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

+ * + * @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 excludeFields) { + return reflectionHashCode(object, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + // ------------------------------------------------------------------------- + + /** + *

+ * Uses reflection to build a valid hash code from the fields of {@code object}. + *

+ * + *

+ * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

+ * + *

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

+ * + *

+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * {@code Object}. + *

+ * + *

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

+ * + * @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 registry = getRegistry(); + if (registry == null) { + registry = new HashSet<>(); + REGISTRY.set(registry); + } + registry.add(new IDKey(value)); + } + + /** + *

+ * Unregisters the given object. + *

+ * + *

+ * Used by the reflection methods to avoid infinite loops. + * + * @param value + * The object to unregister. + * @since 2.3 + */ + private static void unregister(final Object value) { + final Set 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. + *

+ * + *

+ * Prime numbers are preferred, especially for the multiplier. + *

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

+ *

+ * This adds {@code 1} when true, and {@code 0} when false to the {@code hashCode}. + *

+ *

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

+ *

+ * This is in accordance with the Effective Java design. + *

+ * + * @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 {@code 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 (object.getClass().isArray()) { + // 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 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 multi dimensional arrays + if (object instanceof long[]) { + append((long[]) object); + } else if (object instanceof int[]) { + append((int[]) object); + } else if (object instanceof short[]) { + append((short[]) object); + } else if (object instanceof char[]) { + append((char[]) object); + } else if (object instanceof byte[]) { + append((byte[]) object); + } else if (object instanceof double[]) { + append((double[]) object); + } else if (object instanceof float[]) { + append((float[]) object); + } else if (object instanceof boolean[]) { + append((boolean[]) object); + } else { + // Not an array of primitives + append((Object[]) object); + } + } + + /** + *

+ * Append a {@code hashCode} for an {@code 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; + } + + /** + *

+ * 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 + */ + public int toHashCode() { + return iTotal; + } + + /** + * Returns the computed {@code hashCode}. + * + * @return {@code hashCode} based on the fields appended + * + * @since 3.0 + */ + @Override + public Integer build() { + return Integer.valueOf(toHashCode()); + } + + /** + *

+ * 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(); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/HashCodeExclude.java b/after/src/main/java/org/apache/commons/lang3/builder/HashCodeExclude.java new file mode 100755 index 0000000..d1c3661 --- /dev/null +++ b/after/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/after/src/main/java/org/apache/commons/lang3/builder/IDKey.java b/after/src/main/java/org/apache/commons/lang3/builder/IDKey.java new file mode 100644 index 0000000..426848a --- /dev/null +++ b/after/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 + id = System.identityHashCode(_value); + // There have been some cases (LANG-459) that return the + // same identity hash code for different objects. So + // the value is also added to disambiguate these cases. + value = _value; + } + + /** + * returns 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/after/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java b/after/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java new file mode 100644 index 0000000..6ff7c8d --- /dev/null +++ b/after/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}.

+ * + *

To use this class write code as follows:

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

+ * This will produce a toString of the format:
+ * Person@7f54[
+ *   name=Stephen,
+ *   age=29,
+ *   smoker=false,
+ *   job=Job@43cd2[
+ *     title=Manager
+ *   ]
+ * ] + *
+ *

+ * + * @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/after/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java b/after/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java new file mode 100644 index 0000000..6895869 --- /dev/null +++ b/after/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}.

+ * + *

To use this class write code as follows:

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

This will produce a toString of the format: + * {@code Person@7f54[name=Stephen,age=29,smoker=false,job=Job@43cd2[title=Manager]]}

+ * + * @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 {@code Class}. + * By default, this method always returns {@code true}, but may be overwritten by + * sub-classes to filter specific classes. + * + * @param clazz + * The class to test. + * @return Whether or not to recursively format the given {@code Class}. + */ + protected boolean accept(final Class clazz) { + return true; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java b/after/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java new file mode 100644 index 0000000..5b60bd8 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.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.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. + *

+ *

+ * All non-static, non-transient fields (including inherited fields) + * of the objects to diff are discovered using reflection and compared + * for differences. + *

+ * + *

+ * To use this class, write code as follows: + *

+ * + *
+ * public class Person implements Diffable<Person> {
+ *   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();
+ *   }
+ * }
+ * 
+ * + *

+ * The {@code ToStringStyle} passed to the constructor is embedded in the + * returned {@code DiffResult} and influences the style of the + * {@code DiffResult.toString()} method. This style choice can be overridden by + * calling {@link DiffResult#toString(ToStringStyle)}. + *

+ * @param + * type of the left and right object to diff. + * @see Diffable + * @see Diff + * @see DiffResult + * @see ToStringStyle + * @since 3.6 + */ +public class ReflectionDiffBuilder implements Builder> { + + private final Object left; + private final Object right; + private final DiffBuilder diffBuilder; + + /** + *

+ * Constructs a builder for the specified objects with the specified style. + *

+ * + *

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

+ * @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 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/after/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java b/after/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java new file mode 100644 index 0000000..a763029 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.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.builder; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.lang3.ArraySorter; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.Validate; + +/** + *

+ * Assists in implementing {@link Object#toString()} methods using reflection. + *

+ *

+ * This class uses reflection to determine the fields to append. Because these fields are usually private, the class + * uses {@link java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObject[], boolean)} to + * change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions are + * set up correctly. + *

+ *

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

+ *

+ * A typical invocation for this method would look like: + *

+ *
+ * public String toString() {
+ *     return ReflectionToStringBuilder.toString(this);
+ * }
+ * 
+ *

+ * You can also use the builder to debug 3rd party objects: + *

+ *
+ * System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
+ * 
+ *

+ * A subclass can control field output by overriding the methods: + *

+ *
    + *
  • {@link #accept(java.lang.reflect.Field)}
  • + *
  • {@link #getValue(java.lang.reflect.Field)}
  • + *
+ *

+ * For example, this method does not include the {@code password} field in the returned {@code String}: + *

+ *
+ * public String toString() {
+ *     return (new ReflectionToStringBuilder(this) {
+ *         protected boolean accept(Field f) {
+ *             return super.accept(f) && !f.getName().equals("password");
+ *         }
+ *     }).toString();
+ * }
+ * 
+ *

+ * Alternatively the {@link ToStringExclude} annotation can be used to exclude fields from being incorporated in the + * result. + *

+ *

+ * It is also possible to use the {@link ToStringSummary} annotation to output the summary information instead of the + * detailed information of a field. + *

+ *

+ * The exact format of the {@code toString} is determined by the {@link ToStringStyle} passed into the constructor. + *

+ * + *

+ * Note: 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}. + *

+ * + * @since 2.0 + */ +public class ReflectionToStringBuilder extends ToStringBuilder { + + /** + *

+ * Builds a {@code toString} value using the default {@code ToStringStyle} through reflection. + *

+ * + *

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

+ * + *

+ * Transient members will be not be included, as they are likely derived. Static fields will not be included. + * Superclass fields will be appended. + *

+ * + * @param object + * the Object to be output + * @return the String result + * @throws IllegalArgumentException + * if the Object is {@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. + *

+ * + *

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

+ * + *

+ * Transient members will be not be included, as they are likely derived. Static fields will not be included. + * Superclass fields will be appended. + *

+ * + *

+ * If the style is {@code null}, the default {@code ToStringStyle} is used. + *

+ * + * @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 {@code 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. + *

+ * + *

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

+ * + *

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

+ * + *

+ * Static fields will not be included. Superclass fields will be appended. + *

+ * + *

+ * If the style is {@code null}, the default {@code ToStringStyle} is used. + *

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

+ * + *

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

+ * + *

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

+ * + *

+ * If the {@code outputStatics} is {@code true}, static fields will be output, otherwise they are + * ignored. + *

+ * + *

+ * Static fields will not be included. Superclass fields will be appended. + *

+ * + *

+ * If the style is {@code null}, the default {@code ToStringStyle} is used. + *

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

+ * + *

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

+ * + *

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

+ * + *

+ * If the {@code outputStatics} is {@code true}, static fields will be output, otherwise they are + * ignored. + *

+ * + *

+ * Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as + * {@code java.lang.Object}. + *

+ * + *

+ * If the style is {@code null}, the default {@code ToStringStyle} is used. + *

+ * + * @param + * 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 String toString( + final T object, final ToStringStyle style, final boolean outputTransients, + final boolean outputStatics, final Class reflectUpToClass) { + return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics) + .toString(); + } + + /** + *

+ * Builds a {@code toString} value through reflection. + *

+ * + *

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

+ * + *

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

+ * + *

+ * If the {@code outputStatics} is {@code true}, static fields will be output, otherwise they are + * ignored. + *

+ * + *

+ * Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as + * {@code java.lang.Object}. + *

+ * + *

+ * If the style is {@code null}, the default {@code ToStringStyle} is used. + *

+ * + * @param + * 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 String toString( + final T object, final ToStringStyle style, final boolean outputTransients, + final boolean outputStatics, final boolean excludeNullValues, final Class reflectUpToClass) { + return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics, excludeNullValues) + .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 excludeFieldNames) { + return toStringExclude(object, toNoNullStringArray(excludeFieldNames)); + } + + /** + * 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 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) { + final List list = new ArrayList<>(array.length); + for (final Object e : array) { + if (e != null) { + list.add(e.toString()); + } + } + return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } + + + /** + * Builds a String for a toString method excluding the given field names. + * + * @param object + * The object to "toString". + * @param excludeFieldNames + * The field names to exclude + * @return The toString value. + */ + public static String toStringExclude(final Object object, final String... excludeFieldNames) { + return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString(); + } + + private static Object checkNotNull(final Object obj) { + return Validate.notNull(obj, "obj"); + } + + /** + * 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; + + /** + * The last super class to stop appending fields for. + */ + private Class upToClass; + + /** + *

+ * Constructor. + *

+ * + *

+ * This constructor outputs using the default style set with {@code setDefaultStyle}. + *

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

+ * Constructor. + *

+ * + *

+ * If the style is {@code null}, the default style is used. + *

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

+ * Constructor. + *

+ * + *

+ * If the style is {@code null}, the default style is used. + *

+ * + *

+ * If the buffer is {@code null}, a new one is created. + *

+ * + * @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 {@code 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(checkNotNull(object), style, buffer); + } + + /** + * Constructor. + * + * @param + * 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 {@code 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 ReflectionToStringBuilder( + final T object, final ToStringStyle style, final StringBuffer buffer, + final Class reflectUpToClass, final boolean outputTransients, final boolean outputStatics) { + super(checkNotNull(object), style, buffer); + this.setUpToClass(reflectUpToClass); + this.setAppendTransients(outputTransients); + this.setAppendStatics(outputStatics); + } + + /** + * Constructor. + * + * @param + * 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 {@code 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 ReflectionToStringBuilder( + final T object, final ToStringStyle style, final StringBuffer buffer, + final Class reflectUpToClass, final boolean outputTransients, final boolean outputStatics, + final boolean excludeNullValues) { + super(checkNotNull(object), style, buffer); + this.setUpToClass(reflectUpToClass); + this.setAppendTransients(outputTransients); + this.setAppendStatics(outputStatics); + this.setExcludeNullValues(excludeNullValues); + } + + /** + * Returns whether or not to append the given {@code Field}. + *
    + *
  • Transient fields are appended only if {@link #isAppendTransients()} returns {@code true}. + *
  • Static fields are appended only if {@link #isAppendStatics()} returns {@code true}. + *
  • Inner class fields are not appended.
  • + *
+ * + * @param field + * The Field to test. + * @return Whether or not to append the given {@code 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; + } + return !field.isAnnotationPresent(ToStringExclude.class); + } + + /** + *

+ * Appends the fields and values defined by the given object of the given Class. + *

+ * + *

+ * If a cycle is detected as an object is "toString()'ed", such an object is rendered as if + * {@code Object.toString()} had been called and not implemented by the object. + *

+ * + * @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()); + } + } + } + } + + /** + * @return Returns the excludeFieldNames. + */ + public String[] getExcludeFieldNames() { + return this.excludeFieldNames.clone(); + } + + /** + *

+ * Gets the last super class to stop appending fields for. + *

+ * + * @return The last super class to stop appending fields for. + */ + public Class getUpToClass() { + return this.upToClass; + } + + /** + *

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

+ * Append to the {@code toString} an {@code 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 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 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 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(); + } + Class clazz = this.getObject().getClass(); + this.appendFieldsIn(clazz); + while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) { + clazz = clazz.getSuperclass(); + this.appendFieldsIn(clazz); + } + return super.toString(); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java b/after/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java new file mode 100644 index 0000000..72cd70f --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java @@ -0,0 +1,558 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +/** + *

Works with {@link ToStringBuilder} to create a {@code toString}.

+ * + *

This class is intended to be used as a singleton. + * There is no need to instantiate a new style each time. + * Simply instantiate the class once, customize the values as required, and + * store the result in a public static final variable for the rest of the + * program to access.

+ * + * @since 1.0 + */ +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() { // NOPMD as this is implementing the abstract class + return super.isUseClassName(); + } + + /** + *

Sets whether to use the class name.

+ * + * @param useClassName the new useClassName flag + */ + @Override + public void setUseClassName(final boolean useClassName) { // NOPMD as this is implementing the abstract class + super.setUseClassName(useClassName); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to output short or long class names.

+ * + * @return the current useShortClassName flag + * @since 2.0 + */ + @Override + public boolean isUseShortClassName() { // NOPMD as this is implementing the abstract class + return super.isUseShortClassName(); + } + + /** + *

Sets whether to output short or long class names.

+ * + * @param useShortClassName the new useShortClassName flag + * @since 2.0 + */ + @Override + public void setUseShortClassName(final boolean useShortClassName) { // NOPMD as this is implementing the abstract class + super.setUseShortClassName(useShortClassName); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use the identity hash code.

+ * @return the current useIdentityHashCode flag + */ + @Override + public boolean isUseIdentityHashCode() { // NOPMD as this is implementing the abstract class + return super.isUseIdentityHashCode(); + } + + /** + *

Sets whether to use the identity hash code.

+ * + * @param useIdentityHashCode the new useIdentityHashCode flag + */ + @Override + public void setUseIdentityHashCode(final boolean useIdentityHashCode) { // NOPMD as this is implementing the abstract class + super.setUseIdentityHashCode(useIdentityHashCode); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use the field names passed in.

+ * + * @return the current useFieldNames flag + */ + @Override + public boolean isUseFieldNames() { // NOPMD as this is implementing the abstract class + return super.isUseFieldNames(); + } + + /** + *

Sets whether to use the field names passed in.

+ * + * @param useFieldNames the new useFieldNames flag + */ + @Override + public void setUseFieldNames(final boolean useFieldNames) { // NOPMD as this is implementing the abstract class + super.setUseFieldNames(useFieldNames); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use full detail when the caller doesn't + * specify.

+ * + * @return the current defaultFullDetail flag + */ + @Override + public boolean isDefaultFullDetail() { // NOPMD as this is implementing the abstract class + return super.isDefaultFullDetail(); + } + + /** + *

Sets whether to use full detail when the caller doesn't + * specify.

+ * + * @param defaultFullDetail the new defaultFullDetail flag + */ + @Override + public void setDefaultFullDetail(final boolean defaultFullDetail) { // NOPMD as this is implementing the abstract class + super.setDefaultFullDetail(defaultFullDetail); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to output array content detail.

+ * + * @return the current array content detail setting + */ + @Override + public boolean isArrayContentDetail() { // NOPMD as this is implementing the abstract class + return super.isArrayContentDetail(); + } + + /** + *

Sets whether to output array content detail.

+ * + * @param arrayContentDetail the new arrayContentDetail flag + */ + @Override + public void setArrayContentDetail(final boolean arrayContentDetail) { // NOPMD as this is implementing the abstract class + super.setArrayContentDetail(arrayContentDetail); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array start text.

+ * + * @return the current array start text + */ + @Override + public String getArrayStart() { // NOPMD as this is implementing the abstract class + return super.getArrayStart(); + } + + /** + *

Sets the array start text.

+ * + *

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

+ * + * @param arrayStart the new array start text + */ + @Override + public void setArrayStart(final String arrayStart) { // NOPMD as this is implementing the abstract class + super.setArrayStart(arrayStart); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array end text.

+ * + * @return the current array end text + */ + @Override + public String getArrayEnd() { // NOPMD as this is implementing the abstract class + return super.getArrayEnd(); + } + + /** + *

Sets the array end text.

+ * + *

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

+ * + * @param arrayEnd the new array end text + */ + @Override + public void setArrayEnd(final String arrayEnd) { // NOPMD as this is implementing the abstract class + super.setArrayEnd(arrayEnd); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array separator text.

+ * + * @return the current array separator text + */ + @Override + public String getArraySeparator() { // NOPMD as this is implementing the abstract class + return super.getArraySeparator(); + } + + /** + *

Sets the array separator text.

+ * + *

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

+ * + * @param arraySeparator the new array separator text + */ + @Override + public void setArraySeparator(final String arraySeparator) { // NOPMD as this is implementing the abstract class + super.setArraySeparator(arraySeparator); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the content start text.

+ * + * @return the current content start text + */ + @Override + public String getContentStart() { // NOPMD as this is implementing the abstract class + return super.getContentStart(); + } + + /** + *

Sets the content start text.

+ * + *

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

+ * + * @param contentStart the new content start text + */ + @Override + public void setContentStart(final String contentStart) { // NOPMD as this is implementing the abstract class + super.setContentStart(contentStart); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the content end text.

+ * + * @return the current content end text + */ + @Override + public String getContentEnd() { // NOPMD as this is implementing the abstract class + return super.getContentEnd(); + } + + /** + *

Sets the content end text.

+ * + *

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

+ * + * @param contentEnd the new content end text + */ + @Override + public void setContentEnd(final String contentEnd) { // NOPMD as this is implementing the abstract class + super.setContentEnd(contentEnd); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the field name value separator text.

+ * + * @return the current field name value separator text + */ + @Override + public String getFieldNameValueSeparator() { // NOPMD as this is implementing the abstract class + return super.getFieldNameValueSeparator(); + } + + /** + *

Sets the field name value separator text.

+ * + *

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

+ * + * @param fieldNameValueSeparator the new field name value separator text + */ + @Override + public void setFieldNameValueSeparator(final String fieldNameValueSeparator) { // NOPMD as this is implementing the abstract class + super.setFieldNameValueSeparator(fieldNameValueSeparator); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the field separator text.

+ * + * @return the current field separator text + */ + @Override + public String getFieldSeparator() { // NOPMD as this is implementing the abstract class + return super.getFieldSeparator(); + } + + /** + *

Sets the field separator text.

+ * + *

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

+ * + * @param fieldSeparator the new field separator text + */ + @Override + public void setFieldSeparator(final String fieldSeparator) { // NOPMD as this is implementing the abstract class + super.setFieldSeparator(fieldSeparator); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether the field separator should be added at the start + * of each buffer.

+ * + * @return the fieldSeparatorAtStart flag + * @since 2.0 + */ + @Override + public boolean isFieldSeparatorAtStart() { // NOPMD as this is implementing the abstract class + return super.isFieldSeparatorAtStart(); + } + + /** + *

Sets whether the field separator should be added at the start + * of each buffer.

+ * + * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag + * @since 2.0 + */ + @Override + public void setFieldSeparatorAtStart(final boolean fieldSeparatorAtStart) { // NOPMD as this is implementing the abstract class + super.setFieldSeparatorAtStart(fieldSeparatorAtStart); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether the field separator should be added at the end + * of each buffer.

+ * + * @return fieldSeparatorAtEnd flag + * @since 2.0 + */ + @Override + public boolean isFieldSeparatorAtEnd() { // NOPMD as this is implementing the abstract class + return super.isFieldSeparatorAtEnd(); + } + + /** + *

Sets whether the field separator should be added at the end + * of each buffer.

+ * + * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag + * @since 2.0 + */ + @Override + public void setFieldSeparatorAtEnd(final boolean fieldSeparatorAtEnd) { // NOPMD as this is implementing the abstract class + 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() { // NOPMD as this is implementing the abstract class + return super.getNullText(); + } + + /** + *

Sets the text to output when {@code null} found.

+ * + *

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

+ * + * @param nullText the new text to output when {@code null} found + */ + @Override + public void setNullText(final String nullText) { // NOPMD as this is implementing the abstract class + super.setNullText(nullText); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the text to output when a {@code Collection}, + * {@code Map} or {@code Array} size is output.

+ * + *

This is output before the size value.

+ * + * @return the current start of size text + */ + @Override + public String getSizeStartText() { // NOPMD as this is implementing the abstract class + return super.getSizeStartText(); + } + + /** + *

Sets the start text to output when a {@code Collection}, + * {@code Map} or {@code Array} size is output.

+ * + *

This is output before the size value.

+ * + *

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

+ * + * @param sizeStartText the new start of size text + */ + @Override + public void setSizeStartText(final String sizeStartText) { // NOPMD as this is implementing the abstract class + super.setSizeStartText(sizeStartText); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the end text to output when a {@code Collection}, + * {@code Map} or {@code Array} size is output.

+ * + *

This is output after the size value.

+ * + * @return the current end of size text + */ + @Override + public String getSizeEndText() { // NOPMD as this is implementing the abstract class + return super.getSizeEndText(); + } + + /** + *

Sets the end text to output when a {@code Collection}, + * {@code Map} or {@code Array} size is output.

+ * + *

This is output after the size value.

+ * + *

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

+ * + * @param sizeEndText the new end of size text + */ + @Override + public void setSizeEndText(final String sizeEndText) { // NOPMD as this is implementing the abstract class + super.setSizeEndText(sizeEndText); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the start text to output when an {@code Object} is + * output in summary mode.

+ * + *

This is output before the size value.

+ * + * @return the current start of summary text + */ + @Override + public String getSummaryObjectStartText() { // NOPMD as this is implementing the abstract class + return super.getSummaryObjectStartText(); + } + + /** + *

Sets the start text to output when an {@code Object} is + * output in summary mode.

+ * + *

This is output before the size value.

+ * + *

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

+ * + * @param summaryObjectStartText the new start of summary text + */ + @Override + public void setSummaryObjectStartText(final String summaryObjectStartText) { // NOPMD as this is implementing the abstract class + super.setSummaryObjectStartText(summaryObjectStartText); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the end text to output when an {@code Object} is + * output in summary mode.

+ * + *

This is output after the size value.

+ * + * @return the current end of summary text + */ + @Override + public String getSummaryObjectEndText() { // NOPMD as this is implementing the abstract class + return super.getSummaryObjectEndText(); + } + + /** + *

Sets the end text to output when an {@code Object} is + * output in summary mode.

+ * + *

This is output after the size value.

+ * + *

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

+ * + * @param summaryObjectEndText the new end of summary text + */ + @Override + public void setSummaryObjectEndText(final String summaryObjectEndText) { // NOPMD as this is implementing the abstract class + super.setSummaryObjectEndText(summaryObjectEndText); + } + + //--------------------------------------------------------------------- + +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java b/after/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java new file mode 100644 index 0000000..631c6e4 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java @@ -0,0 +1,1077 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.Validate; + +/** + *

Assists in implementing {@link Object#toString()} methods.

+ * + *

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:

+ *
    + *
  • allowing field names
  • + *
  • handling all types consistently
  • + *
  • handling nulls consistently
  • + *
  • outputting arrays and multi-dimensional arrays
  • + *
  • enabling the detail level to be controlled for Objects and Collections
  • + *
  • handling class hierarchies
  • + *
+ * + *

To use this class write code as follows:

+ * + *
+ * public class Person {
+ *   String name;
+ *   int age;
+ *   boolean smoker;
+ *
+ *   ...
+ *
+ *   public String toString() {
+ *     return new ToStringBuilder(this).
+ *       append("name", name).
+ *       append("age", age).
+ *       append("smoker", smoker).
+ *       toString();
+ *   }
+ * }
+ * 
+ * + *

This will produce a toString of the format: + * {@code Person@7f54[name=Stephen,age=29,smoker=false]}

+ * + *

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

+ * + *

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.

+ * + *

A typical invocation for this method would look like:

+ * + *
+ * public String toString() {
+ *   return ToStringBuilder.reflectionToString(this);
+ * }
+ * 
+ * + *

You can also use the builder to debug 3rd party objects:

+ * + *
+ * System.out.println("An object: " + ToStringBuilder.reflectionToString(anObject));
+ * 
+ * + *

The exact format of the {@code toString} is determined by + * the {@link ToStringStyle} passed into the constructor.

+ * + * @since 1.0 + */ +public class ToStringBuilder implements Builder { + + /** + * The default style of output to use, not null. + */ + private static volatile ToStringStyle defaultStyle = ToStringStyle.DEFAULT_STYLE; + + //---------------------------------------------------------------------------- + + /** + *

Gets the default {@code ToStringStyle} to use.

+ * + *

This method gets a singleton default value, typically for the whole JVM. + * Changing this default should generally only be done during application startup. + * It is recommended to pass a {@code ToStringStyle} to the constructor instead + * of using this global default.

+ * + *

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.

+ * + *

One reason for changing the default could be to have a verbose style during + * development and a compact style in production.

+ * + * @return the default {@code ToStringStyle}, never null + */ + public static ToStringStyle getDefaultStyle() { + return defaultStyle; + } + + /** + *

Sets the default {@code ToStringStyle} to use.

+ * + *

This method sets a singleton default value, typically for the whole JVM. + * Changing this default should generally only be done during application startup. + * It is recommended to pass a {@code ToStringStyle} to the constructor instead + * of changing this global default.

+ * + *

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

+ * + * @param style the default {@code ToStringStyle} + * @throws IllegalArgumentException if the style is {@code null} + */ + public static void setDefaultStyle(final ToStringStyle style) { + defaultStyle = Validate.notNull(style, "style"); + } + + //---------------------------------------------------------------------------- + /** + *

Uses {@code 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 {@code 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 {@code 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 {@code ReflectionToStringBuilder} to generate a + * {@code toString} for the specified object.

+ * + * @param 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 String reflectionToString( + final T object, + final ToStringStyle style, + final boolean outputTransients, + final Class reflectUpToClass) { + return ReflectionToStringBuilder.toString(object, style, outputTransients, false, reflectUpToClass); + } + + //---------------------------------------------------------------------------- + + /** + * Current toString buffer, not null. + */ + private final StringBuffer buffer; + /** + * The object being output, may be null. + */ + private final Object object; + /** + * The style of output to use, not null. + */ + private final ToStringStyle style; + + /** + *

Constructs a builder for the specified object using the default output style.

+ * + *

This default style is obtained from {@link #getDefaultStyle()}.

+ * + * @param object the Object to build a {@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.

+ * + *

If the style is {@code null}, the default style is used.

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

+ * + *

If the style is {@code null}, the default style is used.

+ * + *

If the buffer is {@code null}, a new one is created.

+ * + * @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 {@code 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 {@code 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 {@code 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.

+ * + *

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

+ * + *

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.

+ * + *

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

+ * + *

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

+ * + *

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

+ * + *

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

+ * + *

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 long[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the {@code toString} an {@code 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 {@code 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 {@code 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 {@code Object} + * array.

+ * + *

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

+ * + *

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 Object toString() + * method. Appends the class name followed by + * {@link System#identityHashCode(java.lang.Object)}.

+ * + * @param srcObject the {@code Object} whose class name and id to output + * @return this + * @since 2.0 + */ + public ToStringBuilder appendAsObjectToString(final Object srcObject) { + ObjectUtils.identityToString(this.getStringBuffer(), srcObject); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append the {@code toString} from the superclass.

+ * + *

This method assumes that the superclass uses the same {@code ToStringStyle} + * as this one.

+ * + *

If {@code superToString} is {@code null}, no change is made.

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

+ * + *

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.

+ * + *
+     *   private AnotherObject delegate;
+     *   private String fieldInThisClass;
+     *
+     *   public String toString() {
+     *     return new ToStringBuilder(this).
+     *       appendToString(delegate.toString()).
+     *       append(fieldInThisClass).
+     *       toString();
+     *   }
+ * + *

This method assumes that the other object uses the same {@code ToStringStyle} + * as this one.

+ * + *

If the {@code toString} is {@code null}, no change is made.

+ * + * @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 {@code Object} being output.

+ * + * @return The object being output. + * @since 2.0 + */ + public Object getObject() { + return object; + } + + /** + *

Gets the {@code StringBuffer} being populated.

+ * + * @return the {@code StringBuffer} being populated + */ + public StringBuffer getStringBuffer() { + return buffer; + } + + //---------------------------------------------------------------------------- + + /** + *

Gets the {@code ToStringStyle} being used.

+ * + * @return the {@code ToStringStyle} being used + * @since 2.0 + */ + public ToStringStyle getStyle() { + return style; + } + + /** + *

Returns the built {@code toString}.

+ * + *

This method appends the end of data indicator, and can only be called once. + * Use {@link #getStringBuffer} to get the current string state.

+ * + *

If the object is {@code null}, return the style's {@code nullText}

+ * + * @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/after/src/main/java/org/apache/commons/lang3/builder/ToStringExclude.java b/after/src/main/java/org/apache/commons/lang3/builder/ToStringExclude.java new file mode 100755 index 0000000..2b399f2 --- /dev/null +++ b/after/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/after/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java b/after/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java new file mode 100644 index 0000000..c2860d2 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java @@ -0,0 +1,2669 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 {@code String} formatting for {@link ToStringBuilder}. + * The main public interface is always via {@code ToStringBuilder}.

+ * + *

These classes are intended to be used as {@code Singletons}. + * There is no need to instantiate a new style each time. A program + * will generally use one of the predefined constants on this class. + * Alternatively, the {@link StandardToStringStyle} class can be used + * to set the individual settings. Thus most styles can be achieved + * without subclassing.

+ * + *

If required, a subclass can override as many or as few of the + * methods as it requires. Each object type (from {@code boolean} + * to {@code long} to {@code Object} to {@code int[]}) has + * its own methods to output it. Most have two versions, detail and summary. + * + *

For example, the detail version of the array based methods will + * output the whole array, whereas the summary method will just output + * the array length.

+ * + *

If you want to format the output of certain objects, such as dates, you + * must create a subclass and override a method. + *

+ *
+ * public class MyStyle extends ToStringStyle {
+ *   protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
+ *     if (value instanceof Date) {
+ *       value = new SimpleDateFormat("yyyy-MM-dd").format(value);
+ *     }
+ *     buffer.append(value);
+ *   }
+ * }
+ * 
+ * + * @since 1.0 + */ +@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: + * + *
+     * Person@182f0db[name=John Doe,age=33,smoker=false]
+     * 
+ */ + 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: + * + *
+     * Person@182f0db[
+     *   name=John Doe
+     *   age=33
+     *   smoker=false
+     * ]
+     * 
+ */ + public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle(); + + /** + * The no field names toString style. Using the + * {@code Person} example from {@link ToStringBuilder}, the output + * would look like this: + * + *
+     * Person@182f0db[John Doe,33,false]
+     * 
+ */ + public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle(); + + /** + * The short prefix toString style. Using the {@code Person} example + * from {@link ToStringBuilder}, the output would look like this: + * + *
+     * Person[name=John Doe,age=33,smoker=false]
+     * 
+ * + * @since 2.1 + */ + public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle(); + + /** + * The simple toString style. Using the {@code Person} + * example from {@link ToStringBuilder}, the output would look like this: + * + *
+     * John Doe,33,false
+     * 
+ */ + 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: + * + *
+     * [name=John Doe,age=33,smoker=false]
+     * 
+ * + * @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: + * + *
+     * {"name": "John Doe", "age": 33, "smoker": true}
+     * 
+ * + * Note: 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 json.org + */ + 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> 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 + */ + static Map 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 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 m = getRegistry(); + if (m == null) { + REGISTRY.set(new WeakHashMap<>()); + } + getRegistry().put(value, null); + } + } + + /** + *

+ * Unregisters the given object. + *

+ * + *

+ * Used by the reflection methods to avoid infinite loops. + *

+ * + * @param value + * The object to unregister. + */ + static void unregister(final Object value) { + if (value != null) { + final Map 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 '{'. + */ + 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 '<null>'}. + */ + private String nullText = ""; + + /** + * The summary size text start {@code '<size'}. + */ + private String sizeStartText = "Constructor.

+ */ + protected ToStringStyle() { + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} the superclass toString.

+ *

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

+ * + *

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

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

Append to the {@code toString} another toString.

+ *

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

+ * + *

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

+ * + * @param buffer the {@code 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); + } + } + } + + /** + *

Append to the {@code toString} the start of data indicator.

+ * + * @param buffer the {@code StringBuffer} to populate + * @param object the {@code 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); + } + } + } + + /** + *

Append to the {@code toString} the end of data indicator.

+ * + * @param buffer the {@code StringBuffer} to populate + * @param object the {@code 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 {@code StringBuffer} to populate + * @since 2.0 + */ + protected void removeLastFieldSeparator(final StringBuffer buffer) { + if (StringUtils.endsWith(buffer, fieldSeparator)) { + buffer.setLength(buffer.length() - fieldSeparator.length()); + } + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} an {@code Object} + * value, printing the full {@code toString} of the + * {@code Object} passed in.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} an {@code Object}, + * correctly interpreting its type.

+ * + *

This method performs the main lookup by Class type to correctly + * route arrays, {@code Collections}, {@code Maps} and + * {@code Objects} to the appropriate method.

+ * + *

Either detail or summary views can be specified.

+ * + *

If a cycle is detected, an object will be appended with the + * {@code Object.toString()} format.

+ * + * @param buffer the {@code 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 (value.getClass().isArray()) { + if (detail) { + appendDetail(buffer, fieldName, (Object[]) value); + } else { + appendSummary(buffer, fieldName, (Object[]) value); + } + + } else if (detail) { + appendDetail(buffer, fieldName, value); + } else { + appendSummary(buffer, fieldName, value); + } + } finally { + unregister(value); + } + } + + /** + *

Append to the {@code toString} an {@code 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 {@code 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); + } + + /** + *

Append to the {@code toString} an {@code Object} + * value, printing the full detail of the {@code Object}.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code Collection}.

+ * + * @param buffer the {@code StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param coll the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code Map}.

+ * + * @param buffer the {@code StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param map the {@code 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); + } + + /** + *

Append to the {@code toString} an {@code Object} + * value, printing a summary of the {@code Object}.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code long} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code long} + * value.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} an {@code int} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} an {@code int} + * value.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code short} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code short} + * value.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code byte} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code byte} + * value.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code char} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code char} + * value.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code double} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code double} + * value.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code float} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code float} + * value.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code boolean} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a {@code boolean} + * value.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} an {@code Object} + * array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} the detail of an + * {@code Object} array.

+ * + * @param buffer the {@code 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++) { + final Object item = array[i]; + appendDetail(buffer, fieldName, i, item); + } + buffer.append(arrayEnd); + } + + /** + *

Append to the {@code toString} the detail of an + * {@code Object} array item.

+ * + * @param buffer the {@code 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); + } + } + + /** + *

Append to the {@code toString} the detail of an array type.

+ * + * @param buffer the {@code 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++) { + final Object item = Array.get(array, i); + appendDetail(buffer, fieldName, i, item); + } + buffer.append(arrayEnd); + } + + /** + *

Append to the {@code toString} a summary of an + * {@code Object} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code long} + * array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the detail of a + * {@code long} array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a summary of a + * {@code long} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} an {@code int} + * array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the detail of an + * {@code int} array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a summary of an + * {@code int} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code short} + * array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the detail of a + * {@code short} array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a summary of a + * {@code short} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code byte} + * array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the detail of a + * {@code byte} array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a summary of a + * {@code byte} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code char} + * array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the detail of a + * {@code char} array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a summary of a + * {@code char} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code double} + * array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the detail of a + * {@code double} array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a summary of a + * {@code double} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code float} + * array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the detail of a + * {@code float} array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a summary of a + * {@code float} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} a {@code boolean} + * array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the detail of a + * {@code boolean} array.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a summary of a + * {@code boolean} array.

+ * + * @param buffer the {@code 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the {@code toString} the class name.

+ * + * @param buffer the {@code StringBuffer} to populate + * @param object the {@code 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()); + } + } + } + + /** + *

Append the {@link System#identityHashCode(java.lang.Object)}.

+ * + * @param buffer the {@code StringBuffer} to populate + * @param object the {@code 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(Integer.toHexString(System.identityHashCode(object))); + } + } + + /** + *

Append to the {@code toString} the content start.

+ * + * @param buffer the {@code StringBuffer} to populate + */ + protected void appendContentStart(final StringBuffer buffer) { + buffer.append(contentStart); + } + + /** + *

Append to the {@code toString} the content end.

+ * + * @param buffer the {@code StringBuffer} to populate + */ + protected void appendContentEnd(final StringBuffer buffer) { + buffer.append(contentEnd); + } + + /** + *

Append to the {@code toString} an indicator for {@code null}.

+ * + *

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

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} the field separator.

+ * + * @param buffer the {@code StringBuffer} to populate + */ + protected void appendFieldSeparator(final StringBuffer buffer) { + buffer.append(fieldSeparator); + } + + /** + *

Append to the {@code toString} the field start.

+ * + * @param buffer the {@code 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); + } + } + + /** + *

Append to the {@code toString} the field end.

+ * + * @param buffer the {@code 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); + } + + /** + *

Append to the {@code toString} a size summary.

+ * + *

The size summary is used to summarize the contents of + * {@code Collections}, {@code Maps} and arrays.

+ * + *

The output consists of a prefix, the passed in size + * and a suffix.

+ * + *

The default format is {@code '<size=n>'}.

+ * + * @param buffer the {@code 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.

+ * + *

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.

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

+ * + *

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

+ * + * @param cls the {@code 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.

+ * + *

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

+ * + * @param arrayStart the new array start text + */ + protected void setArrayStart(String arrayStart) { + if (arrayStart == null) { + arrayStart = StringUtils.EMPTY; + } + this.arrayStart = arrayStart; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array end text.

+ * + * @return the current array end text + */ + protected String getArrayEnd() { + return arrayEnd; + } + + /** + *

Sets the array end text.

+ * + *

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

+ * + * @param arrayEnd the new array end text + */ + protected void setArrayEnd(String arrayEnd) { + if (arrayEnd == null) { + arrayEnd = 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.

+ * + *

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

+ * + * @param arraySeparator the new array separator text + */ + protected void setArraySeparator(String arraySeparator) { + if (arraySeparator == null) { + arraySeparator = 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.

+ * + *

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

+ * + * @param contentStart the new content start text + */ + protected void setContentStart(String contentStart) { + if (contentStart == null) { + contentStart = 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.

+ * + *

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

+ * + * @param contentEnd the new content end text + */ + protected void setContentEnd(String contentEnd) { + if (contentEnd == null) { + contentEnd = 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.

+ * + *

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

+ * + * @param fieldNameValueSeparator the new field name value separator text + */ + protected void setFieldNameValueSeparator(String fieldNameValueSeparator) { + if (fieldNameValueSeparator == null) { + fieldNameValueSeparator = 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.

+ * + *

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

+ * + * @param fieldSeparator the new field separator text + */ + protected void setFieldSeparator(String fieldSeparator) { + if (fieldSeparator == null) { + fieldSeparator = 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.

+ * + *

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

+ * + * @param nullText the new text to output when null found + */ + protected void setNullText(String nullText) { + if (nullText == null) { + nullText = StringUtils.EMPTY; + } + this.nullText = nullText; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the start text to output when a {@code Collection}, + * {@code Map} or array size is output.

+ * + *

This is output before the size value.

+ * + * @return the current start of size text + */ + protected String getSizeStartText() { + return sizeStartText; + } + + /** + *

Sets the start text to output when a {@code Collection}, + * {@code Map} or array size is output.

+ * + *

This is output before the size value.

+ * + *

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

+ * + * @param sizeStartText the new start of size text + */ + protected void setSizeStartText(String sizeStartText) { + if (sizeStartText == null) { + sizeStartText = StringUtils.EMPTY; + } + this.sizeStartText = sizeStartText; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the end text to output when a {@code Collection}, + * {@code Map} or array size is output.

+ * + *

This is output after the size value.

+ * + * @return the current end of size text + */ + protected String getSizeEndText() { + return sizeEndText; + } + + /** + *

Sets the end text to output when a {@code Collection}, + * {@code Map} or array size is output.

+ * + *

This is output after the size value.

+ * + *

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

+ * + * @param sizeEndText the new end of size text + */ + protected void setSizeEndText(String sizeEndText) { + if (sizeEndText == null) { + sizeEndText = StringUtils.EMPTY; + } + this.sizeEndText = sizeEndText; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the start text to output when an {@code Object} is + * output in summary mode.

+ * + *

This is output before the size value.

+ * + * @return the current start of summary text + */ + protected String getSummaryObjectStartText() { + return summaryObjectStartText; + } + + /** + *

Sets the start text to output when an {@code Object} is + * output in summary mode.

+ * + *

This is output before the size value.

+ * + *

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

+ * + * @param summaryObjectStartText the new start of summary text + */ + protected void setSummaryObjectStartText(String summaryObjectStartText) { + if (summaryObjectStartText == null) { + summaryObjectStartText = StringUtils.EMPTY; + } + this.summaryObjectStartText = summaryObjectStartText; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the end text to output when an {@code Object} is + * output in summary mode.

+ * + *

This is output after the size value.

+ * + * @return the current end of summary text + */ + protected String getSummaryObjectEndText() { + return summaryObjectEndText; + } + + /** + *

Sets the end text to output when an {@code Object} is + * output in summary mode.

+ * + *

This is output after the size value.

+ * + *

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

+ * + * @param summaryObjectEndText the new end of summary text + */ + protected void setSummaryObjectEndText(String summaryObjectEndText) { + if (summaryObjectEndText == null) { + summaryObjectEndText = StringUtils.EMPTY; + } + this.summaryObjectEndText = summaryObjectEndText; + } + + //---------------------------------------------------------------------------- + + /** + *

Default {@code ToStringStyle}.

+ * + *

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

+ */ + private static final class DefaultToStringStyle extends ToStringStyle { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + *

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + DefaultToStringStyle() { + } + + /** + *

Ensure {@code Singleton} after serialization.

+ * + * @return the singleton + */ + private Object readResolve() { + return DEFAULT_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

{@code ToStringStyle} that does not print out + * the field names.

+ * + *

This is an inner class rather than using + * {@code StandardToStringStyle} to ensure its immutability. + */ + private static final class NoFieldNameToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + NoFieldNameToStringStyle() { + this.setUseFieldNames(false); + } + + /** + *

Ensure {@code Singleton} after serialization.

+ * + * @return the singleton + */ + private Object readResolve() { + return NO_FIELD_NAMES_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

{@code ToStringStyle} that prints out the short + * class name and no identity hashcode.

+ * + *

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

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

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + ShortPrefixToStringStyle() { + this.setUseShortClassName(true); + this.setUseIdentityHashCode(false); + } + + /** + *

Ensure Singleton after serialization.

+ * @return the singleton + */ + private Object readResolve() { + return SHORT_PREFIX_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

{@code ToStringStyle} that does not print out the + * classname, identity hashcode, content start or field name.

+ * + *

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

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

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + SimpleToStringStyle() { + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + this.setUseFieldNames(false); + this.setContentStart(StringUtils.EMPTY); + this.setContentEnd(StringUtils.EMPTY); + } + + /** + *

Ensure Singleton after serialization.

+ * @return the singleton + */ + private Object readResolve() { + return SIMPLE_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

{@code ToStringStyle} that outputs on multiple lines.

+ * + *

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

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

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + MultiLineToStringStyle() { + this.setContentStart("["); + this.setFieldSeparator(System.lineSeparator() + " "); + this.setFieldSeparatorAtStart(true); + this.setContentEnd(System.lineSeparator() + "]"); + } + + /** + *

Ensure {@code Singleton} after serialization.

+ * + * @return the singleton + */ + private Object readResolve() { + return MULTI_LINE_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

{@code ToStringStyle} that does not print out the classname + * and identity hash code but prints content start and field names.

+ * + *

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

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

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + NoClassNameToStringStyle() { + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + } + + /** + *

Ensure {@code Singleton} after serialization.

+ * + * @return the singleton + */ + private Object readResolve() { + return NO_CLASS_NAME_STYLE; + } + + } + + // ---------------------------------------------------------------------------- + + /** + *

+ * {@code ToStringStyle} that outputs with JSON format. + *

+ * + *

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

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

+ * Constructor. + *

+ * + *

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

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

+ * Ensure {@code Singleton} after serialization. + *

+ * + * @return the singleton + */ + private Object readResolve() { + return JSON_STYLE; + } + + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/ToStringSummary.java b/after/src/main/java/org/apache/commons/lang3/builder/ToStringSummary.java new file mode 100644 index 0000000..c6dad14 --- /dev/null +++ b/after/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}. + * + *

+ * Notice that not all {@link ToStringStyle} implementations support the + * appendSummary method. + *

+ * + * @since 3.8 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ToStringSummary { + // empty +} diff --git a/after/src/main/java/org/apache/commons/lang3/builder/package-info.java b/after/src/main/java/org/apache/commons/lang3/builder/package-info.java new file mode 100644 index 0000000..0043587 --- /dev/null +++ b/after/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.

+ * + *

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.

+ * + * @see java.lang.Object#equals(Object) + * @see java.lang.Object#toString() + * @see java.lang.Object#hashCode() + * @see java.lang.Comparable#compareTo(Object) + * + * @since 1.0 + */ +package org.apache.commons.lang3.builder; diff --git a/after/src/main/java/org/apache/commons/lang3/compare/ComparableUtils.java b/after/src/main/java/org/apache/commons/lang3/compare/ComparableUtils.java new file mode 100644 index 0000000..c757a52 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/compare/ComparableUtils.java @@ -0,0 +1,208 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + *

Utility library to provide helper methods for translating {@link Comparable#compareTo} result into a boolean.

+ * + *

Example: {@code boolean x = is(myComparable).lessThanOrEqualTo(otherComparable)}

+ * + *

#ThreadSafe#

+ * + * @since 3.10 + */ +public class ComparableUtils { + + /** + * Provides access to the available methods + * + * @param the type of objects that this object may be compared against. + */ + public static class ComparableCheckBuilder> { + + 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 type of the test object + * @return a predicate for true if the tested object is between b and c + */ + public static > Predicate 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 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 > Predicate 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 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 > Predicate 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 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 > Predicate 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 type of the base object + * @return a builder object with further methods + */ + public static > ComparableCheckBuilder 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 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 > Predicate 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 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 > Predicate lt(final A b) { + return a -> is(a).lessThan(b); + } + + private ComparableUtils() {} +} diff --git a/after/src/main/java/org/apache/commons/lang3/compare/ObjectToStringComparator.java b/after/src/main/java/org/apache/commons/lang3/compare/ObjectToStringComparator.java new file mode 100644 index 0000000..2bb5840 --- /dev/null +++ b/after/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, 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/after/src/main/java/org/apache/commons/lang3/compare/package-info.java b/after/src/main/java/org/apache/commons/lang3/compare/package-info.java new file mode 100644 index 0000000..99136ed --- /dev/null +++ b/after/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/after/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java b/after/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java new file mode 100644 index 0000000..a27d7f8 --- /dev/null +++ b/after/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 the type of the value monitored by this circuit breaker + * @since 3.5 + */ +public abstract class AbstractCircuitBreaker implements CircuitBreaker { + + /** + * The name of the open property as it is passed to registered + * change listeners. + */ + public static final String PROPERTY_NAME = "open"; + + /** The current state of this circuit breaker. */ + protected final AtomicReference state = new AtomicReference<>(State.CLOSED); + + /** An object for managing change listeners registered at this instance. */ + private final PropertyChangeSupport changeSupport; + + /** + * Creates an {@code AbstractCircuitBreaker}. It also creates an internal {@code PropertyChangeSupport}. + */ + public AbstractCircuitBreaker() { + changeSupport = new PropertyChangeSupport(this); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isOpen() { + return isOpen(state.get()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isClosed() { + return !isOpen(); + } + + /** + * {@inheritDoc} + */ + @Override + public abstract boolean checkState(); + + /** + * {@inheritDoc} + */ + @Override + public abstract boolean incrementAndCheckState(T increment); + + /** + * {@inheritDoc} + */ + @Override + public void close() { + changeState(State.CLOSED); + } + + /** + * {@inheritDoc} + */ + @Override + public void open() { + changeState(State.OPEN); + } + + /** + * Converts the given state value to a boolean open property. + * + * @param state the state to be converted + * @return the boolean open flag + */ + protected static boolean isOpen(final State state) { + return state == State.OPEN; + } + + /** + * Changes the internal state of this circuit breaker. If there is actually a change + * of the state value, all registered change listeners are notified. + * + * @param newState the new state to be set + */ + protected void changeState(final State newState) { + if (state.compareAndSet(newState.oppositeState(), newState)) { + changeSupport.firePropertyChange(PROPERTY_NAME, !isOpen(newState), isOpen(newState)); + } + } + + /** + * Adds a change listener to this circuit breaker. This listener is notified whenever + * the state of this circuit breaker changes. If the listener is + * null, it is silently ignored. + * + * @param listener the listener to be added + */ + public void addChangeListener(final PropertyChangeListener listener) { + changeSupport.addPropertyChangeListener(listener); + } + + /** + * Removes the specified change listener from this circuit breaker. + * + * @param listener the listener to be removed + */ + public void removeChangeListener(final PropertyChangeListener listener) { + changeSupport.removePropertyChangeListener(listener); + } + + /** + * An internal enumeration representing the different states of a circuit + * breaker. This class also contains some logic for performing state + * transitions. This is done to avoid complex if-conditions in the code of + * {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java new file mode 100644 index 0000000..ac4b85e --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 {@code ConcurrentInitializer} interface + * based on an {@link AtomicReference} variable. + *

+ *

+ * This class maintains a member field of type {@code AtomicReference}. It + * implements the following algorithm to create and initialize an object in its + * {@link #get()} method: + *

+ *
    + *
  • First it is checked whether the {@code AtomicReference} variable contains + * already a value. If this is the case, the value is directly returned.
  • + *
  • Otherwise the {@link #initialize()} method is called. This method must be + * defined in concrete subclasses to actually create the managed object.
  • + *
  • After the object was created by {@link #initialize()} it is checked + * whether the {@code 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.
  • + *
  • Otherwise the value stored in the {@code AtomicReference} is returned.
  • + *
+ *

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

+ *

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

+ * + * @since 3.0 + * @param the type of the object managed by this initializer class + */ +public abstract class AtomicInitializer implements ConcurrentInitializer { + /** Holds the reference to the managed object. */ + private final AtomicReference 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 {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java new file mode 100644 index 0000000..5bc91f1 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.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.concurrent; + +import java.util.concurrent.atomic.AtomicReference; + +/** + *

+ * A specialized {@code ConcurrentInitializer} implementation which is similar + * to {@link AtomicInitializer}, but ensures that the {@link #initialize()} + * method is called only once. + *

+ *

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

+ *

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

+ *

+ * From its semantics this class has the same properties as + * {@link LazyInitializer}. It is a "save" 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 + * {@code 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. + *

+ * + * @since 3.0 + * @param the type of the object managed by this initializer class + */ +public abstract class AtomicSafeInitializer implements + ConcurrentInitializer { + /** A guard which ensures that initialize() is called only once. */ + private final AtomicReference> factory = + new AtomicReference<>(); + + /** Holds the reference to the managed object. */ + private final AtomicReference reference = new AtomicReference<>(); + + /** + * 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 + * {@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()}. 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/after/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java new file mode 100644 index 0000000..963dee3 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.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.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. + *

+ *

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

+ *

+ * 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): + *

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

+ * After the construction of a {@code 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 {@code 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. + *

+ *

+ * {@code BackgroundInitializer} is a thin wrapper around a {@code Future} + * object and uses an {@code ExecutorService} for running the background + * initialization task. It is possible to pass in an {@code 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 {@code ExecutorService} has been provided, {@code + * BackgroundInitializer} creates a temporary {@code ExecutorService} and + * destroys it when initialization is complete. + *

+ *

+ * The methods provided by {@code BackgroundInitializer} provide for minimal + * interaction with the wrapped {@code Future} object. It is also possible to + * obtain the {@code Future} object directly. Then the enhanced functionality + * offered by {@code Future} can be used, e.g. to check whether the background + * operation is complete or to cancel the operation. + *

+ * + * @since 3.0 + * @param the type of the object managed by this initializer class + */ +public abstract class BackgroundInitializer implements + ConcurrentInitializer { + /** 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 future; // @GuardedBy("this") + + /** + * Creates a new instance of {@code BackgroundInitializer}. No external + * {@code ExecutorService} is used. + */ + protected BackgroundInitializer() { + this(null); + } + + /** + * Creates a new instance of {@code BackgroundInitializer} and initializes + * it with the given {@code ExecutorService}. If the {@code 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 {@code ExecutorService} to be used for task + * execution + */ + protected BackgroundInitializer(final ExecutorService exec) { + setExternalExecutor(exec); + } + + /** + * Returns the external {@code ExecutorService} to be used by this class. + * + * @return the {@code ExecutorService} + */ + public final synchronized ExecutorService getExternalExecutor() { + return externalExecutor; + } + + /** + * Returns a flag whether this {@code 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 {@code 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 + * {@code ExecutorService} or to use a specially configured one. If no + * {@code 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 {@code 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 {@code 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 true, + * following invocations will return false. + * + * @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 {@code InterruptedException} are wrapped in a + * {@link ConcurrentException}. Calling this method before {@link #start()} + * was called causes an {@code 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 {@code Future} object that was created when {@link #start()} + * was called. Therefore this method can only be called after {@code + * start()}. + * + * @return the {@code Future} object wrapped by this initializer + * @throws IllegalStateException if {@link #start()} has not been called + */ + public synchronized Future getFuture() { + if (future == null) { + throw new IllegalStateException("start() must be called first!"); + } + + return future; + } + + /** + * Returns the {@code ExecutorService} that is actually used for executing + * the background task. This method can be called after {@link #start()} + * (before {@code start()} it returns null). If an external executor + * was set, this is also the active executor. Otherwise this method returns + * the temporary executor that was created by this object. + * + * @return the {@code 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 {@code 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 {@code Callable} + * object returned by this method is passed to the {@code ExecutorService}. + * This implementation returns a task that invokes the {@link #initialize()} + * method. If a temporary {@code ExecutorService} is used, it is destroyed + * at the end of the task. + * + * @param execDestroy the {@code ExecutorService} to be destroyed by the + * task + * @return a task for the background initialization + */ + private Callable createTask(final ExecutorService execDestroy) { + return new InitializationTask(execDestroy); + } + + /** + * Creates the {@code ExecutorService} to be used. This method is called if + * no {@code ExecutorService} was provided at construction time. + * + * @return the {@code ExecutorService} to be used + */ + private ExecutorService createExecutor() { + return Executors.newFixedThreadPool(getTaskCount()); + } + + private class InitializationTask implements Callable { + /** Stores the executor service to be destroyed at the end. */ + private final ExecutorService execFinally; + + /** + * Creates a new instance of {@code InitializationTask} and initializes + * it with the {@code ExecutorService} to be destroyed at the end. + * + * @param exec the {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java b/after/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java new file mode 100644 index 0000000..631f2e4 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java @@ -0,0 +1,374 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.lang3.Validate; + +/** + *

+ * An implementation of the {@code ThreadFactory} interface that provides some + * configuration options for the threads it creates. + *

+ *

+ * A {@code ThreadFactory} is used for instance by an {@code ExecutorService} to + * create the threads it uses for executing tasks. In many cases users do not + * have to care about a {@code ThreadFactory} because the default one used by an + * {@code ExecutorService} will do. However, if there are special requirements + * for the threads, a custom {@code ThreadFactory} has to be created. + *

+ *

+ * This class provides some frequently needed configuration options for the + * threads it creates. These are the following: + *

+ *
    + *
  • 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 format strings 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.
  • + *
  • 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.
  • + *
  • 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.
  • + *
  • The {@code UncaughtExceptionHandler} for the thread. This handler is + * called if an uncaught exception occurs within the thread.
  • + *
+ *

+ * {@code 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 {@code ThreadFactory} is + * used. + *

+ *

+ * Instances of {@code BasicThreadFactory} are not created directly, but the + * nested {@code 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 {@code BasicThreadFactory} is created and + * installed in an {@code ExecutorService}: + *

+ * + *
+ * // Create a factory that produces daemon threads with a naming pattern and
+ * // a priority
+ * BasicThreadFactory factory = new BasicThreadFactory.Builder()
+ *     .namingPattern("workerthread-%d")
+ *     .daemon(true)
+ *     .priority(Thread.MAX_PRIORITY)
+ *     .build();
+ * // Create an executor service for single-threaded execution
+ * ExecutorService exec = Executors.newSingleThreadExecutor(factory);
+ * 
+ * + * @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 {@code ThreadFactoryImpl} and configures it + * from the specified {@code Builder} object. + * + * @param builder the {@code 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 {@code ThreadFactory}. This factory is used for + * actually creating threads. This method never returns null. If no + * {@code ThreadFactory} was passed when this object was created, a default + * thread factory is returned. + * + * @return the wrapped {@code ThreadFactory} + */ + public final ThreadFactory getWrappedFactory() { + return wrappedFactory; + } + + /** + * Returns the naming pattern for naming newly created threads. Result can + * be null 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 true, this factory object + * calls {@code setDaemon(true)} on the newly created threads. Result can be + * null if no daemon flag was provided at creation time. + * + * @return the daemon flag + */ + public final Boolean getDaemonFlag() { + return daemon; + } + + /** + * Returns the priority of the threads created by this factory. Result can + * be null if no priority was specified. + * + * @return the priority for newly created threads + */ + public final Integer getPriority() { + return priority; + } + + /** + * Returns the {@code UncaughtExceptionHandler} for the threads created by + * this factory. Result can be null if no handler was provided. + * + * @return the {@code 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 {@code 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 builder class for creating instances of {@code + * BasicThreadFactory}. + *

+ *

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

+ * + */ + public static class Builder + implements org.apache.commons.lang3.builder.Builder { + + /** The wrapped factory. */ + private ThreadFactory wrappedFactory; + + /** The uncaught exception handler. */ + private Thread.UncaughtExceptionHandler exceptionHandler; + + /** The naming pattern. */ + private String namingPattern; + + /** The priority. */ + private Integer priority; + + /** The daemon flag. */ + private Boolean daemon; + + /** + * Sets the {@code ThreadFactory} to be wrapped by the new {@code + * BasicThreadFactory}. + * + * @param factory the wrapped {@code ThreadFactory} (must not be + * null) + * @return a reference to this {@code Builder} + * @throws NullPointerException if the passed in {@code ThreadFactory} + * is null + */ + public Builder wrappedFactory(final ThreadFactory factory) { + Validate.notNull(factory, "factory"); + + wrappedFactory = factory; + return this; + } + + /** + * Sets the naming pattern to be used by the new {@code + * BasicThreadFactory}. + * + * @param pattern the naming pattern (must not be null) + * @return a reference to this {@code Builder} + * @throws NullPointerException if the naming pattern is null + */ + public Builder namingPattern(final String pattern) { + Validate.notNull(pattern, "pattern"); + + namingPattern = pattern; + return this; + } + + /** + * Sets the daemon flag for the new {@code BasicThreadFactory}. If this + * flag is set to true the new thread factory will create daemon + * threads. + * + * @param daemon the value of the daemon flag + * @return a reference to this {@code 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 {@code 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 {@code BasicThreadFactory}. + * + * @param handler the {@code UncaughtExceptionHandler} (must not be + * null) + * @return a reference to this {@code Builder} + * @throws NullPointerException if the exception handler is null + */ + public Builder uncaughtExceptionHandler( + final Thread.UncaughtExceptionHandler handler) { + Validate.notNull(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 {@code 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 {@code BasicThreadFactory} + */ + @Override + public BasicThreadFactory build() { + final BasicThreadFactory factory = new BasicThreadFactory(this); + reset(); + return factory; + } + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java new file mode 100644 index 0000000..26d7d58 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.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.Callable; +import java.util.concurrent.ExecutorService; + +import org.apache.commons.lang3.Validate; + +/** + *

+ * A specialized {@link BackgroundInitializer} implementation that wraps a + * {@code Callable} object. + *

+ *

+ * An instance of this class is initialized with a {@code Callable} object when + * it is constructed. The implementation of the {@link #initialize()} method + * defined in the super class delegates to this {@code Callable} so that the + * {@code Callable} is executed in the background thread. + *

+ *

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

+ *

+ * Usage of this class is very similar to the default usage pattern of the + * {@link BackgroundInitializer} class: Just create an instance and provide the + * {@code Callable} object to be executed, then call the initializer's + * {@link #start()} method. This causes the {@code Callable} to be executed in + * another thread. When the results of the {@code 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: + *

+ * + *
+ * // a Callable that performs a complex computation
+ * Callable<Integer> computationCallable = new MyComputationCallable();
+ * // setup the background initializer
+ * CallableBackgroundInitializer<Integer> 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();
+ * 
+ * + * + * @since 3.0 + * @param the type of the object managed by this initializer class + */ +public class CallableBackgroundInitializer extends BackgroundInitializer { + /** The Callable to be executed. */ + private final Callable callable; + + /** + * Creates a new instance of {@code CallableBackgroundInitializer} and sets + * the {@code Callable} to be executed in a background thread. + * + * @param call the {@code Callable} (must not be null) + * @throws IllegalArgumentException if the {@code Callable} is null + */ + public CallableBackgroundInitializer(final Callable call) { + checkCallable(call); + callable = call; + } + + /** + * Creates a new instance of {@code CallableBackgroundInitializer} and + * initializes it with the {@code Callable} to be executed in a background + * thread and the {@code ExecutorService} for managing the background + * execution. + * + * @param call the {@code Callable} (must not be null) + * @param exec an external {@code ExecutorService} to be used for task + * execution + * @throws IllegalArgumentException if the {@code Callable} is null + */ + public CallableBackgroundInitializer(final Callable call, final ExecutorService exec) { + super(exec); + checkCallable(call); + callable = call; + } + + /** + * Performs initialization in a background thread. This implementation + * delegates to the {@code 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 {@code Callable} and throws an exception if it is + * undefined. + * + * @param callable the object to check + * @throws IllegalArgumentException if the {@code Callable} is null + */ + private void checkCallable(final Callable callable) { + Validate.notNull(callable, "callable"); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java b/after/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java new file mode 100644 index 0000000..7420e70 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 Circuit Breaker component. + *

+ *

+ * A circuit breaker 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 closed, meaning that the + * resource can be used. If problems are encountered when using the resource, the circuit + * breaker can switch into state open; then access to this resource is + * prohibited. Depending on a concrete implementation, it is possible that the circuit + * breaker switches back to state closed when the resource becomes available + * again. + *

+ *

+ * This interface defines a generic protocol of a circuit breaker component. It should be + * sufficiently generic to be applied to multiple different use cases. + *

+ * + * @param the type of the value monitored by this circuit breaker + * @since 3.5 + */ +public interface CircuitBreaker { + /** + * Returns the current open state of this circuit breaker. A return value of + * true means that the circuit breaker is currently open indicating a + * problem in the monitored 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 + * true 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 {@code CLOSED}; a value + * of true typically means that the current operation can continue. + * + * @return true if the circuit breaker is now closed; + * false 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 true if the circuit breaker is now closed; + * false otherwise + */ + boolean incrementAndCheckState(T increment); +} diff --git a/after/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java b/after/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java new file mode 100644 index 0000000..5f8c5fc --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.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; + +/** + *

+ * 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 {@code CircuitBreakingException}. + */ + public CircuitBreakingException() { + } + + /** + * Creates a new instance of {@code 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 {@code 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 {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/Computable.java b/after/src/main/java/org/apache/commons/lang3/concurrent/Computable.java new file mode 100644 index 0000000..a74d1b1 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/Computable.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; + +/** + *

Definition of an interface for a wrapper around a calculation that takes a single parameter and returns a result.

+ * + *

This interface allows for wrapping a calculation into a class so that it maybe passed around an application.

+ * + * @param the type of the input to the calculation + * @param the type of the output of the calculation + * + * @since 3.6 + */ +public interface Computable { + + /** + * 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/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java b/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java new file mode 100644 index 0000000..b5cbd05 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.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.concurrent; + +/** + *

+ * An exception class used for reporting error conditions related to accessing + * data of background tasks. + *

+ *

+ * The purpose of this exception class is analogous to the default JDK exception + * class {@link java.util.concurrent.ExecutionException}, i.e. it wraps an + * exception that occurred during the execution of a task. However, in contrast + * to {@code ExecutionException}, it wraps only checked exceptions. Runtime + * exceptions are thrown directly. + *

+ * + * @since 3.0 + */ +public class ConcurrentException extends Exception { + /** + * The serial version UID. + */ + private static final long serialVersionUID = 6622707671812226130L; + + /** + * Creates a new, uninitialized instance of {@code ConcurrentException}. + */ + protected ConcurrentException() { + } + + /** + * Creates a new instance of {@code 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 {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java new file mode 100644 index 0000000..5532f74 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentInitializer.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.concurrent; + +/** + *

+ * Definition of an interface for the thread-safe initialization of objects. + *

+ *

+ * The idea behind this interface is to provide access to an object in a + * thread-safe manner. A {@code 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. + *

+ *

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

+ * + * @since 3.0 + * @param the type of the object managed by this initializer class + */ +public interface ConcurrentInitializer { + /** + * 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 {@code ConcurrentException} + * @throws ConcurrentException if an error occurred during initialization of + * the object + */ + T get() throws ConcurrentException; +} diff --git a/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java b/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java new file mode 100644 index 0000000..4a1d95d --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.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.concurrent; + +/** + *

+ * An exception class used for reporting runtime error conditions related to + * accessing data of background tasks. + *

+ *

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

+ * + * @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 {@code 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 {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java b/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java new file mode 100644 index 0000000..044b7e9 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java @@ -0,0 +1,392 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 {@code ExecutionException} and + * creates a {@code ConcurrentException} with the checked cause if + * necessary. This method performs the following checks on the cause of the + * passed in exception: + *
    + *
  • If the passed in exception is null or the cause is + * null, this method returns null.
  • + *
  • If the cause is a runtime exception, it is directly thrown.
  • + *
  • If the cause is an error, it is directly thrown, too.
  • + *
  • 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.
  • + *
+ * + * @param ex the exception to be processed + * @return a {@code 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 {@code ExecutionException} and + * creates a {@code 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 {@code 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 {@code 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 {@code 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 {@code ConcurrentException}, which is thrown. If the passed in + * exception is null 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 {@code 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 {@code Throwable} is a checked exception. If + * not, an exception is thrown. + * + * @param ex the {@code Throwable} to check + * @return a flag whether the passed in exception is a checked exception + * @throws IllegalArgumentException if the {@code 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 {@code 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 {@code ConcurrentInitializer} and returns the + * object produced by the initializer. This method just invokes the {@code + * get()} method of the given {@code ConcurrentInitializer}. It is + * null-safe: if the argument is null, result is also + * null. + * + * @param the type of the object produced by the initializer + * @param initializer the {@code ConcurrentInitializer} to be invoked + * @return the object managed by the {@code ConcurrentInitializer} + * @throws ConcurrentException if the {@code ConcurrentInitializer} throws + * an exception + */ + public static T initialize(final ConcurrentInitializer initializer) + throws ConcurrentException { + return initializer != null ? initializer.get() : null; + } + + /** + * Invokes the specified {@code 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 the type of the object produced by the initializer + * @param initializer the {@code ConcurrentInitializer} to be invoked + * @return the object managed by the {@code ConcurrentInitializer} + * @throws ConcurrentRuntimeException if the initializer throws an exception + */ + public static T initializeUnchecked(final ConcurrentInitializer initializer) { + try { + return initialize(initializer); + } catch (final ConcurrentException cex) { + throw new ConcurrentRuntimeException(cex.getCause()); + } + } + + //----------------------------------------------------------------------- + /** + *

+ * Puts a value in the specified {@code ConcurrentMap} if the key is not yet + * present. This method works similar to the {@code putIfAbsent()} method of + * the {@code ConcurrentMap} interface, but the value returned is different. + * Basically, this method is equivalent to the following code fragment: + *

+ * + *
+     * if (!map.containsKey(key)) {
+     *     map.put(key, value);
+     *     return value;
+     * } else {
+     *     return map.get(key);
+     * }
+     * 
+ * + *

+ * except that the action is performed atomically. So this method always + * returns the value which is stored in the map. + *

+ *

+ * This method is null-safe: It accepts a null map as input + * without throwing an exception. In this case the return value is + * null, too. + *

+ * + * @param the type of the keys of the map + * @param the type of the values of the map + * @param map the map to be modified + * @param key the key of the value to be added + * @param value the value to be added + * @return the value stored in the map after this operation + */ + public static V putIfAbsent(final ConcurrentMap 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 null; in this + * case this method simply returns null. + * + * @param the type of the keys of the map + * @param the type of the values of the map + * @param map the map to be modified + * @param key the key of the value to be added + * @param init the {@link ConcurrentInitializer} for creating the value + * @return the value stored in the map after this operation; this may or may + * not be the object created by the {@link ConcurrentInitializer} + * @throws ConcurrentException if the initializer throws an exception + */ + public static V createIfAbsent(final ConcurrentMap map, final K key, + final ConcurrentInitializer init) throws ConcurrentException { + if (map == null || init == null) { + return null; + } + + final V value = map.get(key); + if (value == null) { + return putIfAbsent(map, key, init.get()); + } + return value; + } + + /** + * Checks if a concurrent map contains a key and creates a corresponding + * value if not, suppressing checked exceptions. This method calls + * {@code createIfAbsent()}. If a {@link ConcurrentException} is thrown, it + * is caught and re-thrown as a {@link ConcurrentRuntimeException}. + * + * @param the type of the keys of the map + * @param the type of the values of the map + * @param map the map to be modified + * @param key the key of the value to be added + * @param init the {@link ConcurrentInitializer} for creating the value + * @return the value stored in the map after this operation; this may or may + * not be the object created by the {@link ConcurrentInitializer} + * @throws ConcurrentRuntimeException if the initializer throws an exception + */ + public static V createIfAbsentUnchecked(final ConcurrentMap map, + final K key, final ConcurrentInitializer init) { + try { + return createIfAbsent(map, key, init); + } catch (final ConcurrentException cex) { + throw new ConcurrentRuntimeException(cex.getCause()); + } + } + + //----------------------------------------------------------------------- + /** + *

+ * Gets an implementation of {@code Future} that is immediately done + * and returns the specified constant value. + *

+ *

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

+ * + * @param the type of the value used by this {@code Future} object + * @param value the constant value to return, may be null + * @return an instance of Future that will return the value, never null + */ + public static Future constantFuture(final T value) { + return new ConstantFuture<>(value); + } + + /** + * A specialized {@code Future} implementation which wraps a constant value. + * @param the type of the value wrapped by this class + */ + static final class ConstantFuture implements Future { + /** The constant value. */ + private final T value; + + /** + * Creates a new instance of {@code ConstantFuture} and initializes it + * with the constant value. + * + * @param value the value (may be null) + */ + ConstantFuture(final T value) { + this.value = value; + } + + /** + * {@inheritDoc} This implementation always returns true because + * the constant object managed by this {@code Future} implementation is + * always available. + */ + @Override + public boolean isDone() { + return true; + } + + /** + * {@inheritDoc} This implementation just returns the constant value. + */ + @Override + public T get() { + return value; + } + + /** + * {@inheritDoc} This implementation just returns the constant value; it + * does not block, therefore the timeout has no meaning. + */ + @Override + public T get(final long timeout, final TimeUnit unit) { + return value; + } + + /** + * {@inheritDoc} This implementation always returns false; there + * is no background process which could be cancelled. + */ + @Override + public boolean isCancelled() { + return false; + } + + /** + * {@inheritDoc} The cancel operation is not supported. This + * implementation always returns false. + */ + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + return false; + } + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java new file mode 100644 index 0000000..4c6bc3e --- /dev/null +++ b/after/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. + *

+ *

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

+ *

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

+ * + * @since 3.0 + * @param the type of the object managed by this initializer + */ +public class ConstantInitializer implements ConcurrentInitializer { + /** 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 {@code 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 null, then {@code get()} will return + * null, 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 getObject() != null ? getObject().hashCode() : 0; + } + + /** + * Compares this object with another one. This implementation returns + * true if and only if the passed in object is an instance of + * {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java b/after/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java new file mode 100644 index 0000000..051f924 --- /dev/null +++ b/after/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.util.EnumMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + *

+ * A simple implementation of the Circuit Breaker pattern + * that counts specific events. + *

+ *

+ * A circuit breaker can be used to protect an application against unreliable + * services or unexpected load. A newly created {@code EventCountCircuitBreaker} object is + * initially in state closed 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 open. 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 closed state after a + * certain time frame if the number of events received goes below a threshold. + *

+ *

+ * When a {@code EventCountCircuitBreaker} object is constructed the following parameters + * can be provided: + *

+ *
    + *
  • A threshold for the number of events that causes a state transition to + * open state. If more events are received in the configured check interval, the + * circuit breaker switches to open state.
  • + *
  • 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."
  • + *
  • 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.
  • + *
+ *

+ * This class supports the following typical use cases: + *

+ *

+ * Protecting against load peaks + *

+ *

+ * 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 + * {@code 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 {@code 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 closed: + *

+ * + *
+ * 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
+ *     }
+ * }
+ * 
+ *

+ * Deal with an unreliable service + *

+ *

+ * 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: + *

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

+ * 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 {@code PropertyChangeListener} objects that get notified whenever + * a state transition occurs. This is useful, for instance to directly react on a freshly + * detected error condition. + *

+ *

+ * Implementation notes: + *

+ *
    + *
  • 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.
  • + *
  • 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.
  • + *
  • 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).
  • + *
+ * @since 3.5 + */ +public class EventCountCircuitBreaker extends AbstractCircuitBreaker { + + /** A map for accessing the strategy objects for the different states. */ + private static final Map STRATEGY_MAP = createStrategyMap(); + + /** Stores information about the current check interval. */ + private final AtomicReference 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code 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 1 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 true if the circuit breaker is now closed; + * false 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 {@code 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 false, 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 + * {@code 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 {@code 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 {@code 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 {@code StateStrategy} object responsible for the given state. + * + * @param state the state + * @return the corresponding {@code StateStrategy} + * @throws CircuitBreakingException if the strategy cannot be resolved + */ + private static StateStrategy stateStrategy(final State state) { + return STRATEGY_MAP.get(state); + } + + /** + * Creates the map with strategy objects. It allows access for a strategy for a given + * state. + * + * @return the strategy map + */ + private static Map createStrategyMap() { + final Map map = new EnumMap<>(State.class); + map.put(State.CLOSED, new StateStrategyClosed()); + map.put(State.OPEN, new StateStrategyOpen()); + return map; + } + + /** + * An internally used data class holding information about the checks performed by + * this class. Basically, the number of received events and the start time of the + * current check interval are stored. + */ + private static 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code CircuitBreaker} + * @param currentData the current {@code CheckIntervalData} object + * @param nextData the updated {@code 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 + * {@code CircuitBreaker}. + * + * @param breaker the {@code CircuitBreaker} + * @return the check interval to be applied + */ + protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker); + } + + /** + * A specialized {@code 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 {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java new file mode 100644 index 0000000..7c755d8 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.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; + +/** + *

+ * This class provides a generic implementation of the lazy initialization + * pattern. + *

+ *

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

+ *

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

+ *

+ * 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 {@code LazyInitializer} has to be created: + *

+ * + *
+ * public class ComplexObjectInitializer extends LazyInitializer<ComplexObject> {
+ *     @Override
+ *     protected ComplexObject initialize() {
+ *         return new ComplexObject();
+ *     }
+ * }
+ * 
+ * + *

+ * 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: + *

+ * + *
+ * // Create an instance of the lazy initializer
+ * ComplexObjectInitializer initializer = new ComplexObjectInitializer();
+ * ...
+ * // When the object is actually needed:
+ * ComplexObject cobj = initializer.get();
+ * 
+ * + *

+ * 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 volatile member field). + *

+ * + * @since 3.0 + * @param the type of the object managed by this initializer class + */ +public abstract class LazyInitializer implements ConcurrentInitializer { + + 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 {@code 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/after/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java new file mode 100644 index 0000000..feb074b --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.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.concurrent; + +import java.util.concurrent.Callable; +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.concurrent.FutureTask; + +/** + *

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

+ *

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

+ *

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

+ * + * @param + * the type of the input to the calculation + * @param + * the type of the output of the calculation + * + * @since 3.6 + */ +public class Memoizer implements Computable { + + private final ConcurrentMap> cache = new ConcurrentHashMap<>(); + private final Computable computable; + private final boolean recalculate; + + /** + *

+ * Constructs a Memoizer for the provided Computable calculation. + *

+ *

+ * If a calculation is thrown an exception for any reason, this exception + * will be cached and returned for all future calls with the provided + * parameter. + *

+ * + * @param computable + * the computation whose results should be memorized + */ + public Memoizer(final Computable 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 computable, final boolean recalculate) { + this.computable = computable; + this.recalculate = recalculate; + } + + /** + *

+ * This method will return the result of the calculation and cache it, if it + * has not previously been calculated. + *

+ *

+ * This cache will also cache exceptions that occur during the computation + * if the {@code recalculate} parameter is 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. + *

+ * + * @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) { + Future future = cache.get(arg); + if (future == null) { + final Callable eval = () -> computable.compute(arg); + final FutureTask futureTask = new FutureTask<>(eval); + future = cache.putIfAbsent(arg, futureTask); + if (future == null) { + future = futureTask; + futureTask.run(); + } + } + 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; + } else if (throwable instanceof Error) { + throw (Error) throwable; + } else { + throw new IllegalStateException("Unchecked exception", throwable); + } + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java b/after/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java new file mode 100644 index 0000000..08c9b34 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java @@ -0,0 +1,345 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Set; +import java.util.concurrent.ExecutorService; + +import org.apache.commons.lang3.Validate; + +/** + *

+ * A specialized {@link BackgroundInitializer} implementation that can deal with + * multiple background initialization tasks. + *

+ *

+ * 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 {@code 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 {@code ExecutorService} and shares it + * with the {@code BackgroundInitializer} objects it is responsible for; so the + * using application need not bother with these details. + *

+ *

+ * The typical usage scenario for {@code MultiBackgroundInitializer} is as + * follows: + *

+ *
    + *
  • Create a new instance of the class. Optionally pass in a pre-configured + * {@code ExecutorService}. Alternatively {@code MultiBackgroundInitializer} can + * create a temporary {@code ExecutorService} and delete it after initialization + * is complete.
  • + *
  • 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.
  • + *
  • After all initializers have been added, call the {@link #start()} method. + *
  • + *
  • 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.
  • + *
+ *

+ * {@code MultiBackgroundInitializer} starts a special controller task that + * starts all {@code BackgroundInitializer} objects added to the instance. + * Before the an initializer is started it is checked whether this initializer + * already has an {@code ExecutorService} set. If this is the case, this {@code + * ExecutorService} is used for running the background task. Otherwise the + * current {@code ExecutorService} of this {@code MultiBackgroundInitializer} is + * shared with the initializer. + *

+ *

+ * The easiest way of using this class is to let it deal with the management of + * an {@code ExecutorService} itself: If no external {@code ExecutorService} is + * provided, the class creates a temporary {@code ExecutorService} (that is + * capable of executing all background tasks in parallel) and destroys it at the + * end of background processing. + *

+ *

+ * Alternatively an external {@code 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 {@code ExecutorService}. + * Important note: 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: {@code MultiBackgroundInitializer} starts a task that + * starts all registered {@code BackgroundInitializer} objects and waits for + * their completion. If for instance a single threaded {@code ExecutorService} + * is used, none of the background tasks can be executed, and the task created + * by {@code MultiBackgroundInitializer} waits forever. + *

+ * + * @since 3.0 + */ +public class MultiBackgroundInitializer + extends + BackgroundInitializer { + /** A map with the child initializers. */ + private final Map> childInitializers = + new HashMap<>(); + + /** + * Creates a new instance of {@code MultiBackgroundInitializer}. + */ + public MultiBackgroundInitializer() { + } + + /** + * Creates a new instance of {@code MultiBackgroundInitializer} and + * initializes it with the given external {@code ExecutorService}. + * + * @param exec the {@code ExecutorService} for executing the background + * tasks + */ + public MultiBackgroundInitializer(final ExecutorService exec) { + super(exec); + } + + /** + * Adds a new {@code BackgroundInitializer} to this object. When this + * {@code 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 null) + * @param backgroundInitializer the {@code BackgroundInitializer} to add (must not be + * null) + * @throws IllegalArgumentException if a required parameter is missing + * @throws IllegalStateException if {@code start()} has already been called + */ + public void addInitializer(final String name, final BackgroundInitializer backgroundInitializer) { + Validate.notNull(name, "name"); + Validate.notNull(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 {@code 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() { + int result = 1; + + for (final BackgroundInitializer bi : childInitializers.values()) { + result += bi.getTaskCount(); + } + + return result; + } + + /** + * Creates the results object. This implementation starts all child {@code + * BackgroundInitializer} objects. Then it collects their results and + * creates a {@code 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> inits; + synchronized (this) { + // create a snapshot to operate on + inits = new HashMap<>( + childInitializers); + } + + // start the child initializers + final ExecutorService exec = getActiveExecutor(); + for (final BackgroundInitializer bi : inits.values()) { + if (bi.getExternalExecutor() == null) { + // share the executor service if necessary + bi.setExternalExecutor(exec); + } + bi.start(); + } + + // collect the results + final Map results = new HashMap<>(); + final Map excepts = new HashMap<>(); + for (final Map.Entry> e : inits.entrySet()) { + try { + results.put(e.getKey(), e.getValue().get()); + } catch (final ConcurrentException cex) { + excepts.put(e.getKey(), cex); + } + } + + return new MultiBackgroundInitializerResults(inits, results, excepts); + } + + /** + * A data class for storing the results of the background initialization + * performed by {@code MultiBackgroundInitializer}. Objects of this inner + * class are returned by {@link MultiBackgroundInitializer#initialize()}. + * They allow access to all result objects produced by the + * {@link BackgroundInitializer} objects managed by the owning instance. It + * is also possible to retrieve status information about single + * {@link BackgroundInitializer}s, i.e. whether they completed normally or + * caused an exception. + */ + public static class MultiBackgroundInitializerResults { + /** A map with the child initializers. */ + private final Map> initializers; + + /** A map with the result objects. */ + private final Map resultObjects; + + /** A map with the exceptions. */ + private final Map exceptions; + + /** + * Creates a new instance of {@code MultiBackgroundInitializerResults} + * and initializes it with maps for the {@code BackgroundInitializer} + * objects, their result objects and the exceptions thrown by them. + * + * @param inits the {@code BackgroundInitializer} objects + * @param results the result objects + * @param excepts the exceptions + */ + private MultiBackgroundInitializerResults( + final Map> inits, + final Map results, + final Map excepts) { + initializers = inits; + resultObjects = results; + exceptions = excepts; + } + + /** + * Returns the {@code BackgroundInitializer} with the given name. If the + * name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@code BackgroundInitializer} + * @return the {@code 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 + * {@code BackgroundInitializer} caused an exception, null is + * returned. If the name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@code 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 {@code BackgroundInitializer} with the + * given name caused an exception. + * + * @param name the name of the {@code 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 {@code ConcurrentException} object that was thrown by the + * {@code BackgroundInitializer} with the given name. If this + * initializer did not throw an exception, the return value is + * null. If the name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@code 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 {@code BackgroundInitializer} + * objects managed by the {@code MultiBackgroundInitializer}. + * + * @return an (unmodifiable) set with the names of the managed {@code + * BackgroundInitializer} objects + */ + public Set initializerNames() { + return Collections.unmodifiableSet(initializers.keySet()); + } + + /** + * Returns a flag whether the whole initialization was successful. This + * is the case if no child initializer has thrown an exception. + * + * @return a flag whether the initialization was successful + */ + public boolean isSuccessful() { + return exceptions.isEmpty(); + } + + /** + * Checks whether an initializer with the given name exists. If not, + * throws an exception. If it exists, the associated child initializer + * is returned. + * + * @param name the name to check + * @return the initializer with this name + * @throws NoSuchElementException if the name is unknown + */ + private BackgroundInitializer checkName(final String name) { + final BackgroundInitializer init = initializers.get(name); + if (init == null) { + throw new NoSuchElementException( + "No child initializer with name " + name); + } + + return init; + } + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java b/after/src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java new file mode 100644 index 0000000..4434ae8 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java @@ -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. + */ +package org.apache.commons.lang3.concurrent; + +import java.util.concurrent.atomic.AtomicLong; + +/** + *

+ * A simple implementation of the Circuit Breaker pattern + * that opens if the requested increment amount is greater than a given threshold. + *

+ * + *

+ * 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 open state. + *

+ * + *

+ * An example of use case could be a memory circuit breaker. + *

+ * + *
+ * 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
+ *     }
+ * }
+ * 
+ * + *

#Thread safe#

+ * @since 3.5 + */ +public class ThresholdCircuitBreaker extends AbstractCircuitBreaker { + /** + * 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 {@code 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} + * + *

Resets the internal counter back to its initial value (zero).

+ */ + @Override + public void close() { + super.close(); + this.used.set(INITIAL_COUNT); + } + + /** + * {@inheritDoc} + * + *

If the threshold is zero, the circuit breaker will be in a permanent open state.

+ */ + @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/after/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java b/after/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java new file mode 100644 index 0000000..28e0eb1 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java @@ -0,0 +1,465 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 semaphore implementation that provides a number of + * permits in a given time frame. + *

+ *

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

+ *

+ * 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 {@code TimedSemaphore} could be installed to guarantee that + * only a given number of database queries are issued per second. + *

+ *

+ * A thread class for performing database queries could look as follows: + *

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

+ * The following code fragment shows how a {@code TimedSemaphore} is created + * that allows only 10 operations per second and passed to the statistics + * thread: + *

+ * + *
+ * TimedSemaphore sem = new TimedSemaphore(1, TimeUnit.SECOND, 10);
+ * StatisticsThread thread = new StatisticsThread(sem);
+ * thread.start();
+ * 
+ * + *

+ * When creating an instance the time period for the semaphore must be + * specified. {@code 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. + *

+ *

+ * Client code that uses {@code TimedSemaphore} has to call the + * {@link #acquire()} method in each processing step. {@code 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. + *

+ *

+ * 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 + * true, and the calling thread can continue with its action. + * If the semaphore is already at its limit, {@code tryAcquire()} immediately + * returns false without blocking; the calling thread must + * then abort its action. This usage scenario prevents blocking of threads. + *

+ *

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

+ *

+ * When the {@code 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 {@code 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. + *

+ * + * @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 {@code 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 null, 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 <= {@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 any more. + * This method can be invoked an arbitrary number of times. All invocations + * after the first one do not have any effect. + */ + public synchronized void shutdown() { + if (!shutdown) { + + if (ownExecutor) { + // if the executor was created by this instance, it has + // to be shutdown + getExecutorService().shutdownNow(); + } + if (task != null) { + task.cancel(false); + } + + shutdown = true; + } + } + + /** + * Tests whether the {@link #shutdown()} method has been called on this + * object. If this method returns true, this instance cannot be used + * any longer. + * + * @return a flag whether a shutdown has been performed + */ + public synchronized boolean isShutdown() { + return shutdown; + } + + /** + * Acquires a permit from this semaphore. This method will block if + * the limit for the current period has already been reached. If + * {@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 {@code 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 + * true. Otherwise, this method returns immediately with the result + * false. + * + * @return true if a permit could be acquired; false + * otherwise + * @throws IllegalStateException if this semaphore is already shut down + * @since 3.5 + */ + public synchronized boolean tryAcquire() { + prepareAcquire(); + return acquirePermit(); + } + + /** + * 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 mean time 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/after/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java b/after/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java new file mode 100644 index 0000000..0c43a6b --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java @@ -0,0 +1,378 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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., & Johnson, R. (1998). Visitor. In Design patterns elements of reusable object oriented software (pp. 331-344). Reading: Addison Wesley.]. + *

+ *

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

+ *

+ * Using this class is fairly straightforward: + *

+ *
    + *
  1. 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.
  2. + *
  3. 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.
  4. + *
  5. 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)}.
  6. + *
+ *

+ * Example: A thread safe logger class. + *

+ * + *
+ *   public class SimpleLogger {
+ *
+ *     private final StampedLockVisitor<PrintStream> lock;
+ *
+ *     public SimpleLogger(OutputStream out) {
+ *         lock = LockingVisitors.stampedLockVisitor(new PrintStream(out));
+ *     }
+ *
+ *     public void log(String message) {
+ *         lock.acceptWriteLocked((ps) -> ps.println(message));
+ *     }
+ *
+ *     public void log(byte[] buffer) {
+ *         lock.acceptWriteLocked((ps) -> { ps.write(buffer); ps.println(); });
+ *     }
+ * 
+ * + * @since 3.11 + */ +public class LockingVisitors { + + /** + * Wraps a domain object and a lock for access by lambdas. + * + * @param the wrapped object type. + * @param the wrapped lock type. + */ + public static class LockVisitor { + + /** + * 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 readLockSupplier; + + /** + * Supplies the write lock, usually from the lock object. + */ + private final Supplier 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 readLockSupplier, final Supplier 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): + *

+ *
    + *
  1. Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a + * lock is granted.
  2. + *
  3. Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.
  4. + *
  5. Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the + * lock will be released anyways.
  6. + *
+ * + * @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 consumer) { + lockAcceptUnlock(readLockSupplier, consumer); + } + + /** + *

+ * Provides write (exclusive) access to the locked (hidden) object. More precisely, what the method will do (in + * the given order): + *

+ *
    + *
  1. Obtain a write (shared) lock on the locked (hidden) object. The current thread may block, until such a + * lock is granted.
  2. + *
  3. Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.
  4. + *
  5. Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the + * lock will be released anyways.
  6. + *
+ * + * @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 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): + *

+ *
    + *
  1. Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a + * lock is granted.
  2. + *
  3. Invokes the given {@link FailableFunction function}, passing the locked object as the parameter, + * receiving the functions result.
  4. + *
  5. Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the + * lock will be released anyways.
  6. + *
  7. Return the result object, that has been received from the functions invocation.
  8. + *
+ *

+ * Example: 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: + *

+ *
+         * private Lock<List<Object>> listLock;
+         *
+         * public int getCurrentListSize() {
+         *     final Integer sizeInteger = listLock.applyReadLocked((list) -> Integer.valueOf(list.size));
+         *     return sizeInteger.intValue();
+         * }
+         * 
+ * + * @param 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 applyReadLocked(final FailableFunction 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): + *

+ *
    + *
  1. Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a + * lock is granted.
  2. + *
  3. Invokes the given {@link FailableFunction function}, passing the locked object as the parameter, + * receiving the functions result.
  4. + *
  5. Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the + * lock will be released anyways.
  6. + *
  7. Return the result object, that has been received from the functions invocation.
  8. + *
+ * + * @param 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 applyWriteLocked(final FailableFunction 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 lockSupplier, final FailableConsumer 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 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 lockApplyUnlock(final Supplier lockSupplier, final FailableFunction 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 The locked (hidden) objects type. + */ + public static class ReadWriteLockVisitor extends LockVisitor { + + /** + * 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 The locked (hidden) objects type. + */ + public static class StampedLockVisitor extends LockVisitor { + + /** + * 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 The locked objects type. + * @param object The locked (hidden) object. + * @return The created instance, a {@link StampedLockVisitor lock} for the given object. + */ + public static ReadWriteLockVisitor reentrantReadWriteLockVisitor(final O object) { + return new LockingVisitors.ReadWriteLockVisitor<>(object, new ReentrantReadWriteLock()); + } + + /** + * Creates a new instance of {@link StampedLockVisitor} with the given (hidden) object. + * + * @param The locked objects type. + * @param object The locked (hidden) object. + * @return The created instance, a {@link StampedLockVisitor lock} for the given object. + */ + public static StampedLockVisitor stampedLockVisitor(final O object) { + return new LockingVisitors.StampedLockVisitor<>(object, new StampedLock()); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/concurrent/locks/package-info.java b/after/src/main/java/org/apache/commons/lang3/concurrent/locks/package-info.java new file mode 100644 index 0000000..2b5fef7 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/locks/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 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/after/src/main/java/org/apache/commons/lang3/concurrent/package-info.java b/after/src/main/java/org/apache/commons/lang3/concurrent/package-info.java new file mode 100644 index 0000000..49f98a8 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/concurrent/package-info.java @@ -0,0 +1,436 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.

+ * + *

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: + *

+ * + *
+ * 
+ * public interface ConcurrentInitializer<T> {
+ *    T get() throws ConcurrentException;
+ * }
+ * 
+ * 
+ * + *

A {@code 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: + *

+ * + *

{@link org.apache.commons.lang3.concurrent.ConstantInitializer} is a very straightforward implementation of the {@code 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 {@code ConcurrentInitializer}. + *

+ * + *

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. + * {@code LazyInitializer} implements the double-check idiom for an instance field as discussed in Joshua Bloch's "Effective Java", 2nd edition, item 71. + * It uses volatile fields to reduce the amount of synchronization. + * Note that this idiom is appropriate for instance fields only. + * For static fields there are superior alternatives.

+ * + *

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 {@code LazyInitializer} comes into play. + * We create a specialized subclass for creating and initializing an instance of our messaging service. + * {@code 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:

+ * + *
+ * 
+ * public class MessagingServiceInitializer extends LazyInitializer<MessagingService> {
+ *   protected MessagingService initialize() throws ConcurrentException {
+ *     // Do all necessary steps to create and initialize the service object
+ *     MessagingService service = ...
+ *     return service;
+ *   }
+ * }
+ * 
+ * 
+ * + *

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:

+ * + *
+ * 
+ * public class ServerThread implements Runnable {
+ *  // The initializer for obtaining the messaging service.
+ *  private final ConcurrentInitializer<MessagingService> initializer;
+ *
+ *  public ServerThread(ConcurrentInitializer<MessagingService> 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();
+ *        }
+ *      }
+ *    }
+ *  }
+ * }
+ * 
+ * 
+ * + *

The {@link org.apache.commons.lang3.concurrent.AtomicInitializer} class is very similar to {@code 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 extends declaration to refer to {@code AtomicInitializer<MessagingService>} as super class.

+ * + *

With {@link org.apache.commons.lang3.concurrent.AtomicSafeInitializer} there is yet another variant implementing the lazy initializing pattern. + * Its implementation is close to {@code AtomicInitializer}; it also uses atomic variables internally and therefore does not need synchronization. + * The name "Safe" 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 {@code LazyInitializer}.

+ * + *

Now, which one of the lazy initializer implementations should you use? + * First of all we have to state that is 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.

+ * + *

{@code 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 {@code AtomicInitializer} is a good choice. + * {@code AtomicSafeInitializer} and {@code LazyInitializer} both guarantee that the initialization method is called only once. + * Because {@code AtomicSafeInitializer} does not use synchronization it is probably slightly more efficient than {@code LazyInitializer}, but the concrete numbers might depend on the level of concurrency.

+ * + *

Another implementation of the {@code 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 {@code 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.

+ * + *

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 {@code BackgroundInitializer} can be created for this purpose:

+ * + *
+ * 
+ * public class URLLoader extends BackgroundInitializer<String> {
+ *   // 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);
+ *     }
+ *   }
+ * }
+ * 
+ * 
+ * + *

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:

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

Related to {@code 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 {@code MultiBackgroundInitializer} instance is created. + * Then an arbitrary number of {@code 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.

+ * + *

With {@code 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 {@code BackgroundInitializer} implementations exist. + * The following example fragment shows the usage of {@code MultiBackgroundInitializer} for this purpose:

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

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.

+ * + *

If background initializers - including {@code 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 {@code ExecutorService} when the initializer is constructed. + * That way client code can configure the {@code ExecutorService} according to its specific needs; for instance, the number of threads available could be limited.

+ * + *

Utility Classes

+ * + *

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 {@code 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 {@code 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. + *

+ * + *

The {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#extractCause(java.util.concurrent.ExecutionException)} method does this work for you. + * It is passed an {@code 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 + * ({@code ConcurrentException} is a new exception class in the {@code o.a.c.l.concurrent} package). + * So if you get such a {@code ConcurrentException}, you can be sure that the original cause for the {@code 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.

+ * + *

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 {@code ExecutionException} and throw the resulting {@code ConcurrentException} or {@code ConcurrentRuntimeException}. + * This makes it easy to transform an {@code ExecutionException} into a {@code ConcurrentException} ignoring unchecked exceptions:

+ * + *
+ * 
+ * Future<Object> future = ...;
+ * try {
+ *   Object result = future.get();
+ *   ...
+ * } catch (ExecutionException eex) {
+ *   ConcurrentUtils.handleCause(eex);
+ * }
+ * 
+ * 
+ * + *

There is also some support for the concurrent initializers introduced in the last sub section. + * The {@code initialize()} method is passed a {@code ConcurrentInitializer} object and returns the object created by this initializer. + * It is null-safe. + * The {@code initializeUnchecked()} method works analogously, but a {@code ConcurrentException} throws by the initializer is rethrown as a {@code ConcurrentRuntimeException}. + * This is especially useful if the specific {@code 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:

+ * + *
+ * 
+ * ConcurrentInitializer<MyClass> initializer = ...;
+ * try {
+ *   MyClass obj = initializer.get();
+ *   // do something with obj
+ * } catch (ConcurrentException cex) {
+ *   // exception handling
+ * }
+ * 
+ * 
+ * + *

Using the {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#initializeUnchecked(ConcurrentInitializer)} method, this becomes:

+ * + *
+ * 
+ * ConcurrentInitializer<MyClass> initializer = ...;
+ * MyClass obj = ConcurrentUtils.initializeUnchecked(initializer);
+ * // do something with obj
+ * 
+ * 
+ * + *

Another utility class deals with the creation of threads. + * When using the Executor 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 {@code ThreadFactory}, so one has to start from scratch.

+ * + *

With {@link org.apache.commons.lang3.concurrent.BasicThreadFactory} Commons Lang has an implementation of {@code 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 {@code 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:

+ * + *
+ * 
+ * BasicThreadFactory factory = new BasicThreadFactory.Builder()
+ *   .namingPattern("worker-thread-%d")
+ *   .daemon(true)
+ *   .uncaughtExceptionHandler(myHandler)
+ *   .build();
+ * ExecutorService exec = Executors.newSingleThreadExecutor(factory);
+ * 
+ * 
+ * + *

The nested {@code Builder} class defines some methods for configuring the new {@code 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 {@code String.format()}. + * The placeholder %d is replaced by an increasing counter value. + * An instance can wrap another {@code 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.

+ * + *

Synchronization objects

+ * + *

The {@code concurrent} package also provides some support for specific synchronization problems with threads.

+ * + *

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

+ * + *

What are use cases for {@code 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 {@code 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.

+ * + *

The following code examples demonstrate parts of the implementation of such a scenario. + * First the batch application has to create an instance of {@code TimedSemaphore} and to initialize its properties with default values:

+ * + * {@code TimedSemaphore semaphore = new TimedSemaphore(1, TimeUnit.SECONDS, 100);} + * + *

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.

+ * + *
+ * 
+ * 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
+ *     }
+ *   }
+ * }
+ * 
+ * 
+ * + *

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.

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

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.

+ */ +package org.apache.commons.lang3.concurrent; diff --git a/after/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java b/after/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java new file mode 100644 index 0000000..315a612 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.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.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.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +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. + *

+ * + *

+ * To use this class, suppose you want to support ActionEvents. You would do: + *

+ *

+ * public class MyActionEventSource
+ * {
+ *   private EventListenerSupport<ActionListener> actionListeners =
+ *       EventListenerSupport.create(ActionListener.class);
+ *
+ *   public void someMethodThatFiresAction()
+ *   {
+ *     ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
+ *     actionListeners.fire().actionPerformed(e);
+ *   }
+ * }
+ * 
+ * + *

+ * Serializing an {@link EventListenerSupport} instance will result in any + * non-{@link Serializable} listeners being silently dropped. + *

+ * + * @param the type of event listener that is supported by this proxy. + * + * @since 3.0 + */ +public class EventListenerSupport 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 listeners = new CopyOnWriteArrayList<>(); + + /** + * The proxy representing the collection of listeners. Calls to this proxy + * object will 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 the type of the listener interface + * @param listenerInterface the type of listener interface that will receive + * events posted using this class. + * + * @return an EventListenerSupport object which supports the specified + * listener type. + * + * @throws NullPointerException if {@code listenerInterface} is + * {@code null}. + * @throws IllegalArgumentException if {@code listenerInterface} is + * not an interface. + */ + public static EventListenerSupport create(final Class listenerInterface) { + return new EventListenerSupport<>(listenerInterface); + } + + /** + * 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 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 listenerInterface, final ClassLoader classLoader) { + this(); + Validate.notNull(listenerInterface, "listenerInterface"); + Validate.notNull(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) { + Validate.notNull(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) { + Validate.notNull(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 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); + + @SuppressWarnings("unchecked") // Will throw CCE here if not correct + final + Class listenerInterface = (Class) srcListeners.getClass().getComponentType(); + + initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader()); + } + + /** + * Initialize transient fields. + * @param listenerInterface the class of the listener interface + * @param classLoader the class loader to be used + */ + private void initializeTransientFields(final Class listenerInterface, final ClassLoader classLoader) { + @SuppressWarnings("unchecked") // Will throw CCE here if not correct + final + L[] array = (L[]) Array.newInstance(listenerInterface, 0); + this.prototypeArray = array; + createProxy(listenerInterface, classLoader); + } + + /** + * Create the proxy object. + * @param listenerInterface the class of the listener interface + * @param classLoader the class loader to be used + */ + private void createProxy(final Class listenerInterface, final ClassLoader classLoader) { + proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, + new Class[] { listenerInterface }, createInvocationHandler())); + } + + /** + * Create the {@link InvocationHandler} responsible for broadcasting calls + * to the managed listeners. Subclasses can override to provide custom behavior. + * @return ProxyInvocationHandler + */ + protected InvocationHandler createInvocationHandler() { + return new ProxyInvocationHandler(); + } + + /** + * An invocation handler used to dispatch the event(s) to all the listeners. + */ + protected class ProxyInvocationHandler implements InvocationHandler { + + /** + * Propagates the method call to all registered listeners in place of + * the proxy listener object. + * + * @param unusedProxy the proxy object representing a listener on which the + * invocation was called; not used + * @param method the listener method that will be called on all of the + * listeners. + * @param args event arguments to propagate to the listeners. + * @return the result of the method call + * @throws Throwable if an error occurs + */ + @Override + public Object invoke(final Object unusedProxy, final Method method, final Object[] args) throws Throwable { + for (final L listener : listeners) { + method.invoke(listener, args); + } + return null; + } + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/event/EventUtils.java b/after/src/main/java/org/apache/commons/lang3/event/EventUtils.java new file mode 100644 index 0000000..c5baf62 --- /dev/null +++ b/after/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 the event listener type + * + * @throws IllegalArgumentException if the object doesn't support the listener type + */ + public static void addEventListener(final Object eventSource, final Class 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 RuntimeException("Unable to add listener.", e.getCause()); + } + } + + /** + * Binds an event listener to a specific method on a specific object. + * + * @param 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 void bindEventsToMethod(final Object target, final String methodName, final Object eventSource, + final Class 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 eventTypes; + + /** + * Creates a new instance of {@code 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/after/src/main/java/org/apache/commons/lang3/event/package-info.java b/after/src/main/java/org/apache/commons/lang3/event/package-info.java new file mode 100644 index 0000000..0227a16 --- /dev/null +++ b/after/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/after/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java b/after/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java new file mode 100644 index 0000000..5fff98c --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.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.exception; + +/** + * Exception thrown when a clone cannot be created. In contrast to + * {@link CloneNotSupportedException} this is a {@link RuntimeException}. + * + * @since 3.0 + */ +public class CloneFailedException extends RuntimeException { + // ~ Static fields/initializers --------------------------------------------- + + private static final long serialVersionUID = 20091223L; + + // ~ Constructors ----------------------------------------------------------- + + /** + * Constructs a CloneFailedException. + * + * @param message description of the exception + */ + 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/after/src/main/java/org/apache/commons/lang3/exception/ContextedException.java b/after/src/main/java/org/apache/commons/lang3/exception/ContextedException.java new file mode 100644 index 0000000..3555a8c --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/exception/ContextedException.java @@ -0,0 +1,255 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.exception; + +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.tuple.Pair; + +/** + *

+ * An exception that provides an easy and safe way to add contextual information. + *

+ * An exception trace itself is often insufficient to provide rapid diagnosis of the issue. + * Frequently what is needed is a select few pieces of local contextual data. + * Providing this data is tricky however, due to concerns over formatting and nulls. + *

+ * The contexted exception approach allows the exception to be created together with a + * list of context label-value pairs. This additional information is automatically included in + * the message and printed stack trace. + *

+ * An unchecked version of this exception is provided by ContextedRuntimeException. + *

+ *

+ * To use this class write code as follows: + *

+ *
+ *   try {
+ *     ...
+ *   } catch (Exception e) {
+ *     throw new ContextedException("Error posting account transaction", e)
+ *          .addContextValue("Account Number", accountNumber)
+ *          .addContextValue("Amount Posted", amountPosted)
+ *          .addContextValue("Previous Balance", previousBalance);
+ *   }
+ * }
+ * 
+ *

+ * or improve diagnose data at a higher level: + *

+ *
+ *   try {
+ *     ...
+ *   } catch (ContextedException e) {
+ *     throw e.setContextValue("Transaction Id", transactionId);
+ *   } catch (Exception e) {
+ *     if (e instanceof ExceptionContext) {
+ *       e.setContextValue("Transaction Id", transactionId);
+ *     }
+ *     throw e;
+ *   }
+ * }
+ * 
+ *

+ * The output in a printStacktrace() (which often is written to a log) would look something like the following: + *

+ *
+ * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction
+ *  Exception Context:
+ *  [1:Account Number=null]
+ *  [2:Amount Posted=100.00]
+ *  [3:Previous Balance=-2.17]
+ *  [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
+ *
+ *  ---------------------------------
+ *  at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
+ *  ..... (rest of trace)
+ * 
+ * + * @see ContextedRuntimeException + * @since 3.0 + */ +public class ContextedException extends Exception implements ExceptionContext { + + /** The serialization version. */ + private static final long serialVersionUID = 20110706L; + /** The context where the data is stored. */ + private final ExceptionContext exceptionContext; + + /** + * Instantiates ContextedException without message or cause. + *

+ * The context information is stored using a default implementation. + */ + public ContextedException() { + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedException with message, but without cause. + *

+ * The context information is stored using a default implementation. + * + * @param message the exception message, may be null + */ + public ContextedException(final String message) { + super(message); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedException with cause, but without message. + *

+ * The context information is stored using a default implementation. + * + * @param cause the underlying cause of the exception, may be null + */ + public ContextedException(final Throwable cause) { + super(cause); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedException with cause and message. + *

+ * The context information is stored using a default implementation. + * + * @param message the exception message, may be null + * @param cause the underlying cause of the exception, may be null + */ + public ContextedException(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. + *

+ * Note: This exception is only serializable if the object added is serializable. + *

+ * + * @param label a textual label associated with information, {@code null} not recommended + * @param value information needed to understand exception, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + @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. + *

+ * Note: This exception is only serializable if the object added as value is serializable. + *

+ * + * @param label a textual label associated with information, {@code null} not recommended + * @param value information needed to understand exception, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + @Override + public ContextedException setContextValue(final String label, final Object value) { + exceptionContext.setContextValue(label, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List 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> getContextEntries() { + return this.exceptionContext.getContextEntries(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContextLabels() { + return exceptionContext.getContextLabels(); + } + + /** + * Provides the message explaining the exception, including the contextual data. + * + * @see java.lang.Throwable#getMessage() + * @return the message, never null + */ + @Override + public String getMessage() { + return getFormattedExceptionMessage(super.getMessage()); + } + + /** + * Provides the message explaining the exception without the contextual data. + * + * @see java.lang.Throwable#getMessage() + * @return the message + * @since 3.0.1 + */ + public String getRawMessage() { + return super.getMessage(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getFormattedExceptionMessage(final String baseMessage) { + return exceptionContext.getFormattedExceptionMessage(baseMessage); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java b/after/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java new file mode 100644 index 0000000..fc28e41 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java @@ -0,0 +1,256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.exception; + +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.tuple.Pair; + +/** + *

+ * A runtime exception that provides an easy and safe way to add contextual information. + *

+ * An exception trace itself is often insufficient to provide rapid diagnosis of the issue. + * Frequently what is needed is a select few pieces of local contextual data. + * Providing this data is tricky however, due to concerns over formatting and nulls. + *

+ * The contexted exception approach allows the exception to be created together with a + * list of context label-value pairs. This additional information is automatically included in + * the message and printed stack trace. + *

+ * A checked version of this exception is provided by ContextedException. + *

+ *

+ * To use this class write code as follows: + *

+ *
+ *   try {
+ *     ...
+ *   } catch (Exception e) {
+ *     throw new ContextedRuntimeException("Error posting account transaction", e)
+ *          .addContextValue("Account Number", accountNumber)
+ *          .addContextValue("Amount Posted", amountPosted)
+ *          .addContextValue("Previous Balance", previousBalance);
+ *   }
+ * }
+ * 
+ *

+ * or improve diagnose data at a higher level: + *

+ *
+ *   try {
+ *     ...
+ *   } catch (ContextedRuntimeException e) {
+ *     throw e.setContextValue("Transaction Id", transactionId);
+ *   } catch (Exception e) {
+ *     if (e instanceof ExceptionContext) {
+ *       e.setContextValue("Transaction Id", transactionId);
+ *     }
+ *     throw e;
+ *   }
+ * }
+ * 
+ *

+ * The output in a printStacktrace() (which often is written to a log) would look something like the following: + *

+ *
+ * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction
+ *  Exception Context:
+ *  [1:Account Number=null]
+ *  [2:Amount Posted=100.00]
+ *  [3:Previous Balance=-2.17]
+ *  [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
+ *
+ *  ---------------------------------
+ *  at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88)
+ *  ..... (rest of trace)
+ * 
+ * + * @see ContextedException + * @since 3.0 + */ +public class ContextedRuntimeException extends RuntimeException implements ExceptionContext { + + /** The serialization version. */ + private static final long serialVersionUID = 20110706L; + /** The context where the data is stored. */ + private final ExceptionContext exceptionContext; + + /** + * Instantiates ContextedRuntimeException without message or cause. + *

+ * The context information is stored using a default implementation. + */ + public ContextedRuntimeException() { + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedRuntimeException with message, but without cause. + *

+ * The context information is stored using a default implementation. + * + * @param message the exception message, may be null + */ + public ContextedRuntimeException(final String message) { + super(message); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedRuntimeException with cause, but without message. + *

+ * The context information is stored using a default implementation. + * + * @param cause the underlying cause of the exception, may be null + */ + public ContextedRuntimeException(final Throwable cause) { + super(cause); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedRuntimeException with cause and message. + *

+ * The context information is stored using a default implementation. + * + * @param message the exception message, may be null + * @param cause the underlying cause of the exception, may be null + */ + public ContextedRuntimeException(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. + *

+ * Note: This exception is only serializable if the object added is serializable. + *

+ * + * @param label a textual label associated with information, {@code null} not recommended + * @param value information needed to understand exception, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + @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. + *

+ * Note: This exception is only serializable if the object added as value is serializable. + *

+ * + * @param label a textual label associated with information, {@code null} not recommended + * @param value information needed to understand exception, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + @Override + public ContextedRuntimeException setContextValue(final String label, final Object value) { + exceptionContext.setContextValue(label, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List 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> getContextEntries() { + return this.exceptionContext.getContextEntries(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContextLabels() { + return exceptionContext.getContextLabels(); + } + + /** + * Provides the message explaining the exception, including the contextual data. + * + * @see java.lang.Throwable#getMessage() + * @return the message, never null + */ + @Override + public String getMessage() { + return getFormattedExceptionMessage(super.getMessage()); + } + + /** + * Provides the message explaining the exception without the contextual data. + * + * @see java.lang.Throwable#getMessage() + * @return the message + * @since 3.0.1 + */ + public String getRawMessage() { + return super.getMessage(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getFormattedExceptionMessage(final String baseMessage) { + return exceptionContext.getFormattedExceptionMessage(baseMessage); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java b/after/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java new file mode 100644 index 0000000..6386742 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.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.exception; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +/** + * Default implementation of the context storing the label-value pairs for contexted exceptions. + *

+ * This implementation is serializable, however this is dependent on the values that + * are added also being serializable. + *

+ * + * @see ContextedException + * @see ContextedRuntimeException + * @since 3.0 + */ +public class DefaultExceptionContext implements ExceptionContext, Serializable { + + /** The serialization version. */ + private static final long serialVersionUID = 20110706L; + + /** The list storing the label-data pairs. */ + private final List> contextValues = new ArrayList<>(); + + /** + * {@inheritDoc} + */ + @Override + public DefaultExceptionContext addContextValue(final String label, final Object value) { + contextValues.add(new ImmutablePair<>(label, value)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public DefaultExceptionContext setContextValue(final String label, final Object value) { + contextValues.removeIf(p -> StringUtils.equals(label, p.getKey())); + addContextValue(label, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getContextValues(final String label) { + final List values = new ArrayList<>(); + for (final Pair pair : contextValues) { + if (StringUtils.equals(label, pair.getKey())) { + values.add(pair.getValue()); + } + } + return values; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getFirstContextValue(final String label) { + for (final Pair pair : contextValues) { + if (StringUtils.equals(label, pair.getKey())) { + return pair.getValue(); + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContextLabels() { + final Set labels = new HashSet<>(); + for (final Pair pair : contextValues) { + labels.add(pair.getKey()); + } + return labels; + } + + /** + * {@inheritDoc} + */ + @Override + public List> getContextEntries() { + return contextValues; + } + + /** + * Builds the message containing the contextual information. + * + * @param baseMessage the base exception message without context information appended + * @return the exception message with context information appended, never null + */ + @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 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(); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/exception/ExceptionContext.java b/after/src/main/java/org/apache/commons/lang3/exception/ExceptionContext.java new file mode 100644 index 0000000..f055d5b --- /dev/null +++ b/after/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. + *

+ * Implementations are expected to manage the pairs in a list-style collection + * that keeps the pairs in the sequence of their addition. + *

+ * + * @see ContextedException + * @see ContextedRuntimeException + * @since 3.0 + */ +public interface ExceptionContext { + + /** + * Adds a contextual label-value pair into this context. + *

+ * The pair will be added to the context, independently of an already + * existing pair with the same label. + *

+ * + * @param label the label of the item to add, {@code null} not recommended + * @param value the value of item to add, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + ExceptionContext addContextValue(String label, Object value); + + /** + * Sets a contextual label-value pair into this context. + *

+ * The pair will be added normally, but any existing label-value pair with + * the same label is removed from the context. + *

+ * + * @param label the label of the item to add, {@code null} not recommended + * @param value the value of item to add, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + ExceptionContext setContextValue(String label, Object value); + + /** + * Retrieves all the contextual data values associated with the label. + * + * @param label the label to get the contextual values for, may be {@code null} + * @return the contextual values associated with the label, never {@code null} + */ + List getContextValues(String label); + + /** + * Retrieves the first available contextual data value associated with the label. + * + * @param label the label to get the contextual value for, may be {@code null} + * @return the first contextual value associated with the label, may be {@code null} + */ + Object getFirstContextValue(String label); + + /** + * Retrieves the full set of labels defined in the contextual data. + * + * @return the set of labels, not {@code null} + */ + Set getContextLabels(); + + /** + * Retrieves the full list of label-value pairs defined in the contextual data. + * + * @return the list of pairs, not {@code null} + */ + List> getContextEntries(); + + /** + * Gets the contextualized error message based on a base message. + * This will add the context label-value pairs to the message. + * + * @param baseMessage the base exception message without context information appended + * @return the exception message with context information appended, not {@code null} + */ + String getFormattedExceptionMessage(String baseMessage); + +} diff --git a/after/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java b/after/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java new file mode 100644 index 0000000..6ba42d2 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java @@ -0,0 +1,942 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.List; +import java.util.Objects; +import java.util.StringTokenizer; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; + +/** + *

Provides utilities for manipulating and examining + * {@code 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.

+ * + *

Package private for accessibility by test suite.

+ */ + static final String WRAPPED_MARKER = " [wrapped] "; + + //----------------------------------------------------------------------- + /** + *

Introspects the {@code Throwable} to obtain the cause.

+ * + *

The method searches for methods with specific names that return a + * {@code Throwable} object. This will pick up most wrapping exceptions, + * including those from JDK 1.4. + * + *

The default list searched for are:

+ *
    + *
  • {@code getCause()}
  • + *
  • {@code getNextException()}
  • + *
  • {@code getTargetException()}
  • + *
  • {@code getException()}
  • + *
  • {@code getSourceException()}
  • + *
  • {@code getRootCause()}
  • + *
  • {@code getCausedByException()}
  • + *
  • {@code getNested()}
  • + *
+ * + *

If none of the above is found, returns {@code null}.

+ * + * @param throwable the throwable to introspect for a cause, may be null + * @return the cause of the {@code 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 {@code Throwable} to obtain the cause.

+ * + *

A {@code null} set of method names means use the default set. + * A {@code null} in the set of method names will be ignored.

+ * + * @param throwable the throwable to introspect for a cause, may be null + * @param methodNames the method names, null treated as default set + * @return the cause of the {@code 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; + } + + for (final String methodName : methodNames) { + if (methodName != null) { + final Throwable legacyCause = getCauseUsingMethodName(throwable, methodName); + if (legacyCause != null) { + return legacyCause; + } + } + } + + return null; + } + + /** + *

Finds a {@code 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) { + Method method = null; + try { + method = throwable.getClass().getMethod(methodName); + } catch (final NoSuchMethodException | SecurityException ignored) { // NOPMD + // exception ignored + } + + if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) { + try { + return (Throwable) method.invoke(throwable); + } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) { // NOPMD + // exception ignored + } + } + return null; + } + + //----------------------------------------------------------------------- + /** + *

Returns the default names used when searching for the cause of an exception.

+ * + *

This may be modified and used in the overloaded getCause(Throwable, String[]) method.

+ * + * @return cloned array of the default method names + * @since 3.0 + * @deprecated This feature will be removed in Lang 4.0 + */ + @Deprecated + public static String[] getDefaultCauseMethodNames() { + return ArrayUtils.clone(CAUSE_METHOD_NAMES); + } + + //----------------------------------------------------------------------- + /** + * Gets a short message summarising the exception. + *

+ * The message returned is of the form + * {ClassNameWithoutPackage}: {ThrowableMessage} + * + * @param th the throwable to get a message for, null returns empty string + * @return the message, non-null + * @since 2.2 + */ + public static String getMessage(final Throwable th) { + if (th == null) { + return StringUtils.EMPTY; + } + final String clsName = ClassUtils.getShortClassName(th, null); + final String msg = th.getMessage(); + return clsName + ": " + StringUtils.defaultString(msg); + } + + /** + *

Introspects the {@code Throwable} to obtain the root cause.

+ * + *

This method walks through the exception chain to the last element, + * "root" of the tree, using {@link Throwable#getCause()}, and + * returns that exception.

+ * + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. If the throwable parameter + * has a cause of itself, then null will be returned. If the throwable + * parameter cause chain loops, the last element in the chain before the + * loop is returned.

+ * + * @param throwable the throwable to get the root cause for, may be null + * @return the root cause of the {@code Throwable}, + * {@code null} if null throwable input + */ + public static Throwable getRootCause(final Throwable throwable) { + final List list = getThrowableList(throwable); + return list.isEmpty() ? null : list.get(list.size() - 1); + } + + //----------------------------------------------------------------------- + /** + * Gets a short message summarising the root cause exception. + *

+ * The message returned is of the form + * {ClassNameWithoutPackage}: {ThrowableMessage} + * + * @param th the throwable to get a message for, null returns empty string + * @return the message, non-null + * @since 2.2 + */ + public static String getRootCauseMessage(final Throwable th) { + Throwable root = getRootCause(th); + root = root == null ? th : root; + return getMessage(root); + } + + //----------------------------------------------------------------------- + /** + *

Creates a compact stack trace for the root cause of the supplied + * {@code Throwable}.

+ * + *

The output of this method is consistent across JDK versions. + * It consists of the root exception followed by each of its wrapping + * exceptions separated by '[wrapped]'. Note that this is the opposite + * order to the JDK1.4 display.

+ * + * @param throwable the throwable to examine, may be null + * @return an array of stack trace frames, never null + * @since 2.0 + */ + public static String[] getRootCauseStackTrace(final Throwable throwable) { + if (throwable == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + final Throwable[] throwables = getThrowables(throwable); + final int count = throwables.length; + final List frames = new ArrayList<>(); + List nextTrace = getStackFrameList(throwables[count - 1]); + for (int i = count; --i >= 0;) { + final List trace = nextTrace; + if (i != 0) { + nextTrace = getStackFrameList(throwables[i - 1]); + removeCommonFrames(trace, nextTrace); + } + if (i == count - 1) { + frames.add(throwables[i].toString()); + } else { + frames.add(WRAPPED_MARKER + throwables[i].toString()); + } + frames.addAll(trace); + } + return frames.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } + + /** + *

Produces a {@code List} of stack frames - the message + * is not included. Only the trace of the specified exception is + * returned, any caused by trace is stripped.

+ * + *

This works in most cases - it will only fail if the exception + * message contains a line that starts with: + * {@code "   at".}

+ * + * @param t is any throwable + * @return List of stack frames + */ + static List getStackFrameList(final Throwable t) { + final String stackTrace = getStackTrace(t); + final String linebreak = System.lineSeparator(); + final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak); + final List list = new ArrayList<>(); + boolean traceStarted = false; + while (frames.hasMoreTokens()) { + final String token = frames.nextToken(); + // Determine if the line starts with 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; + } + + //----------------------------------------------------------------------- + /** + *

Returns an array where each element is a line from the argument.

+ * + *

The end of line is determined by the value of {@link System#lineSeparator()}.

+ * + * @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 list = new ArrayList<>(); + while (frames.hasMoreTokens()) { + list.add(frames.nextToken()); + } + return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } + + /** + *

Captures the stack trace associated with the specified + * {@code Throwable} object, decomposing it into a list of + * stack frames.

+ * + *

The result of this method vary by JDK version as this method + * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. + * On JDK1.3 and earlier, the cause exception will not be shown + * unless the specified throwable alters printStackTrace.

+ * + * @param throwable the {@code 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.

+ * + *

The result of this method vary by JDK version as this method + * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. + * On JDK1.3 and earlier, the cause exception will not be shown + * unless the specified throwable alters printStackTrace.

+ * + * @param throwable the {@code Throwable} to be examined + * @return the stack trace as generated by the exception's + * {@code printStackTrace(PrintWriter)} method + */ + public static String getStackTrace(final Throwable throwable) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + //----------------------------------------------------------------------- + /** + *

Counts the number of {@code Throwable} objects in the + * exception chain.

+ * + *

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

+ * + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the + * chain is already in the result set.

+ * + * @param throwable the throwable to inspect, may be null + * @return the count of throwables, zero if null input + */ + public static int getThrowableCount(final Throwable throwable) { + return getThrowableList(throwable).size(); + } + + /** + *

Returns the list of {@code Throwable} objects in the + * exception chain.

+ * + *

A throwable without cause will return a list containing + * one element - the input throwable. + * A throwable with one cause will return a list containing + * two elements. - the input throwable and the cause throwable. + * A {@code null} throwable will return a list of size zero.

+ * + *

This method handles recursive cause structures that might + * otherwise cause infinite loops. The cause chain is processed until + * the end is reached, or until the next item in the chain is already + * in the result set.

+ * + * @param throwable the throwable to inspect, may be null + * @return the list of throwables, never null + * @since 2.2 + */ + public static List getThrowableList(Throwable throwable) { + final List list = new ArrayList<>(); + while (throwable != null && !list.contains(throwable)) { + list.add(throwable); + throwable = throwable.getCause(); + } + return list; + } + + /** + *

Returns the list of {@code Throwable} objects in the + * exception chain.

+ * + *

A throwable without cause will return an array containing + * one element - the input throwable. + * A throwable with one cause will return an array containing + * two elements. - the input throwable and the cause throwable. + * A {@code null} throwable will return an array of size zero.

+ * + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the + * chain is already in the result set.

+ * + * @see #getThrowableList(Throwable) + * @param throwable the throwable to inspect, may be null + * @return the array of throwables, never null + */ + public static Throwable[] getThrowables(final Throwable throwable) { + final List list = getThrowableList(throwable); + return list.toArray(ArrayUtils.EMPTY_THROWABLE_ARRAY); + } + + /** + * Does 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 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 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 {@code Throwable} + * that matches the specified class (exactly) in the exception chain. + * Subclasses of the specified class do not match - see + * {@link #indexOfType(Throwable, Class)} for the opposite.

+ * + *

A {@code null} throwable returns {@code -1}. + * A {@code null} type returns {@code -1}. + * No match in the chain returns {@code -1}.

+ * + * @param throwable the throwable to inspect, may be null + * @param clazz the class to search for, subclasses do not match, null returns -1 + * @return the index into the throwable chain, -1 if no match or null input + */ + public static int indexOfThrowable(final Throwable throwable, final Class clazz) { + return indexOf(throwable, clazz, 0, false); + } + + /** + *

Returns the (zero-based) index of the first {@code Throwable} + * that matches the specified type in the exception chain from + * a specified index. + * Subclasses of the specified class do not match - see + * {@link #indexOfType(Throwable, Class, int)} for the opposite.

+ * + *

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

+ * + * @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 clazz, final int fromIndex) { + return indexOf(throwable, clazz, fromIndex, false); + } + + /** + *

Returns the (zero-based) index of the first {@code Throwable} + * that matches the specified class or subclass in the exception chain. + * Subclasses of the specified class do match - see + * {@link #indexOfThrowable(Throwable, Class)} for the opposite.

+ * + *

A {@code null} throwable returns {@code -1}. + * A {@code null} type returns {@code -1}. + * No match in the chain returns {@code -1}.

+ * + * @param throwable the throwable to inspect, may be null + * @param type the type to search for, subclasses match, null returns -1 + * @return the index into the throwable chain, -1 if no match or null input + * @since 2.1 + */ + public static int indexOfType(final Throwable throwable, final Class type) { + return indexOf(throwable, type, 0, true); + } + + /** + *

Returns the (zero-based) index of the first {@code Throwable} + * that matches the specified type in the exception chain from + * a specified index. + * Subclasses of the specified class do match - see + * {@link #indexOfThrowable(Throwable, Class)} for the opposite.

+ * + *

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

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

+ * + *

The compact stack trace starts with the root cause and prints + * stack frames up to the place where it was caught and wrapped. + * Then it prints the wrapped exception and continues with stack frames + * until the wrapper exception is caught and wrapped again, etc.

+ * + *

The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

+ * + *

The method is equivalent to {@code printStackTrace} for throwables + * that don't have nested causes.

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

+ * + *

The compact stack trace starts with the root cause and prints + * stack frames up to the place where it was caught and wrapped. + * Then it prints the wrapped exception and continues with stack frames + * until the wrapper exception is caught and wrapped again, etc.

+ * + *

The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

+ * + *

The method is equivalent to {@code printStackTrace} for throwables + * that don't have nested causes.

+ * + * @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"); + final String[] trace = getRootCauseStackTrace(throwable); + for (final String element : trace) { + printStream.println(element); + } + printStream.flush(); + } + + /** + *

Prints a compact stack trace for the root cause of a throwable.

+ * + *

The compact stack trace starts with the root cause and prints + * stack frames up to the place where it was caught and wrapped. + * Then it prints the wrapped exception and continues with stack frames + * until the wrapper exception is caught and wrapped again, etc.

+ * + *

The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

+ * + *

The method is equivalent to {@code printStackTrace} for throwables + * that don't have nested causes.

+ * + * @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"); + final String[] trace = getRootCauseStackTrace(throwable); + for (final String element : trace) { + printWriter.println(element); + } + 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 IllegalArgumentException if either argument is null + * @since 2.0 + */ + public static void removeCommonFrames(final List causeFrames, final List wrapperFrames) { + if (causeFrames == null || wrapperFrames == null) { + throw new IllegalArgumentException("The List must not be null"); + } + int causeFrameIndex = causeFrames.size() - 1; + int wrapperFrameIndex = wrapperFrames.size() - 1; + while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) { + // Remove the frame from the cause trace if it is the same + // as in the wrapper trace + final String causeFrame = causeFrames.get(causeFrameIndex); + final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex); + if (causeFrame.equals(wrapperFrame)) { + causeFrames.remove(causeFrameIndex); + } + causeFrameIndex--; + wrapperFrameIndex--; + } + } + + /** + * Throw 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. + *

+ * The use of this technique may be controversial, but exceedingly useful to + * library developers. + * + * 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 + * } + * } + * + *

+ * This is an alternative to the more conservative approach of wrapping the + * checked exception in a RuntimeException: + * + * 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 + * } + * } + * + *

+ * 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. + * + * @param throwable + * The throwable to rethrow. + * @param 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 rethrow(final Throwable throwable) { + // claim that the typeErasure invocation throws a RuntimeException + return ExceptionUtils.typeErasure(throwable); + } + + /** + *

Worker method for the {@code throwableOfType} methods.

+ * + * @param 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 throwableOf(final Throwable throwable, final Class 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 {@code 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.

+ * + *

A {@code null} throwable returns {@code null}. + * A {@code null} type returns {@code null}. + * No match in the chain returns {@code null}.

+ * + * @param 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 throwableOfThrowable(final Throwable throwable, final Class clazz) { + return throwableOf(throwable, clazz, 0, false); + } + + /** + *

Returns the first {@code 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.

+ * + *

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

+ * + * @param 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 throwableOfThrowable(final Throwable throwable, final Class clazz, final int fromIndex) { + return throwableOf(throwable, clazz, fromIndex, false); + } + + /** + *

Returns the throwable of the first {@code 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..

+ * + *

A {@code null} throwable returns {@code null}. + * A {@code null} type returns {@code null}. + * No match in the chain returns {@code null}.

+ * + * @param 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 throwableOfType(final Throwable throwable, final Class type) { + return throwableOf(throwable, type, 0, true); + } + + /** + *

Returns the first {@code 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.

+ * + *

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

+ * + * @param 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 throwableOfType(final Throwable throwable, final Class type, final int fromIndex) { + return throwableOf(throwable, type, fromIndex, true); + } + + /** + * Claim a Throwable is another Exception 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 typeErasure(final Throwable throwable) throws T { + throw (T) throwable; + } + + /** + * Throw 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. + *

+ * 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. + * + * @param throwable + * The throwable to rethrow. + * @param 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 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 {@code ExceptionUtils} to be created, although that is not + * normally necessary. + *

+ */ + public ExceptionUtils() { + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/exception/package-info.java b/after/src/main/java/org/apache/commons/lang3/exception/package-info.java new file mode 100644 index 0000000..5992010 --- /dev/null +++ b/after/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.

+ *

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.

+ *

Lastly, {@link org.apache.commons.lang3.exception.ExceptionUtils} also contains {@code Throwable} manipulation + * and examination routines.

+ * + * @since 1.0 + */ +package org.apache.commons.lang3.exception; diff --git a/after/src/main/java/org/apache/commons/lang3/function/Failable.java b/after/src/main/java/org/apache/commons/lang3/function/Failable.java new file mode 100644 index 0000000..164f6aa --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/function/Failable.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.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.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: + * + *
+ * Consumer<java.lang.reflect.Method-> consumer = m -> {
+ *     try {
+ *         m.invoke(o, args);
+ *     } catch (Throwable t) {
+ *         throw Failable.rethrow(t);
+ *     }
+ * };
+ * 
+ * + *

+ * By replacing a {@link java.util.function.Consumer Consumer<O>} with a {@link FailableConsumer + * FailableConsumer<O,? extends Throwable>}, this can be written like follows: + *

+ * + *
+ * Functions.accept((m) -> m.invoke(o, args));
+ * 
+ * + *

+ * Obviously, the second version is much more concise and the spirit of Lambda expressions is met better than the second + * version. + *

+ * + * @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 the type of the first argument the consumer accepts + * @param the type of the second argument the consumer accepts + * @param the type of checked exception the consumer may throw + */ + public static void accept(final FailableBiConsumer 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 the type the consumer accepts + * @param the type of checked exception the consumer may throw + */ + public static void accept(final FailableConsumer 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 the type of checked exception the consumer may throw + */ + public static void accept(final FailableDoubleConsumer 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 the type of checked exception the consumer may throw + */ + public static void accept(final FailableIntConsumer 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 the type of checked exception the consumer may throw + */ + public static void accept(final FailableLongConsumer 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 the type of the first argument the function accepts + * @param the type of the second argument the function accepts + * @param the return type of the function + * @param the type of checked exception the function may throw + * @return the value returned from the function + */ + public static R apply(final FailableBiFunction 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 the type of the argument the function accepts + * @param the return type of the function + * @param the type of checked exception the function may throw + * @return the value returned from the function + */ + public static R apply(final FailableFunction 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 the type of checked exception the function may throw + * @return the value returned from the function + */ + public static double applyAsDouble(final FailableDoubleBinaryOperator function, + final double left, final double right) { + return getAsDouble(() -> function.applyAsDouble(left, right)); + } + + /** + * Converts the given {@link FailableBiConsumer} into a standard {@link BiConsumer}. + * + * @param the type of the first argument of the consumers + * @param the type of the second argument of the consumers + * @param consumer a failable {@code BiConsumer} + * @return a standard {@code BiConsumer} + */ + public static BiConsumer asBiConsumer(final FailableBiConsumer consumer) { + return (input1, input2) -> accept(consumer, input1, input2); + } + + /** + * Converts the given {@link FailableBiFunction} into a standard {@link BiFunction}. + * + * @param the type of the first argument of the input of the functions + * @param the type of the second argument of the input of the functions + * @param the type of the output of the functions + * @param function a {@code FailableBiFunction} + * @return a standard {@code BiFunction} + */ + public static BiFunction asBiFunction(final FailableBiFunction function) { + return (input1, input2) -> apply(function, input1, input2); + } + + /** + * Converts the given {@link FailableBiPredicate} into a standard {@link BiPredicate}. + * + * @param the type of the first argument used by the predicates + * @param the type of the second argument used by the predicates + * @param predicate a {@code FailableBiPredicate} + * @return a standard {@code BiPredicate} + */ + public static BiPredicate asBiPredicate(final FailableBiPredicate predicate) { + return (input1, input2) -> test(predicate, input1, input2); + } + + /** + * Converts the given {@link FailableCallable} into a standard {@link Callable}. + * + * @param the type used by the callables + * @param callable a {@code FailableCallable} + * @return a standard {@code Callable} + */ + public static Callable asCallable(final FailableCallable callable) { + return () -> call(callable); + } + + /** + * Converts the given {@link FailableConsumer} into a standard {@link Consumer}. + * + * @param the type used by the consumers + * @param consumer a {@code FailableConsumer} + * @return a standard {@code Consumer} + */ + public static Consumer asConsumer(final FailableConsumer consumer) { + return input -> accept(consumer, input); + } + + /** + * Converts the given {@link FailableFunction} into a standard {@link Function}. + * + * @param the type of the input of the functions + * @param the type of the output of the functions + * @param function a {code FailableFunction} + * @return a standard {@code Function} + */ + public static Function asFunction(final FailableFunction function) { + return input -> apply(function, input); + } + + /** + * Converts the given {@link FailablePredicate} into a standard {@link Predicate}. + * + * @param the type used by the predicates + * @param predicate a {@code FailablePredicate} + * @return a standard {@code Predicate} + */ + public static Predicate asPredicate(final FailablePredicate predicate) { + return input -> test(predicate, input); + } + + /** + * Converts the given {@link FailableRunnable} into a standard {@link Runnable}. + * + * @param runnable a {@code FailableRunnable} + * @return a standard {@code Runnable} + */ + public static Runnable asRunnable(final FailableRunnable runnable) { + return () -> run(runnable); + } + + /** + * Converts the given {@link FailableSupplier} into a standard {@link Supplier}. + * + * @param the type supplied by the suppliers + * @param supplier a {@code FailableSupplier} + * @return a standard {@code Supplier} + */ + public static Supplier asSupplier(final FailableSupplier supplier) { + return () -> get(supplier); + } + + /** + * Calls a callable and rethrows any exception as a {@link RuntimeException}. + * + * @param callable the callable to call + * @param the return type of the callable + * @param the type of checked exception the callable may throw + * @return the value returned from the callable + */ + public static V call(final FailableCallable callable) { + return get(callable::call); + } + + /** + * Invokes a supplier, and returns the result. + * + * @param supplier The supplier to invoke. + * @param The suppliers output type. + * @param The type of checked exception, which the supplier can throw. + * @return The object, which has been created by the supplier + */ + public static T get(final FailableSupplier 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 The type of checked exception, which the supplier can throw. + * @return The boolean, which has been created by the supplier + */ + public static boolean getAsBoolean(final FailableBooleanSupplier 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 The type of checked exception, which the supplier can throw. + * @return The double, which has been created by the supplier + */ + public static double getAsDouble(final FailableDoubleSupplier 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 The type of checked exception, which the supplier can throw. + * @return The int, which has been created by the supplier + */ + public static int getAsInt(final FailableIntSupplier 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 The type of checked exception, which the supplier can throw. + * @return The long, which has been created by the supplier + */ + public static long getAsLong(final FailableLongSupplier 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 The type of checked exception, which the supplier can throw. + * @return The short, which has been created by the supplier + */ + public static short getAsShort(final FailableShortSupplier 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 + * {@code RuntimeException} or {@code Error} then the argument will be rethrown without modification. If the + * exception is {@code IOException} then it will be wrapped into a {@code UncheckedIOException}. In every other + * cases the exception will be wrapped into a {@code + * UndeclaredThrowableException} + *

+ * + *

+ * 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: + *

+ * + *
+     * throw rethrow(myUncheckedException);
+     * 
+ * + *

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

+ * + * @param throwable The throwable to rethrow ossibly 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; + } else if (throwable instanceof Error) { + throw (Error) throwable; + } else if (throwable instanceof IOException) { + throw new UncheckedIOException((IOException) throwable); + } else { + throw new UndeclaredThrowableException(throwable); + } + } + + /** + * Runs a runnable and rethrows any exception as a {@link RuntimeException}. + * + * @param runnable The runnable to run + * @param the type of checked exception the runnable may throw + */ + public static void run(final FailableRunnable 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 + * + *
+     * Functions.stream(collection.stream());
+     * 
+ * + * @param collection The collection, which is being converted into a {@link FailableStream}. + * @param The collections element type. (In turn, the result streams element type.) + * @return The created {@link FailableStream}. + */ + public static FailableStream stream(final Collection 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 The streams element type. + * @return The created {@link FailableStream}. + */ + public static FailableStream stream(final Stream 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 the type of the first argument the predicate tests + * @param the type of the second argument the predicate tests + * @param the type of checked exception the predicate may throw + * @return the boolean value returned by the predicate + */ + public static boolean test(final FailableBiPredicate 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 the type of argument the predicate tests + * @param the type of checked exception the predicate may throw + * @return the boolean value returned by the predicate + */ + public static boolean test(final FailablePredicate 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 all + * 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 first failure (AKA + * {@link Throwable} is rethrown. Example use: + * + *
+     * final FileInputStream fis = new FileInputStream("my.file");
+     * Functions.tryWithResources(useInputStream(fis), null, () -> fis.close());
+     * 
+ * + * @param action The action to execute. This object will 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. All 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 action, + final FailableConsumer errorHandler, + final FailableRunnable... resources) { + final FailableConsumer actualErrorHandler; + if (errorHandler == null) { + actualErrorHandler = Failable::rethrow; + } else { + actualErrorHandler = errorHandler; + } + if (resources != null) { + for (final FailableRunnable failableRunnable : resources) { + Objects.requireNonNull(failableRunnable, "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 all + * 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 first failure (AKA + * {@link Throwable} is rethrown. Example use: + * + *
+     * final FileInputStream fis = new FileInputStream("my.file");
+     * Functions.tryWithResources(useInputStream(fis), () -> fis.close());
+     * 
+ * + * @param action The action to execute. This object will always be invoked. + * @param resources The resource actions to execute. All 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 action, + final FailableRunnable... resources) { + tryWithResources(action, null, resources); + } + + private Failable() { + // empty + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableBiConsumer.java b/after/src/main/java/org/apache/commons/lang3/function/FailableBiConsumer.java new file mode 100644 index 0000000..8bfe6d5 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Consumed type 1. + * @param Consumed type 2. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableBiConsumer { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableBiConsumer NOP = (t, u) -> {/* NOP */}; + + /** + * Returns The NOP singleton. + * + * @param Consumed type 1. + * @param Consumed type 2. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableBiConsumer 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 {@code FailableBiConsumer} like {@link BiConsumer#andThen(BiConsumer)}. + * + * @param after the operation to perform after this one. + * @return a composed {@code FailableBiConsumer} like {@link BiConsumer#andThen(BiConsumer)}. + * @throws NullPointerException when {@code after} is null. + */ + default FailableBiConsumer andThen(final FailableBiConsumer after) { + Objects.requireNonNull(after); + return (t, u) -> { + accept(t, u); + after.accept(t, u); + }; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableBiFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableBiFunction.java new file mode 100644 index 0000000..23af54f --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Input type 1. + * @param Input type 2. + * @param Return type. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableBiFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableBiFunction NOP = (t, u) -> null; + + /** + * Returns The NOP singleton. + * + * @param Consumed type 1. + * @param Consumed type 2. + * @param Return type. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableBiFunction nop() { + return NOP; + } + + /** + * Returns a composed {@code FailableBiFunction} that like {@link BiFunction#andThen(Function)}. + * + * @param 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 {@code FailableBiFunction} that like {@link BiFunction#andThen(Function)}. + * @throws NullPointerException when {@code after} is null. + */ + default FailableBiFunction andThen(final FailableFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableBiPredicate.java b/after/src/main/java/org/apache/commons/lang3/function/FailableBiPredicate.java new file mode 100644 index 0000000..5258daa --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Predicate type 1. + * @param Predicate type 2. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableBiPredicate { + + /** FALSE singleton */ + @SuppressWarnings("rawtypes") + FailableBiPredicate FALSE = (t, u) -> false; + + /** TRUE singleton */ + @SuppressWarnings("rawtypes") + FailableBiPredicate TRUE = (t, u) -> true; + + /** + * Returns The FALSE singleton. + * + * @param Consumed type 1. + * @param Consumed type 2. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableBiPredicate falsePredicate() { + return FALSE; + } + + /** + * Returns The FALSE TRUE. + * + * @param Consumed type 1. + * @param Consumed type 2. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableBiPredicate truePredicate() { + return TRUE; + } + + /** + * Returns a composed {@code FailableBiPredicate} like {@link BiPredicate#and(BiPredicate)}. + * + * @param other a predicate that will be logically-ANDed with this predicate. + * @return a composed {@code FailableBiPredicate} like {@link BiPredicate#and(BiPredicate)}. + * @throws NullPointerException if other is null + */ + default FailableBiPredicate and(final FailableBiPredicate 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 negate() { + return (final T t, final U u) -> !test(t, u); + } + + /** + * Returns a composed {@code FailableBiPredicate} like {@link BiPredicate#and(BiPredicate)}. + * + * @param other a predicate that will be logically-ORed with this predicate. + * @return a composed {@code FailableBiPredicate} like {@link BiPredicate#and(BiPredicate)}. + * @throws NullPointerException if other is null + */ + default FailableBiPredicate or(final FailableBiPredicate 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/after/src/main/java/org/apache/commons/lang3/function/FailableBooleanSupplier.java b/after/src/main/java/org/apache/commons/lang3/function/FailableBooleanSupplier.java new file mode 100644 index 0000000..89facde --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableBooleanSupplier { + + /** + * Supplies a boolean. + * + * @return a result + * @throws E if the supplier fails + */ + boolean getAsBoolean() throws E; +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableCallable.java b/after/src/main/java/org/apache/commons/lang3/function/FailableCallable.java new file mode 100644 index 0000000..7e3a4d4 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Return type. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableCallable { + + /** + * Calls the callable. + * + * @return The value returned from the callable + * @throws E if the callable fails + */ + R call() throws E; +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableConsumer.java b/after/src/main/java/org/apache/commons/lang3/function/FailableConsumer.java new file mode 100644 index 0000000..aa7a8de --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/function/FailableConsumer.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.function; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * A functional interface like {@link Consumer} that declares a {@code Throwable}. + * + * @param Consumed type 1. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableConsumer { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableConsumer NOP = t -> {/* NOP */}; + + /** + * Returns The NOP singleton. + * + * @param Consumed type 1. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableConsumer 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 {@code Consumer} like {@link Consumer#andThen(Consumer)}. + * + * @param after the operation to perform after this operation + * @return a composed {@code Consumer} like {@link Consumer#andThen(Consumer)}. + * @throws NullPointerException when {@code after} is null + */ + default FailableConsumer andThen(final FailableConsumer after) { + Objects.requireNonNull(after); + return (final T t) -> { + accept(t); + after.accept(t); + }; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleBinaryOperator.java b/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleBinaryOperator.java new file mode 100644 index 0000000..aefc8b7 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableDoubleBinaryOperator { + + /** + * 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/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleConsumer.java b/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleConsumer.java new file mode 100644 index 0000000..edd71fb --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableDoubleConsumer { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableDoubleConsumer NOP = t -> {/* NOP */}; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableDoubleConsumer 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 {@code FailableDoubleConsumer} like {@link DoubleConsumer#andThen(DoubleConsumer)}. + * + * @param after the operation to perform after this one. + * @return a composed {@code FailableDoubleConsumer} like {@link DoubleConsumer#andThen(DoubleConsumer)}. + * @throws NullPointerException when {@code after} is null. + */ + default FailableDoubleConsumer andThen(final FailableDoubleConsumer after) { + Objects.requireNonNull(after); + return (final double t) -> { + accept(t); + after.accept(t); + }; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleFunction.java new file mode 100644 index 0000000..935c956 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Return type. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableDoubleFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableDoubleFunction NOP = t -> null; + + /** + * Returns The NOP singleton. + * + * @param Return type. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableDoubleFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableDoublePredicate.java b/after/src/main/java/org/apache/commons/lang3/function/FailableDoublePredicate.java new file mode 100644 index 0000000..ec6b2cc --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableDoublePredicate { + + /** FALSE singleton */ + @SuppressWarnings("rawtypes") + FailableDoublePredicate FALSE = t -> false; + + /** TRUE singleton */ + @SuppressWarnings("rawtypes") + FailableDoublePredicate TRUE = t -> true; + + /** + * Returns The FALSE singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableDoublePredicate falsePredicate() { + return FALSE; + } + + /** + * Returns The FALSE TRUE. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableDoublePredicate truePredicate() { + return TRUE; + } + + /** + * Returns a composed {@code FailableDoublePredicate} like {@link DoublePredicate#and(DoublePredicate)}. + * + * @param other a predicate that will be logically-ANDed with this predicate. + * @return a composed {@code FailableDoublePredicate} like {@link DoublePredicate#and(DoublePredicate)}. + * @throws NullPointerException if other is null + */ + default FailableDoublePredicate and(final FailableDoublePredicate 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 negate() { + return t -> !test(t); + } + + /** + * Returns a composed {@code FailableDoublePredicate} like {@link DoublePredicate#and(DoublePredicate)}. + * + * @param other a predicate that will be logically-ORed with this predicate. + * @return a composed {@code FailableDoublePredicate} like {@link DoublePredicate#and(DoublePredicate)}. + * @throws NullPointerException if other is null + */ + default FailableDoublePredicate or(final FailableDoublePredicate 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/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleSupplier.java b/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleSupplier.java new file mode 100644 index 0000000..0024ef2 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableDoubleSupplier { + + /** + * Supplies a double. + * + * @return a result + * @throws E if the supplier fails + */ + double getAsDouble() throws E; +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleToIntFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleToIntFunction.java new file mode 100644 index 0000000..90f77b3 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableDoubleToIntFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableDoubleToIntFunction NOP = t -> 0; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableDoubleToIntFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleToLongFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleToLongFunction.java new file mode 100644 index 0000000..44120dc --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableDoubleToLongFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableDoubleToLongFunction NOP = t -> 0; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableDoubleToLongFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleUnaryOperator.java b/after/src/main/java/org/apache/commons/lang3/function/FailableDoubleUnaryOperator.java new file mode 100644 index 0000000..9e542fd --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +public interface FailableDoubleUnaryOperator { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableDoubleUnaryOperator NOP = t -> 0d; + + /** + * Returns a unary operator that always returns its input argument. + * + * @param Thrown exception. + * @return a unary operator that always returns its input argument + */ + static FailableDoubleUnaryOperator identity() { + return t -> t; + } + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableDoubleUnaryOperator nop() { + return NOP; + } + + /** + * Returns a composed {@code FailableDoubleUnaryOperator} like + * {@link DoubleUnaryOperator#andThen(DoubleUnaryOperator)}. + * + * @param after the operator to apply after this one. + * @return a composed {@code FailableDoubleUnaryOperator} like + * {@link DoubleUnaryOperator#andThen(DoubleUnaryOperator)}. + * @throws NullPointerException if after is null. + * @see #compose(FailableDoubleUnaryOperator) + */ + default FailableDoubleUnaryOperator andThen(final FailableDoubleUnaryOperator 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 {@code FailableDoubleUnaryOperator} like + * {@link DoubleUnaryOperator#compose(DoubleUnaryOperator)}. + * + * @param before the operator to apply before this one. + * @return a composed {@code FailableDoubleUnaryOperator} like + * {@link DoubleUnaryOperator#compose(DoubleUnaryOperator)}. + * @throws NullPointerException if before is null. + * @see #andThen(FailableDoubleUnaryOperator) + */ + default FailableDoubleUnaryOperator compose(final FailableDoubleUnaryOperator before) { + Objects.requireNonNull(before); + return (final double v) -> applyAsDouble(before.applyAsDouble(v)); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableFunction.java new file mode 100644 index 0000000..715e265 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Input type 1. + * @param Return type. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableFunction NOP = t -> null; + + /** + * Returns a function that always returns its input argument. + * + * @param the type of the input and output objects to the function + * @param Thrown exception. + * @return a function that always returns its input argument + */ + static FailableFunction identity() { + return t -> t; + } + + /** + * Returns The NOP singleton. + * + * @param Consumed type 1. + * @param Return type. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableFunction nop() { + return NOP; + } + + /** + * Returns a composed {@code FailableFunction} like {@link Function#andThen(Function)}. + * + * @param the output type of the {@code after} function, and of the composed function. + * @return a composed {@code FailableFunction} like {@link Function#andThen(Function)}. + * @param after the operation to perform after this one. + * @throws NullPointerException when {@code after} is null. + */ + default FailableFunction andThen(final FailableFunction 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 {@code FailableFunction} like {@link Function#compose(Function)}. + * + * @param the input type to the {@code before} function, and to the composed function. + * @param before the operator to apply before this one. + * @return a a composed {@code FailableFunction} like {@link Function#compose(Function)}. + * @throws NullPointerException if before is null. + * @see #andThen(FailableFunction) + */ + default FailableFunction compose(final FailableFunction before) { + Objects.requireNonNull(before); + return (final V v) -> apply(before.apply(v)); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableIntBinaryOperator.java b/after/src/main/java/org/apache/commons/lang3/function/FailableIntBinaryOperator.java new file mode 100644 index 0000000..bc22630 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableIntBinaryOperator { + + /** + * 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/after/src/main/java/org/apache/commons/lang3/function/FailableIntConsumer.java b/after/src/main/java/org/apache/commons/lang3/function/FailableIntConsumer.java new file mode 100644 index 0000000..ee7a15f --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableIntConsumer { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableIntConsumer NOP = t -> {/* NOP */}; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableIntConsumer 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 {@code FailableIntConsumer} like {@link IntConsumer#andThen(IntConsumer)}. + * + * @param after the operation to perform after this one. + * @return a composed {@code FailableLongConsumer} like {@link IntConsumer#andThen(IntConsumer)}. + * @throws NullPointerException if {@code after} is null + */ + default FailableIntConsumer andThen(final FailableIntConsumer after) { + Objects.requireNonNull(after); + return (final int t) -> { + accept(t); + after.accept(t); + }; + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableIntFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableIntFunction.java new file mode 100644 index 0000000..9a39688 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Return type. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableIntFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableIntFunction NOP = t -> null; + + /** + * Returns The NOP singleton. + * + * @param Return type. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableIntFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableIntPredicate.java b/after/src/main/java/org/apache/commons/lang3/function/FailableIntPredicate.java new file mode 100644 index 0000000..724d6bf --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableIntPredicate { + + /** FALSE singleton */ + @SuppressWarnings("rawtypes") + FailableIntPredicate FALSE = t -> false; + + /** TRUE singleton */ + @SuppressWarnings("rawtypes") + FailableIntPredicate TRUE = t -> true; + + /** + * Returns The FALSE singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableIntPredicate falsePredicate() { + return FALSE; + } + + /** + * Returns The FALSE TRUE. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableIntPredicate truePredicate() { + return TRUE; + } + + /** + * Returns a composed {@code FailableIntPredicate} like {@link IntPredicate#and(IntPredicate)}. + * + * @param other a predicate that will be logically-ANDed with this predicate. + * @return a composed {@code FailableIntPredicate} like {@link IntPredicate#and(IntPredicate)}. + * @throws NullPointerException if other is null + */ + default FailableIntPredicate and(final FailableIntPredicate 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 negate() { + return t -> !test(t); + } + + /** + * Returns a composed {@code FailableIntPredicate} like {@link IntPredicate#and(IntPredicate)}. + * + * @param other a predicate that will be logically-ORed with this predicate. + * @return a composed {@code FailableIntPredicate} like {@link IntPredicate#and(IntPredicate)}. + * @throws NullPointerException if other is null + */ + default FailableIntPredicate or(final FailableIntPredicate 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/after/src/main/java/org/apache/commons/lang3/function/FailableIntSupplier.java b/after/src/main/java/org/apache/commons/lang3/function/FailableIntSupplier.java new file mode 100644 index 0000000..d1478aa --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableIntSupplier { + + /** + * Supplies an int. + * + * @return a result + * @throws E if the supplier fails + */ + int getAsInt() throws E; +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableIntToDoubleFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableIntToDoubleFunction.java new file mode 100644 index 0000000..64e8c89 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableIntToDoubleFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableIntToDoubleFunction NOP = t -> 0d; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableIntToDoubleFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableIntToLongFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableIntToLongFunction.java new file mode 100644 index 0000000..89eb311 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableIntToLongFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableIntToLongFunction NOP = t -> 0L; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableIntToLongFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableIntUnaryOperator.java b/after/src/main/java/org/apache/commons/lang3/function/FailableIntUnaryOperator.java new file mode 100644 index 0000000..578e532 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +public interface FailableIntUnaryOperator { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableIntUnaryOperator NOP = t -> 0; + + /** + * Returns a unary operator that always returns its input argument. + * + * @param Thrown exception. + * @return a unary operator that always returns its input argument + */ + static FailableIntUnaryOperator identity() { + return t -> t; + } + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableIntUnaryOperator nop() { + return NOP; + } + + /** + * Returns a composed {@code FailableDoubleUnaryOperator} like {@link IntUnaryOperator#andThen(IntUnaryOperator)}. + * + * @param after the operator to apply after this one. + * @return a composed {@code FailableIntUnaryOperator} like {@link IntUnaryOperator#andThen(IntUnaryOperator)}. + * @throws NullPointerException if after is null. + * @see #compose(FailableIntUnaryOperator) + */ + default FailableIntUnaryOperator andThen(final FailableIntUnaryOperator 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 {@code FailableIntUnaryOperator} like {@link IntUnaryOperator#compose(IntUnaryOperator)}. + * + * @param before the operator to apply before this one. + * @return a composed {@code FailableIntUnaryOperator} like {@link IntUnaryOperator#compose(IntUnaryOperator)}. + * @throws NullPointerException if before is null. + * @see #andThen(FailableIntUnaryOperator) + */ + default FailableIntUnaryOperator compose(final FailableIntUnaryOperator before) { + Objects.requireNonNull(before); + return (final int v) -> applyAsInt(before.applyAsInt(v)); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableLongBinaryOperator.java b/after/src/main/java/org/apache/commons/lang3/function/FailableLongBinaryOperator.java new file mode 100644 index 0000000..a7a3e08 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableLongBinaryOperator { + + /** + * 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/after/src/main/java/org/apache/commons/lang3/function/FailableLongConsumer.java b/after/src/main/java/org/apache/commons/lang3/function/FailableLongConsumer.java new file mode 100644 index 0000000..de5bd4f --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableLongConsumer { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableLongConsumer NOP = t -> {/* NOP */}; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableLongConsumer 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 {@code FailableLongConsumer} like {@link LongConsumer#andThen(LongConsumer)}. + * + * @param after the operation to perform after this one. + * @return a composed {@code FailableLongConsumer} like {@link LongConsumer#andThen(LongConsumer)}. + * @throws NullPointerException if {@code after} is null + */ + default FailableLongConsumer andThen(final FailableLongConsumer after) { + Objects.requireNonNull(after); + return (final long t) -> { + accept(t); + after.accept(t); + }; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableLongFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableLongFunction.java new file mode 100644 index 0000000..a7501b4 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Return type. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableLongFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableLongFunction NOP = t -> null; + + /** + * Returns The NOP singleton. + * + * @param Return type. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableLongFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableLongPredicate.java b/after/src/main/java/org/apache/commons/lang3/function/FailableLongPredicate.java new file mode 100644 index 0000000..be97ca9 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableLongPredicate { + + /** FALSE singleton */ + @SuppressWarnings("rawtypes") + FailableLongPredicate FALSE = t -> false; + + /** TRUE singleton */ + @SuppressWarnings("rawtypes") + FailableLongPredicate TRUE = t -> true; + + /** + * Returns The FALSE singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableLongPredicate falsePredicate() { + return FALSE; + } + + /** + * Returns The FALSE TRUE. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableLongPredicate truePredicate() { + return TRUE; + } + + /** + * Returns a composed {@code FailableLongPredicate} like {@link LongPredicate#and(LongPredicate)}. + * + * @param other a predicate that will be logically-ANDed with this predicate. + * @return a composed {@code FailableLongPredicate} like {@link LongPredicate#and(LongPredicate)}. + * @throws NullPointerException if other is null + */ + default FailableLongPredicate and(final FailableLongPredicate 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 negate() { + return t -> !test(t); + } + + /** + * Returns a composed {@code FailableLongPredicate} like {@link LongPredicate#and(LongPredicate)}. + * + * @param other a predicate that will be logically-ORed with this predicate. + * @return a composed {@code FailableLongPredicate} like {@link LongPredicate#and(LongPredicate)}. + * @throws NullPointerException if other is null + */ + default FailableLongPredicate or(final FailableLongPredicate 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/after/src/main/java/org/apache/commons/lang3/function/FailableLongSupplier.java b/after/src/main/java/org/apache/commons/lang3/function/FailableLongSupplier.java new file mode 100644 index 0000000..07d246d --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableLongSupplier { + + /** + * Supplies a long. + * + * @return a result + * @throws E if the supplier fails + */ + long getAsLong() throws E; +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableLongToDoubleFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableLongToDoubleFunction.java new file mode 100644 index 0000000..9b7dc22 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableLongToDoubleFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableLongToDoubleFunction NOP = t -> 0d; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableLongToDoubleFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableLongToIntFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableLongToIntFunction.java new file mode 100644 index 0000000..a6866bb --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableLongToIntFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableLongToIntFunction NOP = t -> 0; + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableLongToIntFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableLongUnaryOperator.java b/after/src/main/java/org/apache/commons/lang3/function/FailableLongUnaryOperator.java new file mode 100644 index 0000000..b997eb0 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +public interface FailableLongUnaryOperator { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableLongUnaryOperator NOP = t -> 0L; + + /** + * Returns a unary operator that always returns its input argument. + * + * @param Thrown exception. + * @return a unary operator that always returns its input argument + */ + static FailableLongUnaryOperator identity() { + return t -> t; + } + + /** + * Returns The NOP singleton. + * + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableLongUnaryOperator nop() { + return NOP; + } + + /** + * Returns a composed {@code FailableDoubleUnaryOperator} like {@link LongUnaryOperator#andThen(LongUnaryOperator)}. + * + * @param after the operator to apply after this one. + * @return a composed {@code FailableLongUnaryOperator} like {@link LongUnaryOperator#andThen(LongUnaryOperator)}. + * @throws NullPointerException if after is null. + * @see #compose(FailableLongUnaryOperator) + */ + default FailableLongUnaryOperator andThen(final FailableLongUnaryOperator 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 {@code FailableLongUnaryOperator} like {@link LongUnaryOperator#compose(LongUnaryOperator)}. + * + * @param before the operator to apply before this one. + * @return a composed {@code FailableLongUnaryOperator} like {@link LongUnaryOperator#compose(LongUnaryOperator)}. + * @throws NullPointerException if before is null. + * @see #andThen(FailableLongUnaryOperator) + */ + default FailableLongUnaryOperator compose(final FailableLongUnaryOperator before) { + Objects.requireNonNull(before); + return (final long v) -> applyAsLong(before.applyAsLong(v)); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableObjDoubleConsumer.java b/after/src/main/java/org/apache/commons/lang3/function/FailableObjDoubleConsumer.java new file mode 100644 index 0000000..b81bf69 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the object argument to the operation. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableObjDoubleConsumer { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableObjDoubleConsumer NOP = (t, u) -> {/* NOP */}; + + /** + * Returns The NOP singleton. + * + * @param the type of the object argument to the operation. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableObjDoubleConsumer 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/after/src/main/java/org/apache/commons/lang3/function/FailableObjIntConsumer.java b/after/src/main/java/org/apache/commons/lang3/function/FailableObjIntConsumer.java new file mode 100644 index 0000000..483f0f4 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the object argument to the operation. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableObjIntConsumer { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableObjIntConsumer NOP = (t, u) -> {/* NOP */}; + + /** + * Returns The NOP singleton. + * + * @param the type of the object argument to the operation. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableObjIntConsumer 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/after/src/main/java/org/apache/commons/lang3/function/FailableObjLongConsumer.java b/after/src/main/java/org/apache/commons/lang3/function/FailableObjLongConsumer.java new file mode 100644 index 0000000..378ff0f --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the object argument to the operation. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableObjLongConsumer { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableObjLongConsumer NOP = (t, u) -> {/* NOP */}; + + /** + * Returns The NOP singleton. + * + * @param the type of the object argument to the operation. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableObjLongConsumer 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/after/src/main/java/org/apache/commons/lang3/function/FailablePredicate.java b/after/src/main/java/org/apache/commons/lang3/function/FailablePredicate.java new file mode 100644 index 0000000..d2ecbc5 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Predicate type. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailablePredicate { + + /** FALSE singleton */ + @SuppressWarnings("rawtypes") + FailablePredicate FALSE = t -> false; + + /** TRUE singleton */ + @SuppressWarnings("rawtypes") + FailablePredicate TRUE = t -> true; + + /** + * Returns The FALSE singleton. + * + * @param Predicate type. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailablePredicate falsePredicate() { + return FALSE; + } + + /** + * Returns The FALSE TRUE. + * + * @param Predicate type. + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailablePredicate truePredicate() { + return TRUE; + } + + /** + * Returns a composed {@code FailablePredicate} like {@link Predicate#and(Predicate)}. + * + * @param other a predicate that will be logically-ANDed with this predicate. + * @return a composed {@code FailablePredicate} like {@link Predicate#and(Predicate)}. + * @throws NullPointerException if other is null + */ + default FailablePredicate and(final FailablePredicate 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 negate() { + return t -> !test(t); + } + + /** + * Returns a composed {@code FailablePredicate} like {@link Predicate#and(Predicate)}. + * + * @param other a predicate that will be logically-ORed with this predicate. + * @return a composed {@code FailablePredicate} like {@link Predicate#and(Predicate)}. + * @throws NullPointerException if other is null + */ + default FailablePredicate or(final FailablePredicate 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/after/src/main/java/org/apache/commons/lang3/function/FailableRunnable.java b/after/src/main/java/org/apache/commons/lang3/function/FailableRunnable.java new file mode 100644 index 0000000..06bd72f --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableRunnable { + + /** + * Runs the function. + * + * @throws E Thrown when the function fails. + */ + void run() throws E; +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableShortSupplier.java b/after/src/main/java/org/apache/commons/lang3/function/FailableShortSupplier.java new file mode 100644 index 0000000..d6731e3 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Thrown exception. + * @since 3.12.0 + */ +@FunctionalInterface +public interface FailableShortSupplier { + + /** + * Supplies an int. + * + * @return a result + * @throws E if the supplier fails + */ + short getAsShort() throws E; +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableSupplier.java b/after/src/main/java/org/apache/commons/lang3/function/FailableSupplier.java new file mode 100644 index 0000000..1a10956 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param Return type. + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableSupplier { + + /** + * Supplies an object + * + * @return a result + * @throws E if the supplier fails + */ + R get() throws E; +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/FailableToDoubleBiFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableToDoubleBiFunction.java new file mode 100644 index 0000000..a3a23a4 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableToDoubleBiFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableToDoubleBiFunction NOP = (t, u) -> 0d; + + /** + * Returns The NOP singleton. + * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableToDoubleBiFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableToDoubleFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableToDoubleFunction.java new file mode 100644 index 0000000..73aa6ce --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the argument to the function + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableToDoubleFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableToDoubleFunction NOP = t -> 0d; + + /** + * Returns The NOP singleton. + * + * @param the type of the argument to the function + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableToDoubleFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableToIntBiFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableToIntBiFunction.java new file mode 100644 index 0000000..9bb3901 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableToIntBiFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableToIntBiFunction NOP = (t, u) -> 0; + + /** + * Returns The NOP singleton. + * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableToIntBiFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableToIntFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableToIntFunction.java new file mode 100644 index 0000000..d95cb33 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the argument to the function + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableToIntFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableToIntFunction NOP = t -> 0; + + /** + * Returns The NOP singleton. + * + * @param the type of the argument to the function + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableToIntFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableToLongBiFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableToLongBiFunction.java new file mode 100644 index 0000000..64e6c6b --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableToLongBiFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableToLongBiFunction NOP = (t, u) -> 0; + + /** + * Returns The NOP singleton. + * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableToLongBiFunction 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/after/src/main/java/org/apache/commons/lang3/function/FailableToLongFunction.java b/after/src/main/java/org/apache/commons/lang3/function/FailableToLongFunction.java new file mode 100644 index 0000000..f04b029 --- /dev/null +++ b/after/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 {@code Throwable}. + * + * @param the type of the first argument to the function + * @param Thrown exception. + * @since 3.11 + */ +@FunctionalInterface +public interface FailableToLongFunction { + + /** NOP singleton */ + @SuppressWarnings("rawtypes") + FailableToLongFunction NOP = t -> 0L; + + /** + * Returns The NOP singleton. + * + * @param the type of the argument to the function + * @param Thrown exception. + * @return The NOP singleton. + */ + static FailableToLongFunction 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/after/src/main/java/org/apache/commons/lang3/function/ToBooleanBiFunction.java b/after/src/main/java/org/apache/commons/lang3/function/ToBooleanBiFunction.java new file mode 100644 index 0000000..09bdb73 --- /dev/null +++ b/after/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 the type of the first argument to the function. + * @param the type of the second argument to the function. + * + * @see BiFunction + * @since 3.12.0 + */ +@FunctionalInterface +public interface ToBooleanBiFunction { + + /** + * 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/after/src/main/java/org/apache/commons/lang3/function/TriFunction.java b/after/src/main/java/org/apache/commons/lang3/function/TriFunction.java new file mode 100644 index 0000000..7d8b81c --- /dev/null +++ b/after/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}. + * + *

+ * This is a functional interface whose functional method is + * {@link #apply(Object, Object, Object)}. + *

+ * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param the type of the third argument to the function + * @param the type of the result of the function + * + * @see Function + * @since 3.12.0 + */ +@FunctionalInterface +public interface TriFunction { + + /** + * Applies this function to the given arguments. + * + * @param t the first function argument + * @param u the second function argument + * @param v the third function argument + * @return the function result + */ + R apply(T t, U u, V v); + + /** + * Returns a composed function that first applies this function to its input, and then applies the {@code after} + * function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the + * composed function. + * + * @param 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 TriFunction andThen(final Function after) { + Objects.requireNonNull(after); + return (final T t, final U u, final V v) -> after.apply(apply(t, u, v)); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/function/package-info.java b/after/src/main/java/org/apache/commons/lang3/function/package-info.java new file mode 100644 index 0000000..330e7c7 --- /dev/null +++ b/after/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. + * + *

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

+ * + * @since 3.11 + */ +package org.apache.commons.lang3.function; diff --git a/after/src/main/java/org/apache/commons/lang3/math/Fraction.java b/after/src/main/java/org/apache/commons/lang3/math/Fraction.java new file mode 100644 index 0000000..5979d8c --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/math/Fraction.java @@ -0,0 +1,923 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.apache.commons.lang3.Validate; + +/** + *

{@code Fraction} is a {@code Number} implementation that + * stores fractions accurately.

+ * + *

This class is immutable, and interoperable with most methods that accept + * a {@code Number}.

+ * + *

Note that this class is intended for common use cases, it is int + * based and thus suffers from various overflow issues. For a BigInteger based + * equivalent, please see the Commons Math BigFraction class.

+ * + * @since 2.0 + */ +public final class Fraction extends Number implements Comparable { + + /** + * Required for serialization support. Lang version 2.0. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 65382027393090L; + + /** + * {@code Fraction} representation of 0. + */ + public static final Fraction ZERO = new Fraction(0, 1); + /** + * {@code Fraction} representation of 1. + */ + public static final Fraction ONE = new Fraction(1, 1); + /** + * {@code Fraction} representation of 1/2. + */ + public static final Fraction ONE_HALF = new Fraction(1, 2); + /** + * {@code Fraction} representation of 1/3. + */ + public static final Fraction ONE_THIRD = new Fraction(1, 3); + /** + * {@code Fraction} representation of 2/3. + */ + public static final Fraction TWO_THIRDS = new Fraction(2, 3); + /** + * {@code Fraction} representation of 1/4. + */ + public static final Fraction ONE_QUARTER = new Fraction(1, 4); + /** + * {@code Fraction} representation of 2/4. + */ + public static final Fraction TWO_QUARTERS = new Fraction(2, 4); + /** + * {@code Fraction} representation of 3/4. + */ + public static final Fraction THREE_QUARTERS = new Fraction(3, 4); + /** + * {@code Fraction} representation of 1/5. + */ + public static final Fraction ONE_FIFTH = new Fraction(1, 5); + /** + * {@code Fraction} representation of 2/5. + */ + public static final Fraction TWO_FIFTHS = new Fraction(2, 5); + /** + * {@code Fraction} representation of 3/5. + */ + public static final Fraction THREE_FIFTHS = new Fraction(3, 5); + /** + * {@code 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 {@code 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 {@code Fraction} instance with the 2 parts + * of a fraction Y/Z.

+ * + *

Any negative signs are resolved to be on the numerator.

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

Creates a {@code Fraction} instance with the 3 parts + * of a fraction X Y/Z.

+ * + *

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

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

Creates a reduced {@code Fraction} instance with the 2 parts + * of a fraction Y/Z.

+ * + *

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

+ * + *

Any negative signs are resolved to be on the numerator.

+ * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance, with the numerator and denominator reduced + * @throws ArithmeticException if the denominator is {@code zero} + */ + public static Fraction getReducedFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (numerator == 0) { + return ZERO; // normalize zero. + } + // allow 2^k/-2^31 as a valid fraction (where k>0) + if (denominator == Integer.MIN_VALUE && (numerator & 1) == 0) { + numerator /= 2; + denominator /= 2; + } + if (denominator < 0) { + if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate"); + } + numerator = -numerator; + denominator = -denominator; + } + // simplify fraction. + final int gcd = greatestCommonDivisor(numerator, denominator); + numerator /= gcd; + denominator /= gcd; + return new Fraction(numerator, denominator); + } + + /** + *

Creates a {@code Fraction} instance from a {@code double} value.

+ * + *

This method uses the + * continued fraction algorithm, computing a maximum of + * 25 convergents and bounding the denominator by 10,000.

+ * + * @param value the double value to convert + * @return a new fraction instance that is close to the value + * @throws ArithmeticException if {@code |value| > 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 = 0; // the current, setup in calculation + int denom2 = 0; // the current, setup in calculation + int a1 = (int) value; + int a2 = 0; + double x1 = 1; + double x2 = 0; + double y1 = value - a1; + double y2 = 0; + 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 {@code String}.

+ * + *

The formats accepted are:

+ * + *
    + *
  1. {@code double} String containing a dot
  2. + *
  3. 'X Y/Z'
  4. + *
  5. 'Y/Z'
  6. + *
  7. 'X' (a simple whole number)
  8. + *
+ *

and a .

+ * + * @param str the string to parse, must not be {@code null} + * @return the new {@code Fraction} instance + * @throws NullPointerException if the string is {@code null} + * @throws NumberFormatException if the number format is invalid + */ + public static Fraction getFraction(String str) { + Validate.notNull(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); + } + + // Accessors + //------------------------------------------------------------------- + + /** + *

Gets the numerator part of the fraction.

+ * + *

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

+ * + * @return the numerator fraction part + */ + public int getNumerator() { + return numerator; + } + + /** + *

Gets the denominator part of the fraction.

+ * + * @return the denominator fraction part + */ + public int getDenominator() { + return denominator; + } + + /** + *

Gets the proper numerator, always positive.

+ * + *

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

+ * + *

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

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

Gets the proper whole part of the fraction.

+ * + *

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

+ * + *

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

+ * + * @return the whole fraction part of a proper fraction, that includes the sign + */ + public int getProperWhole() { + return numerator / denominator; + } + + // Number methods + //------------------------------------------------------------------- + + /** + *

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

Reduce the fraction to the smallest values for the numerator and + * denominator, returning the result.

+ * + *

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

+ * + * @return a new reduced fraction instance, or this if no simplification possible + */ + public Fraction reduce() { + if (numerator == 0) { + return equals(ZERO) ? this : ZERO; + } + final int gcd = greatestCommonDivisor(Math.abs(numerator), denominator); + if (gcd == 1) { + return this; + } + return getFraction(numerator / gcd, denominator / gcd); + } + + /** + *

Gets a fraction that is the inverse (1/fraction) of this one.

+ * + *

The returned fraction is not reduced.

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

Gets a fraction that is the negative (-fraction) of this one.

+ * + *

The returned fraction is not reduced.

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

Gets a fraction that is the positive equivalent of this one.

+ *

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

+ * + *

The returned fraction is not reduced.

+ * + * @return {@code this} if it is positive, or a new positive fraction + * instance with the opposite signed numerator + */ + public Fraction abs() { + if (numerator >= 0) { + return this; + } + return negate(); + } + + /** + *

Gets a fraction that is raised to the passed in power.

+ * + *

The returned fraction is in reduced form.

+ * + * @param power the power to raise the fraction to + * @return {@code this} if the power is one, {@code 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; + } else if (power == 0) { + return ONE; + } else 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); + } else { + 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 + } + + // Arithmetic + //------------------------------------------------------------------- + + /** + * 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 {@code 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 {@code 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 {@code 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) { + Validate.notNull(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 {@code 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) { + Validate.notNull(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 {@code 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) { + Validate.notNull(fraction, "fraction"); + if (fraction.numerator == 0) { + throw new ArithmeticException("The fraction to divide by must not be zero"); + } + return multiplyBy(fraction.invert()); + } + + // Basics + //------------------------------------------------------------------- + + /** + *

Compares this fraction to another object to test if they are equal.

. + * + *

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

+ * + * @param obj the reference object with which to compare + * @return {@code true} if this object is equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Fraction)) { + return false; + } + final Fraction other = (Fraction) obj; + return getNumerator() == other.getNumerator() && getDenominator() == other.getDenominator(); + } + + /** + *

Gets a hashCode for the fraction.

+ * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (hashCode == 0) { + // hash code update should be atomic. + hashCode = 37 * (37 * 17 + getNumerator()) + getDenominator(); + } + return hashCode; + } + + /** + *

Compares this object to another based on size.

+ * + *

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 {@code 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 {@code String}.

+ * + *

The format used is 'numerator/denominator' always. + * + * @return a {@code String} form of the fraction + */ + @Override + public String toString() { + if (toString == null) { + toString = getNumerator() + "/" + getDenominator(); + } + return toString; + } + + /** + *

Gets the fraction as a proper {@code String} in the format X Y/Z.

+ * + *

The format used in 'wholeNumber numerator/denominator'. + * If the whole number is zero it will be omitted. If the numerator is zero, + * only the whole number is returned.

+ * + * @return a {@code 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/after/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java b/after/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java new file mode 100644 index 0000000..42d3356 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.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.math; + +import org.apache.commons.lang3.Validate; + +/** + *

Provides IEEE-754r variants of NumberUtils methods.

+ * + *

See: http://en.wikipedia.org/wiki/IEEE_754r

+ * + * @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) { + Validate.notNull(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) { + Validate.notNull(array, "array"); + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns min + float min = array[0]; + for (int i = 1; i < array.length; i++) { + min = min(array[i], min); + } + + return min; + } + + /** + *

Gets the minimum of three {@code double} values.

+ * + *

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

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static double min(final double a, final double b, final double c) { + return min(min(a, b), c); + } + + /** + *

Gets the minimum of two {@code double} values.

+ * + *

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

+ * + * @param a value 1 + * @param b value 2 + * @return the smallest of the values + */ + public static double min(final double a, final double b) { + if (Double.isNaN(a)) { + return b; + } else + if (Double.isNaN(b)) { + return a; + } else { + return Math.min(a, b); + } + } + + /** + *

Gets the minimum of three {@code float} values.

+ * + *

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

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static float min(final float a, final float b, final float c) { + return min(min(a, b), c); + } + + /** + *

Gets the minimum of two {@code float} values.

+ * + *

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

+ * + * @param a value 1 + * @param b value 2 + * @return the smallest of the values + */ + public static float min(final float a, final float b) { + if (Float.isNaN(a)) { + return b; + } else + if (Float.isNaN(b)) { + return a; + } else { + 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) { + Validate.notNull(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) { + Validate.notNull(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.

+ * + *

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

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

Gets the maximum of two {@code double} values.

+ * + *

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

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

Gets the maximum of three {@code float} values.

+ * + *

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

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the 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.

+ * + *

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

+ * + * @param a value 1 + * @param b value 2 + * @return the largest of the values + */ + public static float max(final float a, final float b) { + if (Float.isNaN(a)) { + return b; + } else + if (Float.isNaN(b)) { + return a; + } else { + return Math.max(a, b); + } + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/math/NumberUtils.java b/after/src/main/java/org/apache/commons/lang3/math/NumberUtils.java new file mode 100644 index 0000000..45c5dbf --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/math/NumberUtils.java @@ -0,0 +1,1832 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 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); + + + /** + *

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

+ * + *

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

+ */ + public NumberUtils() { + } + + //----------------------------------------------------------------------- + /** + *

Convert a {@code String} to an {@code int}, returning + * {@code zero} if the conversion fails.

+ * + *

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

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

Convert a {@code String} to an {@code int}, returning a + * default value if the conversion fails.

+ * + *

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

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

Convert a {@code String} to a {@code long}, returning + * {@code zero} if the conversion fails.

+ * + *

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

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

Convert a {@code String} to a {@code long}, returning a + * default value if the conversion fails.

+ * + *

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

+ * + *
+     *   NumberUtils.toLong(null, 1L) = 1L
+     *   NumberUtils.toLong("", 1L)   = 1L
+     *   NumberUtils.toLong("1", 0L)  = 1L
+     * 
+ * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the long represented by the string, or the default if conversion fails + * @since 2.1 + */ + public static long toLong(final String str, final long defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Long.parseLong(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + *

Convert a {@code String} to a {@code float}, returning + * {@code 0.0f} if the conversion fails.

+ * + *

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

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

Convert a {@code String} to a {@code float}, returning a + * default value if the conversion fails.

+ * + *

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

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

Convert a {@code String} to a {@code double}, returning + * {@code 0.0d} if the conversion fails.

+ * + *

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

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

Convert a {@code String} to a {@code double}, returning a + * default value if the conversion fails.

+ * + *

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

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

Convert a {@code BigDecimal} to a {@code double}.

+ * + *

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

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

Convert a {@code BigDecimal} to a {@code double}.

+ * + *

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

+ * + *
+     *   NumberUtils.toDouble(null, 1.1d)                     = 1.1d
+     *   NumberUtils.toDouble(BigDecimal.valudOf(8.5d), 1.1d) = 8.5d
+     * 
+ * + * @param value the {@code BigDecimal} to convert, may be {@code null}. + * @param defaultValue the default value + * @return the double represented by the {@code BigDecimal} or the + * defaultValue if the {@code 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 {@code String} to a {@code byte}, returning + * {@code zero} if the conversion fails.

+ * + *

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

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

Convert a {@code String} to a {@code byte}, returning a + * default value if the conversion fails.

+ * + *

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

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

Convert a {@code String} to a {@code short}, returning + * {@code zero} if the conversion fails.

+ * + *

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

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

Convert a {@code String} to an {@code short}, returning a + * default value if the conversion fails.

+ * + *

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

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

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

+ * + * @param value the {@code BigDecimal} to convert, may be null. + * @return the scaled, with appropriate rounding, {@code BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final BigDecimal value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a {@code BigDecimal} to a {@code BigDecimal} whose scale is the + * specified value with a {@code RoundingMode} applied. If the input {@code value} + * is {@code null}, we simply return {@code BigDecimal.ZERO}. + * + * @param value the {@code 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, {@code 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 {@code Float} to a {@code BigDecimal} with a scale of + * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied + * {@code value} is null, then {@code BigDecimal.ZERO} is returned. + * + *

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

+ * + * @param value the {@code Float} to convert, may be null. + * @return the scaled, with appropriate rounding, {@code BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final Float value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a {@code Float} to a {@code BigDecimal} whose scale is the + * specified value with a {@code RoundingMode} applied. If the input {@code value} + * is {@code null}, we simply return {@code BigDecimal.ZERO}. + * + * @param value the {@code 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, {@code 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 {@code Double} to a {@code BigDecimal} with a scale of + * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied + * {@code value} is null, then {@code BigDecimal.ZERO} is returned. + * + *

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

+ * + * @param value the {@code Double} to convert, may be null. + * @return the scaled, with appropriate rounding, {@code BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final Double value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a {@code Double} to a {@code BigDecimal} whose scale is the + * specified value with a {@code RoundingMode} applied. If the input {@code value} + * is {@code null}, we simply return {@code BigDecimal.ZERO}. + * + * @param value the {@code 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, {@code 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 {@code String} to a {@code BigDecimal} with a scale of + * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied + * {@code value} is null, then {@code BigDecimal.ZERO} is returned. + * + *

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

+ * + * @param value the {@code String} to convert, may be null. + * @return the scaled, with appropriate rounding, {@code BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final String value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a {@code String} to a {@code BigDecimal} whose scale is the + * specified value with a {@code RoundingMode} applied. If the input {@code value} + * is {@code null}, we simply return {@code BigDecimal.ZERO}. + * + * @param value the {@code 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, {@code 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.

+ * + *

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

+ *

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.

+ * + *

If a type specifier is not found, it will check for a decimal point + * and then try successively larger types from {@code Integer} to + * {@code BigInteger} and from {@code Float} to + * {@code BigDecimal}.

+ * + *

+ * Integral values with a leading {@code 0} will be interpreted as octal; the returned number will + * be Integer, Long or BigDecimal as appropriate. + *

+ * + *

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

+ * + *

This method does not trim the input string, i.e., strings with leading + * or trailing spaces will generate NumberFormatExceptions.

+ * + * @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", "-0x", "-0X", "#", "-#"}; + final int length = str.length(); + int pfxLen = 0; + for (final String pfx : hex_prefixes) { + if (str.startsWith(pfx)) { + pfxLen += pfx.length(); + 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') { // count leading zeroes + pfxLen++; + } else { + break; + } + } + 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 + + 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 { + dec = str.substring(decPos + 1); + } + 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 { + mant = getMantissa(str); + } + dec = null; + } + if (!Character.isDigit(lastChar) && lastChar != '.') { + 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); + final boolean allZeros = isAllZeros(mant) && isAllZeros(exp); + 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 nfe) { // NOPMD + // 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 && !allZeros)) { + //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 nfe) { // NOPMD + // ignore the bad number + } + //$FALL-THROUGH$ + case 'd' : + case 'D' : + try { + final Double d = createDouble(str); + if (!(d.isInfinite() || d.doubleValue() == 0.0D && !allZeros)) { + return d; + } + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + try { + return createBigDecimal(numeric); + } catch (final NumberFormatException e) { // NOPMD + // 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 nfe) { // NOPMD + // ignore the bad number + } + try { + return createLong(str); + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + return createBigInteger(str); + } + + //Must be a Float, Double, BigDecimal + final boolean allZeros = isAllZeros(mant) && isAllZeros(exp); + try { + final Float f = createFloat(str); + final Double d = createDouble(str); + if (!f.isInfinite() + && !(f.floatValue() == 0.0F && !allZeros) + && f.toString().equals(d.toString())) { + return f; + } + if (!d.isInfinite() && !(d.doubleValue() == 0.0D && !allZeros)) { + final BigDecimal b = createBigDecimal(str); + if (b.compareTo(BigDecimal.valueOf(d.doubleValue())) == 0) { + return d; + } + return b; + } + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + return createBigDecimal(str); + } + + /** + *

Utility method for {@link #createNumber(java.lang.String)}.

+ * + *

Returns mantissa of the given number.

+ * + * @param str the string representation of the number + * @return mantissa of the given number + */ + private static String getMantissa(final String str) { + return getMantissa(str, str.length()); + } + + /** + *

Utility method for {@link #createNumber(java.lang.String)}.

+ * + *

Returns mantissa of the given number.

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

+ * + *

Returns {@code true} if s is {@code null}.

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

Convert a {@code String} to a {@code Float}.

+ * + *

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

+ * + * @param str a {@code String} to convert, may be null + * @return converted {@code 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 {@code String} to a {@code Double}.

+ * + *

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

+ * + * @param str a {@code String} to convert, may be null + * @return converted {@code 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 {@code String} to a {@code Integer}, handling + * hex (0xhhhh) and octal (0dddd) notations. + * N.B. a leading zero means octal; spaces are not trimmed.

+ * + *

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

+ * + * @param str a {@code String} to convert, may be null + * @return converted {@code 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 {@code String} to a {@code Long}; + * since 3.1 it handles hex (0Xhhhh) and octal (0ddd) notations. + * N.B. a leading zero means octal; spaces are not trimmed.

+ * + *

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

+ * + * @param str a {@code String} to convert, may be null + * @return converted {@code 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 {@code String} to a {@code BigInteger}; + * since 3.2 it handles hex (0x or #) and octal (0) notations.

+ * + *

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

+ * + * @param str a {@code String} to convert, may be null + * @return converted {@code 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; + } + int pos = 0; // offset within string + int radix = 10; + boolean negate = false; // need to negate later? + if (str.startsWith("-")) { + negate = true; + 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 {@code String} to a {@code BigDecimal}.

+ * + *

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

+ * + * @param str a {@code String} to convert, may be null + * @return converted {@code 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); + } + + // Min in array + //-------------------------------------------------------------------- + /** + *

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 IllegalArgumentException 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 IllegalArgumentException 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 IllegalArgumentException 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 IllegalArgumentException 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 IllegalArgumentException 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 IllegalArgumentException 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; + } + + // Max in array + //-------------------------------------------------------------------- + /** + *

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 IllegalArgumentException 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 IllegalArgumentException 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 IllegalArgumentException 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 IllegalArgumentException 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 IllegalArgumentException 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 IllegalArgumentException 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 either {@code null} or empty + */ + private static void validateArray(final Object array) { + Validate.notNull(array, "array"); + Validate.isTrue(Array.getLength(array) != 0, "Array cannot be empty."); + } + + // 3 param min + //----------------------------------------------------------------------- + /** + *

Gets the minimum of three {@code long} values.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static long min(long a, final long b, final long c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + *

Gets the minimum of three {@code int} values.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static int min(int a, final int b, final int c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + *

Gets the minimum of three {@code short} values.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static short min(short a, final short b, final short c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + *

Gets the minimum of three {@code byte} values.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static byte min(byte a, final byte b, final byte c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + *

Gets the minimum of three {@code double} values.

+ * + *

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

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

Gets the minimum of three {@code float} values.

+ * + *

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

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

Gets the maximum of three {@code long} values.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static long max(long a, final long b, final long c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + *

Gets the maximum of three {@code int} values.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static int max(int a, final int b, final int c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + *

Gets the maximum of three {@code short} values.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static short max(short a, final short b, final short c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + *

Gets the maximum of three {@code byte} values.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static byte max(byte a, final byte b, final byte c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + *

Gets the maximum of three {@code double} values.

+ * + *

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

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

Gets the maximum of three {@code float} values.

+ * + *

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

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

Checks whether the {@code String} contains only + * digit characters.

+ * + *

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

+ * + * @param str the {@code 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.

+ * + *

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

+ * + *

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

+ * + *

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

+ * + *

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

+ * + * @param str the {@code 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.

+ * + *

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

+ * + *

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

+ * + *

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

+ * + *

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

+ * + * @param str the {@code 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; + } else 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.

+ * + *

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.

+ * + *

Hexadecimal and scientific notations are not considered parsable. + * See {@link #isCreatable(String)} on those cases.

+ * + *

{@code Null} and empty String will return {@code false}.

+ * + * @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/after/src/main/java/org/apache/commons/lang3/math/package-info.java b/after/src/main/java/org/apache/commons/lang3/math/package-info.java new file mode 100644 index 0000000..bd56cdd --- /dev/null +++ b/after/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 Commons Math for a more complete set of mathematical classes. + * These classes are immutable, and therefore thread-safe.

+ * + *

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.

+ * + *

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.

+ * + * @since 2.0 + */ +package org.apache.commons.lang3.math; diff --git a/after/src/main/java/org/apache/commons/lang3/mutable/Mutable.java b/after/src/main/java/org/apache/commons/lang3/mutable/Mutable.java new file mode 100644 index 0000000..6aa3182 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/Mutable.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.mutable; + +/** + * Provides mutable access to a value. + *

+ * {@code Mutable} is used as a generic interface to the implementations in this package. + *

+ * A typical use case would be to enable a primitive or string to be passed to a method and allow that method to + * effectively change the value of the primitive/string. Another use case is to store a frequently changing primitive in + * a collection (for example a total in a map) without needing to create new Integer/Long wrapper objects. + * + * @param the type to set and get + * @since 2.1 + */ +public interface Mutable { + + /** + * Gets the value of this mutable. + * + * @return the stored value + */ + T getValue(); + + /** + * Sets the value of this mutable. + * + * @param value + * the value to store + * @throws NullPointerException + * if the object is null and null is invalid + * @throws ClassCastException + * if the type is invalid + */ + void setValue(T value); + +} diff --git a/after/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java b/after/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java new file mode 100644 index 0000000..62e0a85 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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. + *

+ * Note that as MutableBoolean does not extend Boolean, it is not treated by String.format as a Boolean parameter. + * + * @see Boolean + * @since 2.2 + */ +public class MutableBoolean implements Mutable, Serializable, Comparable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -4830728138360036487L; + + /** The mutable value. */ + private boolean value; + + /** + * Constructs a new MutableBoolean with the default value of false. + */ + public MutableBoolean() { + } + + /** + * 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 {@code 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/after/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java b/after/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java new file mode 100644 index 0000000..98e3d55 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java @@ -0,0 +1,388 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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. + *

+ * Note that as MutableByte does not extend Byte, it is not treated by String.format as a Byte parameter. + * + * @see Byte + * @since 2.1 + */ +public class MutableByte extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -1585823265L; + + /** The mutable value. */ + private byte value; + + /** + * Constructs a new MutableByte with the default value of zero. + */ + public MutableByte() { + } + + /** + * 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 {@code 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/after/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java b/after/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java new file mode 100644 index 0000000..1de5cd6 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.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.mutable; + +/** + * A mutable {@code double} wrapper. + *

+ * Note that as MutableDouble does not extend Double, it is not treated by String.format as a Double parameter. + * + * @see Double + * @since 2.1 + */ +public class MutableDouble extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1587163916L; + + /** The mutable value. */ + private double value; + + /** + * Constructs a new MutableDouble with the default value of zero. + */ + public MutableDouble() { + } + + /** + * 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 {@code 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. + *

+ * Note that in most cases, for two instances of class {@code Double},{@code d1} and {@code d2}, + * the value of {@code d1.equals(d2)} is {@code true} if and only if

+ * + *
+     *   d1.doubleValue() == d2.doubleValue()
+     * 
+ * + *
+ *

+ * also has the value {@code true}. However, there are two exceptions: + *

    + *
  • 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}. + *
  • 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. + *
+ * + * @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/after/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java b/after/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java new file mode 100644 index 0000000..5019161 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java @@ -0,0 +1,416 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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. + *

+ * Note that as MutableFloat does not extend Float, it is not treated by String.format as a Float parameter. + * + * @see Float + * @since 2.1 + */ +public class MutableFloat extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 5787169186L; + + /** The mutable value. */ + private float value; + + /** + * Constructs a new MutableFloat with the default value of zero. + */ + public MutableFloat() { + } + + /** + * 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 {@code 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. + *

+ * Note that in most cases, for two instances of class {@code Float},{@code f1} and {@code f2}, + * the value of {@code f1.equals(f2)} is {@code true} if and only if

+ * + *
+     *   f1.floatValue() == f2.floatValue()
+     * 
+ * + *
+ *

+ * also has the value {@code true}. However, there are two exceptions: + *

    + *
  • 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}. + *
  • 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}. + *
+ * 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 java.lang.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/after/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java b/after/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java new file mode 100644 index 0000000..b3b7824 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java @@ -0,0 +1,378 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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. + *

+ * Note that as MutableInt does not extend Integer, it is not treated by String.format as an Integer parameter. + * + * @see Integer + * @since 2.1 + */ +public class MutableInt extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 512176391864L; + + /** The mutable value. */ + private int value; + + /** + * Constructs a new MutableInt with the default value of zero. + */ + public MutableInt() { + } + + /** + * 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 a Integer instance containing the value from this mutable, never null + */ + public Integer toInteger() { + return Integer.valueOf(intValue()); + } + + //----------------------------------------------------------------------- + /** + * Compares this object to the specified object. The result is {@code true} if and only if the argument is + * not {@code null} and is a {@code 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/after/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java b/after/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java new file mode 100644 index 0000000..0344a16 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java @@ -0,0 +1,378 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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. + *

+ * Note that as MutableLong does not extend Long, it is not treated by String.format as a Long parameter. + * + * @see Long + * @since 2.1 + */ +public class MutableLong extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 62986528375L; + + /** The mutable value. */ + private long value; + + /** + * Constructs a new MutableLong with the default value of zero. + */ + public MutableLong() { + } + + /** + * 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 {@code 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/after/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java b/after/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java new file mode 100644 index 0000000..28a61b2 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/MutableObject.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.mutable; + +import java.io.Serializable; + +/** + * A mutable {@code Object} wrapper. + * + * @param the type to set and get + * @since 2.1 + */ +public class MutableObject implements Mutable, Serializable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 86241875189L; + + /** The mutable value. */ + private T value; + + /** + * Constructs a new MutableObject with the default value of {@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 {@code MutableObject} object that contains the same {@code 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 value == null ? 0 : value.hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return value == null ? "null" : value.toString(); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java b/after/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java new file mode 100644 index 0000000..d0cc861 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java @@ -0,0 +1,388 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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. + *

+ * Note that as MutableShort does not extend Short, it is not treated by String.format as a Short parameter. + * + * @see Short + * @since 2.1 + */ +public class MutableShort extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -2135791679L; + + /** The mutable value. */ + private short value; + + /** + * Constructs a new MutableShort with the default value of zero. + */ + public MutableShort() { + } + + /** + * 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 {@code 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/after/src/main/java/org/apache/commons/lang3/mutable/package-info.java b/after/src/main/java/org/apache/commons/lang3/mutable/package-info.java new file mode 100644 index 0000000..c5b8347 --- /dev/null +++ b/after/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/after/src/main/java/org/apache/commons/lang3/package-info.java b/after/src/main/java/org/apache/commons/lang3/package-info.java new file mode 100644 index 0000000..5098296 --- /dev/null +++ b/after/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.

+ * + *

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.

+ * + *

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.

+ * + *

You will find deprecated methods as you stroll through the Lang documentation. These are removed in the next major version.

+ * + *

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.

+ * + *

String manipulation - StringUtils, StringEscapeUtils, RandomStringUtils

+ * + *

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.

+ * + *

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.

+ * + *

Character handling - CharSetUtils, CharSet, CharRange, CharUtils

+ * + *

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

+ * + *

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

+ * + *

JVM interaction - SystemUtils, CharEncoding

+ * + *

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 if (SystemUtils.isJavaVersionAtLeast(1.3f)) {, make a note and move on.

+ * + *

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.

+ * + *

Serialization - SerializationUtils, SerializationException

+ * + *

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.

+ * + *

Assorted functions - ObjectUtils, ClassUtils, ArrayUtils, BooleanUtils

+ * + *

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

+ * + *

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

+ * + *

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.

+ * + *
    + *
  • 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.
  • + *
  • {@code add(xxx[], xxx)} will add a primitive type to an array, resizing the array as you'd expect. Object is also supported.
  • + *
  • {@code clone(xxx[])} clones a primitive or Object array.
  • + *
  • {@code contains(xxx[], xxx)} searches for a primitive or Object in a primitive or Object array.
  • + *
  • {@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)}
  • + *
  • {@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.
  • + *
  • {@code isEmpty(xxx[])} lets you know if an array is zero-sized or null.
  • + *
  • {@code isSameLength(xxx[], xxx[])} returns true if the arrays are the same length.
  • + *
  • 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)}.
  • + *
  • Nearing the end now. The {@code reverse(xxx[])} method turns an array around.
  • + *
  • The {@code subarray(xxx[], int, int)} method splices an array out of a larger array.
  • + *
  • Primitive to primitive wrapper conversion is handled by the {@code toObject(xxx[])} and {@code toPrimitive(Xxx[])} methods.
  • + *
+ * + *

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.

+ * + *

Using toMap

+ *
+ * 
+ * Map colorMap = ArrayUtils.toMap(new String[][] {{
+ *   {"RED", "#FF0000"},
+ *   {"GREEN", "#00FF00"},
+ *   {"BLUE", "#0000FF"}
+ * });
+ * 
+ * 
+ * + *

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.

+ * + *

Flotsam - BitField, Validate

+ *

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.

+ *

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

+ * + * @since 1.0 + */ +package org.apache.commons.lang3; diff --git a/after/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java b/after/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java new file mode 100644 index 0000000..3b9fafd --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java @@ -0,0 +1,301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.lang.reflect.Modifier; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.Validate; + +/** + *

Utility reflection methods focused on constructors, modeled after + * {@link MethodUtils}.

+ * + *

Known Limitations

+ *

Accessing Public Constructors In A Default Access Superclass

+ *

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.

+ * + *

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

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

+ * + *

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

+ */ + public ConstructorUtils() { + } + + /** + *

Returns a new instance of the specified class inferring the right constructor + * from the types of the arguments.

+ * + *

This locates and calls a constructor. + * The constructor signature must match the argument types by assignment compatibility.

+ * + * @param 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(java.lang.Class, java.lang.Object[], java.lang.Class[]) + */ + public static T invokeConstructor(final Class cls, Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeConstructor(cls, args, parameterTypes); + } + + /** + *

Returns a new instance of the specified class choosing the right constructor + * from the list of parameter types.

+ * + *

This locates and calls a constructor. + * The constructor signature must match the parameter types by assignment compatibility.

+ * + * @param 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 invokeConstructor(final Class cls, Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Constructor 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.

+ * + *

This locates and calls a constructor. + * The constructor signature must match the argument types exactly.

+ * + * @param 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(java.lang.Class, java.lang.Object[], java.lang.Class[]) + */ + public static T invokeExactConstructor(final Class cls, Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeExactConstructor(cls, args, parameterTypes); + } + + /** + *

Returns a new instance of the specified class choosing the right constructor + * from the list of parameter types.

+ * + *

This locates and calls a constructor. + * The constructor signature must match the parameter types exactly.

+ * + * @param 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 invokeExactConstructor(final Class cls, Object[] args, + Class[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException, InstantiationException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Constructor 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.

+ * + *

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

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

Checks if the specified constructor is accessible.

+ * + *

This simply ensures that the constructor is accessible.

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

Finds an accessible constructor with compatible parameters.

+ * + *

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

+ * + *

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

+ * + * @param the constructor type + * @param cls the class to find a constructor for, not {@code null} + * @param parameterTypes find method with compatible parameters + * @return the constructor, null if no matching accessible constructor found + * @throws NullPointerException if {@code cls} is {@code null} + */ + public static Constructor getMatchingAccessibleConstructor(final Class cls, + final Class... parameterTypes) { + Validate.notNull(cls, "cls"); + // see if we can find the constructor directly + // most of the time this works and it's much faster + try { + final Constructor ctor = cls.getConstructor(parameterTypes); + MemberUtils.setAccessibleWorkaround(ctor); + return ctor; + } catch (final NoSuchMethodException e) { // NOPMD - Swallow + } + Constructor result = null; + /* + * (1) Class.getConstructors() is documented to return Constructor so as + * long as the array is not subsequently modified, everything's fine. + */ + final Constructor[] ctors = cls.getConstructors(); + + // return best match: + for (Constructor ctor : ctors) { + // compare parameters + if (MemberUtils.isMatchingConstructor(ctor, parameterTypes)) { + // get accessible version of constructor + ctor = getAccessibleConstructor(ctor); + if (ctor != null) { + MemberUtils.setAccessibleWorkaround(ctor); + if (result == null || MemberUtils.compareConstructorFit(ctor, result, parameterTypes) < 0) { + // temporary variable for annotation, see comment above (1) + @SuppressWarnings("unchecked") + final + Constructor constructor = (Constructor) ctor; + result = constructor; + } + } + } + } + return result; + } + + /** + * Learn 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 (!Modifier.isPublic(cls.getModifiers())) { + return false; + } + cls = cls.getEnclosingClass(); + } + return true; + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java b/after/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java new file mode 100644 index 0000000..16817f5 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java @@ -0,0 +1,854 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 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. + *

+ * 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. + * + * @since 2.5 + */ +public class FieldUtils { + + /** + * {@link FieldUtils} instances should NOT be constructed in standard programming. + *

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

+ */ + 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) { + final Field field = getField(cls, fieldName, false); + MemberUtils.setAccessibleWorkaround(field); + return field; + } + + /** + * 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) { + Validate.notNull(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 (!Modifier.isPublic(field.getModifiers())) { + if (forceAccess) { + field.setAccessible(true); + } else { + continue; + } + } + return field; + } catch (final NoSuchFieldException ex) { // NOPMD + // ignore + } + } + + final List> interfaces = ClassUtils.getAllInterfaces(cls); + if (interfaces == null) { + return null; + } + + // 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 : interfaces) { + 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 ex) { // NOPMD + // 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) { + Validate.notNull(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) { + field.setAccessible(true); + } else { + return null; + } + } + return field; + } catch (final NoSuchFieldException e) { // NOPMD + // 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) { + final List allFieldsList = getAllFieldsList(cls); + return allFieldsList.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 an array of Fields (possibly empty). + * @throws IllegalArgumentException + * if the class is {@code null} + * @since 3.2 + */ + public static List getAllFieldsList(final Class cls) { + Validate.notNull(cls, "cls"); + final List allFields = new ArrayList<>(); + Class currentClass = cls; + while (currentClass != null) { + final Field[] declaredFields = currentClass.getDeclaredFields(); + Collections.addAll(allFields, declaredFields); + currentClass = currentClass.getSuperclass(); + } + return allFields; + } + + /** + * Gets 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 annotationCls) { + final List annotatedFieldsList = getFieldsListWithAnnotation(cls, annotationCls); + return annotatedFieldsList.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 getFieldsListWithAnnotation(final Class cls, final Class annotationCls) { + Validate.notNull(annotationCls, "annotationCls"); + final List allFields = getAllFieldsList(cls); + final List annotatedFields = new ArrayList<>(); + for (final Field field : allFields) { + if (field.getAnnotation(annotationCls) != null) { + annotatedFields.add(field); + } + } + return annotatedFields; + } + + /** + * 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 { + Validate.notNull(field, "field"); + Validate.isTrue(Modifier.isStatic(field.getModifiers()), "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 { + Validate.notNull(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 { + Validate.notNull(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 { + Validate.notNull(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 { + Validate.notNull(field, "field"); + Validate.isTrue(Modifier.isStatic(field.getModifiers()), "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 { + Validate.notNull(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: http://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) { + Validate.notNull(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 { + Validate.notNull(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 { + Validate.notNull(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/after/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java b/after/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java new file mode 100644 index 0000000..5cca15c --- /dev/null +++ b/after/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)}.

+ * + *

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

+ */ + public InheritanceUtils() { + } + + /** + *

Returns the number of inheritance hops between two classes.

+ * + * @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/after/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java b/after/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java new file mode 100644 index 0000000..83b9ca5 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java @@ -0,0 +1,319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 + */ +abstract 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 }; + + /** + * XXX 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 o the AccessibleObject to set as accessible + * @return a boolean indicating whether the accessibility of the object was set to true. + */ + static boolean setAccessibleWorkaround(final AccessibleObject o) { + if (o == null || o.isAccessible()) { + return false; + } + final Member m = (Member) o; + if (!o.isAccessible() && Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) { + try { + o.setAccessible(true); + return true; + } catch (final SecurityException e) { // NOPMD + // ignore in favor of subsequent IllegalAccessException + } + } + return false; + } + + /** + * Returns 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; + } + + /** + * Returns whether a {@link Member} is accessible. + * @param m Member to check + * @return {@code true} if {@code m} is accessible + */ + static boolean isAccessible(final Member m) { + return m != null && Modifier.isPublic(m.getModifiers()) && !m.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 required 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/after/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java b/after/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java new file mode 100644 index 0000000..c289f44 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java @@ -0,0 +1,1032 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Modifier; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.*; +import java.util.stream.Collectors; + +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; + +import static java.util.stream.Collectors.toList; + +/** + *

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

+ * + *

Known Limitations

+ *

Accessing Public Methods In A Default Access Superclass

+ *

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.

+ * + *

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

+ * + * @since 2.5 + */ +public class MethodUtils { + + private static final Comparator METHOD_BY_SIGNATURE = Comparator.comparing(Method::toString); + + /** + *

{@link MethodUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as + * {@code MethodUtils.getAccessibleMethod(method)}.

+ * + *

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

+ */ + public MethodUtils() { + } + + /** + *

Invokes a named method without parameters.

+ * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This is a convenient wrapper for + * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * + * @since 3.4 + */ + public static Object invokeMethod(final Object object, final String methodName) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + return invokeMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); + } + + /** + *

Invokes a named method without parameters.

+ * + *

This is a convenient wrapper for + * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param forceAccess force access to invoke method even if it's not accessible + * @param methodName get method with this name + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * + * @since 3.5 + */ + public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return invokeMethod(object, forceAccess, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); + } + + /** + *

Invokes a named method whose parameter type matches the object type.

+ * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} object + * would match a {@code boolean} primitive.

+ * + *

This is a convenient wrapper for + * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + */ + public static Object invokeMethod(final Object object, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeMethod(object, methodName, args, parameterTypes); + } + + /** + *

Invokes a named method whose parameter type matches the object type.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} object + * would match a {@code boolean} primitive.

+ * + *

This is a convenient wrapper for + * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param forceAccess force access to invoke method even if it's not accessible + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * + * @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); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeMethod(object, forceAccess, methodName, args, parameterTypes); + } + + /** + *

Invokes a named method whose parameter type matches the object type.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} object + * would match a {@code boolean} primitive.

+ * + * @param object invoke method on this object + * @param forceAccess force access to invoke method even if it's not accessible + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * @since 3.5 + */ + public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + args = ArrayUtils.nullToEmpty(args); + + final String messagePrefix; + Method method = null; + + if (forceAccess) { + messagePrefix = "No such method: "; + method = getMatchingMethod(object.getClass(), + methodName, parameterTypes); + if (method != null && !method.isAccessible()) { + method.setAccessible(true); + } + } else { + messagePrefix = "No such accessible method: "; + method = getMatchingAccessibleMethod(object.getClass(), + methodName, parameterTypes); + } + + if (method == null) { + throw new NoSuchMethodException(messagePrefix + + methodName + "() on object: " + + object.getClass().getName()); + } + args = toVarArgs(method, args); + + return method.invoke(object, args); + } + + /** + *

Invokes a named method whose parameter type matches the object type.

+ * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} object + * would match a {@code boolean} primitive.

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + */ + public static Object invokeMethod(final Object object, final String methodName, + final Object[] args, final Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + return invokeMethod(object, false, methodName, args, parameterTypes); + } + + /** + *

Invokes a method whose parameter types match exactly the object + * types.

+ * + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod}(Class, String, Class[])}.

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + * + * @since 3.4 + */ + public static Object invokeExactMethod(final Object object, final String methodName) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + return invokeExactMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); + } + + /** + *

Invokes a method with no parameters.

+ * + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod}(Class, String, Class[])}.

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactMethod(final Object object, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeExactMethod(object, methodName, args, parameterTypes); + } + + /** + *

Invokes a method whose parameter types match exactly the parameter + * types given.

+ * + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactMethod(final Object object, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Method method = getAccessibleMethod(object.getClass(), methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on object: " + + object.getClass().getName()); + } + return method.invoke(object, args); + } + + /** + *

Invokes a {@code static} method whose parameter types match exactly the parameter + * types given.

+ * + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.

+ * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod(final Class cls, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Method method = getAccessibleMethod(cls, methodName, parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on class: " + cls.getName()); + } + return method.invoke(null, args); + } + + /** + *

Invokes a named {@code static} method whose parameter type matches the object type.

+ * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} class + * would match a {@code boolean} primitive.

+ * + *

This is a convenient wrapper for + * {@link #invokeStaticMethod(Class, String, Object[], Class[])}. + *

+ * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod(final Class cls, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeStaticMethod(cls, methodName, args, parameterTypes); + } + + /** + *

Invokes a named {@code static} method whose parameter type matches the object type.

+ * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} class + * would match a {@code boolean} primitive.

+ * + * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod(final Class cls, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Method method = getMatchingAccessibleMethod(cls, methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on class: " + cls.getName()); + } + args = toVarArgs(method, args); + return method.invoke(null, args); + } + + private static Object[] toVarArgs(final Method method, Object[] args) { + if (method.isVarArgs()) { + final Class[] methodParameterTypes = method.getParameterTypes(); + args = getVarArgs(args, methodParameterTypes); + } + return args; + } + + /** + *

Given an arguments array passed to a varargs method, return an array of arguments in the canonical form, + * i.e. an array with the declared number of parameters, and whose last parameter is an array of the varargs type. + *

+ * + * @param args the array of arguments passed to the varags method + * @param methodParameterTypes the declared array of method parameter types + * @return an array of the variadic arguments passed to the method + * @since 3.5 + */ + static Object[] getVarArgs(final Object[] args, final Class[] methodParameterTypes) { + if (args.length == methodParameterTypes.length && (args[args.length - 1] == null || + args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) { + // The args array is already in the canonical form for the method. + return args; + } + + // Construct a new array matching the method's declared parameter types. + final Object[] newArgs = new Object[methodParameterTypes.length]; + + // Copy the normal (non-varargs) parameters + System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1); + + // Construct a new array for the variadic parameters + final Class varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType(); + final int varArgLength = args.length - methodParameterTypes.length + 1; + + if (varArgComponentType == null) { + throw new NullPointerException("last method parameter type is not an array"); + } + + Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength); + // Copy the variadic arguments into the varargs array. + System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength); + + if (varArgComponentType.isPrimitive()) { + // unbox from wrapper type to primitive type + varArgsArray = ArrayUtils.toPrimitive(varArgsArray); + } + + // Store the varargs array in the last position of the array to return + newArgs[methodParameterTypes.length - 1] = varArgsArray; + + // Return the canonical varargs array. + return newArgs; + } + + /** + *

Invokes a {@code static} method whose parameter types match exactly the object + * types.

+ * + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.

+ * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod(final Class cls, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeExactStaticMethod(cls, methodName, args, parameterTypes); + } + + /** + *

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 (Modifier.isPublic(cls.getModifiers())) { + 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 (Modifier.isPublic(parentClass.getModifiers())) { + 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}.

+ * + *

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.

+ * + * @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 (!Modifier.isPublic(anInterface.getModifiers())) { + continue; + } + // Does the method exist on this interface? + try { + return anInterface.getDeclaredMethod(methodName, + parameterTypes); + } catch (final NoSuchMethodException e) { // NOPMD + /* + * 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.

+ * + *

This method is used by + * {@link + * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + *

This method can match primitive parameter by passing in wrapper classes. + * For example, a {@code Boolean} will match a primitive {@code boolean} + * parameter. + *

+ * + * @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 { + final Method method = cls.getMethod(methodName, parameterTypes); + MemberUtils.setAccessibleWorkaround(method); + return method; + } catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception + } + // search through all methods + final Method[] methods = cls.getMethods(); + final List matchingMethods = new ArrayList<>(); + for (final Method method : methods) { + // compare name and parameters + if (method.getName().equals(methodName) && + MemberUtils.isMatchingMethod(method, parameterTypes)) { + matchingMethods.add (method); + } + } + + // 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 + * @return The method + * + * @since 3.5 + */ + public static Method getMatchingMethod(final Class cls, final String methodName, + final Class... parameterTypes) { + Validate.notNull(cls, "cls"); + Validate.notEmpty(methodName, "methodName"); + + final List methods = Arrays.stream(cls.getDeclaredMethods()) + .filter(method -> method.getName().equals(methodName)) + .collect(toList()); + + ClassUtils.getAllSuperclasses(cls).stream() + .map(Class::getDeclaredMethods) + .flatMap(Arrays::stream) + .filter(method -> method.getName().equals(methodName)) + .forEach(methods::add); + + for (final Method method : methods) { + if (Arrays.deepEquals(method.getParameterTypes(), parameterTypes)) { + return method; + } + } + + final TreeMap> candidates = new TreeMap<>(); + + methods.stream() + .filter(method -> ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) + .forEach(method -> { + final int distance = distance(parameterTypes, method.getParameterTypes()); + final List candidatesAtDistance = candidates.computeIfAbsent(distance, k -> new ArrayList<>()); + candidatesAtDistance.add(method); + }); + + if (candidates.isEmpty()) { + return null; + } + + final List 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 + Arrays.stream(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; + } else 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<Method> in ascending order from sub- to superclass + * @throws NullPointerException if the specified method is {@code null} + * @since 3.2 + */ + public static Set getOverrideHierarchy(final Method method, final Interfaces interfacesBehavior) { + Validate.notNull(method); + final Set result = new LinkedHashSet<>(); + result.add(method); + + final Class[] parameterTypes = method.getParameterTypes(); + + final Class declaringClass = method.getDeclaringClass(); + + final Iterator> 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, 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 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 getMethodsListWithAnnotation(final Class cls, final Class annotationCls) { + return getMethodsListWithAnnotation(cls, annotationCls, false, false); + } + + /** + * Gets all methods of the given class that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link java.lang.annotation.Annotation} that must be present on a method to be matched + * @param searchSupers + * determines if a lookup in the entire inheritance hierarchy of the given class should be performed + * @param ignoreAccess + * determines if non public methods should be considered + * @return an array of Methods (possibly empty). + * @throws NullPointerException if the class or annotation are {@code null} + * @since 3.6 + */ + public static Method[] getMethodsWithAnnotation(final Class cls, final Class annotationCls, + final boolean searchSupers, final boolean ignoreAccess) { + final List annotatedMethodsList = getMethodsListWithAnnotation(cls, annotationCls, searchSupers, + ignoreAccess); + return annotatedMethodsList.toArray(ArrayUtils.EMPTY_METHOD_ARRAY); + } + + /** + * Gets all methods of the given class that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link Annotation} that must be present on a method to be matched + * @param searchSupers + * determines if a lookup in the entire inheritance hierarchy of the given class should be performed + * @param ignoreAccess + * determines if non public methods should be considered + * @return a list of Methods (possibly empty). + * @throws NullPointerException if either the class or annotation class is {@code null} + * @since 3.6 + */ + public static List getMethodsListWithAnnotation(final Class cls, + final Class annotationCls, + final boolean searchSupers, final boolean ignoreAccess) { + + Validate.notNull(cls, "cls"); + Validate.notNull(annotationCls, "annotationCls"); + final List> classes = (searchSupers ? getAllSuperclassesAndInterfaces(cls) + : new ArrayList<>()); + classes.add(0, cls); + final List annotatedMethods = new ArrayList<>(); + for (final Class acls : classes) { + final Method[] methods = (ignoreAccess ? acls.getDeclaredMethods() : acls.getMethods()); + for (final Method method : methods) { + if (method.getAnnotation(annotationCls) != null) { + annotatedMethods.add(method); + } + } + } + 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.

+ * + *

Stops searching for an annotation once the first annotation of the specified type has been + * found. Additional annotations of the specified type will be silently ignored.

+ * @param + * the annotation type + * @param method + * the {@link Method} to query + * @param annotationCls + * the {@link Annotation} to check if is present on the method + * @param searchSupers + * determines if a lookup in the entire inheritance hierarchy of the given class is performed + * if the annotation was not directly present + * @param ignoreAccess + * determines if underlying method has to be accessible + * @return the first matching annotation, or {@code null} if not found + * @throws NullPointerException if either the method or annotation class is {@code null} + * @since 3.6 + */ + public static A getAnnotation(final Method method, final Class annotationCls, + final boolean searchSupers, final boolean ignoreAccess) { + + Validate.notNull(method, "method"); + Validate.notNull(annotationCls, "annotationCls"); + if (!ignoreAccess && !MemberUtils.isAccessible(method)) { + return null; + } + + A annotation = method.getAnnotation(annotationCls); + + if (annotation == null && searchSupers) { + final Class mcls = method.getDeclaringClass(); + final List> classes = getAllSuperclassesAndInterfaces(mcls); + for (final Class acls : classes) { + final Method equivalentMethod = (ignoreAccess ? MethodUtils.getMatchingMethod(acls, method.getName(), method.getParameterTypes()) + : MethodUtils.getMatchingAccessibleMethod(acls, method.getName(), method.getParameterTypes())); + if (equivalentMethod != null) { + annotation = equivalentMethod.getAnnotation(annotationCls); + if (annotation != null) { + break; + } + } + } + } + + return annotation; + } + + /** + *

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 {@code List} of superclasses and interfaces in order + * going up from this one + * {@code null} if null input + */ + private static List> getAllSuperclassesAndInterfaces(final Class cls) { + if (cls == null) { + return null; + } + + final List> allSuperClassesAndInterfaces = new ArrayList<>(); + final List> allSuperclasses = ClassUtils.getAllSuperclasses(cls); + int superClassIndex = 0; + final List> allInterfaces = ClassUtils.getAllInterfaces(cls); + int interfaceIndex = 0; + while (interfaceIndex < allInterfaces.size() || + superClassIndex < allSuperclasses.size()) { + final Class acls; + if (interfaceIndex >= allInterfaces.size()) { + acls = allSuperclasses.get(superClassIndex++); + } else if ((superClassIndex >= allSuperclasses.size()) || (interfaceIndex < superClassIndex) || !(superClassIndex < interfaceIndex)) { + acls = allInterfaces.get(interfaceIndex++); + } else { + acls = allSuperclasses.get(superClassIndex++); + } + allSuperClassesAndInterfaces.add(acls); + } + return allSuperClassesAndInterfaces; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java b/after/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java new file mode 100644 index 0000000..e1e2898 --- /dev/null +++ b/after/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:

+ *

+ * You might see such a typesafe API as: + *

+ * class Typesafe {
+ *   <T> T obtain(Class<T> type, ...);
+ * }
+ * 
+ * Consumed in the manner of: + *
+ * Foo foo = typesafe.obtain(Foo.class, ...);
+ * 
+ * Yet, you run into problems when you want to do this with a parameterized type: + *
+ * List<String> listOfString = typesafe.obtain(List.class, ...); // could only give us a raw List
+ * 
+ * {@code java.lang.reflect.Type} might provide some value: + *
+ * Type listOfStringType = ...; // firstly, how to obtain this? Doable, but not straightforward.
+ * List<String> listOfString = (List<String>) typesafe.obtain(listOfStringType, ...); // nongeneric Type would necessitate a cast
+ * 
+ * The "type literal" concept was introduced to provide an alternative, i.e.: + *
+ * class Typesafe {
+ *   <T> T obtain(TypeLiteral<T> type, ...);
+ * }
+ * 
+ * Consuming code looks like: + *
+ * List<String> listOfString = typesafe.obtain(new TypeLiteral<List<String>>() {}, ...);
+ * 
+ *

+ * 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 + * {@code Type} instance for any given type, inline. + *

+ *

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: + *

+ *
+ *   <T> T obtain(Typed<T> typed, ...);
+ * 
+ * + * @param the type + * @since 3.2 + */ +public abstract class TypeLiteral implements Typed { + + @SuppressWarnings("rawtypes") + private static final TypeVariable> 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/after/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java b/after/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java new file mode 100644 index 0000000..f34214a --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java @@ -0,0 +1,1932 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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 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 { + 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 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 {@code 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. + * + *

+ * 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 java.lang.Iterable}. Since {@code TreeSet}'s parameter maps + * (indirectly) to {@code Iterable}'s parameter, it will be able to + * determine that based on the super type {@code Iterable>>}, the parameter of + * {@code TreeSet} is {@code ? extends Map>}. + *

+ * + * @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 {@code 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, Type> determineTypeArguments(final Class cls, + final ParameterizedType superParameterizedType) { + Validate.notNull(cls, "cls"); + Validate.notNull(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, 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, 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(Validate.notNull(componentType, "componentType")); + } + + /** + * Formats a {@link GenericArrayType} as a {@link String}. + * + * @param genericArrayType {@code 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) { + Class midClass = null; + + 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) { + Validate.notNull(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) { + Validate.notNull(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) { + Validate.notNull(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 {@code 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, 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 {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments(Class cls, final Class toClass, + final Map, 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, 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.Inner.DeepInner} . + * 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 {@code Map} of the type arguments to their respective type + * variables. + */ + public static Map, 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 {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments( + final ParameterizedType parameterizedType, final Class toClass, + final Map, 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, 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 {@code Map} interface. + * + *

+ * 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. + *

+ * + *

+ * 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 {@code 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. + *

+ * + * @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 {@code 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, 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 {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments(final Type type, final Class toClass, + final Map, 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, 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, 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, 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, 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, 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, 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, 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 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 void mapTypeVariablesToArguments(final Class cls, + final ParameterizedType parameterizedType, final Map, 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>> 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). + * + *

+ * Example, with the variable type declaration: + *

+ * + *
<K extends java.util.Collection<String> &
+     * java.util.List<String>>
+ * + *

+ * since {@code List} is a subinterface of {@code Collection}, + * this method will return the bounds as if the declaration had been: + *

+ * + *
<K extends java.util.List<String>>
+ * + * @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) { + Validate.notNull(bounds, "bounds"); + // don't bother if there's only one (or none) type + if (bounds.length < 2) { + return bounds; + } + + final Set 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} + * @since 3.2 + */ + public static final ParameterizedType parameterize(final Class rawClass, + final Map, Type> typeVariableMap) { + Validate.notNull(rawClass, "rawClass"); + Validate.notNull(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} + * @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 {@code 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} + * @since 3.2 + */ + public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class rawClass, + final Map, Type> typeVariableMap) { + Validate.notNull(rawClass, "rawClass"); + Validate.notNull(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} + * @since 3.2 + */ + public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class rawClass, + final Type... typeArguments) { + Validate.notNull(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, 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) { + Validate.notNull(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 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) { + Validate.notNull(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 }), uses another as a type parameter (such as + * {@code >}), 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, Type> typeVariableMap) { + Validate.notNull(typeVariableMap, "typeVariableMap"); + // all types must be assignable to all the bounds of their mapped + // type variable. + for (final Map.Entry, 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 {@code 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, 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; + } + + /** + * Look up {@code var} in {@code typeVarAssigns} transitively, + * i.e. keep looking until the value found is not 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, Type> typeVarAssigns) { + Type result; + do { + result = typeVarAssigns.get(typeVariable); + if (result instanceof TypeVariable && !result.equals(typeVariable)) { + typeVariable = (TypeVariable) result; + continue; + } + break; + } 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, 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, 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 {@code 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 generic type + * @param type to wrap + * @return Typed<T> + * @since 3.2 + */ + public static Typed wrap(final Class type) { + return wrap((Type) type); + } + + /** + * Wraps the specified {@link Type} in a {@link Typed} wrapper. + * + * @param inferred generic type + * @param type to wrap + * @return Typed<T> + * @since 3.2 + */ + public static Typed wrap(final Type type) { + return () -> type; + } + + /** + * {@code TypeUtils} instances should NOT be constructed in standard + * programming. Instead, the class should be used as + * {@code TypeUtils.isAssignable(cls, toClass)}. + *

+ * This constructor is public to permit tools that require a JavaBean instance + * to operate. + *

+ */ + public TypeUtils() { + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/reflect/Typed.java b/after/src/main/java/org/apache/commons/lang3/reflect/Typed.java new file mode 100644 index 0000000..f8432d5 --- /dev/null +++ b/after/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 the type + * @see TypeLiteral + * @since 3.2 + */ +@FunctionalInterface +public interface Typed { + + /** + * Gets the {@link Type} represented by this entity. + * + * @return Type + */ + Type getType(); +} diff --git a/after/src/main/java/org/apache/commons/lang3/reflect/package-info.java b/after/src/main/java/org/apache/commons/lang3/reflect/package-info.java new file mode 100644 index 0000000..5483643 --- /dev/null +++ b/after/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.

+ *

These classes are immutable, and therefore thread-safe.

+ * + * @since 3.0 + */ +package org.apache.commons.lang3.reflect; diff --git a/after/src/main/java/org/apache/commons/lang3/stream/Streams.java b/after/src/main/java/org/apache/commons/lang3/stream/Streams.java new file mode 100644 index 0000000..cc5a6b6 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/stream/Streams.java @@ -0,0 +1,513 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.lang.reflect.Array; +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.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 + *
+ *     Consumer<java.lang.reflect.Method> consumer = m -> {
+ *         try {
+ *             m.invoke(o, args);
+ *         } catch (Throwable t) {
+ *             throw Failable.rethrow(t);
+ *         }
+ *    };
+ *    stream.forEach(consumer);
+ * 
+ * Using a {@link FailableStream}, this can be rewritten as follows: + *
+ *     Streams.failable(stream).forEach((m) -> m.invoke(o, args));
+ * 
+ * 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 The array type. + */ + public static class ArrayCollector implements Collector, O[]> { + private static final Set characteristics = Collections.emptySet(); + private final Class elementType; + + /** + * Constructs a new instance for the given element type. + * + * @param elementType The element type. + */ + public ArrayCollector(final Class elementType) { + this.elementType = elementType; + } + + @Override + public BiConsumer, O> accumulator() { + return List::add; + } + + @Override + public Set characteristics() { + return characteristics; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> { + left.addAll(right); + return left; + }; + } + + @Override + public Function, O[]> finisher() { + return list -> { + @SuppressWarnings("unchecked") + final O[] array = (O[]) Array.newInstance(elementType, list.size()); + return list.toArray(array); + }; + } + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + } + + /** + * A reduced, and simplified version of a {@link Stream} with failable method signatures. + * + * @param The streams element type. + */ + public static class FailableStream { + + private Stream stream; + private boolean terminated; + + /** + * Constructs a new instance with the given {@code stream}. + * + * @param stream The stream. + */ + public FailableStream(final Stream 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. + * + *

+ * This is a short-circuiting terminal operation. + * + * Note This method evaluates the universal quantification of the predicate over the elements of + * the stream (for all x P(x)). If the stream is empty, the quantification is said to be vacuously + * satisfied and is always {@code true} (regardless of P(x)). + * + * @param predicate A non-interfering, stateless predicate to apply to elements of this stream + * @return {@code true} If either all elements of the stream match the provided predicate or the stream is + * empty, otherwise {@code false}. + */ + public boolean allMatch(final FailablePredicate predicate) { + assertNotTerminated(); + return stream().allMatch(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. + * + *

+ * This is a short-circuiting terminal operation. + * + * Note This method evaluates the existential quantification of the predicate over the elements of + * the stream (for some x P(x)). + * + * @param predicate A non-interfering, stateless predicate to apply to elements of this stream + * @return {@code true} if any elements of the stream match the provided predicate, otherwise {@code false} + */ + public boolean anyMatch(final FailablePredicate predicate) { + assertNotTerminated(); + return stream().anyMatch(Failable.asPredicate(predicate)); + } + + 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 {@code Collector}. A + * {@code 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. + * + *

+ * If the underlying stream is parallel, and the {@code 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.) + * + *

+ * This is a terminal operation. + * + *

+ * 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 {@code ArrayList}), no additional synchronization is needed for a parallel + * reduction. + * + * Note The following will accumulate strings into an ArrayList: + * + *

+         *     {@code
+         *     List asList = stringStream.collect(Collectors.toList());
+         * }
+         * 
+ * + *

+ * The following will classify {@code Person} objects by city: + * + *

+         *     {@code
+         *     Map> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));
+         * }
+         * 
+ * + *

+ * The following will classify {@code Person} objects by state and city, cascading two {@code Collector}s + * together: + * + *

+         *     {@code
+         *     Map>> peopleByStateAndCity = personStream
+         *         .collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));
+         * }
+         * 
+ * + * @param the type of the result + * @param
the intermediate accumulation type of the {@code Collector} + * @param collector the {@code Collector} describing the reduction + * @return the result of the reduction + * @see #collect(Supplier, BiConsumer, BiConsumer) + * @see Collectors + */ + public R collect(final Collector 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 {@code ArrayList}, and elements are + * incorporated by updating the state of the result rather than by replacing the result. This produces a result + * equivalent to: + * + *
+         * {@code
+         *     R result = supplier.get();
+         *     for (T element : this stream)
+         *         accumulator.accept(result, element);
+         *     return result;
+         * }
+         * 
+ * + *

+ * Like {@link #reduce(Object, BinaryOperator)}, {@code collect} operations can be parallelized without + * requiring additional synchronization. + * + *

+ * This is a terminal operation. + * + * 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 + * {@code ArrayList}: + * + *

+         *     {@code
+         *     List asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
+         * }
+         * 
+ * + *

+ * The following will take a stream of strings and concatenates them into a single string: + * + *

+         *     {@code
+         *     String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
+         *         .toString();
+         * }
+         * 
+ * + * @param type of the result + * @param
Type of the accumulator. + * @param pupplier 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 R collect(final Supplier pupplier, final BiConsumer accumulator, + final BiConsumer combiner) { + makeTerminated(); + return stream().collect(pupplier, accumulator, combiner); + } + + /** + * Returns a FailableStream consisting of the elements of this stream that match the given FailablePredicate. + * + *

+ * This is an intermediate operation. + * + * @param predicate a non-interfering, stateless predicate to apply to each element to determine if it should be + * included. + * @return the new stream + */ + public FailableStream filter(final FailablePredicate predicate) { + assertNotTerminated(); + stream = stream.filter(Failable.asPredicate(predicate)); + return this; + } + + /** + * Performs an action for each element of this stream. + * + *

+ * This is a terminal operation. + * + *

+ * The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation + * does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the + * benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever + * thread the library chooses. If the action accesses shared state, it is responsible for providing the required + * synchronization. + * + * @param action a non-interfering action to perform on the elements + */ + public void forEach(final FailableConsumer action) { + makeTerminated(); + stream().forEach(Failable.asConsumer(action)); + } + + protected void makeTerminated() { + assertNotTerminated(); + terminated = true; + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of this stream. + * + *

+ * This is an intermediate operation. + * + * @param The element type of the new stream + * @param mapper A non-interfering, stateless function to apply to each element + * @return the new stream + */ + public FailableStream map(final FailableFunction mapper) { + assertNotTerminated(); + return new FailableStream<>(stream.map(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: + * + *

+         * {@code
+         *     T result = identity;
+         *     for (T element : this stream)
+         *         result = accumulator.apply(result, element)
+         *     return result;
+         * }
+         * 
+ * + * but is not constrained to execute sequentially. + * + *

+ * 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. + * + *

+ * This is a terminal operation. + * + * Note Sum, min, max, average, and string concatenation are all special cases of reduction. Summing a + * stream of numbers can be expressed as: + * + *

+         *     {@code
+         *     Integer sum = integers.reduce(0, (a, b) -> a + b);
+         * }
+         * 
+ * + * or: + * + *
+         *     {@code
+         *     Integer sum = integers.reduce(0, Integer::sum);
+         * }
+         * 
+ * + *

+ * 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. + * + * @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 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 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: + * + *

+     * 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());
+     * 
+ * + * as follows: + * + *
+     * final List<O> list;
+     * final Method m;
+     * final List<String> strList = Failable.stream(list.stream()).map((o) -> (String) m.invoke(o))
+     *     .collect(Collectors.toList());
+     * 
+ * + * While the second version may not be quite as efficient (because it depends on the creation of + * additional, intermediate objects, of type FailableStream), it is much more concise, and readable, and meets the + * spirit of Lambdas better than the first version. + * + * @param The streams element type. + * @param stream The stream, which is being converted. + * @return The {@link FailableStream}, which has been created by converting the stream. + */ + public static FailableStream stream(final Collection stream) { + return stream(stream.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: + * + *
+     * 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());
+     * 
+ * + * as follows: + * + *
+     * final List<O> list;
+     * final Method m;
+     * final List<String> strList = Failable.stream(list.stream()).map((o) -> (String) m.invoke(o))
+     *     .collect(Collectors.toList());
+     * 
+ * + * While the second version may not be quite as efficient (because it depends on the creation of + * additional, intermediate objects, of type FailableStream), it is much more concise, and readable, and meets the + * spirit of Lambdas better than the first version. + * + * @param The streams element type. + * @param stream The stream, which is being converted. + * @return The {@link FailableStream}, which has been created by converting the stream. + */ + public static FailableStream stream(final Stream stream) { + return new FailableStream<>(stream); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new array. + * + * @param pElementType Type of an element in the array. + * @param the type of the input elements + * @return a {@code Collector} which collects all the input elements into an array, in encounter order + */ + public static Collector toArray(final Class pElementType) { + return new ArrayCollector<>(pElementType); + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/stream/package-info.java b/after/src/main/java/org/apache/commons/lang3/stream/package-info.java new file mode 100644 index 0000000..b2deefc --- /dev/null +++ b/after/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}. + * + *

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/after/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java b/after/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java new file mode 100644 index 0000000..32d3d95 --- /dev/null +++ b/after/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 commons-text + * + * CompositeFormat 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/after/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java b/after/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java new file mode 100644 index 0000000..06b4f42 --- /dev/null +++ b/after/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.Iterator; +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 {@code FormatFactory} instances associated with {@code 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 (()? signifies optionality):
+ * {argument-number({@code ,}format-name + * ({@code ,}format-style)?)?} + * + *

+ * format-name and format-style values are trimmed of surrounding whitespace + * in the manner of {@code java.text.MessageFormat}. If format-name denotes + * {@code FormatFactory formatFactoryInstance} in {@code registry}, a {@code Format} + * matching format-name and format-style is requested from + * {@code formatFactoryInstance}. If this is successful, the {@code Format} + * found is used for this format element. + *

+ * + *

NOTICE: 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 + * {@code UnsupportedOperationException} if called. + *

+ * + *

Limitations inherited from {@code java.text.MessageFormat}:

+ *
    + *
  • When using "choice" subformats, support for nested formatting instructions is limited + * to that provided by the base class.
  • + *
  • Thread-safety of {@code Format}s, including {@code MessageFormat} and thus + * {@code ExtendedMessageFormat}, is not guaranteed.
  • + *
+ * + * @since 2.4 + * @deprecated as of 3.6, use commons-text + * + * ExtendedMessageFormat 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 = '\''; + + private String toPattern; + private final Map 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 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 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 foundFormats = new ArrayList<>(); + final ArrayList 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 Iterator it = foundFormats.iterator(); it.hasNext(); i++) { + final Format f = it.next(); + if (f != null) { + origFormats[i] = f; + } + } + 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 e) { // NOPMD + // 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 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 = 0; + 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(); + final int lastHold = start; + for (int i = pos.getIndex(); i < pattern.length(); i++) { + if (c[pos.getIndex()] == QUOTE) { + next(pos); + return appendTo == null ? null : appendTo.append(c, lastHold, + pos.getIndex() - lastHold); + } + 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; + } + for (final Object name : coll) { + if (name != null) { + return true; + } + } + return false; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/text/FormatFactory.java b/after/src/main/java/org/apache/commons/lang3/text/FormatFactory.java new file mode 100644 index 0000000..4476f5b --- /dev/null +++ b/after/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 commons-text + * + * FormatFactory 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 + * {@code 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/after/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java b/after/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java new file mode 100644 index 0000000..35ee8c8 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/text/FormattableUtils.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.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 {@code Formattable} interface.

+ * + *

The {@link Formattable} interface provides basic control over formatting + * when using a {@code Formatter}. It is primarily concerned with numeric precision + * and padding, and is not designed to allow generalised alternate formats.

+ * + * @since 3.0 + * @deprecated as of 3.6, use commons-text + * + * FormattableUtils instead + */ +@Deprecated +public class FormattableUtils { + + /** + * A format that simply outputs the value as a string. + */ + private static final String SIMPLEST_FORMAT = "%s"; + + /** + *

{@code FormattableUtils} instances should NOT be constructed in + * standard programming. Instead, the methods of the class should be invoked + * statically.

+ * + *

This constructor is public to permit tools that require a JavaBean + * instance to operate.

+ */ + public FormattableUtils() { + } + + //----------------------------------------------------------------------- + /** + * Gets the default formatted representation of the specified + * {@code 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 {@code 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 {@code Formattable} + * @param width the width of the output, see {@code Formattable} + * @param precision the precision of the output, see {@code 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 {@code Formattable} + * @param width the width of the output, see {@code Formattable} + * @param precision the precision of the output, see {@code 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 {@code Formattable} + * @param width the width of the output, see {@code Formattable} + * @param precision the precision of the output, see {@code 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 {@code Formattable} + * @param width the width of the output, see {@code Formattable} + * @param precision the precision of the output, see {@code 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 _ellipsis = ObjectUtils.defaultIfNull(ellipsis, StringUtils.EMPTY); + buf.replace(precision - _ellipsis.length(), seq.length(), _ellipsis.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/after/src/main/java/org/apache/commons/lang3/text/StrBuilder.java b/after/src/main/java/org/apache/commons/lang3/text/StrBuilder.java new file mode 100644 index 0000000..7c53271 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/text/StrBuilder.java @@ -0,0 +1,3111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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. + *

+ * The main differences from StringBuffer/StringBuilder are: + *

+ *
    + *
  • Not synchronized
  • + *
  • Not final
  • + *
  • Subclasses have direct access to character array
  • + *
  • Additional methods + *
      + *
    • appendWithSeparators - adds an array of values, with a separator
    • + *
    • appendPadding - adds a length padding characters
    • + *
    • appendFixedLength - adds a fixed width field to the builder
    • + *
    • toCharArray/getChars - simpler ways to get a range of the character array
    • + *
    • delete - delete char or string
    • + *
    • replace - search and replace for a char or string
    • + *
    • leftString/rightString/midString - substring without exceptions
    • + *
    • contains - whether the builder contains a char or string
    • + *
    • size/clear/isEmpty - collections style API methods
    • + *
    + *
  • + *
  • Views + *
      + *
    • asTokenizer - uses the internal buffer as the source of a StrTokenizer
    • + *
    • asReader - uses the internal buffer as the source of a Reader
    • + *
    • asWriter - allows a Writer to write directly to the internal buffer
    • + *
    + *
  • + *
+ *

+ * 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)}. + *

+ * 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. + * + * @since 2.2 + * @deprecated as of 3.6, use commons-text + * + * TextStringBuilder instead + */ +@Deprecated +public class StrBuilder implements CharSequence, Appendable, Serializable, Builder { + + /** + * 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 (nullText != null && nullText.isEmpty()) { + 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; + final int newEnd = length; + size = length; + for (int i = oldEnd; i < newEnd; i++) { + buffer[i] = CharUtils.NUL; + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the current size of the internal character array buffer. + * + * @return the capacity + */ + public int capacity() { + return buffer.length; + } + + /** + * Checks the capacity and ensures that it is at least the size specified. + * + * @param capacity the capacity to ensure + * @return this, to enable chaining + */ + public StrBuilder ensureCapacity(final int capacity) { + if (capacity > buffer.length) { + final char[] old = buffer; + buffer = new char[capacity * 2]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + /** + * Minimizes the capacity to the actual length of the string. + * + * @return this, to enable chaining + */ + public StrBuilder minimizeCapacity() { + if (buffer.length > length()) { + final char[] old = buffer; + buffer = new char[length()]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the length of the string builder. + *

+ * This method is the same as {@link #length()} and is provided to match the + * API of Collections. + * + * @return the length + */ + public int size() { + return size; + } + + /** + * Checks is the string builder is empty (convenience Collections API style method). + *

+ * This method is the same as checking {@link #length()} and is provided to match the + * API of Collections. + * + * @return {@code true} if the size is {@code 0}. + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Checks is the string builder is not empty (convenience Collections API style method). + *

+ * This method is the same as checking {@link #length()} and is provided to match the + * API of Collections. + * + * @return {@code true} if the size is greater than {@code 0}. + * @since 3.12.0 + */ + public boolean isNotEmpty() { + return size > 0; + } + + /** + * Clears the string builder (convenience Collections API style method). + *

+ * This method does not reduce the size of the internal character buffer. + * To do that, call {@code clear()} followed by {@link #minimizeCapacity()}. + *

+ * This method is the same as {@link #setLength(int)} called with zero + * and is provided to match the API of Collections. + * + * @return this, to enable chaining + */ + public StrBuilder clear() { + size = 0; + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the character at the specified index. + * + * @see #setCharAt(int, char) + * @see #deleteCharAt(int) + * @param index the index to retrieve, must be valid + * @return the character at the index + * @throws IndexOutOfBoundsException if the index is invalid + */ + @Override + public char charAt(final int index) { + if (index < 0 || index >= length()) { + throw new StringIndexOutOfBoundsException(index); + } + return buffer[index]; + } + + /** + * Sets the character at the specified index. + * + * @see #charAt(int) + * @see #deleteCharAt(int) + * @param index the index to set + * @param ch the new character + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder setCharAt(final int index, final char ch) { + if (index < 0 || index >= length()) { + throw new StringIndexOutOfBoundsException(index); + } + buffer[index] = ch; + return this; + } + + /** + * Deletes the character at the specified index. + * + * @see #charAt(int) + * @see #setCharAt(int, char) + * @param index the index to delete + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder deleteCharAt(final int index) { + if (index < 0 || index >= size) { + throw new StringIndexOutOfBoundsException(index); + } + deleteImpl(index, index + 1, 1); + return this; + } + + //----------------------------------------------------------------------- + /** + * Copies the builder's character array into a new character array. + * + * @return a new array that represents the contents of the builder + */ + public char[] toCharArray() { + if (size == 0) { + return ArrayUtils.EMPTY_CHAR_ARRAY; + } + final char[] chars = new char[size]; + System.arraycopy(buffer, 0, chars, 0, size); + return chars; + } + + /** + * Copies part of the builder's character array into a new character array. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except that + * if too large it is treated as end of string + * @return a new array that holds part of the contents of the builder + * @throws IndexOutOfBoundsException if startIndex is invalid, + * or if endIndex is invalid (but endIndex greater than size is valid) + */ + public char[] toCharArray(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + final int len = endIndex - startIndex; + if (len == 0) { + return ArrayUtils.EMPTY_CHAR_ARRAY; + } + final char[] chars = new char[len]; + System.arraycopy(buffer, startIndex, chars, 0, len); + return chars; + } + + /** + * Copies the character array into the specified array. + * + * @param destination the destination array, null will cause an array to be created + * @return the input array, unless that was null or too small + */ + public char[] getChars(char[] destination) { + final int len = length(); + if (destination == null || destination.length < len) { + destination = new char[len]; + } + System.arraycopy(buffer, 0, destination, 0, len); + return destination; + } + + /** + * Copies the character array into the specified array. + * + * @param startIndex first index to copy, inclusive, must be valid + * @param endIndex last index, exclusive, must be valid + * @param destination the destination array, must not be null or too small + * @param destinationIndex the index to start copying in destination + * @throws NullPointerException if the array is null + * @throws IndexOutOfBoundsException if any index is invalid + */ + public void getChars(final int startIndex, final int endIndex, final char[] destination, final int destinationIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex < 0 || endIndex > length()) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException("end < start"); + } + System.arraycopy(buffer, startIndex, destination, destinationIndex, endIndex - startIndex); + } + + //----------------------------------------------------------------------- + /** + * If possible, reads chars from the provided {@link Readable} directly into underlying + * character buffer without making extra copies. + * + * @param readable object to read from + * @return the number of characters read + * @throws IOException if an I/O error occurs. + * + * @since 3.4 + * @see #appendTo(Appendable) + */ + public int readFrom(final Readable readable) throws IOException { + final int oldSize = size; + if (readable instanceof Reader) { + final Reader r = (Reader) readable; + ensureCapacity(size + 1); + int read; + while ((read = r.read(buffer, size, buffer.length - size)) != -1) { + size += read; + ensureCapacity(size + 1); + } + } else if (readable instanceof CharBuffer) { + final CharBuffer cb = (CharBuffer) readable; + final int remaining = cb.remaining(); + ensureCapacity(size + remaining); + cb.get(buffer, size, remaining); + size += remaining; + } 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. + *

+ * 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. + * + * @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'; + buffer[size++] = 'e'; + } 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 the element type + * @param array the array to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendAll(@SuppressWarnings("unchecked") final T... array) { + /* + * @SuppressWarnings used to hide warning about vararg usage. We cannot + * use @SafeVarargs, since this method is not final. Using @SuppressWarnings + * is fine, because it isn't inherited by subclasses, so each subclass must + * vouch for itself whether its use of 'array' is safe. + */ + if (ArrayUtils.isNotEmpty(array)) { + for (final Object element : array) { + append(element); + } + } + return this; + } + + /** + * Appends each item in an iterable to the builder without any separators. + * Appending a null iterable will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param iterable the iterable to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendAll(final Iterable iterable) { + if (iterable != null) { + for (final Object o : iterable) { + append(o); + } + } + 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) { + while (it.hasNext()) { + append(it.next()); + } + } + 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)}. + *

+ * This method is useful for adding a separator each time around the + * loop except the first. + *

+     * for (Iterator it = list.iterator(); it.hasNext(); ) {
+     *   appendSeparator(",");
+     *   append(it.next());
+     * }
+     * 
+ * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param 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)}. + *

+ * This method is for example useful for constructing queries + *

+     * StrBuilder whereClause = new StrBuilder();
+     * if (searchCommand.getPriority() != null) {
+     *  whereClause.appendSeparator(" and", " where");
+     *  whereClause.append(" priority = ?")
+     * }
+     * if (searchCommand.getComponent() != null) {
+     *  whereClause.appendSeparator(" and", " where");
+     *  whereClause.append(" component = ?")
+     * }
+     * selectClause.append(whereClause)
+     * 
+ * + * @param standard the separator if builder is not empty, null means no separator + * @param defaultIfEmpty the separator if builder is empty, null means no separator + * @return this, to enable chaining + * @since 2.5 + */ + public StrBuilder appendSeparator(final String standard, final String defaultIfEmpty) { + final String str = isEmpty() ? defaultIfEmpty : standard; + if (str != null) { + append(str); + } + return this; + } + + /** + * Appends a separator if the builder is currently non-empty. + * The separator is appended using {@link #append(char)}. + *

+ * This method is useful for adding a separator each time around the + * loop except the first. + *

+     * for (Iterator it = list.iterator(); it.hasNext(); ) {
+     *   appendSeparator(',');
+     *   append(it.next());
+     * }
+     * 
+ * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param 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)}. + *

+ * This method is useful for adding a separator each time around the + * loop except the first. + *

+ *
+     * for (int i = 0; i < list.size(); i++) {
+     *   appendSeparator(",", i);
+     *   append(list.get(i));
+     * }
+     * 
+ * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator the separator to use, null means no separator + * @param loopIndex the loop index + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(final String separator, final int loopIndex) { + if (separator != null && loopIndex > 0) { + append(separator); + } + return this; + } + + /** + * Appends a separator to the builder if the loop index is greater than zero. + * The separator is appended using {@link #append(char)}. + *

+ * This method is useful for adding a separator each time around the + * loop except the first. + *

+ *
+     * for (int i = 0; i < list.size(); i++) {
+     *   appendSeparator(",", i);
+     *   append(list.get(i));
+     * }
+     * 
+ * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator the separator to use + * @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 = (str == null ? 0 : str.length()); + 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 = (str == null ? 0 : str.length()); + if (len > 0) { + final int index = indexOf(str, 0); + if (index >= 0) { + deleteImpl(index, index + len, len); + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Deletes all parts of the builder that the matcher matches. + *

+ * Matchers can be used to perform advanced deletion behavior. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteAll(final StrMatcher matcher) { + return replace(matcher, null, 0, size, -1); + } + + /** + * Deletes the first match within the builder using the specified matcher. + *

+ * Matchers can be used to perform advanced deletion behavior. + * For example you could write a matcher to delete + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteFirst(final StrMatcher matcher) { + return replace(matcher, null, 0, size, 1); + } + + //----------------------------------------------------------------------- + /** + * Internal method to delete a range without validation. + * + * @param startIndex the start index, must be valid + * @param endIndex the end index (exclusive), must be valid + * @param removeLen the length to remove (endIndex - startIndex), must be valid + * @param insertStr the string to replace with, null means delete range + * @param insertLen the length of the insert string, must be valid + * @throws IndexOutOfBoundsException if any index is invalid + */ + private void replaceImpl(final int startIndex, final int endIndex, final int removeLen, final String insertStr, final int insertLen) { + final int newSize = size - removeLen + insertLen; + if (insertLen != removeLen) { + ensureCapacity(newSize); + System.arraycopy(buffer, endIndex, buffer, startIndex + insertLen, size - endIndex); + size = newSize; + } + if (insertLen > 0) { + insertStr.getChars(0, insertLen, buffer, startIndex); + } + } + + /** + * 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 = (replaceStr == null ? 0 : replaceStr.length()); + 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 = (searchStr == null ? 0 : searchStr.length()); + if (searchLen > 0) { + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + 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 = (searchStr == null ? 0 : searchStr.length()); + if (searchLen > 0) { + final int index = indexOf(searchStr, 0); + if (index >= 0) { + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Replaces all matches within the builder with the replace string. + *

+ * Matchers can be used to perform advanced replace behavior. + * For example you could write a matcher to replace all occurrences + * where the character 'a' is followed by a number. + * + * @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. + *

+ * Matchers can be used to perform advanced replace behavior. + * For example you could write a matcher to replace + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public StrBuilder replaceFirst(final StrMatcher matcher, final String replaceStr) { + return replace(matcher, replaceStr, 0, size, 1); + } + + // ----------------------------------------------------------------------- + /** + * Advanced search and replaces within the builder using a matcher. + *

+ * Matchers can be used to perform advanced behavior. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the string to replace the match with, null is a delete + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @param replaceCount the number of times to replace, -1 for replace all + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if start index is invalid + */ + public StrBuilder replace( + final StrMatcher matcher, final String replaceStr, + final int startIndex, int endIndex, final int replaceCount) { + endIndex = validateRange(startIndex, endIndex); + return replaceImpl(matcher, replaceStr, startIndex, endIndex, replaceCount); + } + + /** + * Replaces within the builder using a matcher. + *

+ * Matchers can be used to perform advanced behavior. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the string to replace the match with, null is a delete + * @param from the start index, must be valid + * @param to the end index (exclusive), must be valid + * @param replaceCount the number of times to replace, -1 for replace all + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if any index is invalid + */ + private StrBuilder replaceImpl( + final StrMatcher matcher, final String replaceStr, + final int from, int to, int replaceCount) { + if (matcher == null || size == 0) { + return this; + } + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + for (int i = from; i < to && replaceCount != 0; i++) { + final char[] buf = buffer; + final int removeLen = matcher.isMatch(buf, i, from, to); + if (removeLen > 0) { + replaceImpl(i, i + removeLen, removeLen, replaceStr, replaceLen); + to = to - removeLen + replaceLen; + i = i + replaceLen - 1; + if (replaceCount > 0) { + replaceCount--; + } + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Reverses the string builder placing each character in the opposite index. + * + * @return this, to enable chaining + */ + public StrBuilder reverse() { + if (size == 0) { + return this; + } + + final int half = size / 2; + final char[] buf = buffer; + for (int leftIdx = 0, rightIdx = size - 1; leftIdx < half; leftIdx++, rightIdx--) { + final char swap = buf[leftIdx]; + buf[leftIdx] = buf[rightIdx]; + buf[rightIdx] = swap; + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Trims the builder by removing characters less than or equal to a space + * from the beginning and end. + * + * @return this, to enable chaining + */ + public StrBuilder trim() { + if (size == 0) { + return this; + } + int len = size; + final char[] buf = buffer; + int pos = 0; + while (pos < len && buf[pos] <= ' ') { + pos++; + } + while (pos < len && buf[len - 1] <= ' ') { + len--; + } + if (len < size) { + delete(len, size); + } + if (pos > 0) { + delete(0, pos); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Checks whether this builder starts with the specified string. + *

+ * Note that this method handles null input quietly, unlike String. + * + * @param str the string to search for, null returns false + * @return true if the builder starts with the string + */ + public boolean startsWith(final String str) { + if (str == null) { + return false; + } + final int len = str.length(); + if (len == 0) { + return true; + } + if (len > size) { + return false; + } + for (int i = 0; i < len; i++) { + if (buffer[i] != str.charAt(i)) { + return false; + } + } + return true; + } + + /** + * Checks whether this builder ends with the specified string. + *

+ * Note that this method handles null input quietly, unlike String. + * + * @param str the string to search for, null returns false + * @return true if the builder ends with the string + */ + public boolean endsWith(final String str) { + if (str == null) { + return false; + } + final int len = str.length(); + if (len == 0) { + return true; + } + if (len > size) { + return false; + } + int pos = size - len; + for (int i = 0; i < len; i++, pos++) { + if (buffer[pos] != str.charAt(i)) { + return false; + } + } + return true; + } + + //----------------------------------------------------------------------- + /** + * {@inheritDoc} + * @since 3.0 + */ + @Override + public CharSequence subSequence(final int startIndex, final int endIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex > size) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException(endIndex - startIndex); + } + return substring(startIndex, endIndex); + } + + /** + * Extracts a portion of this string builder as a string. + * + * @param start the start index, inclusive, must be valid + * @return the new string + * @throws IndexOutOfBoundsException if the index is invalid + */ + public String substring(final int start) { + return substring(start, size); + } + + /** + * Extracts a portion of this string builder as a string. + *

+ * Note: This method treats an endIndex greater than the length of the + * builder as equal to the length of the builder, and continues + * without error, unlike StringBuffer or String. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @return the new string + * @throws IndexOutOfBoundsException if the index is invalid + */ + public String substring(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + return new String(buffer, startIndex, endIndex - startIndex); + } + + /** + * Extracts the leftmost characters from the string builder without + * throwing an exception. + *

+ * This method extracts the left {@code length} characters from + * the builder. If this many characters are not available, the whole + * builder is returned. Thus the returned string may be shorter than the + * length requested. + * + * @param length the number of characters to extract, negative returns empty string + * @return the new string + */ + public String leftString(final int length) { + if (length <= 0) { + return StringUtils.EMPTY; + } else if (length >= size) { + return new String(buffer, 0, size); + } else { + return new String(buffer, 0, length); + } + } + + /** + * Extracts the rightmost characters from the string builder without + * throwing an exception. + *

+ * This method extracts the right {@code length} characters from + * the builder. If this many characters are not available, the whole + * builder is returned. Thus the returned string may be shorter than the + * length requested. + * + * @param length the number of characters to extract, negative returns empty string + * @return the new string + */ + public String rightString(final int length) { + if (length <= 0) { + return StringUtils.EMPTY; + } else if (length >= size) { + return new String(buffer, 0, size); + } else { + return new String(buffer, size - length, length); + } + } + + /** + * Extracts some characters from the middle of the string builder without + * throwing an exception. + *

+ * This method extracts {@code length} characters from the builder + * at the specified index. + * If the index is negative it is treated as zero. + * If the index is greater than the builder size, it is treated as the builder size. + * If the length is negative, the empty string is returned. + * If insufficient characters are available in the builder, as much as possible is returned. + * Thus the returned string may be shorter than the length requested. + * + * @param index the index to start at, negative means zero + * @param length the number of characters to extract, negative returns empty string + * @return the new string + */ + public String midString(int index, final int length) { + if (index < 0) { + index = 0; + } + if (length <= 0 || index >= size) { + return StringUtils.EMPTY; + } + if (size <= index + length) { + return new String(buffer, index, size - index); + } + return new String(buffer, index, length); + } + + //----------------------------------------------------------------------- + /** + * 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. + *

+ * 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. + * + * @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. + *

+ * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @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. + *

+ * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @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, int startIndex) { + startIndex = (Math.max(startIndex, 0)); + if (str == null || startIndex >= size) { + return -1; + } + final int strLen = str.length(); + if (strLen == 1) { + return indexOf(str.charAt(0), startIndex); + } + if (strLen == 0) { + return startIndex; + } + if (strLen > size) { + return -1; + } + final char[] thisBuf = buffer; + final int len = size - strLen + 1; + outer: + for (int i = startIndex; i < len; i++) { + for (int j = 0; j < strLen; j++) { + if (str.charAt(j) != thisBuf[i + j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Searches the string builder using the matcher to find the first match. + *

+ * 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. + * + * @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. + *

+ * 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. + * + * @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. + *

+ * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @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. + *

+ * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @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, int startIndex) { + startIndex = (startIndex >= size ? size - 1 : startIndex); + if (str == null || startIndex < 0) { + return -1; + } + final int strLen = str.length(); + if (strLen > 0 && strLen <= size) { + if (strLen == 1) { + return lastIndexOf(str.charAt(0), startIndex); + } + + outer: + for (int i = startIndex - strLen + 1; i >= 0; i--) { + for (int j = 0; j < strLen; j++) { + if (str.charAt(j) != buffer[i + j]) { + continue outer; + } + } + return i; + } + + } else if (strLen == 0) { + return startIndex; + } + return -1; + } + + /** + * Searches the string builder using the matcher to find the last match. + *

+ * 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. + * + * @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. + *

+ * 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. + * + * @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. + *

+ * This method allows the contents of this builder to be tokenized. + * The tokenizer will be setup by default to tokenize on space, tab, + * newline and formfeed (as per StringTokenizer). These values can be + * changed on the tokenizer class, before retrieving the tokens. + *

+ * The returned tokenizer is linked to this builder. You may intermix + * calls to the builder and tokenizer within certain limits, however + * there is no synchronization. Once the tokenizer has been used once, + * it must be {@link StrTokenizer#reset() reset} to pickup the latest + * changes in the builder. For example: + *

+     * StrBuilder b = new StrBuilder();
+     * b.append("a b ");
+     * StrTokenizer t = b.asTokenizer();
+     * String[] tokens1 = t.getTokenArray();  // returns a,b
+     * b.append("c d ");
+     * String[] tokens2 = t.getTokenArray();  // returns a,b (c and d ignored)
+     * t.reset();              // reset causes builder changes to be picked up
+     * String[] tokens3 = t.getTokenArray();  // returns a,b,c,d
+     * 
+ * In addition to simply intermixing appends and tokenization, you can also + * call the set methods on the tokenizer to alter how it tokenizes. Just + * remember to call reset when you want to pickup builder changes. + *

+ * Calling {@link StrTokenizer#reset(String)} or {@link StrTokenizer#reset(char[])} + * with a non-null value will break the link with the builder. + * + * @return a tokenizer that is linked to this builder + */ + public StrTokenizer asTokenizer() { + return new StrBuilderTokenizer(); + } + + //----------------------------------------------------------------------- + /** + * Gets the contents of this builder as a Reader. + *

+ * This method allows the contents of the builder to be read + * using any standard method that expects a Reader. + *

+ * To use, simply create a {@code StrBuilder}, populate it with + * data, call {@code asReader}, and then read away. + *

+ * The internal character array is shared between the builder and the reader. + * This allows you to append to the builder after creating the reader, + * and the changes will be picked up. + * Note however, that no synchronization occurs, so you must perform + * all operations with the builder and the reader in one thread. + *

+ * The returned reader supports marking, and ignores the flush method. + * + * @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. + *

+ * This method allows you to populate the contents of the builder + * using any standard method that takes a Writer. + *

+ * To use, simply create a {@code StrBuilder}, + * call {@code asWriter}, and populate away. The data is available + * at any time using the methods of the {@code StrBuilder}. + *

+ * The internal character array is shared between the builder and the writer. + * This allows you to intermix calls that append to the builder and + * write using the writer and the changes will be occur correctly. + * Note however, that no synchronization occurs, so you must perform + * all operations with the builder and the writer in one thread. + *

+ * The returned writer ignores the close and flush methods. + * + * @return a writer that populates this builder + */ + public Writer asWriter() { + return new StrBuilderWriter(); + } + + /** + * Appends current contents of this {@code StrBuilder} to the + * provided {@link Appendable}. + *

+ * This method tries to avoid doing any extra copies of contents. + * + * @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. + *

+ * Note that unlike StringBuffer, the string version returned is + * independent of the string builder. + * + * @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 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() == false) { + 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 += 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/after/src/main/java/org/apache/commons/lang3/text/StrLookup.java b/after/src/main/java/org/apache/commons/lang3/text/StrLookup.java new file mode 100644 index 0000000..1a19ce4 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/text/StrLookup.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + * Lookup a String key to a String value. + *

+ * 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. + *

+ * This class comes complete with various factory methods. + * If these do not suffice, you can subclass and implement your own matcher. + *

+ * 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. + * + * @param Unused. + * @since 2.2 + * @deprecated as of 3.6, use commons-text + * + * StringLookupFactory instead + */ +@Deprecated +public abstract class StrLookup { + + /** + * Lookup that always returns null. + */ + private static final StrLookup NONE_LOOKUP = new MapStrLookup<>(null); + + /** + * Lookup based on system properties. + */ + private static final StrLookup SYSTEM_PROPERTIES_LOOKUP = new SystemPropertiesStrLookup(); + + //----------------------------------------------------------------------- + /** + * Returns a lookup which always returns null. + * + * @return a lookup that always returns null, not null + */ + public static StrLookup noneLookup() { + return NONE_LOOKUP; + } + + /** + * Returns a new lookup which uses a copy of the current + * {@link System#getProperties() System properties}. + *

+ * If a security manager blocked access to system properties, then null will + * be returned from every lookup. + *

+ * If a null key is used, this lookup will throw a NullPointerException. + * + * @return a lookup using system properties, not null + */ + public static StrLookup systemPropertiesLookup() { + return SYSTEM_PROPERTIES_LOOKUP; + } + + /** + * Returns a lookup which looks up values using a map. + *

+ * If the map is null, then null will be returned from every lookup. + * The map result object is converted to a string using toString(). + * + * @param the type of the values supported by the lookup + * @param map the map of keys to values, may be null + * @return a lookup using the map, not null + */ + public static StrLookup mapLookup(final Map map) { + return new MapStrLookup<>(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + */ + protected StrLookup() { + } + + /** + * Looks up a String key to a String value. + *

+ * The internal implementation may use any mechanism to return the value. + * The simplest implementation is to use a Map. However, virtually any + * implementation is possible. + *

+ * For example, it would be possible to implement a lookup that used the + * key as a primary key, and looked up the value on demand from the database + * Or, a numeric based implementation could be created that treats the key + * as an integer, increments the value and return the result as a string - + * converting 1 to 2, 15 to 16 etc. + *

+ * The {@link #lookup(String)} method always returns a String, regardless of + * the underlying data, by converting it as necessary. For example: + *

+     * Map<String, Object> map = new HashMap<String, Object>();
+     * map.put("number", Integer.valueOf(2));
+     * assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
+     * 
+ * @param key the key to be looked up, may be null + * @return the matching value, null if no match + */ + public abstract String lookup(String key); + + //----------------------------------------------------------------------- + /** + * Lookup implementation that uses a Map. + */ + static class MapStrLookup extends StrLookup { + + /** Map keys are variable names and value. */ + private final Map map; + + /** + * Creates a new instance backed by a Map. + * + * @param map the map of keys to values, may be null + */ + MapStrLookup(final Map map) { + this.map = map; + } + + /** + * Looks up a String key to a String value using the map. + *

+ * If the map is null, then null is returned. + * The map result object is converted to a string using toString(). + * + * @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 { + /** + * {@inheritDoc} This implementation directly accesses system properties. + */ + @Override + public String lookup(final String key) { + if (!key.isEmpty()) { + try { + return System.getProperty(key); + } catch (final SecurityException scex) { + // Squelched. All lookup(String) will return null. + } + } + return null; + } + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/text/StrMatcher.java b/after/src/main/java/org/apache/commons/lang3/text/StrMatcher.java new file mode 100644 index 0000000..90fa1af --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/text/StrMatcher.java @@ -0,0 +1,439 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.StringUtils; + +/** + * A matcher class that can be queried to determine if a character array + * portion matches. + *

+ * This class comes complete with various factory methods. + * If these do not suffice, you can subclass and implement your own matcher. + * + * @since 2.2 + * @deprecated as of 3.6, use commons-text + * + * StringMatcherFactory 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 (chars == null || chars.length == 0) { + 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. + *

+ * This method is called to check for a match. + * The parameter {@code pos} represents the current position to be + * checked in the string {@code buffer} (a character array which must + * not be changed). + * The API guarantees that {@code pos} is a valid index for {@code buffer}. + *

+ * The character array may be larger than the active area to be matched. + * Only values in the buffer between the specified indices may be accessed. + *

+ * The matching code may check one character or many. + * It may check characters preceding {@code pos} as well as those + * after, so long as no checks exceed the bounds specified. + *

+ * It must return zero for no match, or a positive number if a match was found. + * The number indicates the number of characters that matched. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index (exclusive) of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd); + + /** + * Returns the number of matching characters, zero for no match. + *

+ * This method is called to check for a match. + * The parameter {@code pos} represents the current position to be + * checked in the string {@code buffer} (a character array which must + * not be changed). + * The API guarantees that {@code pos} is a valid index for {@code buffer}. + *

+ * The matching code may check one character or many. + * It may check characters preceding {@code pos} as well as those after. + *

+ * It must return zero for no match, or a positive number if a match was found. + * The number indicates the number of characters that matched. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @return the number of matching characters, zero for no match + * @since 2.4 + */ + public int isMatch(final char[] buffer, final int pos) { + return isMatch(buffer, pos, 0, buffer.length); + } + + //----------------------------------------------------------------------- + /** + * Class used to define a set of characters for matching purposes. + */ + 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 {@code 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 {@code 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/after/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java b/after/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java new file mode 100644 index 0000000..bc52884 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java @@ -0,0 +1,1233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Properties; + +import org.apache.commons.lang3.StringUtils; + +/** + * Substitutes variables within a string by values. + *

+ * 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. + *

+ * Variable values are typically resolved from a map, but could also be resolved + * from system properties, or by supplying a custom variable resolver. + *

+ * The simplest example is to use this class to replace Java System properties. For example: + *

+ * StrSubstitutor.replaceSystemProperties(
+ *      "You are running with java.version = ${java.version} and os.name = ${os.name}.");
+ * 
+ *

+ * 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: + *

+ * Map valuesMap = HashMap();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumps over the ${target}.";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * 
+ * yielding: + *
+ *      The quick brown fox jumps over the lazy dog.
+ * 
+ *

+ * 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: + *

+ * Map valuesMap = HashMap();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumps over the ${target}. ${undefined.number:-1234567890}.";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * 
+ * yielding: + *
+ *      The quick brown fox jumps over the lazy dog. 1234567890.
+ * 
+ *

+ * 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. + *

+ * 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. + *

+ * Sometimes the interpolation's result must contain a variable prefix. As an example + * take the following source text: + *

+ *   The variable ${${name}} must be used.
+ * 
+ * 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}): + *
+ *   The variable ${x} must be used.
+ * 
+ * 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: + *
+ *   The variable $${${name}} must be used.
+ * 
+ *

+ * In some complex scenarios you might even want to perform substitution in the + * names of variables, for instance + *

+ * ${jre-${java.specification.version}}
+ * 
+ * {@code StrSubstitutor} supports this recursive substitution in variable + * names, but it has to be enabled explicitly by setting the + * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables} + * property to true. + *

This class is not thread safe.

+ * + * @since 2.2 + * @deprecated as of 3.6, use commons-text + * + * StringSubstitutor 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 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 String replace(final Object source, final Map valueMap) { + return new StrSubstitutor(valueMap).replace(source); + } + + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the map. This method allows to specify a + * custom variable prefix and suffix + * + * @param 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 String replace(final Object source, final Map valueMap, final String prefix, final String suffix) { + return new StrSubstitutor(valueMap, prefix, suffix).replace(source); + } + + /** + * Replaces all the occurrences of variables in the given source object with their matching + * values from the properties. + * + * @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 valueMap = new HashMap<>(); + final Enumeration propNames = valueProperties.propertyNames(); + while (propNames.hasMoreElements()) { + final String propName = (String) 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 the type of the values in the map + * @param valueMap the map with the variables' values, may be null + */ + public StrSubstitutor(final Map valueMap) { + this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. Uses a default escaping character. + * + * @param 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 StrSubstitutor(final Map valueMap, final String prefix, final String suffix) { + this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. + * + * @param 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 StrSubstitutor(final Map 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 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 StrSubstitutor(final Map 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()) == false) { + return source; + } + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source string as a template. + *

+ * Only the specified portion of the string will be processed. + * The rest of the string is not processed, and is not returned. + * + * @param source the string to replace in, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(final String source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + if (substitute(buf, 0, length) == false) { + 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. + *

+ * Only the specified portion of the array will be processed. + * The rest of the array is not processed, and is not returned. + * + * @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. + *

+ * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, and is not returned. + * + * @param source the buffer to use as a template, not changed, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(final StringBuffer source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + substitute(buf, 0, length); + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source as a template. + * 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. + *

+ * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, and is not returned. + * + * @param source the buffer to use as a template, not changed, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + * @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. + *

+ * Only the specified portion of the builder will be processed. + * The rest of the builder is not processed, and is not returned. + * + * @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. + *

+ * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, but it is not deleted. + * + * @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) == false) { + 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. + *

+ * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, but it is not deleted. + * + * @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) == false) { + 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. + *

+ * Only the specified portion of the builder will be processed. + * The rest of the builder is not processed, but it is not deleted. + * + * @param source the builder to replace in, null returns zero + * @param offset the start offset within the array, must be valid + * @param length the length within the builder to be processed, must be valid + * @return true if altered + */ + public boolean replaceIn(final StrBuilder source, final int offset, final int length) { + if (source == null) { + return false; + } + return substitute(source, offset, length); + } + + //----------------------------------------------------------------------- + /** + * Internal method that substitutes the variables. + *

+ * Most users of this class do not need to call this method. This method will + * be called automatically by another (public) method. + *

+ * Writers of subclasses can override this method if they need access to + * the substitution process at the start or end. + * + * @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 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 = 0; + 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 = 0; + 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 priorVariables) { + if (priorVariables.contains(varName) == false) { + return; + } + final StrBuilder buf = new StrBuilder(256); + buf.append("Infinite loop in property interpolation of "); + buf.append(priorVariables.remove(0)); + buf.append(": "); + buf.appendWithSeparators(priorVariables, "->"); + throw new IllegalStateException(buf.toString()); + } + + /** + * Internal method that resolves the value of a variable. + *

+ * Most users of this class do not need to call this method. This method is + * called automatically by the substitution process. + *

+ * Writers of subclasses can override this method if they need to alter + * how each substitution occurs. The method is passed the variable's name + * and must return the corresponding value. This implementation uses the + * {@link #getVariableResolver()} with the variable's name as the key. + * + * @param variableName the name of the variable, not null + * @param buf the buffer where the substitution is occurring, not null + * @param startPos the start position of the variable including the prefix, valid + * @param endPos the end position of the variable including the suffix, valid + * @return the variable's value or null if the variable is unknown + */ + protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) { + final StrLookup resolver = getVariableResolver(); + if (resolver == null) { + return null; + } + return resolver.lookup(variableName); + } + + // Escape + //----------------------------------------------------------------------- + /** + * 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; + } + + // Prefix + //----------------------------------------------------------------------- + /** + * Gets the variable prefix matcher currently in use. + *

+ * The variable prefix is the character or characters that identify the + * start of a variable. This prefix is expressed in terms of a matcher + * allowing advanced prefix matches. + * + * @return the prefix matcher in use + */ + public StrMatcher getVariablePrefixMatcher() { + return prefixMatcher; + } + + /** + * Sets the variable prefix matcher currently in use. + *

+ * The variable prefix is the character or characters that identify the + * start of a variable. This prefix is expressed in terms of a matcher + * allowing advanced prefix matches. + * + * @param prefixMatcher the prefix matcher to use, null ignored + * @return this, to enable chaining + * @throws IllegalArgumentException if the prefix matcher is null + */ + public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { + if (prefixMatcher == null) { + throw new IllegalArgumentException("Variable prefix matcher must not be null."); + } + this.prefixMatcher = prefixMatcher; + return this; + } + + /** + * Sets the variable prefix to use. + *

+ * The variable prefix is the character or characters that identify the + * start of a variable. This method allows a single character prefix to + * be easily set. + * + * @param prefix the prefix character to use + * @return this, to enable chaining + */ + public StrSubstitutor setVariablePrefix(final char prefix) { + return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); + } + + /** + * Sets the variable prefix to use. + *

+ * The variable prefix is the character or characters that identify the + * start of a variable. This method allows a string prefix to be easily set. + * + * @param prefix the prefix for variables, not null + * @return this, to enable chaining + * @throws IllegalArgumentException if the prefix is null + */ + public StrSubstitutor setVariablePrefix(final String prefix) { + if (prefix == null) { + throw new IllegalArgumentException("Variable prefix must not be null."); + } + return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); + } + + // Suffix + //----------------------------------------------------------------------- + /** + * Gets the variable suffix matcher currently in use. + *

+ * The variable suffix is the character or characters that identify the + * end of a variable. This suffix is expressed in terms of a matcher + * allowing advanced suffix matches. + * + * @return the suffix matcher in use + */ + public StrMatcher getVariableSuffixMatcher() { + return suffixMatcher; + } + + /** + * Sets the variable suffix matcher currently in use. + *

+ * The variable suffix is the character or characters that identify the + * end of a variable. This suffix is expressed in terms of a matcher + * allowing advanced suffix matches. + * + * @param suffixMatcher the suffix matcher to use, null ignored + * @return this, to enable chaining + * @throws IllegalArgumentException if the suffix matcher is null + */ + public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { + if (suffixMatcher == null) { + throw new IllegalArgumentException("Variable suffix matcher must not be null."); + } + this.suffixMatcher = suffixMatcher; + return this; + } + + /** + * Sets the variable suffix to use. + *

+ * The variable suffix is the character or characters that identify the + * end of a variable. This method allows a single character suffix to + * be easily set. + * + * @param suffix the suffix character to use + * @return this, to enable chaining + */ + public StrSubstitutor setVariableSuffix(final char suffix) { + return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); + } + + /** + * Sets the variable suffix to use. + *

+ * The variable suffix is the character or characters that identify the + * end of a variable. This method allows a string suffix to be easily set. + * + * @param suffix the suffix for variables, not null + * @return this, to enable chaining + * @throws IllegalArgumentException if the suffix is null + */ + public StrSubstitutor setVariableSuffix(final String suffix) { + if (suffix == null) { + throw new IllegalArgumentException("Variable suffix must not be null."); + } + return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); + } + + // Variable Default Value Delimiter + //----------------------------------------------------------------------- + /** + * Gets the variable default value delimiter matcher currently in use. + *

+ * The variable default value delimiter is the character or characters that delimit the + * variable name and the variable default value. This delimiter is expressed in terms of a matcher + * allowing advanced variable default value delimiter matches. + *

+ * If it returns null, then the variable default value resolution is disabled. + * + * @return the variable default value delimiter matcher in use, may be null + * @since 3.2 + */ + public StrMatcher getValueDelimiterMatcher() { + return valueDelimiterMatcher; + } + + /** + * Sets the variable default value delimiter matcher to use. + *

+ * The variable default value delimiter is the character or characters that delimit the + * variable name and the variable default value. This delimiter is expressed in terms of a matcher + * allowing advanced variable default value delimiter matches. + *

+ * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution + * becomes disabled. + * + * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null + * @return this, to enable chaining + * @since 3.2 + */ + public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { + this.valueDelimiterMatcher = valueDelimiterMatcher; + return this; + } + + /** + * Sets the variable default value delimiter to use. + *

+ * The variable default value delimiter is the character or characters that delimit the + * variable name and the variable default value. This method allows a single character + * variable default value delimiter to be easily set. + * + * @param valueDelimiter the variable default value delimiter character to use + * @return this, to enable chaining + * @since 3.2 + */ + public StrSubstitutor setValueDelimiter(final char valueDelimiter) { + return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); + } + + /** + * Sets the variable default value delimiter to use. + *

+ * The variable default value delimiter is the character or characters that delimit the + * variable name and the variable default value. This method allows a string + * variable default value delimiter to be easily set. + *

+ * If the {@code valueDelimiter} is null or empty string, then the variable default + * value resolution becomes disabled. + * + * @param valueDelimiter the variable default value delimiter string to use, may be null or empty + * @return this, to enable chaining + * @since 3.2 + */ + public StrSubstitutor setValueDelimiter(final String valueDelimiter) { + if (StringUtils.isEmpty(valueDelimiter)) { + setValueDelimiterMatcher(null); + return this; + } + return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); + } + + // Resolver + //----------------------------------------------------------------------- + /** + * 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; + } + + // Substitution support in variable names + //----------------------------------------------------------------------- + /** + * Returns a flag whether substitution is done in variable names. + * + * @return the substitution in variable names flag + * @since 3.0 + */ + public boolean isEnableSubstitutionInVariables() { + return enableSubstitutionInVariables; + } + + /** + * Sets a flag whether substitution is done in variable names. If set to + * true, the names of variables can contain other variables which are + * processed first before the original variable is evaluated, e.g. + * {@code ${jre-${java.version}}}. The default value is false. + * + * @param enableSubstitutionInVariables the new value of the flag + * @since 3.0 + */ + public void setEnableSubstitutionInVariables( + final boolean enableSubstitutionInVariables) { + this.enableSubstitutionInVariables = enableSubstitutionInVariables; + } + + /** + * Returns the flag controlling whether escapes are preserved during + * substitution. + * + * @return the preserve escape flag + * @since 3.5 + */ + public boolean isPreserveEscapes() { + return preserveEscapes; + } + + /** + * Sets a flag controlling whether escapes are preserved during + * substitution. If set to true, the escape character is retained + * during substitution (e.g. {@code $${this-is-escaped}} remains + * {@code $${this-is-escaped}}). If set to false, the escape + * character is removed during substitution (e.g. + * {@code $${this-is-escaped}} becomes + * {@code ${this-is-escaped}}). The default value is false + * + * @param preserveEscapes true if escapes are to be preserved + * @since 3.5 + */ + public void setPreserveEscapes(final boolean preserveEscapes) { + this.preserveEscapes = preserveEscapes; + } +} diff --git a/after/src/main/java/org/apache/commons/lang3/text/StrTokenizer.java b/after/src/main/java/org/apache/commons/lang3/text/StrTokenizer.java new file mode 100644 index 0000000..01063c7 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/text/StrTokenizer.java @@ -0,0 +1,1111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 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. + *

+ * 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 {@code ListIterator} interface. By default, it is set up + * like {@code StringTokenizer}. + *

+ * The input String is split into a number of tokens. + * Each token is separated from the next String by a delimiter. + * One or more delimiter characters must be specified. + *

+ * Each token may be surrounded by quotes. + * The quote matcher specifies the quote character(s). + * A quote may be escaped within a quoted section by duplicating itself. + *

+ * Between each token and the delimiter are potentially characters that need trimming. + * The trimmer matcher specifies these characters. + * One usage might be to trim whitespace characters. + *

+ * At any point outside the quotes there might potentially be invalid characters. + * The ignored matcher specifies these characters to be removed. + * One usage might be to remove new line characters. + *

+ * Empty tokens may be removed or returned as null. + *

+ * "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)
+ * 
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
StrTokenizer properties and options
PropertyTypeDefault
delimCharSetMatcher{ \t\n\r\f}
quoteNoneMatcher{}
ignoreNoneMatcher{}
emptyTokenAsNullbooleanfalse
ignoreEmptyTokensbooleantrue
+ * + * @since 2.2 + * @deprecated as of 3.6, use commons-text + * + * StringTokenizer instead + */ +@Deprecated +public class StrTokenizer implements ListIterator, 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). + *

+ * You must call a "reset" method to set the string which you want to parse. + * @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). + *

+ * You must call a "reset" method to set the string which you want to parse. + * @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. + *

+ * This constructor is normally used with {@link #reset(String)}. + */ + 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 getTokenList() { + checkTokenized(); + final List list = new ArrayList<>(tokens.length); + list.addAll(Arrays.asList(tokens)); + return list; + } + + /** + * Resets this tokenizer, forgetting all parsing and iteration already completed. + *

+ * This method allows the same tokenizer to be reused for the same String. + * + * @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; + } + + // ListIterator + //----------------------------------------------------------------------- + /** + * 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"); + } + + // Implementation + //----------------------------------------------------------------------- + /** + * Checks if tokenization has been done, and if not then do it. + */ + private void checkTokenized() { + if (tokens == null) { + if (chars == null) { + // still call tokenize as subclass may do some work + final List split = tokenize(null, 0, 0); + tokens = split.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } else { + final List split = tokenize(chars, 0, chars.length); + tokens = split.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } + } + } + + /** + * Internal method to performs the tokenization. + *

+ * Most users of this class do not need to call this method. This method + * will be called automatically by other (public) methods when required. + *

+ * This method exists to allow subclasses to add code before or after the + * tokenization. For example, a subclass could alter the character array, + * offset or count to be parsed, or call the tokenizer multiple times on + * multiple strings. It is also be possible to filter the results. + *

+ * {@code StrTokenizer} will always pass a zero offset and a count + * equal to the length of the array to this method, however a subclass + * may pass other values, or even an entirely different array. + * + * @param srcChars the character array being tokenized, may be null + * @param offset the start position within the character array, must be valid + * @param count the number of characters to tokenize, must be valid + * @return the modifiable list of String tokens, unmodifiable if null array or zero count + */ + protected List tokenize(final char[] srcChars, final int offset, final int count) { + if (srcChars == null || count == 0) { + return Collections.emptyList(); + } + final StrBuilder buf = new StrBuilder(); + final List tokenList = new ArrayList<>(); + int pos = offset; + + // loop around the entire buffer + while (pos >= 0 && pos < count) { + // find next token + pos = readNextToken(srcChars, pos, count, buf, tokenList); + + // handle case where end of string is a delimiter + if (pos >= count) { + addToken(tokenList, StringUtils.EMPTY); + } + } + return tokenList; + } + + /** + * Adds a token to a list, paying attention to the parameters we've set. + * + * @param list the list to add to + * @param tok the token to add + */ + private void addToken(final List list, String tok) { + if (StringUtils.isEmpty(tok)) { + if (isIgnoreEmptyTokens()) { + return; + } + if (isEmptyTokenAsNull()) { + tok = null; + } + } + list.add(tok); + } + + /** + * 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 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 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; + } + + // copy regular character from inside quotes + workArea.append(srcChars[pos++]); + trimStart = workArea.size(); + + } 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 its 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 outside 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; + } + + // Delimiter + //----------------------------------------------------------------------- + /** + * Gets the field delimiter matcher. + * + * @return the delimiter matcher in use + */ + public StrMatcher getDelimiterMatcher() { + return this.delimMatcher; + } + + /** + * Sets the field delimiter matcher. + *

+ * The delimiter is used to separate one token from another. + * + * @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)); + } + + // Quote + //----------------------------------------------------------------------- + /** + * Gets the quote matcher currently in use. + *

+ * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. + * The default value is '"' (double quote). + * + * @return the quote matcher in use + */ + public StrMatcher getQuoteMatcher() { + return quoteMatcher; + } + + /** + * Set the quote matcher to use. + *

+ * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. + * + * @param quote the quote matcher to use, null ignored + * @return this, to enable chaining + */ + public StrTokenizer setQuoteMatcher(final StrMatcher quote) { + if (quote != null) { + this.quoteMatcher = quote; + } + return this; + } + + /** + * Sets the quote character to use. + *

+ * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. + * + * @param quote the quote character to use + * @return this, to enable chaining + */ + public StrTokenizer setQuoteChar(final char quote) { + return setQuoteMatcher(StrMatcher.charMatcher(quote)); + } + + // Ignored + //----------------------------------------------------------------------- + /** + * Gets the ignored character matcher. + *

+ * These characters are ignored when parsing the String, unless they are + * within a quoted region. + * The default value is not to ignore anything. + * + * @return the ignored matcher in use + */ + public StrMatcher getIgnoredMatcher() { + return ignoredMatcher; + } + + /** + * Set the matcher for characters to ignore. + *

+ * These characters are ignored when parsing the String, unless they are + * within a quoted region. + * + * @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. + *

+ * 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)); + } + + // Trimmer + //----------------------------------------------------------------------- + /** + * Gets the trimmer character matcher. + *

+ * These characters are trimmed off on each side of the delimiter + * until the token or quote is found. + * The default value is not to trim anything. + * + * @return the trimmer matcher in use + */ + public StrMatcher getTrimmerMatcher() { + return trimmerMatcher; + } + + /** + * Sets the matcher for characters to trim. + *

+ * These characters are trimmed off on each side of the delimiter + * until the token or quote is found. + * + * @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/after/src/main/java/org/apache/commons/lang3/text/WordUtils.java b/after/src/main/java/org/apache/commons/lang3/text/WordUtils.java new file mode 100644 index 0000000..5a43dc7 --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/text/WordUtils.java @@ -0,0 +1,737 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.

+ * + *

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.

+ * + * @since 2.0 + * @deprecated as of 3.6, use commons-text + * + * WordUtils instead + */ +@Deprecated +public class WordUtils { + + /** + *

{@code WordUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * {@code WordUtils.wrap("foo bar", 20);}.

+ * + *

This constructor is public to permit tools that require a JavaBean + * instance to operate.

+ */ + public WordUtils() { + } + + // Wrapping + //-------------------------------------------------------------------------- + /** + *

Wraps a single line of text, identifying words by {@code ' '}.

+ * + *

New lines will be separated by the system property line separator. + * Very long words, such as URLs will not be wrapped.

+ * + *

Leading spaces on a new line are stripped. + * Trailing spaces are not stripped.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples
inputwrapLengthresult
null*null
""*""
"Here is one line of text that is going to be wrapped after 20 columns."20"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."
"Click here to jump to the commons website - https://commons.apache.org"20"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"
"Click here, https://commons.apache.org, to jump to the commons website"20"Click here,\nhttps://commons.apache.org,\nto jump to the\ncommons website"
+ * + * (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 ' '}.

+ * + *

Leading spaces on a new line are stripped. + * Trailing spaces are not stripped.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples
inputwrapLengthnewLineStringwrapLongWordsresult
null**true/falsenull
""**true/false""
"Here is one line of text that is going to be wrapped after 20 columns."20"\n"true/false"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."
"Here is one line of text that is going to be wrapped after 20 columns."20"<br />"true/false"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."
"Here is one line of text that is going to be wrapped after 20 columns."20nulltrue/false"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."
"Click here to jump to the commons website - https://commons.apache.org"20"\n"false"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"
"Click here to jump to the commons website - https://commons.apache.org"20"\n"true"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"
+ * + * @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}.

+ * + *

Leading spaces on a new line are stripped. + * Trailing spaces are not stripped.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples
inputwrapLengthnewLineStringwrapLongWordswrapOnresult
null**true/false*null
""**true/false*""
"Here is one line of text that is going to be wrapped after 20 columns."20"\n"true/false" ""Here is one line of\ntext that is going\nto be wrapped after\n20 columns."
"Here is one line of text that is going to be wrapped after 20 columns."20"<br />"true/false" ""Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."
"Here is one line of text that is going to be wrapped after 20 columns."20nulltrue/false" ""Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."
"Click here to jump to the commons website - https://commons.apache.org"20"\n"false" ""Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"
"Click here to jump to the commons website - https://commons.apache.org"20"\n"true" ""Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"
"flammable/inflammable"20"\n"true"/""flammable\ninflammable"
+ * @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)}.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code null}. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalize(null)        = null
+     * WordUtils.capitalize("")          = ""
+     * WordUtils.capitalize("i am FINE") = "I Am FINE"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return capitalized String, {@code null} if null String input + * @see #uncapitalize(String) + * @see #capitalizeFully(String) + */ + public static String capitalize(final String str) { + return capitalize(str, null); + } + + /** + *

Capitalizes all the delimiter separated words in a String. + * Only the first character of each word is changed. To convert the + * rest of each word to lowercase at the same time, + * use {@link #capitalizeFully(String, char[])}.

+ * + *

The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be capitalized.

+ * + *

A {@code null} input String returns {@code null}. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalize(null, *)            = null
+     * WordUtils.capitalize("", *)              = ""
+     * WordUtils.capitalize(*, new char[0])     = *
+     * WordUtils.capitalize("i am fine", null)  = "I Am Fine"
+     * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @param delimiters set of characters to determine capitalization, null means whitespace + * @return capitalized String, {@code null} if null String input + * @see #uncapitalize(String) + * @see #capitalizeFully(String) + * @since 2.1 + */ + public static String capitalize(final String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + final char[] buffer = str.toCharArray(); + boolean capitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (isDelimiter(ch, delimiters)) { + capitalizeNext = true; + } else if (capitalizeNext) { + buffer[i] = Character.toTitleCase(ch); + capitalizeNext = false; + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

Converts all the whitespace separated words in a String into capitalized words, + * that is each word is made up of a titlecase character and then a series of + * lowercase characters.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code null}. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalizeFully(null)        = null
+     * WordUtils.capitalizeFully("")          = ""
+     * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return capitalized String, {@code null} if null String input + */ + public static String capitalizeFully(final String str) { + return capitalizeFully(str, null); + } + + /** + *

Converts all the delimiter separated words in a String into capitalized words, + * that is each word is made up of a titlecase character and then a series of + * lowercase characters.

+ * + *

The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be capitalized.

+ * + *

A {@code null} input String returns {@code null}. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalizeFully(null, *)            = null
+     * WordUtils.capitalizeFully("", *)              = ""
+     * WordUtils.capitalizeFully(*, null)            = *
+     * WordUtils.capitalizeFully(*, new char[0])     = *
+     * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @param delimiters set of characters to determine capitalization, null means whitespace + * @return capitalized String, {@code null} if null String input + * @since 2.1 + */ + public static String capitalizeFully(String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + str = str.toLowerCase(); + return capitalize(str, delimiters); + } + + //----------------------------------------------------------------------- + /** + *

Uncapitalizes all the whitespace separated words in a String. + * Only the first character of each word is changed.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * WordUtils.uncapitalize(null)        = null
+     * WordUtils.uncapitalize("")          = ""
+     * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
+     * 
+ * + * @param str the String to uncapitalize, may be null + * @return uncapitalized String, {@code null} if null String input + * @see #capitalize(String) + */ + public static String uncapitalize(final String str) { + return uncapitalize(str, null); + } + + /** + *

Uncapitalizes all the whitespace separated words in a String. + * Only the first character of each word is changed.

+ * + *

The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be uncapitalized.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * WordUtils.uncapitalize(null, *)            = null
+     * WordUtils.uncapitalize("", *)              = ""
+     * WordUtils.uncapitalize(*, null)            = *
+     * WordUtils.uncapitalize(*, new char[0])     = *
+     * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
+     * 
+ * + * @param str the String to uncapitalize, may be null + * @param delimiters set of characters to determine uncapitalization, null means whitespace + * @return uncapitalized String, {@code null} if null String input + * @see #capitalize(String) + * @since 2.1 + */ + public static String uncapitalize(final String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + final char[] buffer = str.toCharArray(); + boolean uncapitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (isDelimiter(ch, delimiters)) { + uncapitalizeNext = true; + } else if (uncapitalizeNext) { + buffer[i] = Character.toLowerCase(ch); + uncapitalizeNext = false; + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

Swaps the case of a String using a word based algorithm.

+ * + *
    + *
  • Upper case character converts to Lower case
  • + *
  • Title case character converts to Lower case
  • + *
  • Lower case character after Whitespace or at start converts to Title case
  • + *
  • Other Lower case character converts to Upper case
  • + *
+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.swapCase(null)                 = null
+     * StringUtils.swapCase("")                   = ""
+     * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+     * 
+ * + * @param str the String to swap case, may be null + * @return the changed String, {@code null} if null String input + */ + public static String swapCase(final String str) { + if (StringUtils.isEmpty(str)) { + return str; + } + final char[] buffer = str.toCharArray(); + + boolean whitespace = true; + + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (Character.isUpperCase(ch) || Character.isTitleCase(ch)) { + buffer[i] = Character.toLowerCase(ch); + whitespace = false; + } else if (Character.isLowerCase(ch)) { + if (whitespace) { + buffer[i] = Character.toTitleCase(ch); + whitespace = false; + } else { + buffer[i] = Character.toUpperCase(ch); + } + } else { + whitespace = Character.isWhitespace(ch); + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

Extracts the initial characters from each word in the String.

+ * + *

All first characters after whitespace are returned as a new string. + * Their case is not changed.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * WordUtils.initials(null)             = null
+     * WordUtils.initials("")               = ""
+     * WordUtils.initials("Ben John Lee")   = "BJL"
+     * WordUtils.initials("Ben J.Lee")      = "BJ"
+     * 
+ * + * @param str the String to get initials from, may be null + * @return String of initial letters, {@code null} if null String input + * @see #initials(String,char[]) + * @since 2.2 + */ + public static String initials(final String str) { + return initials(str, null); + } + + /** + *

Extracts the initial characters from each word in the String.

+ * + *

All first characters after the defined delimiters are returned as a new string. + * Their case is not changed.

+ * + *

If the delimiters array is null, then Whitespace is used. + * Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code null}. + * An empty delimiter array returns an empty String.

+ * + *
+     * WordUtils.initials(null, *)                = null
+     * WordUtils.initials("", *)                  = ""
+     * WordUtils.initials("Ben John Lee", null)   = "BJL"
+     * WordUtils.initials("Ben J.Lee", null)      = "BJ"
+     * WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
+     * WordUtils.initials(*, new char[0])         = ""
+     * 
+ * + * @param str the String to get initials from, may be null + * @param delimiters set of characters to determine words, null means whitespace + * @return String of initial characters, {@code null} if null String input + * @see #initials(String) + * @since 2.2 + */ + public static String initials(final String str, final char... delimiters) { + if (StringUtils.isEmpty(str)) { + return str; + } + if (delimiters != null && delimiters.length == 0) { + return StringUtils.EMPTY; + } + final int strLen = str.length(); + final char[] buf = new char[strLen / 2 + 1]; + int count = 0; + boolean lastWasGap = true; + for (int i = 0; i < strLen; i++) { + final char ch = str.charAt(i); + + if (isDelimiter(ch, delimiters)) { + lastWasGap = true; + } else if (lastWasGap) { + buf[count++] = ch; + lastWasGap = false; + } else { + continue; // ignore ch + } + } + return new String(buf, 0, count); + } + + //----------------------------------------------------------------------- + /** + *

Checks if the String contains all words in the given array.

+ * + *

+ * A {@code null} String will return {@code false}. A {@code null}, zero + * length search array or if one element of array is null will return {@code false}. + *

+ * + *
+     * WordUtils.containsAllWords(null, *)            = false
+     * WordUtils.containsAllWords("", *)              = false
+     * WordUtils.containsAllWords(*, null)            = false
+     * WordUtils.containsAllWords(*, [])              = false
+     * WordUtils.containsAllWords("abcd", "ab", "cd") = false
+     * WordUtils.containsAllWords("abc def", "def", "abc") = true
+     * 
+ * + * + * @param word The CharSequence to check, may be null + * @param words The array of String words to search for, may be null + * @return {@code true} if all search words are found, {@code false} otherwise + * @since 3.5 + */ + public static boolean containsAllWords(final CharSequence word, final CharSequence... words) { + if (StringUtils.isEmpty(word) || ArrayUtils.isEmpty(words)) { + return false; + } + for (final CharSequence w : words) { + if (StringUtils.isBlank(w)) { + return false; + } + final Pattern p = Pattern.compile(".*\\b" + w + "\\b.*"); + if (!p.matcher(word).matches()) { + return false; + } + } + return true; + } + + //----------------------------------------------------------------------- + /** + * Is the character a delimiter. + * + * @param ch the character to check + * @param delimiters the delimiters + * @return true if it is a delimiter + */ + private static boolean isDelimiter(final char ch, final char[] delimiters) { + if (delimiters == null) { + return Character.isWhitespace(ch); + } + for (final char delimiter : delimiters) { + if (ch == delimiter) { + return true; + } + } + return false; + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/text/package-info.java b/after/src/main/java/org/apache/commons/lang3/text/package-info.java new file mode 100644 index 0000000..f80f12a --- /dev/null +++ b/after/src/main/java/org/apache/commons/lang3/text/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. + */ +/** + *

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).

+ * + *

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.

+ * + * @since 2.1 + */ +package org.apache.commons.lang3.text; diff --git a/after/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java b/after/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java new file mode 100644 index 0000000..ce920fc --- /dev/null +++ b/after/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 codepoints from the input. + * + * @since 3.0 + * @deprecated as of 3.6, use commons-text + * + * AggregateTranslator 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 codepoints from the input is the 'winner'. + * Execution stops with the number of consumed codepoints 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/after/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java b/after/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java new file mode 100644 index 0000000..3d59dbf --- /dev/null +++ b/after/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.Writer; +import java.util.Locale; + +/** + * 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 commons-text + * + * CharSequenceTranslator 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 codepoints, represented by an int index into a CharSequence, + * into another set of codepoints. The number of codepoints 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 codepoints 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 RuntimeException(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 out 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 out) throws IOException { + if (out == null) { + throw new IllegalArgumentException("The Writer must not be null"); + } + if (input == null) { + return; + } + int pos = 0; + final int len = input.length(); + while (pos < len) { + final int consumed = translate(input, pos, out); + 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); + out.write(c1); + pos++; + if (Character.isHighSurrogate(c1) && pos < len) { + final char c2 = input.charAt(pos); + if (Character.isLowSurrogate(c2)) { + out.write(c2); + pos++; + } + } + continue; + } + // contract with translators is that they have to understand codepoints + // 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 {@code String} for the given + * character.

+ * + * @param codepoint The codepoint to convert. + * @return An upper case hexadecimal {@code String} + */ + public static String hex(final int codepoint) { + return Integer.toHexString(codepoint).toUpperCase(Locale.ENGLISH); + } + +} diff --git a/after/src/main/java/org/apache/commons/lang3/text/translate/CodePointTranslator.java b/after/src/main/java/org/apache/commons/lang3/text/translate/CodePointTranslator.java new file mode 100644 index 0000000..003dd77 --- /dev/null +++ b/after/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 commons-text + * + * CharSequenceTranslator 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 codepoint 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/after/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java b/after/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java new file mode 100644 index 0000000..864779e --- /dev/null +++ b/after/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 commons-text + * + * EntityArrays instead + */ +@Deprecated +public class EntityArrays { + + /** + * Mapping to escape ISO-8859-1 + * characters to their named HTML 3.x equivalents. + * @return the mapping table + */ + public static String[][] ISO8859_1_ESCAPE() { + return ISO8859_1_ESCAPE.clone(); + } + + private static final String[][] ISO8859_1_ESCAPE = { + {"\u00A0", " "}, // non-breaking space + {"\u00A1", "¡"}, // inverted exclamation mark + {"\u00A2", "¢"}, // cent sign + {"\u00A3", "£"}, // pound sign + {"\u00A4", "¤"}, // currency sign + {"\u00A5", "¥"}, // yen sign = yuan sign + {"\u00A6", "¦"}, // broken bar = broken vertical bar + {"\u00A7", "§"}, // section sign + {"\u00A8", "¨"}, // diaeresis = spacing diaeresis + {"\u00A9", "©"}, // © - copyright sign + {"\u00AA", "ª"}, // feminine ordinal indicator + {"\u00AB", "«"}, // left-pointing double angle quotation mark = left pointing guillemet + {"\u00AC", "¬"}, // not sign + {"\u00AD", "­"}, // soft hyphen = discretionary hyphen + {"\u00AE", "®"}, // ® - registered trademark sign + {"\u00AF", "¯"}, // macron = spacing macron = overline = APL overbar + {"\u00B0", "°"}, // degree sign + {"\u00B1", "±"}, // plus-minus sign = plus-or-minus sign + {"\u00B2", "²"}, // superscript two = superscript digit two = squared + {"\u00B3", "³"}, // superscript three = superscript digit three = cubed + {"\u00B4", "´"}, // acute accent = spacing acute + {"\u00B5", "µ"}, // micro sign + {"\u00B6", "¶"}, // pilcrow sign = paragraph sign + {"\u00B7", "·"}, // middle dot = Georgian comma = Greek middle dot + {"\u00B8", "¸"}, // cedilla = spacing cedilla + {"\u00B9", "¹"}, // superscript one = superscript digit one + {"\u00BA", "º"}, // masculine ordinal indicator + {"\u00BB", "»"}, // right-pointing double angle quotation mark = right pointing guillemet + {"\u00BC", "¼"}, // vulgar fraction one quarter = fraction one quarter + {"\u00BD", "½"}, // vulgar fraction one half = fraction one half + {"\u00BE", "¾"}, // vulgar fraction three quarters = fraction three quarters + {"\u00BF", "¿"}, // inverted question mark = turned question mark + {"\u00C0", "À"}, // À - uppercase A, grave accent + {"\u00C1", "Á"}, // Á - uppercase A, acute accent + {"\u00C2", "Â"}, //  - uppercase A, circumflex accent + {"\u00C3", "Ã"}, // à - uppercase A, tilde + {"\u00C4", "Ä"}, // Ä - uppercase A, umlaut + {"\u00C5", "Å"}, // Å - uppercase A, ring + {"\u00C6", "Æ"}, // Æ - uppercase AE + {"\u00C7", "Ç"}, // Ç - uppercase C, cedilla + {"\u00C8", "È"}, // È - uppercase E, grave accent + {"\u00C9", "É"}, // É - uppercase E, acute accent + {"\u00CA", "Ê"}, // Ê - uppercase E, circumflex accent + {"\u00CB", "Ë"}, // Ë - uppercase E, umlaut + {"\u00CC", "Ì"}, // Ì - uppercase I, grave accent + {"\u00CD", "Í"}, // Í - uppercase I, acute accent + {"\u00CE", "Î"}, // Î - uppercase I, circumflex accent + {"\u00CF", "Ï"}, // Ï - uppercase I, umlaut + {"\u00D0", "Ð"}, // Ð - uppercase Eth, Icelandic + {"\u00D1", "Ñ"}, // Ñ - uppercase N, tilde + {"\u00D2", "Ò"}, // Ò - uppercase O, grave accent + {"\u00D3", "Ó"}, // Ó - uppercase O, acute accent + {"\u00D4", "Ô"}, // Ô - uppercase O, circumflex accent + {"\u00D5", "Õ"}, // Õ - uppercase O, tilde + {"\u00D6", "Ö"}, // Ö - uppercase O, umlaut + {"\u00D7", "×"}, // multiplication sign + {"\u00D8", "Ø"}, // Ø - uppercase O, slash + {"\u00D9", "Ù"}, // Ù - uppercase U, grave accent + {"\u00DA", "Ú"}, // Ú - uppercase U, acute accent + {"\u00DB", "Û"}, // Û - uppercase U, circumflex accent + {"\u00DC", "Ü"}, // Ü - uppercase U, umlaut + {"\u00DD", "Ý"}, // Ý - uppercase Y, acute accent + {"\u00DE", "Þ"}, // Þ - uppercase THORN, Icelandic + {"\u00DF", "ß"}, // ß - lowercase sharps, German + {"\u00E0", "à"}, // à - lowercase a, grave accent + {"\u00E1", "á"}, // á - lowercase a, acute accent + {"\u00E2", "â"}, // â - lowercase a, circumflex accent + {"\u00E3", "ã"}, // ã - lowercase a, tilde + {"\u00E4", "ä"}, // ä - lowercase a, umlaut + {"\u00E5", "å"}, // å - lowercase a, ring + {"\u00E6", "æ"}, // æ - lowercase ae + {"\u00E7", "ç"}, // ç - lowercase c, cedilla + {"\u00E8", "è"}, // è - lowercase e, grave accent + {"\u00E9", "é"}, // é - lowercase e, acute accent + {"\u00EA", "ê"}, // ê - lowercase e, circumflex accent + {"\u00EB", "ë"}, // ë - lowercase e, umlaut + {"\u00EC", "ì"}, // ì - lowercase i, grave accent + {"\u00ED", "í"}, // í - lowercase i, acute accent + {"\u00EE", "î"}, // î - lowercase i, circumflex accent + {"\u00EF", "ï"}, // ï - lowercase i, umlaut + {"\u00F0", "ð"}, // ð - lowercase eth, Icelandic + {"\u00F1", "ñ"}, // ñ - lowercase n, tilde + {"\u00F2", "ò"}, // ò - lowercase o, grave accent + {"\u00F3", "ó"}, // ó - lowercase o, acute accent + {"\u00F4", "ô"}, // ô - lowercase o, circumflex accent + {"\u00F5", "õ"}, // õ - lowercase o, tilde + {"\u00F6", "ö"}, // ö - lowercase o, umlaut + {"\u00F7", "÷"}, // division sign + {"\u00F8", "ø"}, // ø - lowercase o, slash + {"\u00F9", "ù"}, // ù - lowercase u, grave accent + {"\u00FA", "ú"}, // ú - lowercase u, acute accent + {"\u00FB", "û"}, // û - lowercase u, circumflex accent + {"\u00FC", "ü"}, // ü - lowercase u, umlaut + {"\u00FD", "ý"}, // ý - lowercase y, acute accent + {"\u00FE", "þ"}, // þ - lowercase thorn, Icelandic + {"\u00FF", "ÿ"}, // ÿ - lowercase y, umlaut + }; + + /** + * Reverse of {@link #ISO8859_1_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] ISO8859_1_UNESCAPE() { + return ISO8859_1_UNESCAPE.clone(); + } + + private static final String[][] ISO8859_1_UNESCAPE = invert(ISO8859_1_ESCAPE); + + /** + * Mapping to escape additional character entity + * references. Note that this must be used with {@link #ISO8859_1_ESCAPE()} to get the full list of + * HTML 4.0 character entities. + * @return the mapping table + */ + public static String[][] HTML40_EXTENDED_ESCAPE() { + return HTML40_EXTENDED_ESCAPE.clone(); + } + + private static final String[][] HTML40_EXTENDED_ESCAPE = { + // + {"\u0192", "ƒ"}, // latin small f with hook = function= florin, U+0192 ISOtech --> + // + {"\u0391", "Α"}, // greek capital letter alpha, U+0391 --> + {"\u0392", "Β"}, // greek capital letter beta, U+0392 --> + {"\u0393", "Γ"}, // greek capital letter gamma, U+0393 ISOgrk3 --> + {"\u0394", "Δ"}, // greek capital letter delta, U+0394 ISOgrk3 --> + {"\u0395", "Ε"}, // greek capital letter epsilon, U+0395 --> + {"\u0396", "Ζ"}, // greek capital letter zeta, U+0396 --> + {"\u0397", "Η"}, // greek capital letter eta, U+0397 --> + {"\u0398", "Θ"}, // greek capital letter theta, U+0398 ISOgrk3 --> + {"\u0399", "Ι"}, // greek capital letter iota, U+0399 --> + {"\u039A", "Κ"}, // greek capital letter kappa, U+039A --> + {"\u039B", "Λ"}, // greek capital letter lambda, U+039B ISOgrk3 --> + {"\u039C", "Μ"}, // greek capital letter mu, U+039C --> + {"\u039D", "Ν"}, // greek capital letter nu, U+039D --> + {"\u039E", "Ξ"}, // greek capital letter xi, U+039E ISOgrk3 --> + {"\u039F", "Ο"}, // greek capital letter omicron, U+039F --> + {"\u03A0", "Π"}, // greek capital letter pi, U+03A0 ISOgrk3 --> + {"\u03A1", "Ρ"}, // greek capital letter rho, U+03A1 --> + // + {"\u03A3", "Σ"}, // greek capital letter sigma, U+03A3 ISOgrk3 --> + {"\u03A4", "Τ"}, // greek capital letter tau, U+03A4 --> + {"\u03A5", "Υ"}, // greek capital letter upsilon, U+03A5 ISOgrk3 --> + {"\u03A6", "Φ"}, // greek capital letter phi, U+03A6 ISOgrk3 --> + {"\u03A7", "Χ"}, // greek capital letter chi, U+03A7 --> + {"\u03A8", "Ψ"}, // greek capital letter psi, U+03A8 ISOgrk3 --> + {"\u03A9", "Ω"}, // greek capital letter omega, U+03A9 ISOgrk3 --> + {"\u03B1", "α"}, // greek small letter alpha, U+03B1 ISOgrk3 --> + {"\u03B2", "β"}, // greek small letter beta, U+03B2 ISOgrk3 --> + {"\u03B3", "γ"}, // greek small letter gamma, U+03B3 ISOgrk3 --> + {"\u03B4", "δ"}, // greek small letter delta, U+03B4 ISOgrk3 --> + {"\u03B5", "ε"}, // greek small letter epsilon, U+03B5 ISOgrk3 --> + {"\u03B6", "ζ"}, // greek small letter zeta, U+03B6 ISOgrk3 --> + {"\u03B7", "η"}, // greek small letter eta, U+03B7 ISOgrk3 --> + {"\u03B8", "θ"}, // greek small letter theta, U+03B8 ISOgrk3 --> + {"\u03B9", "ι"}, // greek small letter iota, U+03B9 ISOgrk3 --> + {"\u03BA", "κ"}, // greek small letter kappa, U+03BA ISOgrk3 --> + {"\u03BB", "λ"}, // greek small letter lambda, U+03BB ISOgrk3 --> + {"\u03BC", "μ"}, // greek small letter mu, U+03BC ISOgrk3 --> + {"\u03BD", "ν"}, // greek small letter nu, U+03BD ISOgrk3 --> + {"\u03BE", "ξ"}, // greek small letter xi, U+03BE ISOgrk3 --> + {"\u03BF", "ο"}, // greek small letter omicron, U+03BF NEW --> + {"\u03C0", "π"}, // greek small letter pi, U+03C0 ISOgrk3 --> + {"\u03C1", "ρ"}, // greek small letter rho, U+03C1 ISOgrk3 --> + {"\u03C2", "ς"}, // greek small letter final sigma, U+03C2 ISOgrk3 --> + {"\u03C3", "σ"}, // greek small letter sigma, U+03C3 ISOgrk3 --> + {"\u03C4", "τ"}, // greek small letter tau, U+03C4 ISOgrk3 --> + {"\u03C5", "υ"}, // greek small letter upsilon, U+03C5 ISOgrk3 --> + {"\u03C6", "φ"}, // greek small letter phi, U+03C6 ISOgrk3 --> + {"\u03C7", "χ"}, // greek small letter chi, U+03C7 ISOgrk3 --> + {"\u03C8", "ψ"}, // greek small letter psi, U+03C8 ISOgrk3 --> + {"\u03C9", "ω"}, // greek small letter omega, U+03C9 ISOgrk3 --> + {"\u03D1", "ϑ"}, // greek small letter theta symbol, U+03D1 NEW --> + {"\u03D2", "ϒ"}, // greek upsilon with hook symbol, U+03D2 NEW --> + {"\u03D6", "ϖ"}, // greek pi symbol, U+03D6 ISOgrk3 --> + // + {"\u2022", "•"}, // bullet = black small circle, U+2022 ISOpub --> + // + {"\u2026", "…"}, // horizontal ellipsis = three dot leader, U+2026 ISOpub --> + {"\u2032", "′"}, // prime = minutes = feet, U+2032 ISOtech --> + {"\u2033", "″"}, // double prime = seconds = inches, U+2033 ISOtech --> + {"\u203E", "‾"}, // overline = spacing overscore, U+203E NEW --> + {"\u2044", "⁄"}, // fraction slash, U+2044 NEW --> + // + {"\u2118", "℘"}, // script capital P = power set= Weierstrass p, U+2118 ISOamso --> + {"\u2111", "ℑ"}, // blackletter capital I = imaginary part, U+2111 ISOamso --> + {"\u211C", "ℜ"}, // blackletter capital R = real part symbol, U+211C ISOamso --> + {"\u2122", "™"}, // trade mark sign, U+2122 ISOnum --> + {"\u2135", "ℵ"}, // alef symbol = first transfinite cardinal, U+2135 NEW --> + // + // + {"\u2190", "←"}, // leftwards arrow, U+2190 ISOnum --> + {"\u2191", "↑"}, // upwards arrow, U+2191 ISOnum--> + {"\u2192", "→"}, // rightwards arrow, U+2192 ISOnum --> + {"\u2193", "↓"}, // downwards arrow, U+2193 ISOnum --> + {"\u2194", "↔"}, // left right arrow, U+2194 ISOamsa --> + {"\u21B5", "↵"}, // downwards arrow with corner leftwards= carriage return, U+21B5 NEW --> + {"\u21D0", "⇐"}, // leftwards double arrow, U+21D0 ISOtech --> + // + {"\u21D1", "⇑"}, // upwards double arrow, U+21D1 ISOamsa --> + {"\u21D2", "⇒"}, // rightwards double arrow, U+21D2 ISOtech --> + // + {"\u21D3", "⇓"}, // downwards double arrow, U+21D3 ISOamsa --> + {"\u21D4", "⇔"}, // left right double arrow, U+21D4 ISOamsa --> + // + {"\u2200", "∀"}, // for all, U+2200 ISOtech --> + {"\u2202", "∂"}, // partial differential, U+2202 ISOtech --> + {"\u2203", "∃"}, // there exists, U+2203 ISOtech --> + {"\u2205", "∅"}, // empty set = null set = diameter, U+2205 ISOamso --> + {"\u2207", "∇"}, // nabla = backward difference, U+2207 ISOtech --> + {"\u2208", "∈"}, // element of, U+2208 ISOtech --> + {"\u2209", "∉"}, // not an element of, U+2209 ISOtech --> + {"\u220B", "∋"}, // contains as member, U+220B ISOtech --> + // + {"\u220F", "∏"}, // n-ary product = product sign, U+220F ISOamsb --> + // + {"\u2211", "∑"}, // n-ary summation, U+2211 ISOamsb --> + // + {"\u2212", "−"}, // minus sign, U+2212 ISOtech --> + {"\u2217", "∗"}, // asterisk operator, U+2217 ISOtech --> + {"\u221A", "√"}, // square root = radical sign, U+221A ISOtech --> + {"\u221D", "∝"}, // proportional to, U+221D ISOtech --> + {"\u221E", "∞"}, // infinity, U+221E ISOtech --> + {"\u2220", "∠"}, // angle, U+2220 ISOamso --> + {"\u2227", "∧"}, // logical and = wedge, U+2227 ISOtech --> + {"\u2228", "∨"}, // logical or = vee, U+2228 ISOtech --> + {"\u2229", "∩"}, // intersection = cap, U+2229 ISOtech --> + {"\u222A", "∪"}, // union = cup, U+222A ISOtech --> + {"\u222B", "∫"}, // integral, U+222B ISOtech --> + {"\u2234", "∴"}, // therefore, U+2234 ISOtech --> + {"\u223C", "∼"}, // tilde operator = varies with = similar to, U+223C ISOtech --> + // + {"\u2245", "≅"}, // approximately equal to, U+2245 ISOtech --> + {"\u2248", "≈"}, // almost equal to = asymptotic to, U+2248 ISOamsr --> + {"\u2260", "≠"}, // not equal to, U+2260 ISOtech --> + {"\u2261", "≡"}, // identical to, U+2261 ISOtech --> + {"\u2264", "≤"}, // less-than or equal to, U+2264 ISOtech --> + {"\u2265", "≥"}, // greater-than or equal to, U+2265 ISOtech --> + {"\u2282", "⊂"}, // subset of, U+2282 ISOtech --> + {"\u2283", "⊃"}, // superset of, U+2283 ISOtech --> + // , + {"\u2284", "⊄"}, // not a subset of, U+2284 ISOamsn --> + {"\u2286", "⊆"}, // subset of or equal to, U+2286 ISOtech --> + {"\u2287", "⊇"}, // superset of or equal to, U+2287 ISOtech --> + {"\u2295", "⊕"}, // circled plus = direct sum, U+2295 ISOamsb --> + {"\u2297", "⊗"}, // circled times = vector product, U+2297 ISOamsb --> + {"\u22A5", "⊥"}, // up tack = orthogonal to = perpendicular, U+22A5 ISOtech --> + {"\u22C5", "⋅"}, // dot operator, U+22C5 ISOamsb --> + // + // + {"\u2308", "⌈"}, // left ceiling = apl upstile, U+2308 ISOamsc --> + {"\u2309", "⌉"}, // right ceiling, U+2309 ISOamsc --> + {"\u230A", "⌊"}, // left floor = apl downstile, U+230A ISOamsc --> + {"\u230B", "⌋"}, // right floor, U+230B ISOamsc --> + {"\u2329", "⟨"}, // left-pointing angle bracket = bra, U+2329 ISOtech --> + // + {"\u232A", "⟩"}, // right-pointing angle bracket = ket, U+232A ISOtech --> + // + // + {"\u25CA", "◊"}, // lozenge, U+25CA ISOpub --> + // + {"\u2660", "♠"}, // black spade suit, U+2660 ISOpub --> + // + {"\u2663", "♣"}, // black club suit = shamrock, U+2663 ISOpub --> + {"\u2665", "♥"}, // black heart suit = valentine, U+2665 ISOpub --> + {"\u2666", "♦"}, // black diamond suit, U+2666 ISOpub --> + + // + {"\u0152", "Œ"}, // -- latin capital ligature OE, U+0152 ISOlat2 --> + {"\u0153", "œ"}, // -- latin small ligature oe, U+0153 ISOlat2 --> + // + {"\u0160", "Š"}, // -- latin capital letter S with caron, U+0160 ISOlat2 --> + {"\u0161", "š"}, // -- latin small letter s with caron, U+0161 ISOlat2 --> + {"\u0178", "Ÿ"}, // -- latin capital letter Y with diaeresis, U+0178 ISOlat2 --> + // + {"\u02C6", "ˆ"}, // -- modifier letter circumflex accent, U+02C6 ISOpub --> + {"\u02DC", "˜"}, // small tilde, U+02DC ISOdia --> + // + {"\u2002", " "}, // en space, U+2002 ISOpub --> + {"\u2003", " "}, // em space, U+2003 ISOpub --> + {"\u2009", " "}, // thin space, U+2009 ISOpub --> + {"\u200C", "‌"}, // zero width non-joiner, U+200C NEW RFC 2070 --> + {"\u200D", "‍"}, // zero width joiner, U+200D NEW RFC 2070 --> + {"\u200E", "‎"}, // left-to-right mark, U+200E NEW RFC 2070 --> + {"\u200F", "‏"}, // right-to-left mark, U+200F NEW RFC 2070 --> + {"\u2013", "–"}, // en dash, U+2013 ISOpub --> + {"\u2014", "—"}, // em dash, U+2014 ISOpub --> + {"\u2018", "‘"}, // left single quotation mark, U+2018 ISOnum --> + {"\u2019", "’"}, // right single quotation mark, U+2019 ISOnum --> + {"\u201A", "‚"}, // single low-9 quotation mark, U+201A NEW --> + {"\u201C", "“"}, // left double quotation mark, U+201C ISOnum --> + {"\u201D", "”"}, // right double quotation mark, U+201D ISOnum --> + {"\u201E", "„"}, // double low-9 quotation mark, U+201E NEW --> + {"\u2020", "†"}, // dagger, U+2020 ISOpub --> + {"\u2021", "‡"}, // double dagger, U+2021 ISOpub --> + {"\u2030", "‰"}, // per mille sign, U+2030 ISOtech --> + {"\u2039", "‹"}, // single left-pointing angle quotation mark, U+2039 ISO proposed --> + // + {"\u203A", "›"}, // single right-pointing angle quotation mark, U+203A ISO proposed --> + // + {"\u20AC", "€"}, // -- 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 = { + {"\"", """}, // " - double-quote + {"&", "&"}, // & - ampersand + {"<", "<"}, // < - less-than + {">", ">"}, // > - 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 = { + {"'", "'"}, // 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 + * UnicodeEscaper instead + */ +@Deprecated +public class JavaUnicodeEscaper extends UnicodeEscaper { + + /** + *

+ * Constructs a {@code JavaUnicodeEscaper} above the specified value (exclusive). + *

+ * + * @param codepoint + * above which to escape + * @return the newly created {@code UnicodeEscaper} instance + */ + public static JavaUnicodeEscaper above(final int codepoint) { + return outsideOf(0, codepoint); + } + + /** + *

+ * Constructs a {@code JavaUnicodeEscaper} below the specified value (exclusive). + *

+ * + * @param codepoint + * below which to escape + * @return the newly created {@code UnicodeEscaper} instance + */ + public static JavaUnicodeEscaper below(final int codepoint) { + return outsideOf(codepoint, Integer.MAX_VALUE); + } + + /** + *

+ * Constructs a {@code JavaUnicodeEscaper} between the specified values (inclusive). + *

+ * + * @param codepointLow + * above which to escape + * @param codepointHigh + * below which to escape + * @return the newly created {@code UnicodeEscaper} instance + */ + public static JavaUnicodeEscaper between(final int codepointLow, final int codepointHigh) { + return new JavaUnicodeEscaper(codepointLow, codepointHigh, true); + } + + /** + *

+ * Constructs a {@code JavaUnicodeEscaper} outside of the specified values (exclusive). + *

+ * + * @param codepointLow + * below which to escape + * @param codepointHigh + * above which to escape + * @return the newly created {@code UnicodeEscaper} instance + */ + public static JavaUnicodeEscaper outsideOf(final int codepointLow, final int codepointHigh) { + return new JavaUnicodeEscaper(codepointLow, codepointHigh, false); + } + + /** + *

+ * Constructs a {@code 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 codepoint boundary + * @param above + * int value representing the highest codepoint 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 codepoint to a hex string of the form {@code "\\uXXXX\\uXXXX"} + * + * @param codepoint + * a Unicode code point + * @return the hex string for the given codepoint + */ + @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/after/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java b/after/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java new file mode 100644 index 0000000..5e80154 --- /dev/null +++ b/after/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 commons-text + * + * LookupTranslator instead + */ +@Deprecated +public class LookupTranslator extends CharSequenceTranslator { + + private final HashMap lookupMap; + private final HashSet 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 _shortest = Integer.MAX_VALUE; + int _longest = 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 < _shortest) { + _shortest = sz; + } + if (sz > _longest) { + _longest = sz; + } + } + } + shortest = _shortest; + longest = _longest; + } + + /** + * {@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/after/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java b/after/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java new file mode 100644 index 0000000..a57e79f --- /dev/null +++ b/after/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 codepoints to their XML numeric entity escaped value. + * + * @since 3.0 + * @deprecated as of 3.6, use commons-text + * + * NumericEntityEscaper instead + */ +@Deprecated +public class NumericEntityEscaper extends CodePointTranslator { + + private final int below; + private final int above; + private final boolean between; + + /** + *

Constructs a {@code 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 codepoint boundary + * @param above int value representing the highest codepoint 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 {@code NumericEntityEscaper} for all characters.

+ */ + public NumericEntityEscaper() { + this(0, Integer.MAX_VALUE, true); + } + + /** + *

Constructs a {@code NumericEntityEscaper} below the specified value (exclusive).

+ * + * @param codepoint below which to escape + * @return the newly created {@code NumericEntityEscaper} instance + */ + public static NumericEntityEscaper below(final int codepoint) { + return outsideOf(codepoint, Integer.MAX_VALUE); + } + + /** + *

Constructs a {@code NumericEntityEscaper} above the specified value (exclusive).

+ * + * @param codepoint above which to escape + * @return the newly created {@code NumericEntityEscaper} instance + */ + public static NumericEntityEscaper above(final int codepoint) { + return outsideOf(0, codepoint); + } + + /** + *

Constructs a {@code NumericEntityEscaper} between the specified values (inclusive).

+ * + * @param codepointLow above which to escape + * @param codepointHigh below which to escape + * @return the newly created {@code NumericEntityEscaper} instance + */ + public static NumericEntityEscaper between(final int codepointLow, final int codepointHigh) { + return new NumericEntityEscaper(codepointLow, codepointHigh, true); + } + + /** + *

Constructs a {@code NumericEntityEscaper} outside of the specified values (exclusive).

+ * + * @param codepointLow below which to escape + * @param codepointHigh above which to escape + * @return the newly created {@code 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/after/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java b/after/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java new file mode 100644 index 0000000..024a541 --- /dev/null +++ b/after/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 &#[xX]?\d+;? to + * the specific codepoint. + * + * Note that the semicolon is optional. + * + * @since 3.0 + * @deprecated as of 3.6, use commons-text + * + * NumericEntityUnescaper 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