diff --git a/Java-base/commons-validator/Dockerfile b/Java-base/commons-validator/Dockerfile new file mode 100644 index 000000000..e208c4890 --- /dev/null +++ b/Java-base/commons-validator/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:22.04 + +RUN export DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get install -y software-properties-common \ + && add-apt-repository ppa:deadsnakes/ppa \ + && apt-get update \ + && apt-get install -y \ + build-essential \ + git \ + vim \ + jq \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/list/* + +RUN apt-get -y install sudo \ + openjdk-8-jdk \ + maven + +RUN bash -c "echo 2 | update-alternatives --config java" + +COPY src /workspace +WORKDIR /workspace + +RUN mvn install -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false + +RUN mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 + +ENV TZ=Asia/Seoul diff --git a/Java-base/commons-validator/src/BUILDING.txt b/Java-base/commons-validator/src/BUILDING.txt new file mode 100644 index 000000000..6101a0856 --- /dev/null +++ b/Java-base/commons-validator/src/BUILDING.txt @@ -0,0 +1,29 @@ +The code requires at least Java 1.6 to build. + +However, current versions of Maven tend to require at least Java 7. + +If you want to build and test the code using Java 1.6, use the profile -Pjava-1.6, e.g. + +mvn clean test -Pjava-1.6 + +For setting up your Maven installation to enable the use of the profile, please see: + +http://commons.apache.org/commons-parent-pom.html#Testing_with_different_Java_versions + +Building the site will also generally require at least Java 7 to run Maven. +To build the site from scratch, you can use: + +mvn clean site [-Pjava-1.6] + +Also, ensure Maven has enough memory when using Java 7: + +export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=128m" # Unix +set MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=128m" # Windows + +For Java 8+, the MaxPermSize option should be removed: + +export MAVEN_OPTS="-Xmx512m" # Unix +set MAVEN_OPTS="-Xmx512m" # Windows + +There can be problems building the site using Maven 3.0.5 or earlier; +if so please use a later version. diff --git a/Java-base/commons-validator/src/CONTRIBUTING.md b/Java-base/commons-validator/src/CONTRIBUTING.md new file mode 100644 index 000000000..461f0313b --- /dev/null +++ b/Java-base/commons-validator/src/CONTRIBUTING.md @@ -0,0 +1,97 @@ + + +Contributing to Apache Commons Validator +====================== + +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 Validator's scope. ++ Submit a ticket 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. ++ Fork the repository on GitHub. + +Making Changes +-------------- + ++ Create a topic branch from where you want to base your work (this is usually the master/trunk branch). ++ Make commits of logical units. ++ 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. + + Check for unnecessary whitespace with git diff --check before committing. ++ Make sure your commit messages are in the proper format. Your commit message should contain the key of the JIRA issue. ++ Make sure you have added the necessary tests for your changes. ++ Run all the tests with `mvn clean verify` to assure nothing else was accidentally broken. + +Making Trivial Changes +---------------------- + +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 the [Contributor License Agreement][cla] if you haven't already. ++ Push your changes to a topic branch in your fork of the repository. ++ Submit a pull request to the repository in the apache organization. ++ Update your JIRA ticket and include a link to the pull request in the ticket. + +Additional Resources +-------------------- + ++ [Contributing patches](https://commons.apache.org/patches.html) ++ [Apache Commons Validator JIRA project page](https://issues.apache.org/jira/browse/VALIDATOR) ++ [Contributor License Agreement][cla] ++ [General GitHub documentation](https://help.github.com/) ++ [GitHub pull request documentation](https://help.github.com/send-pull-requests/) ++ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons) ++ #apachecommons IRC channel on freenode.org + +[cla]:https://www.apache.org/licenses/#clas diff --git a/Java-base/commons-validator/src/LICENSE.txt b/Java-base/commons-validator/src/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/Java-base/commons-validator/src/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/Java-base/commons-validator/src/NOTICE.txt b/Java-base/commons-validator/src/NOTICE.txt new file mode 100644 index 000000000..e8e528be6 --- /dev/null +++ b/Java-base/commons-validator/src/NOTICE.txt @@ -0,0 +1,5 @@ +Apache Commons Validator +Copyright 2001-2020 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/Java-base/commons-validator/src/PROPOSAL.html b/Java-base/commons-validator/src/PROPOSAL.html new file mode 100644 index 000000000..e7abce042 --- /dev/null +++ b/Java-base/commons-validator/src/PROPOSAL.html @@ -0,0 +1,112 @@ + + + +Proposal for Validator Library Package + + + +
+

Proposal for Validator Package

+
+ +

(0) Rationale

+ +

There is a need for the validation of JavaBeans to validate +user input from forms and validate business rules. There is also a +need to define different validation rules and error messages based on +the user's locale. +

+ +

The Validator package will provide the capability to configure +validators (validation methods) with different method signatures. +So the basic framework can have an interface built on it to deal +with validations on web layers, ejb layers, etc. +

+ +

(1) Scope of the Package

+ +

The package shall create and maintain a package that provides +basic validation functionality. +

+ +

+The package should : +

+

+ +

+Non-goals: +

+

+ +

(1.5) Interaction With Other Packages

+ +

Validator relies on: +

+ + + +

(2) Required Jakarta-Commons Resources

+ + + + + + +

(4) Initial Committers

+ +

The initial committers on the Validator component shall be:

+ + + +

+ + + diff --git a/Java-base/commons-validator/src/README.md b/Java-base/commons-validator/src/README.md new file mode 100644 index 000000000..7e78cc50e --- /dev/null +++ b/Java-base/commons-validator/src/README.md @@ -0,0 +1,104 @@ + + +Apache Commons Validator +=================== + +[![Build Status](https://travis-ci.org/apache/commons-validator.svg?branch=trunk)](https://travis-ci.org/apache/commons-validator) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/commons-validator/commons-validator/badge.svg)](https://maven-badges.herokuapp.com/maven-central/commons-validator/commons-validator/) + +Apache Commons Validator provides the building blocks for both client side validation and server side data validation. + It may be used standalone or with a framework like Struts. + +Documentation +------------- + +More information can be found on the [Apache Commons Validator homepage](https://commons.apache.org/proper/commons-validator). +The [Javadoc](https://commons.apache.org/proper/commons-validator/javadocs/api-release) can be browsed. +Questions related to the usage of Apache Commons Validator 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-validator/download_validator.cgi). + +Alternatively you can pull it from the central Maven repositories: + +```xml + + commons-validator + commons-validator + 1.6 + +``` + +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 Validator? 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/VALIDATOR) ++ [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/Java-base/commons-validator/src/RELEASE-NOTES.txt b/Java-base/commons-validator/src/RELEASE-NOTES.txt new file mode 100644 index 000000000..267620be7 --- /dev/null +++ b/Java-base/commons-validator/src/RELEASE-NOTES.txt @@ -0,0 +1,63 @@ + Apache Commons Validator 1.6 + RELEASE NOTES + +The Apache Commons Validator team is pleased to announce the release of Apache Commons Validator 1.6 + +Apache Commons Validator provides the building blocks for both client side validation and server side data validation. +It may be used standalone or with a framework like Struts. + +This is primarily a maintenance release. +All projects are encouraged to update to this release of Apache Commons Validator. + +Commons Validator requires Java 1.6 or later. + +Main enhancements +================= + +* Modulus Ten Check Digit Implementation +* Generic CreditCard validation (syntax and checkdigit only; does not check IIN) +* CreditCard validation specification by numeric range + +IMPORTANT NOTES +=============== + +BREAKING CHANGES: + + * NONE. + +DEPENDENCIES +============ +The dependencies for Validator have not changed since the 1.4 release. +For the current list of dependencies, please see http://commons.apache.org/validator/dependencies.html + +Changes in this version include: + +New features: +o VALIDATOR-415: Simplify building new CreditCard validators +o VALIDATOR-413: Generic CreditCard validation +o VALIDATOR-394: General Modulus Ten Check Digit Implementation Thanks to Niall Pemberton. + +Fixed Bugs: +o VALIDATOR-420: Query params validator shouldn't accept whitespaces Thanks to Marcin Gasior. +o VALIDATOR-419: Invalid IPv6 addresses that are IPv4-mapped pass InetAddressValidator validation Thanks to Denis Iskhakov. +o VALIDATOR-418: UrlValidatorTest: testIsValid() does not run all tests Thanks to Robert McGuigan. +o VALIDATOR-379: CodeValidator unconditionally trim()s the input string - document the behavior +o VALIDATOR-387: Userinfo without colon should be valid in UrlValidator Thanks to Shumpei Akai. +o VALIDATOR-411: UrlValidator accepts ports above max limit of 16-bit unsigned integer +o VALIDATOR-407: Generic .shop top level domain is considered invalid +o VALIDATOR-405: IBANValidator - Costa Rica entry has been updated in SWIFT docs +o VALIDATOR-401: IBANValidator fails for Seychelles and Ukraine +o VALIDATOR-391: UrlValidator.isValid throws exception for FILEURLs + Fixed code so it handles URLs with no authority field Thanks to Mark E. Scott, Jr. & Jason Loomis. + +Changes: +o IANA TLD lists: Updated to Version 2017020400, Last Updated Sat Feb 4 07:07:01 2017 UTC +o Update to version 73 of SWIFT IBAN list: added BY (Belarus) and IQ (Iraq); fixed Santa Lucia format + + +Historical list of changes: http://commons.apache.org/proper/commons-validator/changes-report.html + +For complete information on Apache Commons Validator, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Apache Commons Validator website: + +http://commons.apache.org/proper/commons-validator/ diff --git a/Java-base/commons-validator/src/checkstyle-suppressions.xml b/Java-base/commons-validator/src/checkstyle-suppressions.xml new file mode 100644 index 000000000..163ba11a0 --- /dev/null +++ b/Java-base/commons-validator/src/checkstyle-suppressions.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/checkstyle.xml b/Java-base/commons-validator/src/checkstyle.xml new file mode 100644 index 000000000..a776bf072 --- /dev/null +++ b/Java-base/commons-validator/src/checkstyle.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/pom.xml b/Java-base/commons-validator/src/pom.xml new file mode 100644 index 000000000..802a64395 --- /dev/null +++ b/Java-base/commons-validator/src/pom.xml @@ -0,0 +1,411 @@ + + + + + 4.0.0 + + + org.apache.commons + commons-parent + 51 + + + commons-validator + commons-validator + 1.7-SNAPSHOT + Apache Commons Validator + + Apache Commons Validator provides the building blocks for both client side validation and server side data validation. + It may be used standalone or with a framework like Struts. + + http://commons.apache.org/proper/commons-validator/ + 2002 + + + validator + org.apache.commons.validator + + 1.7 + RC1 + (requires JDK ${maven.compiler.target}) + VALIDATOR + 12310494 + UTF-8 + + site-content + https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-validator + + 1.7 + 1.7 + UTF-8 + UTF-8 + + + + scm:git:https://gitbox.apache.org/repos/asf/commons-validator + scm:git:https://gitbox.apache.org/repos/asf/commons-validator + https://gitbox.apache.org/repos/asf/commons-validator + + + + jira + http://issues.apache.org/jira/browse/VALIDATOR + + + + + apache.website + Apache Commons Site + scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-validator/ + + + + + clean verify apache-rat:check clirr:check checkstyle:check javadoc:javadoc + + + ${basedir} + META-INF + + NOTICE.txt + LICENSE.txt + + + + ${basedir}/src/main/resources + + + + + maven-assembly-plugin + + + ${basedir}/src/assembly/bin.xml + ${basedir}/src/assembly/src.xml + + gnu + + + + org.apache.maven.plugins + maven-scm-publish-plugin + + + javadocs + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${commons.checkstyle-plugin.version} + + ${basedir}/checkstyle.xml + + config_loc=${basedir} + false + + + + com.github.spotbugs + spotbugs-maven-plugin + ${commons.spotbugs.version} + + + + + + + + commons-beanutils + commons-beanutils + 1.9.4 + + + + commons-digester + commons-digester + 2.1 + + + + commons-beanutils + commons-beanutils + + + commons-logging + commons-logging + + + + + + commons-logging + commons-logging + 1.2 + + + + commons-collections + commons-collections + 3.2.2 + + + + + org.apache.commons + commons-csv + + 1.6 + test + + + + junit + junit + 4.12 + test + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${commons.checkstyle-plugin.version} + + ${basedir}/checkstyle.xml + + config_loc=${basedir} + false + + + + + checkstyle + + + + + + org.codehaus.mojo + cobertura-maven-plugin + ${commons.cobertura.version} + + + org.codehaus.mojo + clirr-maven-plugin + + + maven-pmd-plugin + 3.7 + + ${maven.compiler.target} + + + + + pmd + cpd + + + + + + org.codehaus.mojo + findbugs-maven-plugin + ${commons.findbugs.version} + + Normal + Default + + + + + org.apache.rat + apache-rat-plugin + + + site-content/** + + + + + + + + + setup-checkout + + + site-content + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + prepare-checkout + pre-site + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + jdk9-cldr + + [1.9,) + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + COMPAT,CLDR + + + + + + + + + + + Don Brown + mrdon + mrdon@apache.org + + + Martin Cooper + martinc + martinc@apache.org + + + David Graham + dgraham + dgraham@apache.org + + + Ted Husted + husted + husted@apache.org + + + Rob Leland + rleland + rleland at apache.org + + + Craig McClanahan + craigmcc + craigmcc@apache.org + + + James Mitchell + jmitchell + jmitchell NOSPAM apache.org + EdgeTech, Inc + + + Niall Pemberton + niallp + + + James Turner + turner + turner@apache.org + + + David Winterfeldt + dwinterfeldt + dwinterfeldt@apache.org + + + Henri Yandell + bayard + + + Ben Speakmon + bspeakmon + + + Nick Burch + nick + + + SimoneTripodi + simonetripodi + + + Benedikt Ritter + britter + + + Gary Gregory + ggregory + ggregory@apache.org + http://www.garygregory.com + -5 + + + + + Makoto Uchino + + + + diff --git a/Java-base/commons-validator/src/src/assembly/bin.xml b/Java-base/commons-validator/src/src/assembly/bin.xml new file mode 100644 index 000000000..b57a51355 --- /dev/null +++ b/Java-base/commons-validator/src/src/assembly/bin.xml @@ -0,0 +1,46 @@ + + + bin + + tar.gz + zip + + false + + + + LICENSE.txt + NOTICE.txt + RELEASE-NOTES.txt + README.md + + + + target + + + *.jar + *.js + + + + target/site/apidocs + apidocs + + + diff --git a/Java-base/commons-validator/src/src/assembly/src.xml b/Java-base/commons-validator/src/src/assembly/src.xml new file mode 100644 index 000000000..04a4b96d2 --- /dev/null +++ b/Java-base/commons-validator/src/src/assembly/src.xml @@ -0,0 +1,47 @@ + + + src + + tar.gz + zip + + ${artifactId}-${version}-src + + + + BUILDING.txt + build.properties.sample + build.xml + checkstyle*.xml + CONTRIBUTING.md + LICENSE.txt + license-header.txt + NOTICE.txt + pom.xml + RELEASE-NOTES.txt + README.md + + + + etc + + + src + + + diff --git a/Java-base/commons-validator/src/src/changes/changes.xml b/Java-base/commons-validator/src/src/changes/changes.xml new file mode 100644 index 000000000..b42fc9d85 --- /dev/null +++ b/Java-base/commons-validator/src/src/changes/changes.xml @@ -0,0 +1,1143 @@ + + + + + + + + Release Notes + + + + + + + + IBANValidator fails for El Salvador + Add definition + + + IANA TLD lists: Updated to Version 2018092800, Last Updated Fri Sep 28 07:07:02 2018 UTC + + + Add ISINValidator + + + Update commons digester to 2.1 + + + Field does not synchronize iteration on synchronized list + + + Update Apache Commons BeanUtils dependency from 1.9.2 to 1.9.3. + This picks up BEANUTILS-482: Update commons-collections from 3.2.1 to 3.2.2 (CVE-2015-4852). + + + Update Apache Commons BeanUtils dependency from 1.9.3 to 1.9.4 + This picks up BEANUTILS-520: Mitigate CVE-2014-0114. + + + Add IBAN validator for VA – Vatican City State + + + Generic .gmbh top level domain is considered invalid + + + LongValidator: numbers bigger than the maxvalue are Valid + + + CreditCardValidator default ctor disagrees with Javadoc + + + ISSN Validator extract ISSN from EAN-13 + + + URL validator fails if path starts with double slash and has underscores + + + UrlValidator says "file://bad ^ domain.com/label/test" is valid + + + Leading and trailing spaces in EmailValidator should not be valid + + + EMailValidator: Addresses with leading spaces must not be accepted + + + DomainValidator.getTLDArray does not synch mutable arrays + + + + + + Query params validator shouldn't accept whitespaces + + + Invalid IPv6 addresses that are IPv4-mapped pass InetAddressValidator validation + + + UrlValidatorTest: testIsValid() does not run all tests + + + Simplify building new CreditCard validators + + + Generic CreditCard validation + + + CodeValidator unconditionally trim()s the input string - document the behavior + + + Userinfo without colon should be valid in UrlValidator + + + General Modulus Ten Check Digit Implementation + + + UrlValidator accepts ports above max limit of 16-bit unsigned integer + + + IANA TLD lists: Updated to Version 2017020400, Last Updated Sat Feb 4 07:07:01 2017 UTC + + + Update to version 73 of SWIFT IBAN list: added BY (Belarus) and IQ (Iraq); fixed Santa Lucia format + + + Generic .shop top level domain is considered invalid + + + IBANValidator - Costa Rica entry has been updated in SWIFT docs + + + IBANValidator fails for Seychelles and Ukraine + + + UrlValidator.isValid throws exception for FILEURLs + Fixed code so it handles URLs with no authority field + + + + + Mastercard Series 2 BIN ranges (active from October 2016) added to CreditCardValidator + To disable the new ranges, use option MASTERCARD_PRE_OCT2016 or validator MASTERCARD_VALIDATOR_PRE_OCT2016 + + + org.apache.commons.validator.routines.DomainValidator.ArrayType is not public + + + EmailValidator does not catch invalid email address like dora@.com + + + EmailValidator does not support escaped quotes in a quoted string + + + DomainValidator - allow access to internal arrays + + + Updated to TLD list Version 2016042500, Last Updated Mon Apr 25 07:07:01 2016 UTC + + + + + Email Validator does not support quoted/escaped character in the local part of the email address + + + Update commons-collections from 3.2.1 to 3.2.2. + + + UrlValidator rejects path having two or more successive dots + + + IBANCheckDigit.isValid() returns True for some invalid IBANs + + + UrlValidator does not allow for optional port digits + + + IIBANCheckDigit.calculate does not enforce initial checksum value + Checkdigit field is now unconditionally set to "00" to ensure correct generation + + + UrlValidator does not allow for optional userinfo in the authority + + + ISSN validator and converter to EAN-13 + + + Improve IBAN validation with format checks + + + DateValidatorTest.testCompare() fails with GMT-12 + + + Validate 19 digit VPay (VISA) + + + UrlValidator fails on IPv6 URL + + + UrlValidator rejects new gTLDs with more than 4 characters + Added unit test to show that this has been fixed + + + Make TLD list configurable; + both generic and country-code now support addition and removal + + + Email Validator : .school domain is being rejected + Add Unit test to show it has been fixed + + + Revert EmailValidator to handle top level domains to the behavior prior to VALIDATOR-273. Allow an optional + behavior to allow the behavior VALIDATOR-273 implemented. Note that this is a behavioral change for users + of version 1.4.1, but not for anyone upgrading from a release prior to that. + + Drop the Javascript code entirely + + Local part of the email address should not be longer than 64 bytes + + + IDN.toASCII drops trailing dot in Java 6 & 7 + + + Update to Java 6 + + + + + + URLValidator returns false for http://example.rocks + + + UrlValidator rejects url with Unicode characters in domain label or TLD + + + URLValidator fails validating domain names with a trailing period, which are valid. + + + DomainValidator accepts labels longer than 63 chars and domain name lengths exceeding 255 chars + + + TLD tables should be pre-sorted + + + Create new url validation using rfc3986 and IDN - added new test + + + Should "x.root" validate as a domain name? + Removed "root" from TLD list. + Also "um" and "yu" as they are currently "Not assigned" + + + Logical errors in util.Flags affecting check of multiple flags as well as flag 64 + + + AbstractCheckDigit class does not fully test invalid strings + Fix up the testCalculateInvalid() invalid method to allow for + either invalid checksum or syntax (CheckDigitException) error + when testing the entries in the invalid array. + + + Punycode url is not valid + Top-level domain regex matching was wrong; did not allow embedded "-" as per RFC2396 + + + UrlValidator: isValidAuthority() returning true when supplied authority validator fails + + + UrlValidator does not validate uppercase URL schemes + + + Doc URL update for broken link + + + SedolCheckDigit fails to reject invalid (non-numeric) check digits + + + ISINCheckDigit fails to reject invalid (non-numeric) check digits + + + CUSIPCheckDigit thinks invalid CUSIP is valid + + + Update TLD list to latest version (Version 2014123000) + + + toLowerCase() method is Locale-sensitive and should not be used + Fixed 4 instances in DomainValidator + + + isValid checks if the given address is only IPV4 address and not IPV6 + + + Deprecate the JS part of commons validator + + + DomainValidator uses an O(n) method where an O(1) would be more appropriate + + + EmailValidator does not support mailboxes at TLDs + + + DomainValidator missing sTLD - "xxx" + + + Missing sx tld. + + + Some TLDs are missing from DomainValidator + + + IBANCheckDigitTest.createInvalidCodes(String[] codes) uses wrong values + + + + + + CheckStyle and FindBug Issues - inner classes and key sets + + + Email validation fails with dash or hyphen at end of local address + + + @localhost and @localhost.localdomain email addresses aren't correctly detected as valid + + + UrlValidator.isValid does not properly validate *.travel domains + + + UrlValidator does not validate URL with simple domains (eg: http://hostname ) + + + isValid method for EmailValidator should return false for domain with special characters only + + + formatDate(String value, Locale locale) in GenericTypeValidator uses DateFormat.SHORT instead of DateFormat.DEFAULT + + + isValidURL call returns false for file scheme/protocol when URL is correct + + + gmail testing addresses do not validate + + + EmailValidator.isValid(String) follows RFC822 but violates RFC1034 + + + Performance improvement of DomainValidator by change the regular expression + + + url with brackets is not validated thru URLvalidator class. + + + Banking CheckDigit implementations: ABA, CUSIP, IBAN, ISIN and Sedol + + + Add Diners card validation to CreditCardValidator + + + Add an option to allow 'localhost' as a valid hostname part in the URL + + + Move CreditCardValidator to routines package and refactor to use new CodeValidator + + + Move EmailValidator to routines package + + + New InetAdress Validator implementation + + + Support the 65 prefix for Discover Card + + + Create 1.4 DTD + + + Switch to using Version 0.4.3 of the Dojo Compressor from the maven repo + + + Add script attribute to control script generation + + + clirr Report - EmailValidator.isValidIpAddress() argument type change + + + Null-Stream input to ValidatorResources leads to MalformedURLExceptions + + + validatorUtilities.js - replace colon characters in the function name (JSF/Shale) + + + Move the trim() function from validateRequired.js to validateUtilities.js + + + EmailValidator fails with ArrayIndexOutOfBoundsException on domain names longer than 10 segments + + + UrlValidator fail when path contains "(" / ")" + + + UrlValidator rejects top-level domains (TLDs) with more than 4 characters + + + New generic CodeValidator that validates format, length and Check Digit for a code + + + New Regular Expression validator using JDK 1.4's Regex + + + Factor out Check Digit logic into separate implementations + + + Upgrade to Digester 1.8 + + + Refactor UrlValidator - especially the line 370-ish. + + + Copy remaining Validation Routines to the new routines package + + + Removing ORO dep. from GenericValidator + + + Adding ISBNValidator to GenericValidator + + + Remove the dependency on Jakarta ORO (move to JDK 1.4 regular expression support) + + + Extend ISBN validator to support smooth transition to ISBN-13 / EAN-13 standard + + + JDK 1.4 - change minimum dependency for validator to be JDK 1.4 (was 1.3). Primary reason + for this is to use JDK 1.4+ built in regular expression support and remove the dependency + on Jakarta ORO. + + + + + + Dependencies for Validator 1.3.1 are unchanged since the 1.3.0 release. + N.B. Jakarta ORO has now been marked as an optional dependency + in the project.xml as it is only required by the Email, URL and Regular Expression validations. + + + JavaScript function jcv_isFieldPresent() causes error in IE5 using "undefined". + + + EmailValidator allows control characters (ASCII 0-31 and 127). + + + + + JavaScript Causes HTML Page to Contain Illegal HTML. + + + Additional constructor for ValidatorResources that takes URL["> instead of String[">. + + + Fix loading of Digester rules for custom ValidatorResources implementations. + + + Validator incorrectly storing itself under the FORM_PARAM key rather than + the Form. + + + Urlvalidator returns false for a valid URL containing an underscore. + + + Urlvalidator fails with an ArrayIndexOutOfBoundsException. + + + The ant build.xml doesn't include validator_1_1_3.dtd in the jar. + + + Example does not compile using ant build script. + + + Validating indexed properties fails when null. + + + Fix a thread safety issue in parameter initialization. + + + + + + ValidatorResult only contains last run dependency for the field. + + + Validator argument - resource="false" ignored for arg0 - arg3. + + + Change JavaScript validators to cater for disabled being undefined (an issue in Netscape 4.7). + + +

Add new routines package containing standard validations - first + step in the process of clearly separating standard validation + functions which can be used independantly, from the framework + aspect of Commons Validator.

+

New validators added for Date, Time, Calendar, Byte, Short, + Integer, Long, Float, Double, BigInteger, BigDecimal, + Currency and Percent.

+

See + Routines Package Javadocs

+
+ + Deprecate ValidatorResult's getActionMap() and add getActions() + method to provide an Iterator of the set of action names. + + + Use the Dojo/Rhino JavaScript compressor to created compressed + versions of the static JavaScript files. Additionally create single + file distros of all the static JavaScript in un-compressed and compressed + format. See Dojo/Rhino Compressor. + + + Prefix remaining JavaScript utility methods with "jcv_" to reduce + the likelihood of clashes with other libraries - validator still + needs to be properly namespaced (as per Bug 38184). + + + Change JavaScript validators so that they don't fail when the + field is not present on the form. + + + + + Fix min/max length validation for different line endings. + + + Fix email validator to not allow spaces at the end of the user + component or start of the domain component. + + + Added validator_1_3_0.dtd and changed form rules so that a minimum + of one field is no longer required (i.e. changed (field+) to (field*) + for a form). + + + Resolve issue in JavaScript validation when the prototype library + is used. + + + Re-factor JavaScript error handling into a common method and only + set focus on fields which are not 'hidden' type or hidden by CSS. + + + + + + + Remove static Log instances to avoid problems if deployed via a shared + classloader in a container. See + here + for more details. + + + Reverse change for to Credit Card Validator for visa card blue in France. + + + Fix JavaScript validation for Internet Explorer 5.0. + +
+ + + + Added ISBNValidator for validating book numbers. + + + Upgrade dependency versions to + Commons BeanUtils 1.7.0, + Commons Digester 1.6 + and Commons Logging 1.0.4. + Remove dependency on + Commons Collections + (BeanUtils 1.7.0 has removed its dependency on Collections by including the + few Collections classes required in its distribution). + + + Add support for min or max numeric values. + + + Allow validators to register errors for multiple fields. + + + Fixed EmailValidator failing on valid email addresses. + + + Allow forms to inherit validation rules from other forms. + + + Remove the need to specify an Arguement's position. + + + Deprecated all FastHashMap usage and provided protected get + methods that return generic Maps to be used by subclasses. + + + Handling of float and double should use the locale object. + Fixes 21282 + + + + More informative Exception message when validation method not found. + + + Client-side required validation inconsistent with server-side. + + + EmailValidator allows apostrophes in domain name. + + + Changing the strategy for locating form name/id, now use a common utility + function which works in both IE and Firefox. + Fixes 35127 + and 32760 + + + Validation fails when "name" attribute in form not specified. + + + + + UrlValidator fails http://www.google.com. + + + Email: inexisting dashes and TLD erroneously accepted. + + + + + Float validator can't validate the string with several dot. + + + CreditValidator does not handle Visa correctly. + + + datePattern not supported by JavaScript. + + + validateRequired on a single radio button. + + + Field.validate() cannot be invoked from user-defined code. + + + Locale validation doesn't validate all fields. + + + + + XML file included into validation.xml via entity reference not found. + + + Update maven build to Include DTDs and xdocs in the source distribution. + + + Remove logging of exceptions when the Date validation fails (correctly) with + an invalid date. + + + Add version 1.1.3 of the DTD from the VALIDATOR_1_1_2_BRANCH and change + digester rules so that the old arg0-arg3 values are not ignored for + versions of the DTD prior to 1.2.0. + + + Add 'resource' and 'bundle' elements to the 1.2.0 DTD. + + + Provide access to the result object in ValidatorResult. + + + Validation breaks on multiple validation.xml (eg. with Struts 1.3). + + + GenericTypeValidator does not accept negative Floats/Doubles. + + + correct UrlValidator Javadoc. + + + Search the locale 'hierarchy' of formsets for a Form. + + + Int validation in Java and Javascript have different semantics. + + + Javascript Validation currently uses unsupported DOM method getAttributeNode(). + + + + + + Added getMessage(key) and getMessages() methods to Field + + + Added resource property (including getter/setter) to Msg to support + the 'resource' attribute specified in the DTD. + + + + + + Fixed javascript file reading in Java WebStart environment. + + + Fixed javascript email domain length limited to 2 or 3 chars. + + + + + + Javascript validation doesn't work if a form field is + called "name". + + + Allow multiple forms to be on the same page by + generating a unique variable name based on form name. + Fixes 17667 + + + Validate file extensions for file uploads. + + + Add Support for hidden fields in javascript + validations. + + + The framework will convert checked exceptions into + ValidatorExceptions so any ValidatorException thrown out + of the framework indicates a 'system' exception that + stops validation processing. If a pluggable validation + method throws a ValidatorException it will be rethrown + and passed out of the framework. Any other exception from + a pluggable validation method is still considered a validation + failure rather than a system exception to maintain backwards + compatibility. + + + Added a more flexible card validation system that doesn't + require CreditCardValidator to support every brand of + credit card. + + + Throw RuntimeException if clone fails instead of InternalError. + + + Added Flags.clear(). + + + + + + Add javadoc to javascript, and use + jsdoc to process it. + + + Ignore validation criteria when field is disabled for all field types. + + + Add required check for single checkbox. + + + Let max/min length also cover passwords fields. Don't use + these for checking login pages, only when the user is + modifying the password. + + + Added Field.getArgs(String) to make it easier to retrieve + all of the Args for a given validator. + + + Modify javascript to honor datapattern option. + + + Add ability of required to handle checkboxes, radio, select-one, + and select-multiple field types. + + + Add ability to use required condition on array types like checkboxes. + + + + + + Move Digester rule configuration to XML file and remove + ValidatorResourcesInitializer. ValidatorResources now + knows how to initialize itself. + + + Clean up scopes of methods and variables. + + + Make Arg system more flexible to allow any number of + args in a message. + + + Validate validation.xml files while initializing a Validator + to alert developers to configuration errors. + + + Refactored GenericValidator methods into reusable + objects. These include: CreditCardValidator, EmailValidator, + DateValidator, and UrlValidator. + + +

Backwards Incompatible Changes

+
    +
  • + <msg>'s name and key attributes are now required. The Validator code was + enforcing this constraint so now it's formally defined in the DTD. +
  • +
+
+ +

Deprecated items; see the javadoc for details and replacements.

+
    +
  • + The <arg0-3> elements have been replaced with a single <arg> element + with a new position attribute. Setting position to 0 is the equivalent + of an <arg0> element. +
  • +
  • + Arg.getResource() +
  • +
  • + CreditCardValidator.isValidPrefix() +
  • +
  • + Field.ARG_DEFAULT +
  • +
  • + Field.hDependencies +
  • +
  • + Field.hArg0 - Field.hArg3 +
  • +
  • + Field.addArg0() - Field.addArg3() +
  • +
  • + Field.getArg0() - Field.getArg3() +
  • +
  • + Field.addVarParam() +
  • +
  • + Field.process() +
  • +
  • + Field.processMessageComponents() +
  • +
  • + Field.getDependencies() +
  • +
  • + Form.getFieldMap() +
  • +
  • + Form.process() +
  • +
  • + FormSet.addConstant() +
  • +
  • + FormSet.addConstantParam() +
  • +
  • + FormSet.getForm(Object) +
  • +
  • + FormSet.process() +
  • +
  • + GenericValidator.REGEXP_DELIM +
  • +
  • + GenericValidator.validateCreditCardLuhnCheck() +
  • +
  • + GenericValidator.validateCreditCardPrefixCheck() +
  • +
  • + GenericValidator.getDelimittedRegExp() +
  • +
  • + Validator.BEAN_KEY +
  • +
  • + Validator.VALIDATOR_ACTION_KEY +
  • +
  • + Validator.FIELD_KEY +
  • +
  • + Validator.VALIDATOR_KEY +
  • +
  • + Validator.LOCALE_KEY +
  • +
  • + Validator.hResources +
  • +
  • + Validator.addResource() +
  • +
  • + Validator.getResource() +
  • +
  • + ValidatorAction.process() +
  • +
  • + ValidatorAction.getDependencies() +
  • +
  • + ValidatorResources.put() +
  • +
  • + ValidatorResources.addConstant() +
  • +
  • + ValidatorResources.addConstantParam() +
  • +
  • + ValidatorResources.get() +
  • +
  • + ValidatorResources.processForms() +
  • +
  • + ValidatorResourcesInitializer +
  • +
  • + ValidatorResult.getValid() +
  • +
  • + ValidatorResults.empty() +
  • +
  • + ValidatorResults.get() +
  • +
  • + ValidatorResults.properties() +
  • +
  • + ValidatorUtil +
  • +
+
+
+ + + + GenericValidaor.isEmail bug. + + + NPE in Validator.java after upgrading to Struts 1.1b3. + + + i18n issue, variant not being picked up by Validator. + + + isEmail accepts Umlauts and other non-ASCII characters. + + + Email address validation incorrectly accepts commas. + + + unknown host when loading app. + + + + + + Serialization problem with org.apache.commons.validator.ValidatorResult$ResultStatus. + + + ValidatorResources.get-method not working properly. + + + + + + First Release. + + + + +
diff --git a/Java-base/commons-validator/src/src/changes/release-notes.vm b/Java-base/commons-validator/src/src/changes/release-notes.vm new file mode 100644 index 000000000..e0589caa9 --- /dev/null +++ b/Java-base/commons-validator/src/src/changes/release-notes.vm @@ -0,0 +1,122 @@ +## Licensed to the Apache Software Foundation (ASF) under one +## or more contributor license agreements. See the NOTICE file +## distributed with this work for additional information +## regarding copyright ownership. The ASF licenses this file +## to you under the Apache License, Version 2.0 (the +## "License"); you may not use this file except in compliance +## with the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, +## software distributed under the License is distributed on an +## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +## KIND, either express or implied. See the License for the +## specific language governing permissions and limitations +## under the License. +## + ${project.name} ${version} + RELEASE NOTES + +The ${developmentTeam} is pleased to announce the release of ${project.name} ${version} + +$introduction.replaceAll("(?A simple example of setting up and using the Validator.

+ * + * This simple example shows all the steps needed to set up and use + * the Validator. Note that in most cases, some kind of framework + * would be wrapped around the Validator, such as is the case with + * the Struts Validator Framework. However, should you wish to use + * the Validator against raw Beans in a pure Java application, you + * can see everything you need to know to get it working here. + * + * @version $Revision$ + */ +public class ValidateExample { + + /** + * We need a resource bundle to get our field names and errors messages + * from. Note that this is not strictly required to make the Validator + * work, but is a good coding practice. + */ + private static ResourceBundle apps = + ResourceBundle.getBundle( + "org.apache.commons.validator.example.applicationResources"); + + /** + * This is the main method that will be called to initialize the Validator, create some sample beans, and + * run the Validator against them. + */ + public static void main(String[] args) + throws ValidatorException, IOException, SAXException { + + InputStream in = null; + ValidatorResources resources = null; + + try { + + // Create a new instance of a ValidatorResource, then get a stream + // handle on the XML file with the actions in it, and initialize the + // resources from it. This would normally be done by a servlet + // run during JSP initialization or some other application-startup + // routine. + in = ValidateExample.class.getResourceAsStream("validator-example.xml"); + resources = new ValidatorResources(in); + + } finally { + // Make sure we close the input stream. + if (in != null) { + in.close(); + } + } + + // Create a test bean to validate against. + ValidateBean bean = new ValidateBean(); + + // Create a validator with the ValidateBean actions for the bean + // we're interested in. + Validator validator = new Validator(resources, "ValidateBean"); + + // Tell the validator which bean to validate against. + validator.setParameter(Validator.BEAN_PARAM, bean); + + ValidatorResults results = null; + + // Run the validation actions against the bean. Since all of the properties + // are null, we expect them all to error out except for street2, which has + // no validations (it's an optional property) + + results = validator.validate(); + printResults(bean, results, resources); + + // Now set all the required properties, but make the age a non-integer. + // You'll notice that age will pass the required test, but fail the int + // test. + bean.setLastName("Tester"); + bean.setFirstName("John"); + bean.setStreet1("1 Test Street"); + bean.setCity("Testville"); + bean.setState("TE"); + bean.setPostalCode("12345"); + bean.setAge("Too Old"); + results = validator.validate(); + printResults(bean, results, resources); + + // Now only report failed fields + validator.setOnlyReturnErrors(true); + results = validator.validate(); + printResults(bean, results, resources); + + // Now everything should pass. + validator.setOnlyReturnErrors(false); + bean.setAge("123"); + results = validator.validate(); + printResults(bean, results, resources); + } + + /** + * Dumps out the Bean in question and the results of validating it. + */ + public static void printResults( + ValidateBean bean, + ValidatorResults results, + ValidatorResources resources) { + + boolean success = true; + + // Start by getting the form for the current locale and Bean. + Form form = resources.getForm(Locale.getDefault(), "ValidateBean"); + + System.out.println("\n\nValidating:"); + System.out.println(bean); + + // Iterate over each of the properties of the Bean which had messages. + Iterator propertyNames = results.getPropertyNames().iterator(); + while (propertyNames.hasNext()) { + String propertyName = propertyNames.next(); + + // Get the Field associated with that property in the Form + Field field = form.getField(propertyName); + + // Look up the formatted name of the field from the Field arg0 + String prettyFieldName = apps.getString(field.getArg(0).getKey()); + + // Get the result of validating the property. + ValidatorResult result = results.getValidatorResult(propertyName); + + // Get all the actions run against the property, and iterate over their names. + Iterator keys = result.getActions(); + while (keys.hasNext()) { + String actName = keys.next(); + + // Get the Action for that name. + ValidatorAction action = resources.getValidatorAction(actName); + + // If the result is valid, print PASSED, otherwise print FAILED + System.out.println( + propertyName + + "[" + + actName + + "] (" + + (result.isValid(actName) ? "PASSED" : "FAILED") + + ")"); + + //If the result failed, format the Action's message against the formatted field name + if (!result.isValid(actName)) { + success = false; + String message = apps.getString(action.getMsg()); + Object[] args = { prettyFieldName }; + System.out.println( + " Error message will be: " + + MessageFormat.format(message, args)); + + } + } + } + if (success) { + System.out.println("FORM VALIDATION PASSED"); + } else { + System.out.println("FORM VALIDATION FAILED"); + } + + } + +} diff --git a/Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/applicationResources.properties b/Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/applicationResources.properties new file mode 100644 index 000000000..907fec7ff --- /dev/null +++ b/Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/applicationResources.properties @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The error messages for the Validation Actions +errors.required=The {0} field is required. +errors.int=The {0} field is not an integer. + +# The formatted names of the properties +nameForm.age.displayname=Age +nameForm.lastname.displayname=Last Name +nameForm.firstname.displayname=First Name +nameForm.city.displayname=City +nameForm.state.displayname=State +nameForm.postalCode.displayname=Postal Code +nameForm.street1.displayname=Street Address + diff --git a/Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/validator-example.xml b/Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/validator-example.xml new file mode 100644 index 000000000..80ab2ee8f --- /dev/null +++ b/Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/validator-example.xml @@ -0,0 +1,61 @@ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Arg.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Arg.java new file mode 100644 index 000000000..e3ce7de6a --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Arg.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; + +/** + *

+ * A default argument or an argument for a + * specific validator definition (ex: required) + * can be stored to pass into a message as parameters. This can be used in a + * pluggable validator for constructing locale + * sensitive messages by using java.text.MessageFormat + * or an equivalent class. The resource field can be + * used to determine if the value stored in the argument + * is a value to be retrieved from a locale sensitive + * message retrieval system like java.util.PropertyResourceBundle. + * The resource field defaults to 'true'. + *

+ *

Instances of this class are configured with an <arg> xml element.

+ * + * @version $Revision$ + */ +//TODO mutable non-private fields +public class Arg implements Cloneable, Serializable { + + private static final long serialVersionUID = -8922606779669839294L; + + /** + * The resource bundle name that this Arg's key should be + * resolved in (optional). + * @since Validator 1.1 + */ + protected String bundle = null; + + /** + * The key or value of the argument. + */ + protected String key = null; + + /** + * The name dependency that this argument goes with (optional). + */ + protected String name = null; + + /** + * This argument's position in the message. Set postion=0 to + * make a replacement in this string: "some msg {0}". + * @since Validator 1.1 + */ + protected int position = -1; + + /** + * Whether or not the key is a message resource (optional). Defaults to + * true. If it is 'true', the value will try to be resolved as a message + * resource. + */ + protected boolean resource = true; + + /** + * Creates and returns a copy of this object. + * @return A copy of this object. + */ + @Override + public Object clone() { + try { + return super.clone(); + + } catch(CloneNotSupportedException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Returns the resource bundle name. + * @return the bundle name. + * @since Validator 1.1 + */ + public String getBundle() { + return this.bundle; + } + + /** + * Gets the key/value. + * @return the key value. + */ + public String getKey() { + return this.key; + } + + /** + * Gets the name of the dependency. + * @return the name of the dependency. + */ + public String getName() { + return this.name; + } + + /** + * Argument's replacement position. + * @return This argument's replacement position. + */ + public int getPosition() { + return this.position; + } + + /** + * Tests whether or not the key is a resource key or literal value. + * @return true if key is a resource key. + */ + public boolean isResource() { + return this.resource; + } + + /** + * Sets the resource bundle name. + * @param bundle The new bundle name. + * @since Validator 1.1 + */ + public void setBundle(String bundle) { + this.bundle = bundle; + } + + /** + * Sets the key/value. + * @param key They to access the argument. + */ + public void setKey(String key) { + this.key = key; + } + + /** + * Sets the name of the dependency. + * @param name the name of the dependency. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Set this argument's replacement position. + * @param position set this argument's replacement position. + */ + public void setPosition(int position) { + this.position = position; + } + + /** + * Sets whether or not the key is a resource. + * @param resource If true indicates the key is a resource. + */ + public void setResource(boolean resource) { + this.resource = resource; + } + + /** + * Returns a string representation of the object. + * @return a string representation of the object. + */ + @Override + public String toString() { + StringBuilder results = new StringBuilder(); + + results.append("Arg: name="); + results.append(name); + results.append(" key="); + results.append(key); + results.append(" position="); + results.append(position); + results.append(" bundle="); + results.append(bundle); + results.append(" resource="); + results.append(resource); + results.append("\n"); + + return results.toString(); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/CreditCardValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/CreditCardValidator.java new file mode 100644 index 000000000..29a700e93 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/CreditCardValidator.java @@ -0,0 +1,260 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.validator.util.Flags; + +/** + * Perform credit card validations. + * + *

+ * By default, all supported card types are allowed. You can specify which + * cards should pass validation by configuring the validation options. For + * example, + *

+ * + *
+ * CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);
+ * 
+ * + *

+ * configures the validator to only pass American Express and Visa cards. + * If a card type is not directly supported by this class, you can implement + * the CreditCardType interface and pass an instance into the + * addAllowedCardType method. + *

+ * + *

+ * For a similar implementation in Perl, reference Sean M. Burke's + * script. + * More information is also available + * here. + *

+ * + * @version $Revision$ + * @since Validator 1.1 + * @deprecated Use the new CreditCardValidator in the routines package. This class + * will be removed in a future release. + */ +// CHECKSTYLE:OFF (deprecated code) +@Deprecated +public class CreditCardValidator { + + /** + * Option specifying that no cards are allowed. This is useful if + * you want only custom card types to validate so you turn off the + * default cards with this option. + *
+     * 
+     * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
+     * v.addAllowedCardType(customType);
+     * v.isValid(aCardNumber);
+     * 
+     * 
+ * @since Validator 1.1.2 + */ + public static final int NONE = 0; + + /** + * Option specifying that American Express cards are allowed. + */ + public static final int AMEX = 1 << 0; + + /** + * Option specifying that Visa cards are allowed. + */ + public static final int VISA = 1 << 1; + + /** + * Option specifying that Mastercard cards are allowed. + */ + public static final int MASTERCARD = 1 << 2; + + /** + * Option specifying that Discover cards are allowed. + */ + public static final int DISCOVER = 1 << 3; + + /** + * The CreditCardTypes that are allowed to pass validation. + */ + private final Collection cardTypes = new ArrayList(); + + /** + * Create a new CreditCardValidator with default options. + */ + public CreditCardValidator() { + this(AMEX + VISA + MASTERCARD + DISCOVER); + } + + /** + * Creates a new CreditCardValidator with the specified options. + * @param options Pass in + * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that + * those are the only valid card types. + */ + public CreditCardValidator(int options) { + super(); + + Flags f = new Flags(options); + if (f.isOn(VISA)) { + this.cardTypes.add(new Visa()); + } + + if (f.isOn(AMEX)) { + this.cardTypes.add(new Amex()); + } + + if (f.isOn(MASTERCARD)) { + this.cardTypes.add(new Mastercard()); + } + + if (f.isOn(DISCOVER)) { + this.cardTypes.add(new Discover()); + } + } + + /** + * Checks if the field is a valid credit card number. + * @param card The card number to validate. + * @return Whether the card number is valid. + */ + public boolean isValid(String card) { + if ((card == null) || (card.length() < 13) || (card.length() > 19)) { + return false; + } + + if (!this.luhnCheck(card)) { + return false; + } + + for (Object cardType : this.cardTypes) { + CreditCardType type = (CreditCardType) cardType; + if (type.matches(card)) { + return true; + } + } + + return false; + } + + /** + * Adds an allowed CreditCardType that participates in the card + * validation algorithm. + * @param type The type that is now allowed to pass validation. + * @since Validator 1.1.2 + */ + public void addAllowedCardType(CreditCardType type){ + this.cardTypes.add(type); + } + + /** + * Checks for a valid credit card number. + * @param cardNumber Credit Card Number. + * @return Whether the card number passes the luhnCheck. + */ + protected boolean luhnCheck(String cardNumber) { + // number must be validated as 0..9 numeric first!! + int digits = cardNumber.length(); + int oddOrEven = digits & 1; + long sum = 0; + for (int count = 0; count < digits; count++) { + int digit = 0; + try { + digit = Integer.parseInt(cardNumber.charAt(count) + ""); + } catch(NumberFormatException e) { + return false; + } + + if (((count & 1) ^ oddOrEven) == 0) { // not + digit *= 2; + if (digit > 9) { + digit -= 9; + } + } + sum += digit; + } + + return (sum == 0) ? false : (sum % 10 == 0); + } + + /** + * CreditCardType implementations define how validation is performed + * for one type/brand of credit card. + * @since Validator 1.1.2 + */ + public interface CreditCardType { + + /** + * Returns true if the card number matches this type of credit + * card. Note that this method is not responsible + * for analyzing the general form of the card number because + * CreditCardValidator performs those checks before + * calling this method. It is generally only required to valid the + * length and prefix of the number to determine if it's the correct + * type. + * @param card The card number, never null. + * @return true if the number matches. + */ + boolean matches(String card); + + } + + /** + * Change to support Visa Carte Blue used in France + * has been removed - see Bug 35926 + */ + private static class Visa implements CreditCardType { + private static final String PREFIX = "4"; + @Override + public boolean matches(String card) { + return ( + card.substring(0, 1).equals(PREFIX) + && (card.length() == 13 || card.length() == 16)); + } + } + + private static class Amex implements CreditCardType { + private static final String PREFIX = "34,37,"; + @Override + public boolean matches(String card) { + String prefix2 = card.substring(0, 2) + ","; + return ((PREFIX.contains(prefix2)) && (card.length() == 15)); + } + } + + private static class Discover implements CreditCardType { + private static final String PREFIX = "6011"; + @Override + public boolean matches(String card) { + return (card.substring(0, 4).equals(PREFIX) && (card.length() == 16)); + } + } + + private static class Mastercard implements CreditCardType { + private static final String PREFIX = "51,52,53,54,55,"; + @Override + public boolean matches(String card) { + String prefix2 = card.substring(0, 2) + ","; + return ((PREFIX.contains(prefix2)) && (card.length() == 16)); + } + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/DateValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/DateValidator.java new file mode 100644 index 000000000..aee5be048 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/DateValidator.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; + +/** + *

Perform date validations.

+ *

+ * This class is a Singleton; you can retrieve the instance via the + * getInstance() method. + *

+ * + * @version $Revision$ + * @since Validator 1.1 + * @deprecated Use the new DateValidator, CalendarValidator or TimeValidator in the + * routines package. This class will be removed in a future release. + */ +@Deprecated +public class DateValidator { + + /** + * Singleton instance of this class. + */ + private static final DateValidator DATE_VALIDATOR = new DateValidator(); + + /** + * Returns the Singleton instance of this validator. + * @return A singleton instance of the DateValidator. + */ + public static DateValidator getInstance() { + return DATE_VALIDATOR; + } + + /** + * Protected constructor for subclasses to use. + */ + protected DateValidator() { + super(); + } + + /** + *

Checks if the field is a valid date. The pattern is used with + * java.text.SimpleDateFormat. If strict is true, then the + * length will be checked so '2/12/1999' will not pass validation with + * the format 'MM/dd/yyyy' because the month isn't two digits. + * The setLenient method is set to false for all.

+ * + * @param value The value validation is being performed on. + * @param datePattern The pattern passed to SimpleDateFormat. + * @param strict Whether or not to have an exact match of the datePattern. + * @return true if the date is valid. + */ + public boolean isValid(String value, String datePattern, boolean strict) { + + if (value == null + || datePattern == null + || datePattern.length() <= 0) { + + return false; + } + + SimpleDateFormat formatter = new SimpleDateFormat(datePattern); + formatter.setLenient(false); + + try { + formatter.parse(value); + } catch(ParseException e) { + return false; + } + + if (strict && (datePattern.length() != value.length())) { + return false; + } + + return true; + } + + /** + *

Checks if the field is a valid date. The Locale is + * used with java.text.DateFormat. The setLenient method + * is set to false for all.

+ * + * @param value The value validation is being performed on. + * @param locale The locale to use for the date format, defaults to the default + * system default if null. + * @return true if the date is valid. + */ + public boolean isValid(String value, Locale locale) { + + if (value == null) { + return false; + } + + DateFormat formatter = null; + if (locale != null) { + formatter = DateFormat.getDateInstance(DateFormat.SHORT, locale); + } else { + formatter = + DateFormat.getDateInstance( + DateFormat.SHORT, + Locale.getDefault()); + } + + formatter.setLenient(false); + + try { + formatter.parse(value); + } catch(ParseException e) { + return false; + } + + return true; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/EmailValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/EmailValidator.java new file mode 100644 index 000000000..f82a4c341 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/EmailValidator.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import org.apache.commons.validator.routines.InetAddressValidator; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + *

Perform email validations.

+ *

+ * This class is a Singleton; you can retrieve the instance via the getInstance() method. + *

+ *

+ * Based on a script by Sandeep V. Tamhankar + * http://javascript.internet.com + *

+ *

+ * This implementation is not guaranteed to catch all possible errors in an email address. + * For example, an address like nobody@noplace.somedog will pass validator, even though there + * is no TLD "somedog" + *

. + * + * @version $Revision$ + * @since Validator 1.1 + * @deprecated Use the new EmailValidator in the routines package. This class + * will be removed in a future release. + */ +@Deprecated +public class EmailValidator { + + private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; + private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]"; + private static final String QUOTED_USER = "(\"[^\"]*\")"; + private static final String ATOM = VALID_CHARS + '+'; + private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; + +// NOT USED private static final Pattern LEGAL_ASCII_PATTERN = Pattern.compile("^\\p{ASCII}+$"); +// NOT USED private static final Pattern EMAIL_PATTERN = Pattern.compile("^(.+)@(.+)$"); + private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile("^\\[(.*)\\]$"); + private static final Pattern TLD_PATTERN = Pattern.compile("^([a-zA-Z]+)$"); + + private static final Pattern USER_PATTERN = Pattern.compile("^\\s*" + WORD + "(\\." + WORD + ")*$"); + private static final Pattern DOMAIN_PATTERN = Pattern.compile("^" + ATOM + "(\\." + ATOM + ")*\\s*$"); + private static final Pattern ATOM_PATTERN = Pattern.compile("(" + ATOM + ")"); + + /** + * Singleton instance of this class. + */ + private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(); + + /** + * Returns the Singleton instance of this validator. + * @return singleton instance of this validator. + */ + public static EmailValidator getInstance() { + return EMAIL_VALIDATOR; + } + + /** + * Protected constructor for subclasses to use. + */ + protected EmailValidator() { + super(); + } + + /** + *

Checks if a field has a valid e-mail address.

+ * + * @param email The value validation is being performed on. A null + * value is considered invalid. + * @return true if the email address is valid. + */ + public boolean isValid(String email) { + return org.apache.commons.validator.routines.EmailValidator.getInstance().isValid(email); + } + + /** + * Returns true if the domain component of an email address is valid. + * @param domain being validated. + * @return true if the email address's domain is valid. + */ + protected boolean isValidDomain(String domain) { + boolean symbolic = false; + + // see if domain is an IP address in brackets + Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); + + if (ipDomainMatcher.matches()) { + InetAddressValidator inetAddressValidator = + InetAddressValidator.getInstance(); + if (inetAddressValidator.isValid(ipDomainMatcher.group(1))) { + return true; + } + } else { + // Domain is symbolic name + symbolic = DOMAIN_PATTERN.matcher(domain).matches(); + } + + if (symbolic) { + if (!isValidSymbolicDomain(domain)) { + return false; + } + } else { + return false; + } + + return true; + } + + /** + * Returns true if the user component of an email address is valid. + * @param user being validated + * @return true if the user name is valid. + */ + protected boolean isValidUser(String user) { + return USER_PATTERN.matcher(user).matches(); + } + + /** + * Validates an IP address. Returns true if valid. + * @param ipAddress IP address + * @return true if the ip address is valid. + */ + protected boolean isValidIpAddress(String ipAddress) { + Matcher ipAddressMatcher = IP_DOMAIN_PATTERN.matcher(ipAddress); + for (int i = 1; i <= 4; i++) { // CHECKSTYLE IGNORE MagicNumber + String ipSegment = ipAddressMatcher.group(i); + if (ipSegment == null || ipSegment.length() <= 0) { + return false; + } + + int iIpSegment = 0; + + try { + iIpSegment = Integer.parseInt(ipSegment); + } catch(NumberFormatException e) { + return false; + } + + if (iIpSegment > 255) { // CHECKSTYLE IGNORE MagicNumber + return false; + } + + } + return true; + } + + /** + * Validates a symbolic domain name. Returns true if it's valid. + * @param domain symbolic domain name + * @return true if the symbolic domain name is valid. + */ + protected boolean isValidSymbolicDomain(String domain) { + String[] domainSegment = new String[10]; // CHECKSTYLE IGNORE MagicNumber + boolean match = true; + int i = 0; + Matcher atomMatcher = ATOM_PATTERN.matcher(domain); + while (match) { + match = atomMatcher.matches(); + if (match) { + domainSegment[i] = atomMatcher.group(1); + int l = domainSegment[i].length() + 1; + domain = + (l >= domain.length()) + ? "" + : domain.substring(l); + + i++; + } + } + + int len = i; + + // Make sure there's a host name preceding the domain. + if (len < 2) { + return false; + } + + String tld = domainSegment[len - 1]; + if (tld.length() > 1) { + if (! TLD_PATTERN.matcher(tld).matches()) { + return false; + } + } else { + return false; + } + + return true; + } + /** + * Recursively remove comments, and replace with a single space. The simpler + * regexps in the Email Addressing FAQ are imperfect - they will miss escaped + * chars in atoms, for example. + * Derived From Mail::RFC822::Address + * @param emailStr The email address + * @return address with comments removed. + */ + protected String stripComments(String emailStr) { + String result = emailStr; + String commentPat = "^((?:[^\"\\\\]|\\\\.)*(?:\"(?:[^\"\\\\]|\\\\.)*\"(?:[^\"\\\\]|\111111\\\\.)*)*)\\((?:[^()\\\\]|\\\\.)*\\)/"; + Pattern commentMatcher = Pattern.compile(commentPat); + + while (commentMatcher.matcher(result).matches()) { + result = result.replaceFirst(commentPat, "\1 "); + } + return result; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Field.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Field.java new file mode 100644 index 000000000..6d1c575ae --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Field.java @@ -0,0 +1,958 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.StringTokenizer; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.collections.FastHashMap; // DEPRECATED +import org.apache.commons.validator.util.ValidatorUtils; + +/** + * This contains the list of pluggable validators to run on a field and any + * message information and variables to perform the validations and generate + * error messages. Instances of this class are configured with a + * <field> xml element. + *

+ * The use of FastHashMap is deprecated and will be replaced in a future + * release. + *

+ * + * @version $Revision$ + * @see org.apache.commons.validator.Form + */ +// TODO mutable non-private fields +public class Field implements Cloneable, Serializable { + + private static final long serialVersionUID = -8502647722530192185L; + + /** + * This is the value that will be used as a key if the Arg + * name field has no value. + */ + private static final String DEFAULT_ARG = + "org.apache.commons.validator.Field.DEFAULT"; + + /** + * This indicates an indexed property is being referenced. + */ + public static final String TOKEN_INDEXED = "[]"; + + /** + * The start of a token. + */ + protected static final String TOKEN_START = "${"; + + /** + * The end of a token. + */ + protected static final String TOKEN_END = "}"; + + /** + * A Vriable token. + */ + protected static final String TOKEN_VAR = "var:"; + + /** + * The Field's property name. + */ + protected String property = null; + + /** + * The Field's indexed property name. + */ + protected String indexedProperty = null; + + /** + * The Field's indexed list property name. + */ + protected String indexedListProperty = null; + + /** + * The Field's unique key. + */ + protected String key = null; + + /** + * A comma separated list of validator's this field depends on. + */ + protected String depends = null; + + /** + * The Page Number + */ + protected int page = 0; + + /** + * The flag that indicates whether scripting should be generated + * by the client for client-side validation. + * @since Validator 1.4 + */ + protected boolean clientValidation = true; + + /** + * The order of the Field in the Form. + */ + protected int fieldOrder = 0; + + /** + * Internal representation of this.depends String as a List. This List + * gets updated whenever setDepends() gets called. This List is + * synchronized so a call to setDepends() (which clears the List) won't + * interfere with a call to isDependency(). + */ + private final List dependencyList = Collections.synchronizedList(new ArrayList()); + + /** + * @deprecated Subclasses should use getVarMap() instead. + */ + @Deprecated + protected FastHashMap hVars = new FastHashMap(); // + + /** + * @deprecated Subclasses should use getMsgMap() instead. + */ + @Deprecated + protected FastHashMap hMsgs = new FastHashMap(); // + + /** + * Holds Maps of arguments. args[0] returns the Map for the first + * replacement argument. Start with a 0 length array so that it will + * only grow to the size of the highest argument position. + * @since Validator 1.1 + */ + @SuppressWarnings("unchecked") // cannot instantiate generic array, so have to assume this is OK + protected Map[] args = new Map[0]; + + /** + * Gets the page value that the Field is associated with for + * validation. + * @return The page number. + */ + public int getPage() { + return this.page; + } + + /** + * Sets the page value that the Field is associated with for + * validation. + * @param page The page number. + */ + public void setPage(int page) { + this.page = page; + } + + /** + * Gets the position of the Field in the validation list. + * @return The field position. + */ + public int getFieldOrder() { + return this.fieldOrder; + } + + /** + * Sets the position of the Field in the validation list. + * @param fieldOrder The field position. + */ + public void setFieldOrder(int fieldOrder) { + this.fieldOrder = fieldOrder; + } + + /** + * Gets the property name of the field. + * @return The field's property name. + */ + public String getProperty() { + return this.property; + } + + /** + * Sets the property name of the field. + * @param property The field's property name. + */ + public void setProperty(String property) { + this.property = property; + } + + /** + * Gets the indexed property name of the field. This + * is the method name that can take an int as + * a parameter for indexed property value retrieval. + * @return The field's indexed property name. + */ + public String getIndexedProperty() { + return this.indexedProperty; + } + + /** + * Sets the indexed property name of the field. + * @param indexedProperty The field's indexed property name. + */ + public void setIndexedProperty(String indexedProperty) { + this.indexedProperty = indexedProperty; + } + + /** + * Gets the indexed property name of the field. This + * is the method name that will return an array or a + * Collection used to retrieve the + * list and then loop through the list performing the specified + * validations. + * @return The field's indexed List property name. + */ + public String getIndexedListProperty() { + return this.indexedListProperty; + } + + /** + * Sets the indexed property name of the field. + * @param indexedListProperty The field's indexed List property name. + */ + public void setIndexedListProperty(String indexedListProperty) { + this.indexedListProperty = indexedListProperty; + } + + /** + * Gets the validation rules for this field as a comma separated list. + * @return A comma separated list of validator names. + */ + public String getDepends() { + return this.depends; + } + + /** + * Sets the validation rules for this field as a comma separated list. + * @param depends A comma separated list of validator names. + */ + public void setDepends(String depends) { + this.depends = depends; + + this.dependencyList.clear(); + + StringTokenizer st = new StringTokenizer(depends, ","); + while (st.hasMoreTokens()) { + String depend = st.nextToken().trim(); + + if (depend != null && depend.length() > 0) { + this.dependencyList.add(depend); + } + } + } + + /** + * Add a Msg to the Field. + * @param msg A validation message. + */ + public void addMsg(Msg msg) { + getMsgMap().put(msg.getName(), msg); + } + + /** + * Retrieve a message value. + * @param key Validation key. + * @return A validation message for a specified validator. + */ + public String getMsg(String key) { + Msg msg = getMessage(key); + return (msg == null) ? null : msg.getKey(); + } + + /** + * Retrieve a message object. + * @since Validator 1.1.4 + * @param key Validation key. + * @return A validation message for a specified validator. + */ + public Msg getMessage(String key) { + return getMsgMap().get(key); + } + + /** + * The Field's messages are returned as an + * unmodifiable Map. + * @since Validator 1.1.4 + * @return Map of validation messages for the field. + */ + public Map getMessages() { + return Collections.unmodifiableMap(getMsgMap()); + } + + /** + * Determines whether client-side scripting should be generated + * for this field. The default is true + * @return true for scripting; otherwise false + * @see #setClientValidation(boolean) + * @since Validator 1.4 + */ + public boolean isClientValidation() { + return this.clientValidation; + } + + /** + * Sets the flag that determines whether client-side scripting should + * be generated for this field. + * @param clientValidation the scripting flag + * @see #isClientValidation() + * @since Validator 1.4 + */ + public void setClientValidation(boolean clientValidation) { + this.clientValidation = clientValidation; + } + + /** + * Add an Arg to the replacement argument list. + * @since Validator 1.1 + * @param arg Validation message's argument. + */ + public void addArg(Arg arg) { + // TODO this first if check can go away after arg0, etc. are removed from dtd + if (arg == null || arg.getKey() == null || arg.getKey().length() == 0) { + return; + } + + determineArgPosition(arg); + ensureArgsCapacity(arg); + + Map argMap = this.args[arg.getPosition()]; + if (argMap == null) { + argMap = new HashMap<>(); + this.args[arg.getPosition()] = argMap; + } + + if (arg.getName() == null) { + argMap.put(DEFAULT_ARG, arg); + } else { + argMap.put(arg.getName(), arg); + } + + } + + /** + * Calculate the position of the Arg + */ + private void determineArgPosition(Arg arg) { + + int position = arg.getPosition(); + + // position has been explicity set + if (position >= 0) { + return; + } + + // first arg to be added + if (args == null || args.length == 0) { + arg.setPosition(0); + return; + } + + // determine the position of the last argument with + // the same name or the last default argument + String keyName = arg.getName() == null ? DEFAULT_ARG : arg.getName(); + int lastPosition = -1; + int lastDefault = -1; + for (int i = 0; i < args.length; i++) { + if (args[i] != null && args[i].containsKey(keyName)) { + lastPosition = i; + } + if (args[i] != null && args[i].containsKey(DEFAULT_ARG)) { + lastDefault = i; + } + } + + if (lastPosition < 0) { + lastPosition = lastDefault; + } + + // allocate the next position + arg.setPosition(++lastPosition); + + } + + /** + * Ensures that the args array can hold the given arg. Resizes the array as + * necessary. + * @param arg Determine if the args array is long enough to store this arg's + * position. + */ + private void ensureArgsCapacity(Arg arg) { + if (arg.getPosition() >= this.args.length) { + @SuppressWarnings("unchecked") // cannot check this at compile time, but it is OK + Map[] newArgs = new Map[arg.getPosition() + 1]; + System.arraycopy(this.args, 0, newArgs, 0, this.args.length); + this.args = newArgs; + } + } + + /** + * Gets the default Arg object at the given position. + * @param position Validation message argument's position. + * @return The default Arg or null if not found. + * @since Validator 1.1 + */ + public Arg getArg(int position) { + return this.getArg(DEFAULT_ARG, position); + } + + /** + * Gets the Arg object at the given position. If the key + * finds a null value then the default value will be + * retrieved. + * @param key The name the Arg is stored under. If not found, the default + * Arg for the given position (if any) will be retrieved. + * @param position The Arg number to find. + * @return The Arg with the given name and position or null if not found. + * @since Validator 1.1 + */ + public Arg getArg(String key, int position) { + if ((position >= this.args.length) || (this.args[position] == null)) { + return null; + } + + Arg arg = args[position].get(key); + + // Didn't find default arg so exit, otherwise we would get into + // infinite recursion + if ((arg == null) && key.equals(DEFAULT_ARG)) { + return null; + } + + return (arg == null) ? this.getArg(position) : arg; + } + + /** + * Retrieves the Args for the given validator name. + * @param key The validator's args to retrieve. + * @return An Arg[] sorted by the Args' positions (i.e. the Arg at index 0 + * has a position of 0). + * @since Validator 1.1.1 + */ + public Arg[] getArgs(String key){ + Arg[] argList = new Arg[this.args.length]; + + for (int i = 0; i < this.args.length; i++) { + argList[i] = this.getArg(key, i); + } + + return argList; + } + + /** + * Add a Var to the Field. + * @param v The Validator Argument. + */ + public void addVar(Var v) { + this.getVarMap().put(v.getName(), v); + } + + /** + * Add a Var, based on the values passed in, to the + * Field. + * @param name Name of the validation. + * @param value The Argument's value. + * @param jsType The Javascript type. + */ + public void addVar(String name, String value, String jsType) { + this.addVar(new Var(name, value, jsType)); + } + + /** + * Retrieve a variable. + * @param mainKey The Variable's key + * @return the Variable + */ + public Var getVar(String mainKey) { + return getVarMap().get(mainKey); + } + + /** + * Retrieve a variable's value. + * @param mainKey The Variable's key + * @return the Variable's value + */ + public String getVarValue(String mainKey) { + String value = null; + + Var v = getVarMap().get(mainKey); + if (v != null) { + value = v.getValue(); + } + + return value; + } + + /** + * The Field's variables are returned as an + * unmodifiable Map. + * @return the Map of Variable's for a Field. + */ + public Map getVars() { + return Collections.unmodifiableMap(getVarMap()); + } + + /** + * Gets a unique key based on the property and indexedProperty fields. + * @return a unique key for the field. + */ + public String getKey() { + if (this.key == null) { + this.generateKey(); + } + + return this.key; + } + + /** + * Sets a unique key for the field. This can be used to change + * the key temporarily to have a unique key for an indexed field. + * @param key a unique key for the field + */ + public void setKey(String key) { + this.key = key; + } + + /** + * If there is a value specified for the indexedProperty field then + * true will be returned. Otherwise it will be + * false. + * @return Whether the Field is indexed. + */ + public boolean isIndexed() { + return (indexedListProperty != null && indexedListProperty.length() > 0); + } + + /** + * Generate correct key value. + */ + public void generateKey() { + if (this.isIndexed()) { + this.key = this.indexedListProperty + TOKEN_INDEXED + "." + this.property; + } else { + this.key = this.property; + } + } + + /** + * Replace constants with values in fields and process the depends field + * to create the dependency Map. + */ + void process(Map globalConstants, Map constants) { + this.hMsgs.setFast(false); + this.hVars.setFast(true); + + this.generateKey(); + + // Process FormSet Constants + for (Iterator> i = constants.entrySet().iterator(); i.hasNext();) { + Entry entry = i.next(); + String key1 = entry.getKey(); + String key2 = TOKEN_START + key1 + TOKEN_END; + String replaceValue = entry.getValue(); + + property = ValidatorUtils.replace(property, key2, replaceValue); + + processVars(key2, replaceValue); + + this.processMessageComponents(key2, replaceValue); + } + + // Process Global Constants + for (Iterator> i = globalConstants.entrySet().iterator(); i.hasNext();) { + Entry entry = i.next(); + String key1 = entry.getKey(); + String key2 = TOKEN_START + key1 + TOKEN_END; + String replaceValue = entry.getValue(); + + property = ValidatorUtils.replace(property, key2, replaceValue); + + processVars(key2, replaceValue); + + this.processMessageComponents(key2, replaceValue); + } + + // Process Var Constant Replacement + for (Iterator i = getVarMap().keySet().iterator(); i.hasNext();) { + String key1 = i.next(); + String key2 = TOKEN_START + TOKEN_VAR + key1 + TOKEN_END; + Var var = this.getVar(key1); + String replaceValue = var.getValue(); + + this.processMessageComponents(key2, replaceValue); + } + + hMsgs.setFast(true); + } + + /** + * Replace the vars value with the key/value pairs passed in. + */ + private void processVars(String key, String replaceValue) { + Iterator i = getVarMap().keySet().iterator(); + while (i.hasNext()) { + String varKey = i.next(); + Var var = this.getVar(varKey); + + var.setValue(ValidatorUtils.replace(var.getValue(), key, replaceValue)); + } + + } + + /** + * Replace the args key value with the key/value pairs passed in. + */ + private void processMessageComponents(String key, String replaceValue) { + String varKey = TOKEN_START + TOKEN_VAR; + // Process Messages + if (key != null && !key.startsWith(varKey)) { + for (Iterator i = getMsgMap().values().iterator(); i.hasNext();) { + Msg msg = i.next(); + msg.setKey(ValidatorUtils.replace(msg.getKey(), key, replaceValue)); + } + } + + this.processArg(key, replaceValue); + } + + /** + * Replace the arg Collection key value with the key/value + * pairs passed in. + */ + private void processArg(String key, String replaceValue) { + for (int i = 0; i < this.args.length; i++) { + + Map argMap = this.args[i]; + if (argMap == null) { + continue; + } + + Iterator iter = argMap.values().iterator(); + while (iter.hasNext()) { + Arg arg = iter.next(); + + if (arg != null) { + arg.setKey( + ValidatorUtils.replace(arg.getKey(), key, replaceValue)); + } + } + } + } + + /** + * Checks if the validator is listed as a dependency. + * @param validatorName Name of the validator to check. + * @return Whether the field is dependant on a validator. + */ + public boolean isDependency(String validatorName) { + return this.dependencyList.contains(validatorName); + } + + /** + * Gets an unmodifiable List of the dependencies in the same + * order they were defined in parameter passed to the setDepends() method. + * @return A list of the Field's dependancies. + */ + public List getDependencyList() { + return Collections.unmodifiableList(this.dependencyList); + } + + /** + * Creates and returns a copy of this object. + * @return A copy of the Field. + */ + @Override + public Object clone() { + Field field = null; + try { + field = (Field) super.clone(); + } catch(CloneNotSupportedException e) { + throw new RuntimeException(e.toString()); + } + + @SuppressWarnings("unchecked") // empty array always OK; cannot check this at compile time + final Map[] tempMap = new Map[this.args.length]; + field.args = tempMap; + for (int i = 0; i < this.args.length; i++) { + if (this.args[i] == null) { + continue; + } + + Map argMap = new HashMap<>(this.args[i]); + Iterator> iter = argMap.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + String validatorName = entry.getKey(); + Arg arg = entry.getValue(); + argMap.put(validatorName, (Arg) arg.clone()); + } + field.args[i] = argMap; + } + + field.hVars = ValidatorUtils.copyFastHashMap(hVars); + field.hMsgs = ValidatorUtils.copyFastHashMap(hMsgs); + + return field; + } + + /** + * Returns a string representation of the object. + * @return A string representation of the object. + */ + @Override + public String toString() { + StringBuilder results = new StringBuilder(); + + results.append("\t\tkey = " + key + "\n"); + results.append("\t\tproperty = " + property + "\n"); + results.append("\t\tindexedProperty = " + indexedProperty + "\n"); + results.append("\t\tindexedListProperty = " + indexedListProperty + "\n"); + results.append("\t\tdepends = " + depends + "\n"); + results.append("\t\tpage = " + page + "\n"); + results.append("\t\tfieldOrder = " + fieldOrder + "\n"); + + if (hVars != null) { + results.append("\t\tVars:\n"); + for (Iterator i = getVarMap().keySet().iterator(); i.hasNext();) { + Object key1 = i.next(); + results.append("\t\t\t"); + results.append(key1); + results.append("="); + results.append(getVarMap().get(key1)); + results.append("\n"); + } + } + + return results.toString(); + } + + /** + * Returns an indexed property from the object we're validating. + * + * @param bean The bean to extract the indexed values from. + * @throws ValidatorException If there's an error looking up the property + * or, the property found is not indexed. + */ + Object[] getIndexedProperty(Object bean) throws ValidatorException { + Object indexProp = null; + + try { + indexProp = + PropertyUtils.getProperty(bean, this.getIndexedListProperty()); + + } catch(IllegalAccessException|InvocationTargetException|NoSuchMethodException e) { + throw new ValidatorException(e.getMessage()); + } + + if (indexProp instanceof Collection) { + return ((Collection) indexProp).toArray(); + + } else if (indexProp.getClass().isArray()) { + return (Object[]) indexProp; + + } else { + throw new ValidatorException(this.getKey() + " is not indexed"); + } + + } + /** + * Returns the size of an indexed property from the object we're validating. + * + * @param bean The bean to extract the indexed values from. + * @throws ValidatorException If there's an error looking up the property + * or, the property found is not indexed. + */ + private int getIndexedPropertySize(Object bean) throws ValidatorException { + Object indexProp = null; + + try { + indexProp = + PropertyUtils.getProperty(bean, this.getIndexedListProperty()); + + } catch(IllegalAccessException|InvocationTargetException|NoSuchMethodException e) { + throw new ValidatorException(e.getMessage()); + } + + if (indexProp == null) { + return 0; + } else if (indexProp instanceof Collection) { + return ((Collection)indexProp).size(); + } else if (indexProp.getClass().isArray()) { + return ((Object[])indexProp).length; + } else { + throw new ValidatorException(this.getKey() + " is not indexed"); + } + + } + + /** + * Executes the given ValidatorAction and all ValidatorActions that it + * depends on. + * @return true if the validation succeeded. + */ + private boolean validateForRule( + ValidatorAction va, + ValidatorResults results, + Map actions, + Map params, + int pos) + throws ValidatorException { + + ValidatorResult result = results.getValidatorResult(this.getKey()); + if (result != null && result.containsAction(va.getName())) { + return result.isValid(va.getName()); + } + + if (!this.runDependentValidators(va, results, actions, params, pos)) { + return false; + } + + return va.executeValidationMethod(this, params, results, pos); + } + + /** + * Calls all of the validators that this validator depends on. + * TODO ValidatorAction should know how to run its own dependencies. + * @param va Run dependent validators for this action. + * @param results + * @param actions + * @param pos + * @return true if all of the dependent validations passed. + * @throws ValidatorException If there's an error running a validator + */ + private boolean runDependentValidators( + ValidatorAction va, + ValidatorResults results, + Map actions, + Map params, + int pos) + throws ValidatorException { + + List dependentValidators = va.getDependencyList(); + + if (dependentValidators.isEmpty()) { + return true; + } + + Iterator iter = dependentValidators.iterator(); + while (iter.hasNext()) { + String depend = iter.next(); + + ValidatorAction action = actions.get(depend); + if (action == null) { + this.handleMissingAction(depend); + } + + if (!this.validateForRule(action, results, actions, params, pos)) { + return false; + } + } + + return true; + } + + /** + * Run the configured validations on this field. Run all validations + * in the depends clause over each item in turn, returning when the first + * one fails. + * @param params A Map of parameter class names to parameter values to pass + * into validation methods. + * @param actions A Map of validator names to ValidatorAction objects. + * @return A ValidatorResults object containing validation messages for + * this field. + * @throws ValidatorException If an error occurs during validation. + */ + public ValidatorResults validate(Map params, Map actions) + throws ValidatorException { + + if (this.getDepends() == null) { + return new ValidatorResults(); + } + + ValidatorResults allResults = new ValidatorResults(); + + Object bean = params.get(Validator.BEAN_PARAM); + int numberOfFieldsToValidate = + this.isIndexed() ? this.getIndexedPropertySize(bean) : 1; + + for (int fieldNumber = 0; fieldNumber < numberOfFieldsToValidate; fieldNumber++) { + + ValidatorResults results = new ValidatorResults(); + synchronized(dependencyList) { + Iterator dependencies = this.dependencyList.iterator(); + while (dependencies.hasNext()) { + String depend = dependencies.next(); + + ValidatorAction action = actions.get(depend); + if (action == null) { + this.handleMissingAction(depend); + } + + boolean good = + validateForRule(action, results, actions, params, fieldNumber); + + if (!good) { + allResults.merge(results); + return allResults; + } + } + } + allResults.merge(results); + } + + return allResults; + } + + /** + * Called when a validator name is used in a depends clause but there is + * no know ValidatorAction configured for that name. + * @param name The name of the validator in the depends list. + * @throws ValidatorException + */ + private void handleMissingAction(String name) throws ValidatorException { + throw new ValidatorException("No ValidatorAction named " + name + + " found for field " + this.getProperty()); + } + + /** + * Returns a Map of String Msg names to Msg objects. + * @since Validator 1.2.0 + * @return A Map of the Field's messages. + */ + @SuppressWarnings("unchecked") // FastHashMap does not support generics + protected Map getMsgMap() { + return hMsgs; + } + + /** + * Returns a Map of String Var names to Var objects. + * @since Validator 1.2.0 + * @return A Map of the Field's variables. + */ + @SuppressWarnings("unchecked") // FastHashMap does not support generics + protected Map getVarMap() { + return hVars; + } +} + diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Form.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Form.java new file mode 100644 index 000000000..68488bbba --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Form.java @@ -0,0 +1,352 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.FastHashMap;// DEPRECATED + +/** + *

+ * + * This contains a set of validation rules for a form/JavaBean. The information + * is contained in a list of Field objects. Instances of this class + * are configured with a <form> xml element.

+ * + * The use of FastHashMap is deprecated and will be replaced in a future + * release.

+ * + * @version $Revision$ + */ +//TODO mutable non-private fields +public class Form implements Serializable { + + private static final long serialVersionUID = 6445211789563796371L; + + /** The name/key the set of validation rules is stored under. */ + protected String name = null; + + /** + * List of Fields. Used to maintain the order they were added + * in although individual Fields can be retrieved using Map + * of Fields. + */ + protected List lFields = new ArrayList(); + + /** + * Map of Fields keyed on their property value. + * + * @deprecated Subclasses should use getFieldMap() instead. + */ + @Deprecated + protected FastHashMap hFields = new FastHashMap(); // + + /** + * The name/key of the form which this form extends from. + * + * @since Validator 1.2.0 + */ + protected String inherit = null; + + /** + * Whether or not the this Form was processed for replacing + * variables in strings with their values. + */ + private boolean processed = false; + + /** + * Gets the name/key of the set of validation rules. + * + * @return The name value + */ + public String getName() { + return name; + } + + /** + * Sets the name/key of the set of validation rules. + * + * @param name The new name value + */ + public void setName(String name) { + this.name = name; + } + + /** + * Add a Field to the Form. + * + * @param f The field + */ + public void addField(Field f) { + this.lFields.add(f); + getFieldMap().put(f.getKey(), f); + } + + /** + * A List of Fields is returned as an unmodifiable + * List. + * + * @return The fields value + */ + public List getFields() { + return Collections.unmodifiableList(lFields); + } + + /** + * Returns the Field with the given name or null if this Form has no such + * field. + * + * @param fieldName The field name + * @return The field value + * @since Validator 1.1 + */ + public Field getField(String fieldName) { + return getFieldMap().get(fieldName); + } + + /** + * Returns true if this Form contains a Field with the given name. + * + * @param fieldName The field name + * @return True if this form contains the field by the given name + * @since Validator 1.1 + */ + public boolean containsField(String fieldName) { + return getFieldMap().containsKey(fieldName); + } + + /** + * Merges the given form into this one. For any field in depends + * not present in this form, include it. depends has precedence + * in the way the fields are ordered. + * + * @param depends the form we want to merge + * @since Validator 1.2.0 + */ + protected void merge(Form depends) { + + List templFields = new ArrayList(); + @SuppressWarnings("unchecked") // FastHashMap is not generic + Map temphFields = new FastHashMap(); + Iterator dependsIt = depends.getFields().iterator(); + while (dependsIt.hasNext()) { + Field defaultField = dependsIt.next(); + if (defaultField != null) { + String fieldKey = defaultField.getKey(); + if (!this.containsField(fieldKey)) { + templFields.add(defaultField); + temphFields.put(fieldKey, defaultField); + } + else { + Field old = getField(fieldKey); + getFieldMap().remove(fieldKey); + lFields.remove(old); + templFields.add(old); + temphFields.put(fieldKey, old); + } + } + } + lFields.addAll(0, templFields); + getFieldMap().putAll(temphFields); + } + + /** + * Processes all of the Form's Fields. + * + * @param globalConstants A map of global constants + * @param constants Local constants + * @param forms Map of forms + * @since Validator 1.2.0 + */ + protected void process(Map globalConstants, Map constants, Map forms) { + if (isProcessed()) { + return; + } + + int n = 0;//we want the fields from its parent first + if (isExtending()) { + Form parent = forms.get(inherit); + if (parent != null) { + if (!parent.isProcessed()) { + //we want to go all the way up the tree + parent.process(constants, globalConstants, forms); + } + for (Iterator i = parent.getFields().iterator(); i.hasNext(); ) { + Field f = i.next(); + //we want to be able to override any fields we like + if (getFieldMap().get(f.getKey()) == null) { + lFields.add(n, f); + getFieldMap().put(f.getKey(), f); + n++; + } + } + } + } + hFields.setFast(true); + //no need to reprocess parent's fields, we iterate from 'n' + for (Iterator i = lFields.listIterator(n); i.hasNext(); ) { + Field f = i.next(); + f.process(globalConstants, constants); + } + + processed = true; + } + + /** + * Returns a string representation of the object. + * + * @return string representation + */ + @Override + public String toString() { + StringBuilder results = new StringBuilder(); + + results.append("Form: "); + results.append(name); + results.append("\n"); + + for (Iterator i = lFields.iterator(); i.hasNext(); ) { + results.append("\tField: \n"); + results.append(i.next()); + results.append("\n"); + } + + return results.toString(); + } + + /** + * Validate all Fields in this Form on the given page and below. + * + * @param params A Map of parameter class names to parameter + * values to pass into validation methods. + * @param actions A Map of validator names to ValidatorAction + * objects. + * @param page Fields on pages higher than this will not be + * validated. + * @return A ValidatorResults object containing all + * validation messages. + * @throws ValidatorException + */ + ValidatorResults validate(Map params, Map actions, int page) + throws ValidatorException { + return validate(params, actions, page, null); + } + + /** + * Validate all Fields in this Form on the given page and below. + * + * @param params A Map of parameter class names to parameter + * values to pass into validation methods. + * @param actions A Map of validator names to ValidatorAction + * objects. + * @param page Fields on pages higher than this will not be + * validated. + * @return A ValidatorResults object containing all + * validation messages. + * @throws ValidatorException + * @since 1.2.0 + */ + ValidatorResults validate(Map params, Map actions, int page, String fieldName) + throws ValidatorException { + ValidatorResults results = new ValidatorResults(); + params.put(Validator.VALIDATOR_RESULTS_PARAM, results); + + // Only validate a single field if specified + if (fieldName != null) { + Field field = getFieldMap().get(fieldName); + + if (field == null) { + throw new ValidatorException("Unknown field "+fieldName+" in form "+getName()); + } + params.put(Validator.FIELD_PARAM, field); + + if (field.getPage() <= page) { + results.merge(field.validate(params, actions)); + } + } else { + Iterator fields = this.lFields.iterator(); + while (fields.hasNext()) { + Field field = fields.next(); + + params.put(Validator.FIELD_PARAM, field); + + if (field.getPage() <= page) { + results.merge(field.validate(params, actions)); + } + } + } + + return results; + } + + /** + * Whether or not the this Form was processed for replacing + * variables in strings with their values. + * + * @return The processed value + * @since Validator 1.2.0 + */ + public boolean isProcessed() { + return processed; + } + + /** + * Gets the name/key of the parent set of validation rules. + * + * @return The extends value + * @since Validator 1.2.0 + */ + public String getExtends() { + return inherit; + } + + /** + * Sets the name/key of the parent set of validation rules. + * + * @param inherit The new extends value + * @since Validator 1.2.0 + */ + public void setExtends(String inherit) { + this.inherit = inherit; + } + + /** + * Get extends flag. + * + * @return The extending value + * @since Validator 1.2.0 + */ + public boolean isExtending() { + return inherit != null; + } + + /** + * Returns a Map of String field keys to Field objects. + * + * @return The fieldMap value + * @since Validator 1.2.0 + */ + @SuppressWarnings("unchecked") // FastHashMap is not generic + protected Map getFieldMap() { + return hFields; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/FormSet.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/FormSet.java new file mode 100644 index 000000000..5767c529d --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/FormSet.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.validator; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Holds a set of Forms stored associated with a Locale + * based on the country, language, and variant specified. Instances of this + * class are configured with a <formset> xml element. + * + * @version $Revision$ + */ +public class FormSet implements Serializable { + + private static final long serialVersionUID = -8936513232763306055L; + + /** Logging */ + private transient Log log = LogFactory.getLog(FormSet.class); + + /** + * Whether or not the this FormSet was processed for replacing + * variables in strings with their values. + */ + private boolean processed = false; + + /** Language component of Locale (required). */ + private String language = null; + + /** Country component of Locale (optional). */ + private String country = null; + + /** Variant component of Locale (optional). */ + private String variant = null; + + /** + * A Map of Forms using the name field of the + * Form as the key. + */ + private final Map forms = new HashMap(); + + /** + * A Map of Constants using the name field of the + * Constant as the key. + */ + private final Map constants = new HashMap(); + + /** + * This is the type of FormSets where no locale is specified. + */ + protected final static int GLOBAL_FORMSET = 1; + + /** + * This is the type of FormSets where only language locale is + * specified. + */ + protected final static int LANGUAGE_FORMSET = 2; + + /** + * This is the type of FormSets where only language and country + * locale are specified. + */ + protected final static int COUNTRY_FORMSET = 3; + + /** + * This is the type of FormSets where full locale has been set. + */ + protected final static int VARIANT_FORMSET = 4; + + /** + * Flag indicating if this formSet has been merged with its parent (higher + * rank in Locale hierarchy). + */ + private boolean merged; + + /** + * Has this formSet been merged? + * + * @return true if it has been merged + * @since Validator 1.2.0 + */ + protected boolean isMerged() { + return merged; + } + + /** + * Returns the type of FormSet:GLOBAL_FORMSET, + * LANGUAGE_FORMSET,COUNTRY_FORMSET or VARIANT_FORMSET + * . + * + * @return The type value + * @since Validator 1.2.0 + * @throws NullPointerException if there is inconsistency in the locale + * definition (not sure about this) + */ + protected int getType() { + if (getVariant() != null) { + if (getLanguage() == null || getCountry() == null) { + throw new NullPointerException( + "When variant is specified, country and language must be specified."); + } + return VARIANT_FORMSET; + } + else if (getCountry() != null) { + if (getLanguage() == null) { + throw new NullPointerException( + "When country is specified, language must be specified."); + } + return COUNTRY_FORMSET; + } + else if (getLanguage() != null) { + return LANGUAGE_FORMSET; + } + else { + return GLOBAL_FORMSET; + } + } + + /** + * Merges the given FormSet into this one. If any of depends + * s Forms are not in this FormSet then, include + * them, else merge both Forms. Theoretically we should only + * merge a "parent" formSet. + * + * @param depends FormSet to be merged + * @since Validator 1.2.0 + */ + protected void merge(FormSet depends) { + if (depends != null) { + Map pForms = getForms(); + Map dForms = depends.getForms(); + for (Iterator> it = dForms.entrySet().iterator(); it.hasNext(); ) { + Entry entry = it.next(); + String key = entry.getKey(); + Form pForm = pForms.get(key); + if (pForm != null) {//merge, but principal 'rules', don't overwrite + // anything + pForm.merge(entry.getValue()); + } + else {//just add + addForm(entry.getValue()); + } + } + } + merged = true; + } + + /** + * Whether or not the this FormSet was processed for replacing + * variables in strings with their values. + * + * @return The processed value + */ + public boolean isProcessed() { + return processed; + } + + /** + * Gets the equivalent of the language component of Locale. + * + * @return The language value + */ + public String getLanguage() { + return language; + } + + /** + * Sets the equivalent of the language component of Locale. + * + * @param language The new language value + */ + public void setLanguage(String language) { + this.language = language; + } + + /** + * Gets the equivalent of the country component of Locale. + * + * @return The country value + */ + public String getCountry() { + return country; + } + + /** + * Sets the equivalent of the country component of Locale. + * + * @param country The new country value + */ + public void setCountry(String country) { + this.country = country; + } + + /** + * Gets the equivalent of the variant component of Locale. + * + * @return The variant value + */ + public String getVariant() { + return variant; + } + + /** + * Sets the equivalent of the variant component of Locale. + * + * @param variant The new variant value + */ + public void setVariant(String variant) { + this.variant = variant; + } + + /** + * Add a Constant to the locale level. + * + * @param name The constant name + * @param value The constant value + */ + public void addConstant(String name, String value) { + + if (constants.containsKey(name)) { + getLog().error("Constant '" + name + "' already exists in FormSet[" + + this.displayKey() + "] - ignoring."); + + } else { + constants.put(name, value); + } + + } + + /** + * Add a Form to the FormSet. + * + * @param f The form + */ + public void addForm(Form f) { + + String formName = f.getName(); + if (forms.containsKey(formName)) { + getLog().error("Form '" + formName + "' already exists in FormSet[" + + this.displayKey() + "] - ignoring."); + + } else { + forms.put(f.getName(), f); + } + + } + + /** + * Retrieve a Form based on the form name. + * + * @param formName The form name + * @return The form + */ + public Form getForm(String formName) { + return this.forms.get(formName); + } + + /** + * A Map of Forms is returned as an unmodifiable + * Map with the key based on the form name. + * + * @return The forms map + */ + public Map getForms() { + return Collections.unmodifiableMap(forms); + } + + /** + * Processes all of the Forms. + * + * @param globalConstants Global constants + */ + synchronized void process(Map globalConstants) { + for (Iterator
i = forms.values().iterator(); i.hasNext(); ) { + Form f = i.next(); + f.process(globalConstants, constants, forms); + } + + processed = true; + } + + /** + * Returns a string representation of the object's key. + * + * @return A string representation of the key + */ + public String displayKey() { + StringBuilder results = new StringBuilder(); + if (language != null && language.length() > 0) { + results.append("language="); + results.append(language); + } + if (country != null && country.length() > 0) { + if (results.length() > 0) { + results.append(", "); + } + results.append("country="); + results.append(country); + } + if (variant != null && variant.length() > 0) { + if (results.length() > 0) { + results.append(", "); + } + results.append("variant="); + results.append(variant ); + } + if (results.length() == 0) { + results.append("default"); + } + + return results.toString(); + } + + /** + * Returns a string representation of the object. + * + * @return A string representation + */ + @Override + public String toString() { + StringBuilder results = new StringBuilder(); + + results.append("FormSet: language="); + results.append(language); + results.append(" country="); + results.append(country); + results.append(" variant="); + results.append(variant); + results.append("\n"); + + for (Iterator i = getForms().values().iterator(); i.hasNext(); ) { + results.append(" "); + results.append(i.next()); + results.append("\n"); + } + + return results.toString(); + } + + /** + * Accessor method for Log instance. + * + * The Log instance variable is transient and + * accessing it through this method ensures it + * is re-initialized when this instance is + * de-serialized. + * + * @return The Log instance. + */ + private Log getLog() { + if (log == null) { + log = LogFactory.getLog(FormSet.class); + } + return log; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/FormSetFactory.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/FormSetFactory.java new file mode 100644 index 000000000..679ceeca9 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/FormSetFactory.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import org.xml.sax.Attributes; +import org.apache.commons.digester.AbstractObjectCreationFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Factory class used by Digester to create FormSet's. + * + * @version $Revision$ + * @since Validator 1.2 + */ +public class FormSetFactory extends AbstractObjectCreationFactory { + + /** Logging */ + private transient Log log = LogFactory.getLog(FormSetFactory.class); + + /** + *

Create or retrieve a FormSet for the specified + * attributes.

+ * + * @param attributes The sax attributes for the formset element. + * @return The FormSet for a locale. + * @throws Exception If an error occurs creating the FormSet. + */ + @Override + public Object createObject(Attributes attributes) throws Exception { + + ValidatorResources resources = (ValidatorResources)digester.peek(0); + + String language = attributes.getValue("language"); + String country = attributes.getValue("country"); + String variant = attributes.getValue("variant"); + + return createFormSet(resources, language, country, variant); + + } + + /** + *

Create or retrieve a FormSet based on the language, country + * and variant.

+ * + * @param resources The validator resources. + * @param language The locale's language. + * @param country The locale's country. + * @param variant The locale's language variant. + * @return The FormSet for a locale. + * @since Validator 1.2 + */ + private FormSet createFormSet(ValidatorResources resources, + String language, + String country, + String variant) throws Exception { + + // Retrieve existing FormSet for the language/country/variant + FormSet formSet = resources.getFormSet(language, country, variant); + if (formSet != null) { + if (getLog().isDebugEnabled()) { + getLog().debug("FormSet[" + formSet.displayKey() + "] found - merging."); + } + return formSet; + } + + // Create a new FormSet for the language/country/variant + formSet = new FormSet(); + formSet.setLanguage(language); + formSet.setCountry(country); + formSet.setVariant(variant); + + // Add the FormSet to the validator resources + resources.addFormSet(formSet); + + if (getLog().isDebugEnabled()) { + getLog().debug("FormSet[" + formSet.displayKey() + "] created."); + } + + return formSet; + + } + + /** + * Accessor method for Log instance. + * + * The Log instance variable is transient and + * accessing it through this method ensures it + * is re-initialized when this instance is + * de-serialized. + * + * @return The Log instance. + */ + private Log getLog() { + if (log == null) { + log = LogFactory.getLog(FormSetFactory.class); + } + return log; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/GenericTypeValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/GenericTypeValidator.java new file mode 100644 index 000000000..d3d72a97a --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/GenericTypeValidator.java @@ -0,0 +1,473 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This class contains basic methods for performing validations that return the + * correctly typed class based on the validation performed. + * + * @version $Revision$ + */ +public class GenericTypeValidator implements Serializable { + + private static final long serialVersionUID = 5487162314134261703L; + + private static final Log LOG = LogFactory.getLog(GenericTypeValidator.class); + + /** + * Checks if the value can safely be converted to a byte primitive. + * + * @param value The value validation is being performed on. + * @return the converted Byte value. + */ + public static Byte formatByte(String value) { + if (value == null) { + return null; + } + + try { + return Byte.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + + } + + /** + * Checks if the value can safely be converted to a byte primitive. + * + * @param value The value validation is being performed on. + * @param locale The locale to use to parse the number (system default if + * null) + * @return the converted Byte value. + */ + public static Byte formatByte(String value, Locale locale) { + Byte result = null; + + if (value != null) { + NumberFormat formatter = null; + if (locale != null) { + formatter = NumberFormat.getNumberInstance(locale); + } else { + formatter = NumberFormat.getNumberInstance(Locale.getDefault()); + } + formatter.setParseIntegerOnly(true); + ParsePosition pos = new ParsePosition(0); + Number num = formatter.parse(value, pos); + + // If there was no error and we used the whole string + if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && + num.doubleValue() >= Byte.MIN_VALUE && + num.doubleValue() <= Byte.MAX_VALUE) { + result = Byte.valueOf(num.byteValue()); + } + } + + return result; + } + + /** + * Checks if the value can safely be converted to a short primitive. + * + * @param value The value validation is being performed on. + * @return the converted Short value. + */ + public static Short formatShort(String value) { + if (value == null) { + return null; + } + + try { + return Short.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + + } + + /** + * Checks if the value can safely be converted to a short primitive. + * + * @param value The value validation is being performed on. + * @param locale The locale to use to parse the number (system default if + * null) + * @return the converted Short value. + */ + public static Short formatShort(String value, Locale locale) { + Short result = null; + + if (value != null) { + NumberFormat formatter = null; + if (locale != null) { + formatter = NumberFormat.getNumberInstance(locale); + } else { + formatter = NumberFormat.getNumberInstance(Locale.getDefault()); + } + formatter.setParseIntegerOnly(true); + ParsePosition pos = new ParsePosition(0); + Number num = formatter.parse(value, pos); + + // If there was no error and we used the whole string + if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && + num.doubleValue() >= Short.MIN_VALUE && + num.doubleValue() <= Short.MAX_VALUE) { + result = Short.valueOf(num.shortValue()); + } + } + + return result; + } + + /** + * Checks if the value can safely be converted to a int primitive. + * + * @param value The value validation is being performed on. + * @return the converted Integer value. + */ + public static Integer formatInt(String value) { + if (value == null) { + return null; + } + + try { + return Integer.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + + } + + /** + * Checks if the value can safely be converted to an int primitive. + * + * @param value The value validation is being performed on. + * @param locale The locale to use to parse the number (system default if + * null) + * @return the converted Integer value. + */ + public static Integer formatInt(String value, Locale locale) { + Integer result = null; + + if (value != null) { + NumberFormat formatter = null; + if (locale != null) { + formatter = NumberFormat.getNumberInstance(locale); + } else { + formatter = NumberFormat.getNumberInstance(Locale.getDefault()); + } + formatter.setParseIntegerOnly(true); + ParsePosition pos = new ParsePosition(0); + Number num = formatter.parse(value, pos); + + // If there was no error and we used the whole string + if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && + num.doubleValue() >= Integer.MIN_VALUE && + num.doubleValue() <= Integer.MAX_VALUE) { + result = Integer.valueOf(num.intValue()); + } + } + + return result; + } + + /** + * Checks if the value can safely be converted to a long primitive. + * + * @param value The value validation is being performed on. + * @return the converted Long value. + */ + public static Long formatLong(String value) { + if (value == null) { + return null; + } + + try { + return Long.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + + } + + /** + * Checks if the value can safely be converted to a long primitive. + * + * @param value The value validation is being performed on. + * @param locale The locale to use to parse the number (system default if + * null) + * @return the converted Long value. + */ + public static Long formatLong(String value, Locale locale) { + Long result = null; + + if (value != null) { + NumberFormat formatter = null; + if (locale != null) { + formatter = NumberFormat.getNumberInstance(locale); + } else { + formatter = NumberFormat.getNumberInstance(Locale.getDefault()); + } + formatter.setParseIntegerOnly(true); + ParsePosition pos = new ParsePosition(0); + Number num = formatter.parse(value, pos); + + // If there was no error and we used the whole string + if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && + num.doubleValue() >= Long.MIN_VALUE && + num.doubleValue() <= Long.MAX_VALUE) { + result = Long.valueOf(num.longValue()); + } + } + + return result; + } + + /** + * Checks if the value can safely be converted to a float primitive. + * + * @param value The value validation is being performed on. + * @return the converted Float value. + */ + public static Float formatFloat(String value) { + if (value == null) { + return null; + } + + try { + return Float.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + + } + + /** + * Checks if the value can safely be converted to a float primitive. + * + * @param value The value validation is being performed on. + * @param locale The locale to use to parse the number (system default if + * null) + * @return the converted Float value. + */ + public static Float formatFloat(String value, Locale locale) { + Float result = null; + + if (value != null) { + NumberFormat formatter = null; + if (locale != null) { + formatter = NumberFormat.getInstance(locale); + } else { + formatter = NumberFormat.getInstance(Locale.getDefault()); + } + ParsePosition pos = new ParsePosition(0); + Number num = formatter.parse(value, pos); + + // If there was no error and we used the whole string + if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && + num.doubleValue() >= (Float.MAX_VALUE * -1) && + num.doubleValue() <= Float.MAX_VALUE) { + result = Float.valueOf(num.floatValue()); + } + } + + return result; + } + + /** + * Checks if the value can safely be converted to a double primitive. + * + * @param value The value validation is being performed on. + * @return the converted Double value. + */ + public static Double formatDouble(String value) { + if (value == null) { + return null; + } + + try { + return Double.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + + } + + /** + * Checks if the value can safely be converted to a double primitive. + * + * @param value The value validation is being performed on. + * @param locale The locale to use to parse the number (system default if + * null) + * @return the converted Double value. + */ + public static Double formatDouble(String value, Locale locale) { + Double result = null; + + if (value != null) { + NumberFormat formatter = null; + if (locale != null) { + formatter = NumberFormat.getInstance(locale); + } else { + formatter = NumberFormat.getInstance(Locale.getDefault()); + } + ParsePosition pos = new ParsePosition(0); + Number num = formatter.parse(value, pos); + + // If there was no error and we used the whole string + if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && + num.doubleValue() >= (Double.MAX_VALUE * -1) && + num.doubleValue() <= Double.MAX_VALUE) { + result = Double.valueOf(num.doubleValue()); + } + } + + return result; + } + + /** + * Checks if the field is a valid date. + * + *

The {@code Locale} is used with {@code java.text.DateFormat}. The {@link java.text.DateFormat#setLenient(boolean)} + * method is set to {@code false} for all. + *

+ * + * @param value The value validation is being performed on. + * @param locale The Locale to use to parse the date (system default if null) + * @return the converted Date value. + */ + public static Date formatDate(String value, Locale locale) { + Date date = null; + + if (value == null) { + return null; + } + + try { + // Get the formatters to check against + DateFormat formatterShort = null; + DateFormat formatterDefault = null; + if (locale != null) { + formatterShort = + DateFormat.getDateInstance(DateFormat.SHORT, locale); + formatterDefault = + DateFormat.getDateInstance(DateFormat.DEFAULT, locale); + } else { + formatterShort = + DateFormat.getDateInstance( + DateFormat.SHORT, + Locale.getDefault()); + formatterDefault = + DateFormat.getDateInstance( + DateFormat.DEFAULT, + Locale.getDefault()); + } + + // Turn off lenient parsing + formatterShort.setLenient(false); + formatterDefault.setLenient(false); + + // Firstly, try with the short form + try { + date = formatterShort.parse(value); + } catch (ParseException e) { + // Fall back on the default one + date = formatterDefault.parse(value); + } + } catch (ParseException e) { + // Bad date, so LOG and return null + if (LOG.isDebugEnabled()) { + LOG.debug("Date parse failed value=[" + value + "], " + + "locale=[" + locale + "] " + e); + } + } + + return date; + } + + /** + * Checks if the field is a valid date. + * + *

The pattern is used with {@code java.text.SimpleDateFormat}. + * If strict is true, then the length will be checked so '2/12/1999' will + * not pass validation with the format 'MM/dd/yyyy' because the month isn't + * two digits. The {@link java.text.SimpleDateFormat#setLenient(boolean)} + * method is set to {@code false} for all. + *

+ * + * @param value The value validation is being performed on. + * @param datePattern The pattern passed to {@code SimpleDateFormat}. + * @param strict Whether or not to have an exact match of the + * datePattern. + * @return the converted Date value. + */ + public static Date formatDate(String value, String datePattern, boolean strict) { + Date date = null; + + if (value == null + || datePattern == null + || datePattern.length() == 0) { + return null; + } + + try { + SimpleDateFormat formatter = new SimpleDateFormat(datePattern); + formatter.setLenient(false); + + date = formatter.parse(value); + + if (strict && datePattern.length() != value.length()) { + date = null; + } + } catch (ParseException e) { + // Bad date so return null + if (LOG.isDebugEnabled()) { + LOG.debug("Date parse failed value=[" + value + "], " + + "pattern=[" + datePattern + "], " + + "strict=[" + strict + "] " + e); + } + } + + return date; + } + + /** + * Checks if the field is a valid credit card number. + * + *

Reference Sean M. Burke's + * script.

+ * + * @param value The value validation is being performed on. + * @return the converted Credit Card number. + */ + public static Long formatCreditCard(String value) { + return GenericValidator.isCreditCard(value) ? Long.valueOf(value) : null; + } + +} + diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/GenericValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/GenericValidator.java new file mode 100644 index 000000000..5251ef9b9 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/GenericValidator.java @@ -0,0 +1,435 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; +import java.util.Locale; +import java.util.regex.Pattern; + +import org.apache.commons.validator.routines.UrlValidator; +import org.apache.commons.validator.routines.CreditCardValidator; +import org.apache.commons.validator.routines.DateValidator; +import org.apache.commons.validator.routines.EmailValidator; + +/** + * This class contains basic methods for performing validations. + * + * @version $Revision$ + */ +public class GenericValidator implements Serializable { + + private static final long serialVersionUID = -7212095066891517618L; + + /** + * UrlValidator used in wrapper method. + */ + private static final UrlValidator URL_VALIDATOR = new UrlValidator(); + + /** + * CreditCardValidator used in wrapper method. + */ + private static final CreditCardValidator CREDIT_CARD_VALIDATOR = + new CreditCardValidator(); + + /** + *

Checks if the field isn't null and length of the field is greater + * than zero not including whitespace.

+ * + * @param value The value validation is being performed on. + * @return true if blank or null. + */ + public static boolean isBlankOrNull(String value) { + return ((value == null) || (value.trim().length() == 0)); + } + + /** + *

Checks if the value matches the regular expression.

+ * + * @param value The value validation is being performed on. + * @param regexp The regular expression. + * @return true if matches the regular expression. + */ + public static boolean matchRegexp(String value, String regexp) { + if (regexp == null || regexp.length() <= 0) { + return false; + } + + return Pattern.matches(regexp, value); + } + + /** + *

Checks if the value can safely be converted to a byte primitive.

+ * + * @param value The value validation is being performed on. + * @return true if the value can be converted to a Byte. + */ + public static boolean isByte(String value) { + return (GenericTypeValidator.formatByte(value) != null); + } + + /** + *

Checks if the value can safely be converted to a short primitive.

+ * + * @param value The value validation is being performed on. + * @return true if the value can be converted to a Short. + */ + public static boolean isShort(String value) { + return (GenericTypeValidator.formatShort(value) != null); + } + + /** + *

Checks if the value can safely be converted to a int primitive.

+ * + * @param value The value validation is being performed on. + * @return true if the value can be converted to an Integer. + */ + public static boolean isInt(String value) { + return (GenericTypeValidator.formatInt(value) != null); + } + + /** + *

Checks if the value can safely be converted to a long primitive.

+ * + * @param value The value validation is being performed on. + * @return true if the value can be converted to a Long. + */ + public static boolean isLong(String value) { + return (GenericTypeValidator.formatLong(value) != null); + } + + /** + *

Checks if the value can safely be converted to a float primitive.

+ * + * @param value The value validation is being performed on. + * @return true if the value can be converted to a Float. + */ + public static boolean isFloat(String value) { + return (GenericTypeValidator.formatFloat(value) != null); + } + + /** + *

Checks if the value can safely be converted to a double primitive.

+ * + * @param value The value validation is being performed on. + * @return true if the value can be converted to a Double. + */ + public static boolean isDouble(String value) { + return (GenericTypeValidator.formatDouble(value) != null); + } + + /** + *

Checks if the field is a valid date. The Locale is + * used with java.text.DateFormat. The setLenient method + * is set to false for all.

+ * + * @param value The value validation is being performed on. + * @param locale The locale to use for the date format, defaults to the + * system default if null. + * @return true if the value can be converted to a Date. + */ + public static boolean isDate(String value, Locale locale) { + return DateValidator.getInstance().isValid(value, locale); + } + + /** + *

Checks if the field is a valid date. The pattern is used with + * java.text.SimpleDateFormat. If strict is true, then the + * length will be checked so '2/12/1999' will not pass validation with + * the format 'MM/dd/yyyy' because the month isn't two digits. + * The setLenient method is set to false for all.

+ * + * @param value The value validation is being performed on. + * @param datePattern The pattern passed to SimpleDateFormat. + * @param strict Whether or not to have an exact match of the datePattern. + * @return true if the value can be converted to a Date. + */ + public static boolean isDate(String value, String datePattern, boolean strict) { + // TODO method isValid() not yet supported in routines version + return org.apache.commons.validator.DateValidator.getInstance().isValid(value, datePattern, strict); + } + + /** + *

Checks if a value is within a range (min & max specified + * in the vars attribute).

+ * + * @param value The value validation is being performed on. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is in the specified range. + */ + public static boolean isInRange(byte value, byte min, byte max) { + return ((value >= min) && (value <= max)); + } + + /** + *

Checks if a value is within a range (min & max specified + * in the vars attribute).

+ * + * @param value The value validation is being performed on. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is in the specified range. + */ + public static boolean isInRange(int value, int min, int max) { + return ((value >= min) && (value <= max)); + } + + /** + *

Checks if a value is within a range (min & max specified + * in the vars attribute).

+ * + * @param value The value validation is being performed on. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is in the specified range. + */ + public static boolean isInRange(float value, float min, float max) { + return ((value >= min) && (value <= max)); + } + + /** + *

Checks if a value is within a range (min & max specified + * in the vars attribute).

+ * + * @param value The value validation is being performed on. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is in the specified range. + */ + public static boolean isInRange(short value, short min, short max) { + return ((value >= min) && (value <= max)); + } + + /** + *

Checks if a value is within a range (min & max specified + * in the vars attribute).

+ * + * @param value The value validation is being performed on. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is in the specified range. + */ + public static boolean isInRange(long value, long min, long max) { + return ((value >= min) && (value <= max)); + } + + /** + *

Checks if a value is within a range (min & max specified + * in the vars attribute).

+ * + * @param value The value validation is being performed on. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is in the specified range. + */ + public static boolean isInRange(double value, double min, double max) { + return ((value >= min) && (value <= max)); + } + + /** + * Checks if the field is a valid credit card number. + * @param value The value validation is being performed on. + * @return true if the value is valid Credit Card Number. + */ + public static boolean isCreditCard(String value) { + return CREDIT_CARD_VALIDATOR.isValid(value); + } + + /** + *

Checks if a field has a valid e-mail address.

+ * + * @param value The value validation is being performed on. + * @return true if the value is valid Email Address. + */ + public static boolean isEmail(String value) { + return EmailValidator.getInstance().isValid(value); + } + + /** + *

Checks if a field is a valid url address.

+ * If you need to modify what is considered valid then + * consider using the UrlValidator directly. + * + * @param value The value validation is being performed on. + * @return true if the value is valid Url. + */ + public static boolean isUrl(String value) { + return URL_VALIDATOR.isValid(value); + } + + /** + *

Checks if the value's length is less than or equal to the max.

+ * + * @param value The value validation is being performed on. + * @param max The maximum length. + * @return true if the value's length is less than the specified maximum. + */ + public static boolean maxLength(String value, int max) { + return (value.length() <= max); + } + + /** + *

Checks if the value's adjusted length is less than or equal to the max.

+ * + * @param value The value validation is being performed on. + * @param max The maximum length. + * @param lineEndLength The length to use for line endings. + * @return true if the value's length is less than the specified maximum. + */ + public static boolean maxLength(String value, int max, int lineEndLength) { + int adjustAmount = adjustForLineEnding(value, lineEndLength); + return ((value.length() + adjustAmount) <= max); + } + + /** + *

Checks if the value's length is greater than or equal to the min.

+ * + * @param value The value validation is being performed on. + * @param min The minimum length. + * @return true if the value's length is more than the specified minimum. + */ + public static boolean minLength(String value, int min) { + return (value.length() >= min); + } + + /** + *

Checks if the value's adjusted length is greater than or equal to the min.

+ * + * @param value The value validation is being performed on. + * @param min The minimum length. + * @param lineEndLength The length to use for line endings. + * @return true if the value's length is more than the specified minimum. + */ + public static boolean minLength(String value, int min, int lineEndLength) { + int adjustAmount = adjustForLineEnding(value, lineEndLength); + return ((value.length() + adjustAmount) >= min); + } + + /** + * Calculate an adjustment amount for line endings. + * + * See Bug 37962 for the rational behind this. + * + * @param value The value validation is being performed on. + * @param lineEndLength The length to use for line endings. + * @return the adjustment amount. + */ + private static int adjustForLineEnding(String value, int lineEndLength) { + int nCount = 0; + int rCount = 0; + for (int i = 0; i < value.length(); i++) { + if (value.charAt(i) == '\n') { + nCount++; + } + if (value.charAt(i) == '\r') { + rCount++; + } + } + return ((nCount * lineEndLength) - (rCount + nCount)); + } + + // See http://issues.apache.org/bugzilla/show_bug.cgi?id=29015 WRT the "value" methods + + /** + *

Checks if the value is greater than or equal to the min.

+ * + * @param value The value validation is being performed on. + * @param min The minimum numeric value. + * @return true if the value is >= the specified minimum. + */ + public static boolean minValue(int value, int min) { + return (value >= min); + } + + /** + *

Checks if the value is greater than or equal to the min.

+ * + * @param value The value validation is being performed on. + * @param min The minimum numeric value. + * @return true if the value is >= the specified minimum. + */ + public static boolean minValue(long value, long min) { + return (value >= min); + } + + /** + *

Checks if the value is greater than or equal to the min.

+ * + * @param value The value validation is being performed on. + * @param min The minimum numeric value. + * @return true if the value is >= the specified minimum. + */ + public static boolean minValue(double value, double min) { + return (value >= min); + } + + /** + *

Checks if the value is greater than or equal to the min.

+ * + * @param value The value validation is being performed on. + * @param min The minimum numeric value. + * @return true if the value is >= the specified minimum. + */ + public static boolean minValue(float value, float min) { + return (value >= min); + } + + /** + *

Checks if the value is less than or equal to the max.

+ * + * @param value The value validation is being performed on. + * @param max The maximum numeric value. + * @return true if the value is <= the specified maximum. + */ + public static boolean maxValue(int value, int max) { + return (value <= max); + } + + /** + *

Checks if the value is less than or equal to the max.

+ * + * @param value The value validation is being performed on. + * @param max The maximum numeric value. + * @return true if the value is <= the specified maximum. + */ + public static boolean maxValue(long value, long max) { + return (value <= max); + } + + /** + *

Checks if the value is less than or equal to the max.

+ * + * @param value The value validation is being performed on. + * @param max The maximum numeric value. + * @return true if the value is <= the specified maximum. + */ + public static boolean maxValue(double value, double max) { + return (value <= max); + } + + /** + *

Checks if the value is less than or equal to the max.

+ * + * @param value The value validation is being performed on. + * @param max The maximum numeric value. + * @return true if the value is <= the specified maximum. + */ + public static boolean maxValue(float value, float max) { + return (value <= max); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ISBNValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ISBNValidator.java new file mode 100644 index 000000000..2f921f75e --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ISBNValidator.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +/** + * A class for validating 10 digit ISBN codes. + * Based on this + * + * algorithm + * + * NOTE: This has been replaced by the new + * {@link org.apache.commons.validator.routines.ISBNValidator}. + * + * @version $Revision$ + * @since Validator 1.2.0 + * @deprecated Use the new ISBNValidator in the routines package + */ +@Deprecated +public class ISBNValidator { + + /** + * Default Constructor. + */ + public ISBNValidator() { + super(); + } + + /** + * If the ISBN is formatted with space or dash separators its format is + * validated. Then the digits in the number are weighted, summed, and + * divided by 11 according to the ISBN algorithm. If the result is zero, + * the ISBN is valid. This method accepts formatted or raw ISBN codes. + * + * @param isbn Candidate ISBN number to be validated. null is + * considered invalid. + * @return true if the string is a valid ISBN code. + */ + public boolean isValid(String isbn) { + return org.apache.commons.validator.routines.ISBNValidator.getInstance().isValidISBN10(isbn); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Msg.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Msg.java new file mode 100644 index 000000000..31d122801 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Msg.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; + +/** + * An alternative message can be associated with a Field + * and a pluggable validator instead of using the default message + * stored in the ValidatorAction (aka pluggable validator). + * Instances of this class are configured with a <msg> xml element. + * + * @version $Revision$ + */ +//TODO mutable non-private fields +public class Msg implements Cloneable, Serializable { + + private static final long serialVersionUID = 5690015734364127124L; + + /** + * The resource bundle name that this Msg's key should be + * resolved in (optional). + * @since Validator 1.1 + */ + protected String bundle = null; + + /** + * The key or value of the argument. + */ + protected String key = null; + + /** + * The name dependency that this argument goes with (optional). + */ + protected String name = null; + + /** + * Whether or not the key is a message resource (optional). Defaults to + * true. If it is 'true', the value will try to be resolved as a message + * resource. + * @since Validator 1.1.4 + */ + protected boolean resource = true; + + /** + * Returns the resource bundle name. + * @return The bundle name. + * @since Validator 1.1 + */ + public String getBundle() { + return this.bundle; + } + + /** + * Sets the resource bundle name. + * @param bundle The new bundle name. + * @since Validator 1.1 + */ + public void setBundle(String bundle) { + this.bundle = bundle; + } + + /** + * Gets the name of the dependency. + * @return The dependency name. + */ + public String getName() { + return name; + } + + /** + * Sets the name of the dependency. + * @param name The dependency name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the key/value. + * @return The message key/value. + */ + public String getKey() { + return key; + } + + /** + * Sets the key/value. + * @param key The message key/value. + */ + public void setKey(String key) { + this.key = key; + } + + /** + * Tests whether or not the key is a resource key or literal value. + * @return true if key is a resource key. + * @since Validator 1.1.4 + */ + public boolean isResource() { + return this.resource; + } + + /** + * Sets whether or not the key is a resource. + * @param resource If true indicates the key is a resource. + * @since Validator 1.1.4 + */ + public void setResource(boolean resource) { + this.resource = resource; + } + + /** + * Creates and returns a copy of this object. + * @return A copy of the Msg. + */ + @Override + public Object clone() { + try { + return super.clone(); + + } catch(CloneNotSupportedException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Returns a string representation of the object. + * @return Msg String representation. + */ + @Override + public String toString() { + StringBuilder results = new StringBuilder(); + + results.append("Msg: name="); + results.append(name); + results.append(" key="); + results.append(key); + results.append(" resource="); + results.append(resource); + results.append(" bundle="); + results.append(bundle); + results.append("\n"); + + return results.toString(); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/UrlValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/UrlValidator.java new file mode 100644 index 000000000..076cd919b --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/UrlValidator.java @@ -0,0 +1,468 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.validator.routines.InetAddressValidator; +import org.apache.commons.validator.util.Flags; + +/** + *

Validates URLs.

+ * Behavour of validation is modified by passing in options: + *
    + *
  • ALLOW_2_SLASHES - [FALSE] Allows double '/' characters in the path + * component.
  • + *
  • NO_FRAGMENT- [FALSE] By default fragments are allowed, if this option is + * included then fragments are flagged as illegal.
  • + *
  • ALLOW_ALL_SCHEMES - [FALSE] By default only http, https, and ftp are + * considered valid schemes. Enabling this option will let any scheme pass validation.
  • + *
+ * + *

Originally based in on php script by Debbie Dyer, validation.php v1.2b, Date: 03/07/02, + * http://javascript.internet.com. However, this validation now bears little resemblance + * to the php original.

+ *
+ *   Example of usage:
+ *   Construct a UrlValidator with valid schemes of "http", and "https".
+ *
+ *    String[] schemes = {"http","https"}.
+ *    UrlValidator urlValidator = new UrlValidator(schemes);
+ *    if (urlValidator.isValid("ftp://foo.bar.com/")) {
+ *       System.out.println("url is valid");
+ *    } else {
+ *       System.out.println("url is invalid");
+ *    }
+ *
+ *    prints "url is invalid"
+ *   If instead the default constructor is used.
+ *
+ *    UrlValidator urlValidator = new UrlValidator();
+ *    if (urlValidator.isValid("ftp://foo.bar.com/")) {
+ *       System.out.println("url is valid");
+ *    } else {
+ *       System.out.println("url is invalid");
+ *    }
+ *
+ *   prints out "url is valid"
+ *  
+ * + * @see + * + * Uniform Resource Identifiers (URI): Generic Syntax + * + * + * @version $Revision$ + * @since Validator 1.1 + * @deprecated Use the new UrlValidator in the routines package. This class + * will be removed in a future release. + */ +@Deprecated +public class UrlValidator implements Serializable { + + private static final long serialVersionUID = 24137157400029593L; + + /** + * Allows all validly formatted schemes to pass validation instead of + * supplying a set of valid schemes. + */ + public static final int ALLOW_ALL_SCHEMES = 1 << 0; + + /** + * Allow two slashes in the path component of the URL. + */ + public static final int ALLOW_2_SLASHES = 1 << 1; + + /** + * Enabling this options disallows any URL fragments. + */ + public static final int NO_FRAGMENTS = 1 << 2; + + private static final String ALPHA_CHARS = "a-zA-Z"; + +// NOT USED private static final String ALPHA_NUMERIC_CHARS = ALPHA_CHARS + "\\d"; + + private static final String SPECIAL_CHARS = ";/@&=,.?:+$"; + + private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]"; + + // Drop numeric, and "+-." for now + private static final String AUTHORITY_CHARS_REGEX = "\\p{Alnum}\\-\\."; + + private static final String ATOM = VALID_CHARS + '+'; + + /** + * This expression derived/taken from the BNF for URI (RFC2396). + */ + private static final String URL_REGEX = + "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"; + // 12 3 4 5 6 7 8 9 + private static final Pattern URL_PATTERN = Pattern.compile(URL_REGEX); + + /** + * Schema/Protocol (ie. http:, ftp:, file:, etc). + */ + private static final int PARSE_URL_SCHEME = 2; + + /** + * Includes hostname/ip and port number. + */ + private static final int PARSE_URL_AUTHORITY = 4; + + private static final int PARSE_URL_PATH = 5; + + private static final int PARSE_URL_QUERY = 7; + + private static final int PARSE_URL_FRAGMENT = 9; + + /** + * Protocol (ie. http:, ftp:,https:). + */ + private static final Pattern SCHEME_PATTERN = Pattern.compile("^\\p{Alpha}[\\p{Alnum}\\+\\-\\.]*"); + + private static final String AUTHORITY_REGEX = + "^([" + AUTHORITY_CHARS_REGEX + "]*)(:\\d*)?(.*)?"; + // 1 2 3 4 + private static final Pattern AUTHORITY_PATTERN = Pattern.compile(AUTHORITY_REGEX); + + private static final int PARSE_AUTHORITY_HOST_IP = 1; + + private static final int PARSE_AUTHORITY_PORT = 2; + + /** + * Should always be empty. + */ + private static final int PARSE_AUTHORITY_EXTRA = 3; + + private static final Pattern PATH_PATTERN = Pattern.compile("^(/[-\\w:@&?=+,.!/~*'%$_;]*)?$"); + + private static final Pattern QUERY_PATTERN = Pattern.compile("^(.*)$"); + + private static final Pattern LEGAL_ASCII_PATTERN = Pattern.compile("^\\p{ASCII}+$"); + + private static final Pattern DOMAIN_PATTERN = + Pattern.compile("^" + ATOM + "(\\." + ATOM + ")*$"); + + private static final Pattern PORT_PATTERN = Pattern.compile("^:(\\d{1,5})$"); + + private static final Pattern ATOM_PATTERN = Pattern.compile("^(" + ATOM + ").*?$"); + + private static final Pattern ALPHA_PATTERN = Pattern.compile("^[" + ALPHA_CHARS + "]"); + + /** + * Holds the set of current validation options. + */ + private final Flags options; + + /** + * The set of schemes that are allowed to be in a URL. + */ + private final Set allowedSchemes = new HashSet(); + + /** + * If no schemes are provided, default to this set. + */ + protected String[] defaultSchemes = {"http", "https", "ftp"}; + + /** + * Create a UrlValidator with default properties. + */ + public UrlValidator() { + this(null); + } + + /** + * Behavior of validation is modified by passing in several strings options: + * @param schemes Pass in one or more url schemes to consider valid, passing in + * a null will default to "http,https,ftp" being valid. + * If a non-null schemes is specified then all valid schemes must + * be specified. Setting the ALLOW_ALL_SCHEMES option will + * ignore the contents of schemes. + */ + public UrlValidator(String[] schemes) { + this(schemes, 0); + } + + /** + * Initialize a UrlValidator with the given validation options. + * @param options The options should be set using the public constants declared in + * this class. To set multiple options you simply add them together. For example, + * ALLOW_2_SLASHES + NO_FRAGMENTS enables both of those options. + */ + public UrlValidator(int options) { + this(null, options); + } + + /** + * Behavour of validation is modified by passing in options: + * @param schemes The set of valid schemes. + * @param options The options should be set using the public constants declared in + * this class. To set multiple options you simply add them together. For example, + * ALLOW_2_SLASHES + NO_FRAGMENTS enables both of those options. + */ + public UrlValidator(String[] schemes, int options) { + this.options = new Flags(options); + + if (this.options.isOn(ALLOW_ALL_SCHEMES)) { + return; + } + + if (schemes == null) { + schemes = this.defaultSchemes; + } + + this.allowedSchemes.addAll(Arrays.asList(schemes)); + } + + /** + *

Checks if a field has a valid url address.

+ * + * @param value The value validation is being performed on. A null + * value is considered invalid. + * @return true if the url is valid. + */ + public boolean isValid(String value) { + if (value == null) { + return false; + } + if (!LEGAL_ASCII_PATTERN.matcher(value).matches()) { + return false; + } + + // Check the whole url address structure + Matcher urlMatcher = URL_PATTERN.matcher(value); + if (!urlMatcher.matches()) { + return false; + } + + if (!isValidScheme(urlMatcher.group(PARSE_URL_SCHEME))) { + return false; + } + + if (!isValidAuthority(urlMatcher.group(PARSE_URL_AUTHORITY))) { + return false; + } + + if (!isValidPath(urlMatcher.group(PARSE_URL_PATH))) { + return false; + } + + if (!isValidQuery(urlMatcher.group(PARSE_URL_QUERY))) { + return false; + } + + if (!isValidFragment(urlMatcher.group(PARSE_URL_FRAGMENT))) { + return false; + } + + return true; + } + + /** + * Validate scheme. If schemes[] was initialized to a non null, + * then only those scheme's are allowed. Note this is slightly different + * than for the constructor. + * @param scheme The scheme to validate. A null value is considered + * invalid. + * @return true if valid. + */ + protected boolean isValidScheme(String scheme) { + if (scheme == null) { + return false; + } + + if (!SCHEME_PATTERN.matcher(scheme).matches()) { + return false; + } + + if (options.isOff(ALLOW_ALL_SCHEMES) && !allowedSchemes.contains(scheme)) { + return false; + } + + return true; + } + + /** + * Returns true if the authority is properly formatted. An authority is the combination + * of hostname and port. A null authority value is considered invalid. + * @param authority Authority value to validate. + * @return true if authority (hostname and port) is valid. + */ + protected boolean isValidAuthority(String authority) { + if (authority == null) { + return false; + } + + InetAddressValidator inetAddressValidator = + InetAddressValidator.getInstance(); + + Matcher authorityMatcher = AUTHORITY_PATTERN.matcher(authority); + if (!authorityMatcher.matches()) { + return false; + } + + boolean hostname = false; + // check if authority is IP address or hostname + String hostIP = authorityMatcher.group(PARSE_AUTHORITY_HOST_IP); + boolean ipV4Address = inetAddressValidator.isValid(hostIP); + + if (!ipV4Address) { + // Domain is hostname name + hostname = DOMAIN_PATTERN.matcher(hostIP).matches(); + } + + //rightmost hostname will never start with a digit. + if (hostname) { + // LOW-TECH FIX FOR VALIDATOR-202 + // TODO: Rewrite to use ArrayList and .add semantics: see VALIDATOR-203 + char[] chars = hostIP.toCharArray(); + int size = 1; + for(int i=0; i= hostIP.length()) + ? "" + : hostIP.substring(segmentLength); + + segmentCount++; + } + } + String topLevel = domainSegment[segmentCount - 1]; + if (topLevel.length() < 2 || topLevel.length() > 4) { // CHECKSTYLE IGNORE MagicNumber (deprecated code) + return false; + } + + // First letter of top level must be a alpha + if (!ALPHA_PATTERN.matcher(topLevel.substring(0, 1)).matches()) { + return false; + } + + // Make sure there's a host name preceding the authority. + if (segmentCount < 2) { + return false; + } + } + + if (!hostname && !ipV4Address) { + return false; + } + + String port = authorityMatcher.group(PARSE_AUTHORITY_PORT); + if (port != null && !PORT_PATTERN.matcher(port).matches()) { + return false; + } + + String extra = authorityMatcher.group(PARSE_AUTHORITY_EXTRA); + if (!GenericValidator.isBlankOrNull(extra)) { + return false; + } + + return true; + } + + /** + * Returns true if the path is valid. A null value is considered invalid. + * @param path Path value to validate. + * @return true if path is valid. + */ + protected boolean isValidPath(String path) { + if (path == null) { + return false; + } + + if (!PATH_PATTERN.matcher(path).matches()) { + return false; + } + + int slash2Count = countToken("//", path); + if (options.isOff(ALLOW_2_SLASHES) && (slash2Count > 0)) { + return false; + } + + int slashCount = countToken("/", path); + int dot2Count = countToken("..", path); + if (dot2Count > 0 && (slashCount - slash2Count - 1) <= dot2Count){ + return false; + } + + return true; + } + + /** + * Returns true if the query is null or it's a properly formatted query string. + * @param query Query value to validate. + * @return true if query is valid. + */ + protected boolean isValidQuery(String query) { + if (query == null) { + return true; + } + + return QUERY_PATTERN.matcher(query).matches(); + } + + /** + * Returns true if the given fragment is null or fragments are allowed. + * @param fragment Fragment value to validate. + * @return true if fragment is valid. + */ + protected boolean isValidFragment(String fragment) { + if (fragment == null) { + return true; + } + + return options.isOff(NO_FRAGMENTS); + } + + /** + * Returns the number of times the token appears in the target. + * @param token Token value to be counted. + * @param target Target value to count tokens in. + * @return the number of tokens. + */ + protected int countToken(String token, String target) { + int tokenIndex = 0; + int count = 0; + while (tokenIndex != -1) { + tokenIndex = target.indexOf(token, tokenIndex); + if (tokenIndex > -1) { + tokenIndex++; + count++; + } + } + return count; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Validator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Validator.java new file mode 100644 index 000000000..8b29df27f --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Validator.java @@ -0,0 +1,391 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Validations are processed by the validate method. An instance of + * ValidatorResources is used to define the validators + * (validation methods) and the validation rules for a JavaBean. + * + * @version $Revision$ + */ +// TODO mutable fields should be made private and accessed via suitable methods only +public class Validator implements Serializable { + + private static final long serialVersionUID = -7119418755208731611L; + + /** + * Resources key the JavaBean is stored to perform validation on. + */ + public static final String BEAN_PARAM = "java.lang.Object"; + + /** + * Resources key the ValidatorAction is stored under. + * This will be automatically passed into a validation method + * with the current ValidatorAction if it is + * specified in the method signature. + */ + public static final String VALIDATOR_ACTION_PARAM = + "org.apache.commons.validator.ValidatorAction"; + + /** + * Resources key the ValidatorResults is stored under. + * This will be automatically passed into a validation method + * with the current ValidatorResults if it is + * specified in the method signature. + */ + public static final String VALIDATOR_RESULTS_PARAM = + "org.apache.commons.validator.ValidatorResults"; + + /** + * Resources key the Form is stored under. + * This will be automatically passed into a validation method + * with the current Form if it is + * specified in the method signature. + */ + public static final String FORM_PARAM = "org.apache.commons.validator.Form"; + + /** + * Resources key the Field is stored under. + * This will be automatically passed into a validation method + * with the current Field if it is + * specified in the method signature. + */ + public static final String FIELD_PARAM = "org.apache.commons.validator.Field"; + + /** + * Resources key the Validator is stored under. + * This will be automatically passed into a validation method + * with the current Validator if it is + * specified in the method signature. + */ + public static final String VALIDATOR_PARAM = + "org.apache.commons.validator.Validator"; + + /** + * Resources key the Locale is stored. + * This will be used to retrieve the appropriate + * FormSet and Form to be + * processed. + */ + public static final String LOCALE_PARAM = "java.util.Locale"; + + /** + * The Validator Resources. + */ + protected ValidatorResources resources = null; + + /** + * The name of the form to validate + */ + protected String formName = null; + + /** + * The name of the field on the form to validate + * @since 1.2.0 + */ + protected String fieldName = null; + + /** + * Maps validation method parameter class names to the objects to be passed + * into the method. + */ + protected Map parameters = new HashMap(); // + + /** + * The current page number to validate. + */ + protected int page = 0; + + /** + * The class loader to use for instantiating application objects. + * If not specified, the context class loader, or the class loader + * used to load Digester itself, is used, based on the value of the + * useContextClassLoader variable. + */ + protected transient ClassLoader classLoader = null; + + /** + * Whether or not to use the Context ClassLoader when loading classes + * for instantiating new objects. Default is false. + */ + protected boolean useContextClassLoader = false; + + /** + * Set this to true to not return Fields that pass validation. Only return failures. + */ + protected boolean onlyReturnErrors = false; + + /** + * Construct a Validator that will + * use the ValidatorResources + * passed in to retrieve pluggable validators + * the different sets of validation rules. + * + * @param resources ValidatorResources to use during validation. + */ + public Validator(ValidatorResources resources) { + this(resources, null); + } + + /** + * Construct a Validator that will + * use the ValidatorResources + * passed in to retrieve pluggable validators + * the different sets of validation rules. + * + * @param resources ValidatorResources to use during validation. + * @param formName Key used for retrieving the set of validation rules. + */ + public Validator(ValidatorResources resources, String formName) { + if (resources == null) { + throw new IllegalArgumentException("Resources cannot be null."); + } + + this.resources = resources; + this.formName = formName; + } + + /** + * Construct a Validator that will + * use the ValidatorResources + * passed in to retrieve pluggable validators + * the different sets of validation rules. + * + * @param resources ValidatorResources to use during validation. + * @param formName Key used for retrieving the set of validation rules. + * @param fieldName Key used for retrieving the set of validation rules for a field + * @since 1.2.0 + */ + public Validator(ValidatorResources resources, String formName, String fieldName) { + if (resources == null) { + throw new IllegalArgumentException("Resources cannot be null."); + } + + this.resources = resources; + this.formName = formName; + this.fieldName = fieldName; + } + + /** + * Set a parameter of a pluggable validation method. + * + * @param parameterClassName The full class name of the parameter of the + * validation method that corresponds to the value/instance passed in with it. + * + * @param parameterValue The instance that will be passed into the + * validation method. + */ + public void setParameter(String parameterClassName, Object parameterValue) { + this.parameters.put(parameterClassName, parameterValue); + } + + /** + * Returns the value of the specified parameter that will be used during the + * processing of validations. + * + * @param parameterClassName The full class name of the parameter of the + * validation method that corresponds to the value/instance passed in with it. + * @return value of the specified parameter. + */ + public Object getParameterValue(String parameterClassName) { + return this.parameters.get(parameterClassName); + } + + /** + * Gets the form name which is the key to a set of validation rules. + * @return the name of the form. + */ + public String getFormName() { + return formName; + } + + /** + * Sets the form name which is the key to a set of validation rules. + * @param formName the name of the form. + */ + public void setFormName(String formName) { + this.formName = formName; + } + + /** + * Sets the name of the field to validate in a form (optional) + * + * @param fieldName The name of the field in a form set + * @since 1.2.0 + */ + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + /** + * Gets the page. + * + *

+ * This in conjunction with the page property of + * a {@code Field} can control the processing of fields. If the field's + * page is less than or equal to this page value, it will be processed. + *

+ * + * @return the page number. + */ + public int getPage() { + return page; + } + + /** + * Sets the page. + *

+ * This in conjunction with the page property of + * a {@code Field} can control the processing of fields. If the field's page + * is less than or equal to this page value, it will be processed. + *

+ * + * @param page the page number. + */ + public void setPage(int page) { + this.page = page; + } + + /** + * Clears the form name, resources that were added, and the page that was + * set (if any). This can be called to reinitialize the Validator instance + * so it can be reused. The form name (key to set of validation rules) and any + * resources needed, like the JavaBean being validated, will need to + * set and/or added to this instance again. The + * ValidatorResources will not be removed since it can be used + * again and is thread safe. + */ + public void clear() { + this.formName = null; + this.fieldName = null; + this.parameters = new HashMap(); + this.page = 0; + } + + /** + * Return the boolean as to whether the context classloader should be used. + * @return whether the context classloader should be used. + */ + public boolean getUseContextClassLoader() { + return this.useContextClassLoader; + } + + /** + * Determine whether to use the Context ClassLoader (the one found by + * calling Thread.currentThread().getContextClassLoader()) + * to resolve/load classes that are defined in various rules. If not + * using Context ClassLoader, then the class-loading defaults to + * using the calling-class' ClassLoader. + * + * @param use determines whether to use Context ClassLoader. + */ + public void setUseContextClassLoader(boolean use) { + this.useContextClassLoader = use; + } + + /** + * Return the class loader to be used for instantiating application objects + * when required. This is determined based upon the following rules: + *
    + *
  • The class loader set by setClassLoader(), if any
  • + *
  • The thread context class loader, if it exists and the + * useContextClassLoader property is set to true
  • + *
  • The class loader used to load the Digester class itself. + *
+ * @return the class loader. + */ + public ClassLoader getClassLoader() { + if (this.classLoader != null) { + return this.classLoader; + } + + if (this.useContextClassLoader) { + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + if (contextLoader != null) { + return contextLoader; + } + } + + return this.getClass().getClassLoader(); + } + + /** + * Set the class loader to be used for instantiating application objects + * when required. + * + * @param classLoader The new class loader to use, or null + * to revert to the standard rules + */ + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * Performs validations based on the configured resources. + * + * @return The Map returned uses the property of the + * Field for the key and the value is the number of error the + * field had. + * @throws ValidatorException If an error occurs during validation + */ + public ValidatorResults validate() throws ValidatorException { + Locale locale = (Locale) this.getParameterValue(LOCALE_PARAM); + + if (locale == null) { + locale = Locale.getDefault(); + } + + this.setParameter(VALIDATOR_PARAM, this); + + Form form = this.resources.getForm(locale, this.formName); + if (form != null) { + this.setParameter(FORM_PARAM, form); + return form.validate( + this.parameters, + this.resources.getValidatorActions(), + this.page, + this.fieldName); + } + + return new ValidatorResults(); + } + + /** + * Returns true if the Validator is only returning Fields that fail validation. + * @return whether only failed fields are returned. + */ + public boolean getOnlyReturnErrors() { + return onlyReturnErrors; + } + + /** + * Configures which Fields the Validator returns from the validate() method. Set this + * to true to only return Fields that failed validation. By default, validate() returns + * all fields. + * @param onlyReturnErrors whether only failed fields are returned. + */ + public void setOnlyReturnErrors(boolean onlyReturnErrors) { + this.onlyReturnErrors = onlyReturnErrors; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorAction.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorAction.java new file mode 100644 index 000000000..84973b18b --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorAction.java @@ -0,0 +1,796 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.validator.util.ValidatorUtils; + +/** + * Contains the information to dynamically create and run a validation + * method. This is the class representation of a pluggable validator that can + * be defined in an xml file with the <validator> element. + * + * Note: The validation method is assumed to be thread safe. + * + * @version $Revision$ + */ +public class ValidatorAction implements Serializable { + + private static final long serialVersionUID = 1339713700053204597L; + + /** + * Logger. + */ + private transient Log log = LogFactory.getLog(ValidatorAction.class); + + /** + * The name of the validation. + */ + private String name = null; + + /** + * The full class name of the class containing + * the validation method associated with this action. + */ + private String classname = null; + + /** + * The Class object loaded from the classname. + */ + private Class validationClass = null; + + /** + * The full method name of the validation to be performed. The method + * must be thread safe. + */ + private String method = null; + + /** + * The Method object loaded from the method name. + */ + private Method validationMethod = null; + + /** + *

+ * The method signature of the validation method. This should be a comma + * delimited list of the full class names of each parameter in the correct + * order that the method takes. + *

+ *

+ * Note: java.lang.Object is reserved for the + * JavaBean that is being validated. The ValidatorAction + * and Field that are associated with a field's + * validation will automatically be populated if they are + * specified in the method signature. + *

+ */ + private String methodParams = + Validator.BEAN_PARAM + + "," + + Validator.VALIDATOR_ACTION_PARAM + + "," + + Validator.FIELD_PARAM; + + /** + * The Class objects for each entry in methodParameterList. + */ + private Class[] parameterClasses = null; + + /** + * The other ValidatorActions that this one depends on. If + * any errors occur in an action that this one depends on, this action will + * not be processsed. + */ + private String depends = null; + + /** + * The default error message associated with this action. + */ + private String msg = null; + + /** + * An optional field to contain the name to be used if JavaScript is + * generated. + */ + private String jsFunctionName = null; + + /** + * An optional field to contain the class path to be used to retrieve the + * JavaScript function. + */ + private String jsFunction = null; + + /** + * An optional field to containing a JavaScript representation of the + * java method assocated with this action. + */ + private String javascript = null; + + /** + * If the java method matching the correct signature isn't static, the + * instance is stored in the action. This assumes the method is thread + * safe. + */ + private Object instance = null; + + /** + * An internal List representation of the other ValidatorActions + * this one depends on (if any). This List gets updated + * whenever setDepends() gets called. This is synchronized so a call to + * setDepends() (which clears the List) won't interfere with a call to + * isDependency(). + */ + private final List dependencyList = Collections.synchronizedList(new ArrayList()); + + /** + * An internal List representation of all the validation method's + * parameters defined in the methodParams String. + */ + private final List methodParameterList = new ArrayList(); + + /** + * Gets the name of the validator action. + * @return Validator Action name. + */ + public String getName() { + return name; + } + + /** + * Sets the name of the validator action. + * @param name Validator Action name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the class of the validator action. + * @return Class name of the validator Action. + */ + public String getClassname() { + return classname; + } + + /** + * Sets the class of the validator action. + * @param classname Class name of the validator Action. + */ + public void setClassname(String classname) { + this.classname = classname; + } + + /** + * Gets the name of method being called for the validator action. + * @return The method name. + */ + public String getMethod() { + return method; + } + + /** + * Sets the name of method being called for the validator action. + * @param method The method name. + */ + public void setMethod(String method) { + this.method = method; + } + + /** + * Gets the method parameters for the method. + * @return Method's parameters. + */ + public String getMethodParams() { + return methodParams; + } + + /** + * Sets the method parameters for the method. + * @param methodParams A comma separated list of parameters. + */ + public void setMethodParams(String methodParams) { + this.methodParams = methodParams; + + this.methodParameterList.clear(); + + StringTokenizer st = new StringTokenizer(methodParams, ","); + while (st.hasMoreTokens()) { + String value = st.nextToken().trim(); + + if (value != null && value.length() > 0) { + this.methodParameterList.add(value); + } + } + } + + /** + * Gets the dependencies of the validator action as a comma separated list + * of validator names. + * @return The validator action's dependencies. + */ + public String getDepends() { + return this.depends; + } + + /** + * Sets the dependencies of the validator action. + * @param depends A comma separated list of validator names. + */ + public void setDepends(String depends) { + this.depends = depends; + + this.dependencyList.clear(); + + StringTokenizer st = new StringTokenizer(depends, ","); + while (st.hasMoreTokens()) { + String depend = st.nextToken().trim(); + + if (depend != null && depend.length() > 0) { + this.dependencyList.add(depend); + } + } + } + + /** + * Gets the message associated with the validator action. + * @return The message for the validator action. + */ + public String getMsg() { + return msg; + } + + /** + * Sets the message associated with the validator action. + * @param msg The message for the validator action. + */ + public void setMsg(String msg) { + this.msg = msg; + } + + /** + * Gets the Javascript function name. This is optional and can + * be used instead of validator action name for the name of the + * Javascript function/object. + * @return The Javascript function name. + */ + public String getJsFunctionName() { + return jsFunctionName; + } + + /** + * Sets the Javascript function name. This is optional and can + * be used instead of validator action name for the name of the + * Javascript function/object. + * @param jsFunctionName The Javascript function name. + */ + public void setJsFunctionName(String jsFunctionName) { + this.jsFunctionName = jsFunctionName; + } + + /** + * Sets the fully qualified class path of the Javascript function. + *

+ * This is optional and can be used instead of the setJavascript(). + * Attempting to call both setJsFunction and setJavascript + * will result in an IllegalStateException being thrown.

+ *

+ * If neither setJsFunction or setJavascript is set then + * validator will attempt to load the default javascript definition. + *

+ *
+     * Examples
+     *   If in the validator.xml :
+     * #1:
+     *      <validator name="tire"
+     *            jsFunction="com.yourcompany.project.tireFuncion">
+     *     Validator will attempt to load com.yourcompany.project.validateTireFunction.js from
+     *     its class path.
+     * #2:
+     *    <validator name="tire">
+     *      Validator will use the name attribute to try and load
+     *         org.apache.commons.validator.javascript.validateTire.js
+     *      which is the default javascript definition.
+     * 
+ * @param jsFunction The Javascript function's fully qualified class path. + */ + public void setJsFunction(String jsFunction) { + if (javascript != null) { + throw new IllegalStateException("Cannot call setJsFunction() after calling setJavascript()"); + } + + this.jsFunction = jsFunction; + } + + /** + * Gets the Javascript equivalent of the java class and method + * associated with this action. + * @return The Javascript validation. + */ + public String getJavascript() { + return javascript; + } + + /** + * Sets the Javascript equivalent of the java class and method + * associated with this action. + * @param javascript The Javascript validation. + */ + public void setJavascript(String javascript) { + if (jsFunction != null) { + throw new IllegalStateException("Cannot call setJavascript() after calling setJsFunction()"); + } + + this.javascript = javascript; + } + + /** + * Initialize based on set. + */ + protected void init() { + this.loadJavascriptFunction(); + } + + /** + * Load the javascript function specified by the given path. For this + * implementation, the jsFunction property should contain a + * fully qualified package and script name, separated by periods, to be + * loaded from the class loader that created this instance. + * + * TODO if the path begins with a '/' the path will be intepreted as + * absolute, and remain unchanged. If this fails then it will attempt to + * treat the path as a file path. It is assumed the script ends with a + * '.js'. + */ + protected synchronized void loadJavascriptFunction() { + + if (this.javascriptAlreadyLoaded()) { + return; + } + + if (getLog().isTraceEnabled()) { + getLog().trace(" Loading function begun"); + } + + if (this.jsFunction == null) { + this.jsFunction = this.generateJsFunction(); + } + + String javascriptFileName = this.formatJavascriptFileName(); + + if (getLog().isTraceEnabled()) { + getLog().trace(" Loading js function '" + javascriptFileName + "'"); + } + + this.javascript = this.readJavascriptFile(javascriptFileName); + + if (getLog().isTraceEnabled()) { + getLog().trace(" Loading javascript function completed"); + } + + } + + /** + * Read a javascript function from a file. + * @param javascriptFileName The file containing the javascript. + * @return The javascript function or null if it could not be loaded. + */ + private String readJavascriptFile(String javascriptFileName) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = this.getClass().getClassLoader(); + } + + InputStream is = classLoader.getResourceAsStream(javascriptFileName); + if (is == null) { + is = this.getClass().getResourceAsStream(javascriptFileName); + } + + if (is == null) { + getLog().debug(" Unable to read javascript name "+javascriptFileName); + return null; + } + + StringBuilder buffer = new StringBuilder(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); // TODO encoding + try { + String line = null; + while ((line = reader.readLine()) != null) { + buffer.append(line).append("\n"); + } + + } catch(IOException e) { + getLog().error("Error reading javascript file.", e); + + } finally { + try { + reader.close(); + } catch(IOException e) { + getLog().error("Error closing stream to javascript file.", e); + } + } + + String function = buffer.toString(); + return function.equals("") ? null : function; + } + + /** + * @return A filename suitable for passing to a + * ClassLoader.getResourceAsStream() method. + */ + private String formatJavascriptFileName() { + String name = this.jsFunction.substring(1); + + if (!this.jsFunction.startsWith("/")) { + name = jsFunction.replace('.', '/') + ".js"; + } + + return name; + } + + /** + * @return true if the javascript for this action has already been loaded. + */ + private boolean javascriptAlreadyLoaded() { + return (this.javascript != null); + } + + /** + * Used to generate the javascript name when it is not specified. + */ + private String generateJsFunction() { + StringBuilder jsName = + new StringBuilder("org.apache.commons.validator.javascript"); + + jsName.append(".validate"); + jsName.append(name.substring(0, 1).toUpperCase()); + jsName.append(name.substring(1, name.length())); + + return jsName.toString(); + } + + /** + * Checks whether or not the value passed in is in the depends field. + * @param validatorName Name of the dependency to check. + * @return Whether the named validator is a dependant. + */ + public boolean isDependency(String validatorName) { + return this.dependencyList.contains(validatorName); + } + + /** + * Returns the dependent validator names as an unmodifiable + * List. + * @return List of the validator action's depedents. + */ + public List getDependencyList() { + return Collections.unmodifiableList(this.dependencyList); + } + + /** + * Returns a string representation of the object. + * @return a string representation. + */ + @Override + public String toString() { + StringBuilder results = new StringBuilder("ValidatorAction: "); + results.append(name); + results.append("\n"); + + return results.toString(); + } + + /** + * Dynamically runs the validation method for this validator and returns + * true if the data is valid. + * @param field + * @param params A Map of class names to parameter values. + * @param results + * @param pos The index of the list property to validate if it's indexed. + * @throws ValidatorException + */ + boolean executeValidationMethod( + Field field, + // TODO What is this the correct value type? + // both ValidatorAction and Validator are added as parameters + Map params, + ValidatorResults results, + int pos) + throws ValidatorException { + + params.put(Validator.VALIDATOR_ACTION_PARAM, this); + + try { + if (this.validationMethod == null) { + synchronized(this) { + ClassLoader loader = this.getClassLoader(params); + this.loadValidationClass(loader); + this.loadParameterClasses(loader); + this.loadValidationMethod(); + } + } + + Object[] paramValues = this.getParameterValues(params); + + if (field.isIndexed()) { + this.handleIndexedField(field, pos, paramValues); + } + + Object result = null; + try { + result = + validationMethod.invoke( + getValidationClassInstance(), + paramValues); + + } catch (IllegalArgumentException e) { + throw new ValidatorException(e.getMessage()); + } catch (IllegalAccessException e) { + throw new ValidatorException(e.getMessage()); + } catch (InvocationTargetException e) { + + if (e.getTargetException() instanceof Exception) { + throw (Exception) e.getTargetException(); + + } else if (e.getTargetException() instanceof Error) { + throw (Error) e.getTargetException(); + } + } + + boolean valid = this.isValid(result); + if (!valid || (valid && !onlyReturnErrors(params))) { + results.add(field, this.name, valid, result); + } + + if (!valid) { + return false; + } + + // TODO This catch block remains for backward compatibility. Remove + // this for Validator 2.0 when exception scheme changes. + } catch (Exception e) { + if (e instanceof ValidatorException) { + throw (ValidatorException) e; + } + + getLog().error( + "Unhandled exception thrown during validation: " + e.getMessage(), + e); + + results.add(field, this.name, false); + return false; + } + + return true; + } + + /** + * Load the Method object for the configured validation method name. + * @throws ValidatorException + */ + private void loadValidationMethod() throws ValidatorException { + if (this.validationMethod != null) { + return; + } + + try { + this.validationMethod = + this.validationClass.getMethod(this.method, this.parameterClasses); + + } catch (NoSuchMethodException e) { + throw new ValidatorException("No such validation method: " + + e.getMessage()); + } + } + + /** + * Load the Class object for the configured validation class name. + * @param loader The ClassLoader used to load the Class object. + * @throws ValidatorException + */ + private void loadValidationClass(ClassLoader loader) + throws ValidatorException { + + if (this.validationClass != null) { + return; + } + + try { + this.validationClass = loader.loadClass(this.classname); + } catch (ClassNotFoundException e) { + throw new ValidatorException(e.toString()); + } + } + + /** + * Converts a List of parameter class names into their Class objects. + * Stores the output in {@link #parameterClasses}. This + * array is in the same order as the given List and is suitable for passing + * to the validation method. + * @throws ValidatorException if a class cannot be loaded. + */ + private void loadParameterClasses(ClassLoader loader) + throws ValidatorException { + + if (this.parameterClasses != null) { + return; + } + + Class[] parameterClasses = new Class[this.methodParameterList.size()]; + + for (int i = 0; i < this.methodParameterList.size(); i++) { + String paramClassName = this.methodParameterList.get(i); + + try { + parameterClasses[i] = loader.loadClass(paramClassName); + + } catch (ClassNotFoundException e) { + throw new ValidatorException(e.getMessage()); + } + } + + this.parameterClasses = parameterClasses; + } + + /** + * Converts a List of parameter class names into their values contained in + * the parameters Map. + * @param params A Map of class names to parameter values. + * @return An array containing the value object for each parameter. This + * array is in the same order as the given List and is suitable for passing + * to the validation method. + */ + private Object[] getParameterValues(Map params) { + + Object[] paramValue = new Object[this.methodParameterList.size()]; + + for (int i = 0; i < this.methodParameterList.size(); i++) { + String paramClassName = this.methodParameterList.get(i); + paramValue[i] = params.get(paramClassName); + } + + return paramValue; + } + + /** + * Return an instance of the validation class or null if the validation + * method is static so does not require an instance to be executed. + */ + private Object getValidationClassInstance() throws ValidatorException { + if (Modifier.isStatic(this.validationMethod.getModifiers())) { + this.instance = null; + + } else { + if (this.instance == null) { + try { + this.instance = this.validationClass.newInstance(); + } catch (InstantiationException e) { + String msg = + "Couldn't create instance of " + + this.classname + + ". " + + e.getMessage(); + + throw new ValidatorException(msg); + + } catch (IllegalAccessException e) { + String msg = + "Couldn't create instance of " + + this.classname + + ". " + + e.getMessage(); + + throw new ValidatorException(msg); + } + } + } + + return this.instance; + } + + /** + * Modifies the paramValue array with indexed fields. + * + * @param field + * @param pos + * @param paramValues + */ + private void handleIndexedField(Field field, int pos, Object[] paramValues) + throws ValidatorException { + + int beanIndex = this.methodParameterList.indexOf(Validator.BEAN_PARAM); + int fieldIndex = this.methodParameterList.indexOf(Validator.FIELD_PARAM); + + Object indexedList[] = field.getIndexedProperty(paramValues[beanIndex]); + + // Set current iteration object to the parameter array + paramValues[beanIndex] = indexedList[pos]; + + // Set field clone with the key modified to represent + // the current field + Field indexedField = (Field) field.clone(); + indexedField.setKey( + ValidatorUtils.replace( + indexedField.getKey(), + Field.TOKEN_INDEXED, + "[" + pos + "]")); + + paramValues[fieldIndex] = indexedField; + } + + /** + * If the result object is a Boolean, it will return its + * value. If not it will return false if the object is + * null and true if it isn't. + */ + private boolean isValid(Object result) { + if (result instanceof Boolean) { + Boolean valid = (Boolean) result; + return valid.booleanValue(); + } + return result != null; + } + + /** + * Returns the ClassLoader set in the Validator contained in the parameter + * Map. + */ + private ClassLoader getClassLoader(Map params) { + Validator v = (Validator) params.get(Validator.VALIDATOR_PARAM); + return v.getClassLoader(); + } + + /** + * Returns the onlyReturnErrors setting in the Validator contained in the + * parameter Map. + */ + private boolean onlyReturnErrors(Map params) { + Validator v = (Validator) params.get(Validator.VALIDATOR_PARAM); + return v.getOnlyReturnErrors(); + } + + /** + * Accessor method for Log instance. + * + * The Log instance variable is transient and + * accessing it through this method ensures it + * is re-initialized when this instance is + * de-serialized. + * + * @return The Log instance. + */ + private Log getLog() { + if (log == null) { + log = LogFactory.getLog(ValidatorAction.class); + } + return log; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorException.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorException.java new file mode 100644 index 000000000..c8c45264f --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorException.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +/** + * The base exception for the Validator Framework. All other + * Exceptions thrown during calls to + * Validator.validate() are considered errors. + * + * @version $Revision$ + */ +public class ValidatorException extends Exception { + + private static final long serialVersionUID = 1025759372615616964L; + + /** + * Constructs an Exception with no specified detail message. + */ + public ValidatorException() { + super(); + } + + /** + * Constructs an Exception with the specified detail message. + * + * @param message The error message. + */ + public ValidatorException(String message) { + super(message); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResources.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResources.java new file mode 100644 index 000000000..005d07379 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResources.java @@ -0,0 +1,662 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URL; +import java.util.Collections; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import org.apache.commons.collections.FastHashMap; // DEPRECATED +import org.apache.commons.digester.Digester; +import org.apache.commons.digester.Rule; +import org.apache.commons.digester.xmlrules.DigesterLoader; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xml.sax.SAXException; +import org.xml.sax.Attributes; + +/** + *

+ * General purpose class for storing FormSet objects based + * on their associated Locale. Instances of this class are usually + * configured through a validation.xml file that is parsed in a constructor. + *

+ * + *

Note - Classes that extend this class + * must be Serializable so that instances may be used in distributable + * application server environments.

+ * + *

+ * The use of FastHashMap is deprecated and will be replaced in a future + * release. + *

+ * + * @version $Revision$ + */ +//TODO mutable non-private fields +public class ValidatorResources implements Serializable { + + private static final long serialVersionUID = -8203745881446239554L; + + /** Name of the digester validator rules file */ + private static final String VALIDATOR_RULES = "digester-rules.xml"; + + /** + * The set of public identifiers, and corresponding resource names, for + * the versions of the configuration file DTDs that we know about. There + * MUST be an even number of Strings in this list! + */ + private static final String REGISTRATIONS[] = { + "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN", + "/org/apache/commons/validator/resources/validator_1_0.dtd", + "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0.1//EN", + "/org/apache/commons/validator/resources/validator_1_0_1.dtd", + "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN", + "/org/apache/commons/validator/resources/validator_1_1.dtd", + "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN", + "/org/apache/commons/validator/resources/validator_1_1_3.dtd", + "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.2.0//EN", + "/org/apache/commons/validator/resources/validator_1_2_0.dtd", + "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.3.0//EN", + "/org/apache/commons/validator/resources/validator_1_3_0.dtd", + "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.4.0//EN", + "/org/apache/commons/validator/resources/validator_1_4_0.dtd" + }; + + private transient Log log = LogFactory.getLog(ValidatorResources.class); + + /** + * Map of FormSets stored under + * a Locale key (expressed as a String). + * @deprecated Subclasses should use getFormSets() instead. + */ + @Deprecated + protected FastHashMap hFormSets = new FastHashMap(); // + + /** + * Map of global constant values with + * the name of the constant as the key. + * @deprecated Subclasses should use getConstants() instead. + */ + @Deprecated + protected FastHashMap hConstants = new FastHashMap(); // + + /** + * Map of ValidatorActions with + * the name of the ValidatorAction as the key. + * @deprecated Subclasses should use getActions() instead. + */ + @Deprecated + protected FastHashMap hActions = new FastHashMap(); // + + /** + * The default locale on our server. + */ + protected static Locale defaultLocale = Locale.getDefault(); + + /** + * Create an empty ValidatorResources object. + */ + public ValidatorResources() { + super(); + } + + /** + * This is the default FormSet (without locale). (We probably don't need + * the defaultLocale anymore.) + */ + protected FormSet defaultFormSet; + + /** + * Create a ValidatorResources object from an InputStream. + * + * @param in InputStream to a validation.xml configuration file. It's the client's + * responsibility to close this stream. + * @throws SAXException if the validation XML files are not valid or well + * formed. + * @throws IOException if an I/O error occurs processing the XML files + * @since Validator 1.1 + */ + public ValidatorResources(InputStream in) throws IOException, SAXException { + this(new InputStream[]{in}); + } + + /** + * Create a ValidatorResources object from an InputStream. + * + * @param streams An array of InputStreams to several validation.xml + * configuration files that will be read in order and merged into this object. + * It's the client's responsibility to close these streams. + * @throws SAXException if the validation XML files are not valid or well + * formed. + * @throws IOException if an I/O error occurs processing the XML files + * @since Validator 1.1 + */ + public ValidatorResources(InputStream[] streams) + throws IOException, SAXException { + + super(); + + Digester digester = initDigester(); + for (int i = 0; i < streams.length; i++) { + if (streams[i] == null) { + throw new IllegalArgumentException("Stream[" + i + "] is null"); + } + digester.push(this); + digester.parse(streams[i]); + } + + this.process(); + } + + /** + * Create a ValidatorResources object from an uri + * + * @param uri The location of a validation.xml configuration file. + * @throws SAXException if the validation XML files are not valid or well + * formed. + * @throws IOException if an I/O error occurs processing the XML files + * @since Validator 1.2 + */ + public ValidatorResources(String uri) throws IOException, SAXException { + this(new String[]{uri}); + } + + /** + * Create a ValidatorResources object from several uris + * + * @param uris An array of uris to several validation.xml + * configuration files that will be read in order and merged into this object. + * @throws SAXException if the validation XML files are not valid or well + * formed. + * @throws IOException if an I/O error occurs processing the XML files + * @since Validator 1.2 + */ + public ValidatorResources(String[] uris) + throws IOException, SAXException { + + super(); + + Digester digester = initDigester(); + for (int i = 0; i < uris.length; i++) { + digester.push(this); + digester.parse(uris[i]); + } + + this.process(); + } + + /** + * Create a ValidatorResources object from a URL. + * + * @param url The URL for the validation.xml + * configuration file that will be read into this object. + * @throws SAXException if the validation XML file are not valid or well + * formed. + * @throws IOException if an I/O error occurs processing the XML files + * @since Validator 1.3.1 + */ + public ValidatorResources(URL url) + throws IOException, SAXException { + this(new URL[]{url}); + } + + /** + * Create a ValidatorResources object from several URL. + * + * @param urls An array of URL to several validation.xml + * configuration files that will be read in order and merged into this object. + * @throws SAXException if the validation XML files are not valid or well + * formed. + * @throws IOException if an I/O error occurs processing the XML files + * @since Validator 1.3.1 + */ + public ValidatorResources(URL[] urls) + throws IOException, SAXException { + + super(); + + Digester digester = initDigester(); + for (int i = 0; i < urls.length; i++) { + digester.push(this); + digester.parse(urls[i]); + } + + this.process(); + } + + /** + * Initialize the digester. + */ + private Digester initDigester() { + URL rulesUrl = this.getClass().getResource(VALIDATOR_RULES); + if (rulesUrl == null) { + // Fix for Issue# VALIDATOR-195 + rulesUrl = ValidatorResources.class.getResource(VALIDATOR_RULES); + } + if (getLog().isDebugEnabled()) { + getLog().debug("Loading rules from '" + rulesUrl + "'"); + } + Digester digester = DigesterLoader.createDigester(rulesUrl); + digester.setNamespaceAware(true); + digester.setValidating(true); + digester.setUseContextClassLoader(true); + + // Add rules for arg0-arg3 elements + addOldArgRules(digester); + + // register DTDs + for (int i = 0; i < REGISTRATIONS.length; i += 2) { + URL url = this.getClass().getResource(REGISTRATIONS[i + 1]); + if (url != null) { + digester.register(REGISTRATIONS[i], url.toString()); + } + } + return digester; + } + + private static final String ARGS_PATTERN + = "form-validation/formset/form/field/arg"; + + /** + * Create a Rule to handle arg0-arg3 + * elements. This will allow validation.xml files that use the + * versions of the DTD prior to Validator 1.2.0 to continue + * working. + */ + private void addOldArgRules(Digester digester) { + + // Create a new rule to process args elements + Rule rule = new Rule() { + @Override + public void begin(String namespace, String name, + Attributes attributes) throws Exception { + // Create the Arg + Arg arg = new Arg(); + arg.setKey(attributes.getValue("key")); + arg.setName(attributes.getValue("name")); + if ("false".equalsIgnoreCase(attributes.getValue("resource"))) { + arg.setResource(false); + } + try { + final int length = "arg".length(); // skip the arg prefix + arg.setPosition(Integer.parseInt(name.substring(length))); + } catch (Exception ex) { + getLog().error("Error parsing Arg position: " + + name + " " + arg + " " + ex); + } + + // Add the arg to the parent field + ((Field)getDigester().peek(0)).addArg(arg); + } + }; + + // Add the rule for each of the arg elements + digester.addRule(ARGS_PATTERN + "0", rule); + digester.addRule(ARGS_PATTERN + "1", rule); + digester.addRule(ARGS_PATTERN + "2", rule); + digester.addRule(ARGS_PATTERN + "3", rule); + + } + + /** + * Add a FormSet to this ValidatorResources + * object. It will be associated with the Locale of the + * FormSet. + * @param fs The form set to add. + * @since Validator 1.1 + */ + public void addFormSet(FormSet fs) { + String key = this.buildKey(fs); + if (key.length() == 0) {// there can only be one default formset + if (getLog().isWarnEnabled() && defaultFormSet != null) { + // warn the user he might not get the expected results + getLog().warn("Overriding default FormSet definition."); + } + defaultFormSet = fs; + } else { + FormSet formset = getFormSets().get(key); + if (formset == null) {// it hasn't been included yet + if (getLog().isDebugEnabled()) { + getLog().debug("Adding FormSet '" + fs.toString() + "'."); + } + } else if (getLog().isWarnEnabled()) {// warn the user he might not + // get the expected results + getLog() + .warn("Overriding FormSet definition. Duplicate for locale: " + + key); + } + getFormSets().put(key, fs); + } + } + + /** + * Add a global constant to the resource. + * @param name The constant name. + * @param value The constant value. + */ + public void addConstant(String name, String value) { + if (getLog().isDebugEnabled()) { + getLog().debug("Adding Global Constant: " + name + "," + value); + } + + this.hConstants.put(name, value); + } + + /** + * Add a ValidatorAction to the resource. It also creates an + * instance of the class based on the ValidatorActions + * classname and retrieves the Method instance and sets them + * in the ValidatorAction. + * @param va The validator action. + */ + public void addValidatorAction(ValidatorAction va) { + va.init(); + + getActions().put(va.getName(), va); + + if (getLog().isDebugEnabled()) { + getLog().debug("Add ValidatorAction: " + va.getName() + "," + va.getClassname()); + } + } + + /** + * Get a ValidatorAction based on it's name. + * @param key The validator action key. + * @return The validator action. + */ + public ValidatorAction getValidatorAction(String key) { + return getActions().get(key); + } + + /** + * Get an unmodifiable Map of the ValidatorActions. + * @return Map of validator actions. + */ + public Map getValidatorActions() { + return Collections.unmodifiableMap(getActions()); + } + + /** + * Builds a key to store the FormSet under based on it's + * language, country, and variant values. + * @param fs The Form Set. + * @return generated key for a formset. + */ + protected String buildKey(FormSet fs) { + return + this.buildLocale(fs.getLanguage(), fs.getCountry(), fs.getVariant()); + } + + /** + * Assembles a Locale code from the given parts. + */ + private String buildLocale(String lang, String country, String variant) { + String key = ((lang != null && lang.length() > 0) ? lang : ""); + key += ((country != null && country.length() > 0) ? "_" + country : ""); + key += ((variant != null && variant.length() > 0) ? "_" + variant : ""); + return key; + } + + /** + *

Gets a Form based on the name of the form and the + * Locale that most closely matches the Locale + * passed in. The order of Locale matching is:

+ *
    + *
  1. language + country + variant
  2. + *
  3. language + country
  4. + *
  5. language
  6. + *
  7. default locale
  8. + *
+ * @param locale The Locale. + * @param formKey The key for the Form. + * @return The validator Form. + * @since Validator 1.1 + */ + public Form getForm(Locale locale, String formKey) { + return this.getForm(locale.getLanguage(), locale.getCountry(), locale + .getVariant(), formKey); + } + + /** + *

Gets a Form based on the name of the form and the + * Locale that most closely matches the Locale + * passed in. The order of Locale matching is:

+ *
    + *
  1. language + country + variant
  2. + *
  3. language + country
  4. + *
  5. language
  6. + *
  7. default locale
  8. + *
+ * @param language The locale's language. + * @param country The locale's country. + * @param variant The locale's language variant. + * @param formKey The key for the Form. + * @return The validator Form. + * @since Validator 1.1 + */ + public Form getForm(String language, String country, String variant, + String formKey) { + + Form form = null; + + // Try language/country/variant + String key = this.buildLocale(language, country, variant); + if (key.length() > 0) { + FormSet formSet = getFormSets().get(key); + if (formSet != null) { + form = formSet.getForm(formKey); + } + } + String localeKey = key; + + + // Try language/country + if (form == null) { + key = buildLocale(language, country, null); + if (key.length() > 0) { + FormSet formSet = getFormSets().get(key); + if (formSet != null) { + form = formSet.getForm(formKey); + } + } + } + + // Try language + if (form == null) { + key = buildLocale(language, null, null); + if (key.length() > 0) { + FormSet formSet = getFormSets().get(key); + if (formSet != null) { + form = formSet.getForm(formKey); + } + } + } + + // Try default formset + if (form == null) { + form = defaultFormSet.getForm(formKey); + key = "default"; + } + + if (form == null) { + if (getLog().isWarnEnabled()) { + getLog().warn("Form '" + formKey + "' not found for locale '" + + localeKey + "'"); + } + } else { + if (getLog().isDebugEnabled()) { + getLog().debug("Form '" + formKey + "' found in formset '" + + key + "' for locale '" + localeKey + "'"); + } + } + + return form; + + } + + /** + * Process the ValidatorResources object. Currently sets the + * FastHashMap s to the 'fast' mode and call the processes + * all other resources. Note : The framework calls this + * automatically when ValidatorResources is created from an XML file. If you + * create an instance of this class by hand you must call + * this method when finished. + */ + public void process() { + hFormSets.setFast(true); + hConstants.setFast(true); + hActions.setFast(true); + + this.processForms(); + } + + /** + *

Process the Form objects. This clones the Fields + * that don't exist in a FormSet compared to its parent + * FormSet.

+ */ + private void processForms() { + if (defaultFormSet == null) {// it isn't mandatory to have a + // default formset + defaultFormSet = new FormSet(); + } + defaultFormSet.process(getConstants()); + // Loop through FormSets and merge if necessary + for (Iterator i = getFormSets().keySet().iterator(); i.hasNext();) { + String key = i.next(); + FormSet fs = getFormSets().get(key); + fs.merge(getParent(fs)); + } + + // Process Fully Constructed FormSets + for (Iterator i = getFormSets().values().iterator(); i.hasNext();) { + FormSet fs = i.next(); + if (!fs.isProcessed()) { + fs.process(getConstants()); + } + } + } + + /** + * Finds the given formSet's parent. ex: A formSet with locale en_UK_TEST1 + * has a direct parent in the formSet with locale en_UK. If it doesn't + * exist, find the formSet with locale en, if no found get the + * defaultFormSet. + * + * @param fs + * the formSet we want to get the parent from + * @return fs's parent + */ + private FormSet getParent(FormSet fs) { + + FormSet parent = null; + if (fs.getType() == FormSet.LANGUAGE_FORMSET) { + parent = defaultFormSet; + } else if (fs.getType() == FormSet.COUNTRY_FORMSET) { + parent = getFormSets().get(buildLocale(fs.getLanguage(), + null, null)); + if (parent == null) { + parent = defaultFormSet; + } + } else if (fs.getType() == FormSet.VARIANT_FORMSET) { + parent = getFormSets().get(buildLocale(fs.getLanguage(), fs + .getCountry(), null)); + if (parent == null) { + parent = getFormSets().get(buildLocale(fs.getLanguage(), + null, null)); + if (parent == null) { + parent = defaultFormSet; + } + } + } + return parent; + } + + /** + *

Gets a FormSet based on the language, country + * and variant.

+ * @param language The locale's language. + * @param country The locale's country. + * @param variant The locale's language variant. + * @return The FormSet for a locale. + * @since Validator 1.2 + */ + FormSet getFormSet(String language, String country, String variant) { + + String key = buildLocale(language, country, variant); + + if (key.length() == 0) { + return defaultFormSet; + } + + return getFormSets().get(key); + } + + /** + * Returns a Map of String locale keys to Lists of their FormSets. + * @return Map of Form sets + * @since Validator 1.2.0 + */ + @SuppressWarnings("unchecked") // FastHashMap is not generic + protected Map getFormSets() { + return hFormSets; + } + + /** + * Returns a Map of String constant names to their String values. + * @return Map of Constants + * @since Validator 1.2.0 + */ + @SuppressWarnings("unchecked") // FastHashMap is not generic + protected Map getConstants() { + return hConstants; + } + + /** + * Returns a Map of String ValidatorAction names to their ValidatorAction. + * @return Map of Validator Actions + * @since Validator 1.2.0 + */ + @SuppressWarnings("unchecked") // FastHashMap is not generic + protected Map getActions() { + return hActions; + } + + /** + * Accessor method for Log instance. + * + * The Log instance variable is transient and + * accessing it through this method ensures it + * is re-initialized when this instance is + * de-serialized. + * + * @return The Log instance. + */ + private Log getLog() { + if (log == null) { + log = LogFactory.getLog(ValidatorResources.class); + } + return log; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResult.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResult.java new file mode 100644 index 000000000..8ee444f12 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResult.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Iterator; + +/** + * This contains the results of a set of validation rules processed + * on a JavaBean. + * + * @version $Revision$ + */ +//TODO mutable non-private fields +public class ValidatorResult implements Serializable { + + private static final long serialVersionUID = -3713364681647250531L; + + /** + * Map of results. The key is the name of the ValidatorAction + * and the value is whether or not this field passed or not. + */ + protected Map hAction = new HashMap(); + + /** + * Field being validated. + * TODO This variable is not used. Need to investigate removing it. + */ + protected Field field = null; + + /** + * Constructs a ValidatorResult with the associated field being + * validated. + * @param field Field that was validated. + */ + public ValidatorResult(Field field) { + this.field = field; + } + + /** + * Add the result of a validator action. + * @param validatorName Name of the validator. + * @param result Whether the validation passed or failed. + */ + public void add(String validatorName, boolean result) { + this.add(validatorName, result, null); + } + + /** + * Add the result of a validator action. + * @param validatorName Name of the validator. + * @param result Whether the validation passed or failed. + * @param value Value returned by the validator. + */ + public void add(String validatorName, boolean result, Object value) { + hAction.put(validatorName, new ResultStatus(result, value)); + } + + /** + * Indicate whether a specified validator is in the Result. + * @param validatorName Name of the validator. + * @return true if the validator is in the result. + */ + public boolean containsAction(String validatorName) { + return hAction.containsKey(validatorName); + } + + /** + * Indicate whether a specified validation passed. + * @param validatorName Name of the validator. + * @return true if the validation passed. + */ + public boolean isValid(String validatorName) { + ResultStatus status = hAction.get(validatorName); + return (status == null) ? false : status.isValid(); + } + + /** + * Return the result of a validation. + * @param validatorName Name of the validator. + * @return The validation result. + */ + public Object getResult(String validatorName) { + ResultStatus status = hAction.get(validatorName); + return (status == null) ? null : status.getResult(); + } + + /** + * Return an Iterator of the action names contained in this Result. + * @return The set of action names. + */ + public Iterator getActions() { + return Collections.unmodifiableMap(hAction).keySet().iterator(); + } + + /** + * Return a Map of the validator actions in this Result. + * @return Map of validator actions. + * @deprecated Use getActions() to return the set of actions + * the isValid(name) and getResult(name) methods + * to determine the contents of ResultStatus. + * + */ + @Deprecated + public Map getActionMap() { + return Collections.unmodifiableMap(hAction); + } + + /** + * Returns the Field that was validated. + * @return The Field associated with this result. + */ + public Field getField() { + return this.field; + } + + /** + * Contains the status of the validation. + */ + protected static class ResultStatus implements Serializable { + + private static final long serialVersionUID = 4076665918535320007L; + + private boolean valid = false; + private Object result = null; + + /** + * Construct a Result status. + * @param valid Whether the validator passed or failed. + * @param result Value returned by the validator. + */ + public ResultStatus(boolean valid, Object result) { + this.valid = valid; + this.result = result; + } + /** + * Provided for backwards binary compatibility only. + * + * @param ignored ignored by this method + * @param valid Whether the validator passed or failed. + * @param result Value returned by the validator. + * + * @deprecated Use {@code ResultStatus(boolean, Object)} instead + */ + @Deprecated + public ResultStatus(ValidatorResult ignored, boolean valid, Object result) { + this(valid, result); + } + + /** + * Tests whether or not the validation passed. + * @return true if the result was good. + */ + public boolean isValid() { + return valid; + } + + /** + * Sets whether or not the validation passed. + * @param valid Whether the validation passed. + */ + public void setValid(boolean valid) { + this.valid = valid; + } + + /** + * Gets the result returned by a validation method. + * This can be used to retrieve to the correctly + * typed value of a date validation for example. + * @return The value returned by the validation. + */ + public Object getResult() { + return result; + } + + /** + * Sets the result returned by a validation method. + * This can be used to retrieve to the correctly + * typed value of a date validation for example. + * @param result The value returned by the validation. + */ + public void setResult(Object result) { + this.result = result; + } + + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResults.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResults.java new file mode 100644 index 000000000..144264ed9 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResults.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * This contains the results of a set of validation rules processed + * on a JavaBean. + * + * @version $Revision$ + */ +//TODO mutable non-private fields +public class ValidatorResults implements Serializable { + + private static final long serialVersionUID = -2709911078904924839L; + + /** + * Map of validation results. + */ + protected Map hResults = new HashMap(); + + /** + * Merge another ValidatorResults into mine. + * + * @param results ValidatorResults to merge. + */ + public void merge(ValidatorResults results) { + this.hResults.putAll(results.hResults); + } + + /** + * Add a the result of a validator action. + * + * @param field The field validated. + * @param validatorName The name of the validator. + * @param result The result of the validation. + */ + public void add(Field field, String validatorName, boolean result) { + this.add(field, validatorName, result, null); + } + + /** + * Add a the result of a validator action. + * + * @param field The field validated. + * @param validatorName The name of the validator. + * @param result The result of the validation. + * @param value The value returned by the validator. + */ + public void add( + Field field, + String validatorName, + boolean result, + Object value) { + + ValidatorResult validatorResult = this.getValidatorResult(field.getKey()); + + if (validatorResult == null) { + validatorResult = new ValidatorResult(field); + this.hResults.put(field.getKey(), validatorResult); + } + + validatorResult.add(validatorName, result, value); + } + + /** + * Clear all results recorded by this object. + */ + public void clear() { + this.hResults.clear(); + } + + /** + * Return true if there are no messages recorded + * in this collection, or false otherwise. + * + * @return Whether these results are empty. + */ + public boolean isEmpty() { + return this.hResults.isEmpty(); + } + + /** + * Gets the ValidatorResult associated + * with the key passed in. The key the ValidatorResult + * is stored under is the Field's getKey method. + * + * @param key The key generated from Field (this is often just + * the field name). + * + * @return The result of a specified key. + */ + public ValidatorResult getValidatorResult(String key) { + return this.hResults.get(key); + } + + /** + * Return the set of property names for which at least one message has + * been recorded. + * @return An unmodifiable Set of the property names. + */ + public Set getPropertyNames() { + return Collections.unmodifiableSet(this.hResults.keySet()); + } + + /** + * Get a Map of any Objects returned from + * validation routines. + * + * @return Map of objections returned by validators. + */ + public Map getResultValueMap() { + Map results = new HashMap(); + + for (Iterator i = hResults.keySet().iterator(); i.hasNext();) { + String propertyKey = i.next(); + ValidatorResult vr = this.getValidatorResult(propertyKey); + + for (Iterator x = vr.getActions(); x.hasNext();) { + String actionKey = x.next(); + Object result = vr.getResult(actionKey); + + if (result != null && !(result instanceof Boolean)) { + results.put(propertyKey, result); + } + } + } + + return results; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Var.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Var.java new file mode 100644 index 000000000..a426609a0 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Var.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.Serializable; + +/** + * A variable that can be associated with a Field for + * passing in information to a pluggable validator. Instances of this class are + * configured with a <var> xml element. + * + * @version $Revision$ + */ +public class Var implements Cloneable, Serializable { + + private static final long serialVersionUID = -684185211548420224L; + + /** + * Int Constant for JavaScript type. This can be used + * when auto-generating JavaScript. + */ + public static final String JSTYPE_INT = "int"; + + /** + * String Constant for JavaScript type. This can be used + * when auto-generating JavaScript. + */ + public static final String JSTYPE_STRING = "string"; + + /** + * Regular Expression Constant for JavaScript type. This can be used + * when auto-generating JavaScript. + */ + public static final String JSTYPE_REGEXP = "regexp"; + + /** + * The name of the variable. + */ + private String name = null; + + /** + * The key or value the variable. + */ + private String value = null; + + /** + * The optional JavaScript type of the variable. + */ + private String jsType = null; + + /** + * Whether the variable is a resource [false] + */ + private boolean resource = false; + + /** + * The bundle for a variable (when resource = 'true'). + */ + private String bundle = null; + + /** + * Default Constructor. + */ + public Var() { + super(); + } + + /** + * Constructs a variable with a specified name, value + * and Javascript type. + * @param name Variable name. + * @param value Variable value. + * @param jsType Variable Javascript type. + */ + public Var(String name, String value, String jsType) { + this.name = name; + this.value = value; + this.jsType = jsType; + } + + /** + * Gets the name of the variable. + * @return The name of the variable. + */ + public String getName() { + return this.name; + } + + /** + * Sets the name of the variable. + * @param name The name of the variable. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the value of the variable. + * @return The value of the variable. + */ + public String getValue() { + return this.value; + } + + /** + * Sets the value of the variable. + * @param value The value of the variable. + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Tests whether or not the value is a resource key or literal value. + * @return true if value is a resource key. + * @since Validator 1.2.0 + */ + public boolean isResource() { + return this.resource; + } + + /** + * Sets whether or not the value is a resource. + * @param resource If true indicates the value is a resource. + * @since Validator 1.2.0 + */ + public void setResource(boolean resource) { + this.resource = resource; + } + + /** + * Returns the resource bundle name. + * @return The bundle name. + * @since Validator 1.2.0 + */ + public String getBundle() { + return this.bundle; + } + + /** + * Sets the resource bundle name. + * @param bundle The new bundle name. + * @since Validator 1.2.0 + */ + public void setBundle(String bundle) { + this.bundle = bundle; + } + + /** + * Gets the JavaScript type of the variable. + * @return The Javascript type of the variable. + */ + public String getJsType() { + return this.jsType; + } + + /** + * Sets the JavaScript type of the variable. + * @param jsType The Javascript type of the variable. + */ + public void setJsType(String jsType) { + this.jsType = jsType; + } + + /** + * Creates and returns a copy of this object. + * @return A copy of the variable. + */ + @Override + public Object clone() { + try { + return super.clone(); + + } catch(CloneNotSupportedException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Returns a string representation of the object. + * @return A string representation of the variable. + */ + @Override + public String toString() { + StringBuilder results = new StringBuilder(); + + results.append("Var: name="); + results.append(name); + results.append(" value="); + results.append(value); + results.append(" resource="); + results.append(resource); + if (resource) { + results.append(" bundle="); + results.append(bundle); + } + results.append(" jsType="); + results.append(jsType); + results.append("\n"); + + return results.toString(); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/package.html b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/package.html new file mode 100644 index 000000000..66ba62510 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/package.html @@ -0,0 +1,244 @@ + + + + Package Documentation for org.apache.commons.validator + + +The Validator package provides validation for JavaBeans based on an xml file. +

+ + + + +

Introduction

+ +

A common issue when receiving data either electronically or from +user input is verifying the integrity of the data. This work is +repetitive and becomes even more complicated when different sets +of validation rules need to be applied to the same set of data based +on locale for example. Error messages may also vary by locale. +This package attempts to address some of these issues and +speed development and maintenance of validation rules. +

+ +

In order to use the Validator, the following basic steps are required:

+
    +
  • Create a new instance of the + org.apache.commons.validator.Validator class. Currently + Validator instances may be safely reused if the current ValidatorResources + are the same, as long as + you have completed any previous validation, and you do not try to utilize + a particular Validator instance from more than one thread at a time.
  • +
  • Add any resources + needed to perform the validations. Such as the JavaBean to validate.
  • +
  • Call the validate method on org.apache.commons.validator.Validator.
  • +
+ + +

Overview

+

+ The Commons Validator is a basic validation framework that + lets you define validation rules for a JavaBean in an xml file. + Validators, the validation definition, can also be defined in + the xml file. An example of a validator would be defining + what method and class will be called to perform the validation + for a required field. Validation rules can be grouped together + based on locale and a JavaBean/Form that the rules are associated + with. The framework has basic support for user defined constants + which can be used in some field attributes. +

+

+ Validation rules can be defined in an xml file which keeps + them abstracted from JavaBean you are validating. The + property reference to a field supports nested properties + using the Apache Commons BeanUtils + (http://commons.apache.org/beanutils/) package. + Error messages and the arguments for error messages can be + associated with a fields validation. +

+ + +

Resources

+

+ After a Validator instance is created, instances of + classes can be added to it to be passed into + validation methods by calling the setParameter() + method. Below is a list of reserved parameters (class names). +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Reserved Parameters
Class NameValidator ContstantDescription
java.lang.ObjectValidator.BEAN_PARAMJavaBean that is being validated
java.util.LocaleValidator.LOCALE_PARAM + Locale to use when retrieving a FormSet. + The default locale will be used if one + isn't specified. +
org.apache.commons.validator.ValidatorActionValidator.VALIDATOR_ACTION_PARAM + This is automatically added to a Validator's + resources as a validation is being processed. + If this class name is used when defining + a method signature for a pluggable validator, + the current ValidatorAction will be passed into + the validation method. +
org.apache.commons.validator.FieldValidator.FIELD_PARAM + This is automatically added to a Validator's + resources as a validation is being processed. + If this class name is used when defining + a method signature for a pluggable validator, + the current Field will be passed into + the validation method. +
+ + + +

Usage Example

+

+ This is a basic example setting up a required validator for + a name bean. This example is a working unit test (reference + org.apache.commons.validator.RequiredNameTest and + validator-name-required.xml located under validator/src/test). +

+

+ Create an xml file with your validator and validation rules. + Setup your required validator in your xml file.
+
+ XML Example
+ Validator Example
+ Pluggable Validator Example +

+ + +

XML Example

+

+ Definition of a 'required' pluggable validator.
+

+<form-validation>
+   <global>
+      <validator name="required"
+         classname="org.apache.commons.validator.TestValidator"
+         method="validateRequired"
+         methodParams="java.lang.Object, org.apache.commons.validator.Field"/>
+   </global>
+   <formset>
+   </formset>
+</form-validation>
+
+ +

+ Add validation rules to require a first name and a last name.
+

+<form-validation>
+   <global>
+      <validator name="required"
+         classname="org.apache.commons.validator.TestValidator"
+         method="validateRequired"
+         methodParams="java.lang.Object, org.apache.commons.validator.Field"/>
+   </global>
+
+   <formset>
+      <form    name="nameForm">
+         <field property="firstName" depends="required">
+            <arg0 key="nameForm.firstname.displayname"/>
+         </field>
+         <field property="lastName" depends="required">
+            <arg0 key="nameForm.lastname.displayname"/>
+         </field>
+      </form>
+   </formset>
+
+</form-validation>
+
+ + +

Validator Example

+

+Excerpts from org.apache.commons.validator.RequiredNameTest +

+
+InputStream in = this.getClass().getResourceAsStream("validator-name-required.xml");
+
+// Create an instance of ValidatorResources to initialize from an xml file.
+ValidatorResources resources = new ValidatorResources(in);
+// Create bean to run test on.
+Name name = new Name();
+
+// Construct validator based on the loaded resources and the form key
+Validator validator = new Validator(resources, "nameForm");
+// add the name bean to the validator as a resource
+// for the validations to be performed on.
+validator.setParameter(Validator.BEAN_PARAM, name);
+
+// Get results of the validation.
+Map results = null;
+
+// throws ValidatorException (catch clause not shown here)
+results = validator.validate();
+
+if (results.get("firstName") == null) {
+   // no error
+} else {
+   // number of errors for first name
+   int errors = ((Integer)results.get("firstName")).intValue();
+}
+
+ + +

Pluggable Validator Example

+

+Validation method defined in the 'required' pluggable validator +(excerpt from org.apache.commons.validator.TestValidator). +

+ +
+public static boolean validateRequired(Object bean, Field field) {
+   String value = ValidatorUtil.getValueAsString(bean, field.getProperty());
+      return GenericValidator.isBlankOrNull(value);
+}
+
+ + diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractCalendarValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractCalendarValidator.java new file mode 100644 index 000000000..48d5ff172 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractCalendarValidator.java @@ -0,0 +1,427 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.DateFormatSymbols; +import java.text.Format; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + *

Abstract class for Date/Time/Calendar validation.

+ * + *

This is a base class for building Date / Time + * Validators using format parsing.

+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public abstract class AbstractCalendarValidator extends AbstractFormatValidator { + + private static final long serialVersionUID = -1410008585975827379L; + + private final int dateStyle; + + private final int timeStyle; + + /** + * Construct an instance with the specified strict, + * time and date style parameters. + * + * @param strict true if strict + * Format parsing should be used. + * @param dateStyle the date style to use for Locale validation. + * @param timeStyle the time style to use for Locale validation. + */ + public AbstractCalendarValidator(boolean strict, int dateStyle, int timeStyle) { + super(strict); + this.dateStyle = dateStyle; + this.timeStyle = timeStyle; + } + + /** + *

Validate using the specified Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to format the value. + * @param locale The locale to use for the Format, defaults to the default + * @return true if the value is valid. + */ + @Override + public boolean isValid(String value, String pattern, Locale locale) { + Object parsedValue = parse(value, pattern, locale, (TimeZone)null); + return (parsedValue == null ? false : true); + } + + /** + *

Format an object into a String using + * the default Locale.

+ * + * @param value The value validation is being performed on. + * @param timeZone The Time Zone used to format the date, + * system default if null (unless value is a Calendar. + * @return The value formatted as a String. + */ + public String format(Object value, TimeZone timeZone) { + return format(value, (String)null, (Locale)null, timeZone); + } + + /** + *

Format an object into a String using + * the specified pattern.

+ * + * @param value The value validation is being performed on. + * @param pattern The pattern used to format the value. + * @param timeZone The Time Zone used to format the date, + * system default if null (unless value is a Calendar. + * @return The value formatted as a String. + */ + public String format(Object value, String pattern, TimeZone timeZone) { + return format(value, pattern, (Locale)null, timeZone); + } + + /** + *

Format an object into a String using + * the specified Locale.

+ * + * @param value The value validation is being performed on. + * @param locale The locale to use for the Format. + * @param timeZone The Time Zone used to format the date, + * system default if null (unless value is a Calendar. + * @return The value formatted as a String. + */ + public String format(Object value, Locale locale, TimeZone timeZone) { + return format(value, (String)null, locale, timeZone); + } + + /** + *

Format an object using the specified pattern and/or + * Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to format the value. + * @param locale The locale to use for the Format. + * @return The value formatted as a String. + */ + @Override + public String format(Object value, String pattern, Locale locale) { + return format(value, pattern, locale, (TimeZone)null); + } + + /** + *

Format an object using the specified pattern and/or + * Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to format the value. + * @param locale The locale to use for the Format. + * @param timeZone The Time Zone used to format the date, + * system default if null (unless value is a Calendar. + * @return The value formatted as a String. + */ + public String format(Object value, String pattern, Locale locale, TimeZone timeZone) { + DateFormat formatter = (DateFormat)getFormat(pattern, locale); + if (timeZone != null) { + formatter.setTimeZone(timeZone); + } else if (value instanceof Calendar) { + formatter.setTimeZone(((Calendar)value).getTimeZone()); + } + return format(value, formatter); + } + + /** + *

Format a value with the specified DateFormat.

+ * + * @param value The value to be formatted. + * @param formatter The Format to use. + * @return The formatted value. + */ + @Override + protected String format(Object value, Format formatter) { + if (value == null) { + return null; + } else if (value instanceof Calendar) { + value = ((Calendar)value).getTime(); + } + return formatter.format(value); + } + + /** + *

Checks if the value is valid against a specified pattern.

+ * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed value if valid or null if invalid. + */ + protected Object parse(String value, String pattern, Locale locale, TimeZone timeZone) { + + value = (value == null ? null : value.trim()); + if (value == null || value.length() == 0) { + return null; + } + DateFormat formatter = (DateFormat)getFormat(pattern, locale); + if (timeZone != null) { + formatter.setTimeZone(timeZone); + } + return parse(value, formatter); + + } + + /** + *

Process the parsed value, performing any further validation + * and type conversion required.

+ * + * @param value The parsed object created. + * @param formatter The Format used to parse the value with. + * @return The parsed value converted to the appropriate type + * if valid or null if invalid. + */ + @Override + protected abstract Object processParsedValue(Object value, Format formatter); + + /** + *

Returns a DateFormat for the specified pattern + * and/or Locale.

+ * + * @param pattern The pattern used to validate the value against or + * null to use the default for the Locale. + * @param locale The locale to use for the currency format, system default if null. + * @return The DateFormat to created. + */ + @Override + protected Format getFormat(String pattern, Locale locale) { + DateFormat formatter = null; + boolean usePattern = (pattern != null && pattern.length() > 0); + if (!usePattern) { + formatter = (DateFormat)getFormat(locale); + } else if (locale == null) { + formatter = new SimpleDateFormat(pattern); + } else { + DateFormatSymbols symbols = new DateFormatSymbols(locale); + formatter = new SimpleDateFormat(pattern, symbols); + } + formatter.setLenient(false); + return formatter; + } + + /** + *

Returns a DateFormat for the specified Locale.

+ * + * @param locale The locale a DateFormat is required for, + * system default if null. + * @return The DateFormat to created. + */ + protected Format getFormat(Locale locale) { + + DateFormat formatter = null; + if (dateStyle >= 0 && timeStyle >= 0) { + if (locale == null) { + formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle); + } else { + formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale); + } + } else if (timeStyle >= 0) { + if (locale == null) { + formatter = DateFormat.getTimeInstance(timeStyle); + } else { + formatter = DateFormat.getTimeInstance(timeStyle, locale); + } + } else { + int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT; + if (locale == null) { + formatter = DateFormat.getDateInstance(useDateStyle); + } else { + formatter = DateFormat.getDateInstance(useDateStyle, locale); + } + } + formatter.setLenient(false); + return formatter; + + } + + /** + *

Compares a calendar value to another, indicating whether it is + * equal, less then or more than at a specified level.

+ * + * @param value The Calendar value. + * @param compare The Calendar to check the value against. + * @param field The field level to compare to - e.g. specifying + * Calendar.MONTH will compare the year and month + * portions of the calendar. + * @return Zero if the first value is equal to the second, -1 + * if it is less than the second or +1 if it is greater than the second. + */ + protected int compare(Calendar value, Calendar compare, int field) { + + int result = 0; + + // Compare Year + result = calculateCompareResult(value, compare, Calendar.YEAR); + if (result != 0 || field == Calendar.YEAR) { + return result; + } + + // Compare Week of Year + if (field == Calendar.WEEK_OF_YEAR) { + return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR); + } + + // Compare Day of the Year + if (field == Calendar.DAY_OF_YEAR) { + return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR); + } + + // Compare Month + result = calculateCompareResult(value, compare, Calendar.MONTH); + if (result != 0 || field == Calendar.MONTH) { + return result; + } + + // Compare Week of Month + if (field == Calendar.WEEK_OF_MONTH) { + return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH); + } + + // Compare Date + result = calculateCompareResult(value, compare, Calendar.DATE); + if (result != 0 || (field == Calendar.DATE || + field == Calendar.DAY_OF_WEEK || + field == Calendar.DAY_OF_WEEK_IN_MONTH)) { + return result; + } + + // Compare Time fields + return compareTime(value, compare, field); + + } + + /** + *

Compares a calendar time value to another, indicating whether it is + * equal, less then or more than at a specified level.

+ * + * @param value The Calendar value. + * @param compare The Calendar to check the value against. + * @param field The field level to compare to - e.g. specifying + * Calendar.MINUTE will compare the hours and minutes + * portions of the calendar. + * @return Zero if the first value is equal to the second, -1 + * if it is less than the second or +1 if it is greater than the second. + */ + protected int compareTime(Calendar value, Calendar compare, int field) { + + int result = 0; + + // Compare Hour + result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY); + if (result != 0 || (field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY)) { + return result; + } + + // Compare Minute + result = calculateCompareResult(value, compare, Calendar.MINUTE); + if (result != 0 || field == Calendar.MINUTE) { + return result; + } + + // Compare Second + result = calculateCompareResult(value, compare, Calendar.SECOND); + if (result != 0 || field == Calendar.SECOND) { + return result; + } + + // Compare Milliseconds + if (field == Calendar.MILLISECOND) { + return calculateCompareResult(value, compare, Calendar.MILLISECOND); + } + + throw new IllegalArgumentException("Invalid field: " + field); + + } + + /** + *

Compares a calendar's quarter value to another, indicating whether it is + * equal, less then or more than the specified quarter.

+ * + * @param value The Calendar value. + * @param compare The Calendar to check the value against. + * @param monthOfFirstQuarter The month that the first quarter starts. + * @return Zero if the first quarter is equal to the second, -1 + * if it is less than the second or +1 if it is greater than the second. + */ + protected int compareQuarters(Calendar value, Calendar compare, int monthOfFirstQuarter) { + int valueQuarter = calculateQuarter(value, monthOfFirstQuarter); + int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter); + if (valueQuarter < compareQuarter) { + return -1; + } else if (valueQuarter > compareQuarter) { + return 1; + } else { + return 0; + } + } + + /** + *

Calculate the quarter for the specified Calendar.

+ * + * @param calendar The Calendar value. + * @param monthOfFirstQuarter The month that the first quarter starts. + * @return The calculated quarter. + */ + private int calculateQuarter(Calendar calendar, int monthOfFirstQuarter) { + // Add Year + int year = calendar.get(Calendar.YEAR); + + int month = (calendar.get(Calendar.MONTH) + 1); + int relativeMonth = (month >= monthOfFirstQuarter) + ? (month - monthOfFirstQuarter) + : (month + (12 - monthOfFirstQuarter)); // CHECKSTYLE IGNORE MagicNumber + int quarter = ((relativeMonth / 3) + 1); // CHECKSTYLE IGNORE MagicNumber + // adjust the year if the quarter doesn't start in January + if (month < monthOfFirstQuarter) { + --year; + } + return (year * 10) + quarter; // CHECKSTYLE IGNORE MagicNumber + } + + /** + *

Compares the field from two calendars indicating whether the field for the + * first calendar is equal to, less than or greater than the field from the + * second calendar. + * + * @param value The Calendar value. + * @param compare The Calendar to check the value against. + * @param field The field to compare for the calendars. + * @return Zero if the first calendar's field is equal to the seconds, -1 + * if it is less than the seconds or +1 if it is greater than the seconds. + */ + private int calculateCompareResult(Calendar value, Calendar compare, int field) { + int difference = value.get(field) - compare.get(field); + if (difference < 0) { + return -1; + } else if (difference > 0) { + return 1; + } else { + return 0; + } + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractFormatValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractFormatValidator.java new file mode 100644 index 000000000..1eff3cebd --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractFormatValidator.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.Format; +import java.text.ParsePosition; +import java.util.Locale; +import java.io.Serializable; + +/** + *

Abstract class for Format based Validation.

+ * + *

This is a base class for building Date and Number + * Validators using format parsing.

+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public abstract class AbstractFormatValidator implements Serializable { + + private static final long serialVersionUID = -4690687565200568258L; + + private final boolean strict; + + /** + * Construct an instance with the specified strict setting. + * + * @param strict true if strict + * Format parsing should be used. + */ + public AbstractFormatValidator(boolean strict) { + this.strict = strict; + } + + /** + *

Indicates whether validated values should adhere + * strictly to the Format used.

+ * + *

Typically implementations of Format + * ignore invalid characters at the end of the value + * and just stop parsing. For example parsing a date + * value of 01/01/20x0 using a pattern + * of dd/MM/yyyy will result in a year + * of 20 if strict is set + * to false, whereas setting strict + * to true will cause this value to fail + * validation.

+ * + * @return true if strict Format + * parsing should be used. + */ + public boolean isStrict() { + return strict; + } + + /** + *

Validate using the default Locale. + * + * @param value The value validation is being performed on. + * @return true if the value is valid. + */ + public boolean isValid(String value) { + return isValid(value, (String)null, (Locale)null); + } + + /** + *

Validate using the specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return true if the value is valid. + */ + public boolean isValid(String value, String pattern) { + return isValid(value, pattern, (Locale)null); + } + + /** + *

Validate using the specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the Format, defaults to the default + * @return true if the value is valid. + */ + public boolean isValid(String value, Locale locale) { + return isValid(value, (String)null, locale); + } + + /** + *

Validate using the specified pattern and/or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to format the value. + * @param locale The locale to use for the Format, defaults to the default + * @return true if the value is valid. + */ + public abstract boolean isValid(String value, String pattern, Locale locale); + + /** + *

Format an object into a String using + * the default Locale.

+ * + * @param value The value validation is being performed on. + * @return The value formatted as a String. + */ + public String format(Object value) { + return format(value, (String)null, (Locale)null); + } + + /** + *

Format an object into a String using + * the specified pattern.

+ * + * @param value The value validation is being performed on. + * @param pattern The pattern used to format the value. + * @return The value formatted as a String. + */ + public String format(Object value, String pattern) { + return format(value, pattern, (Locale)null); + } + + /** + *

Format an object into a String using + * the specified Locale.

+ * + * @param value The value validation is being performed on. + * @param locale The locale to use for the Format. + * @return The value formatted as a String. + */ + public String format(Object value, Locale locale) { + return format(value, (String)null, locale); + } + + /** + *

Format an object using the specified pattern and/or + * Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to format the value. + * @param locale The locale to use for the Format. + * @return The value formatted as a String. + */ + public String format(Object value, String pattern, Locale locale) { + Format formatter = getFormat(pattern, locale); + return format(value, formatter); + } + + /** + *

Format a value with the specified Format.

+ * + * @param value The value to be formatted. + * @param formatter The Format to use. + * @return The formatted value. + */ + protected String format(Object value, Format formatter) { + return formatter.format(value); + } + + /** + *

Parse the value with the specified Format.

+ * + * @param value The value to be parsed. + * @param formatter The Format to parse the value with. + * @return The parsed value if valid or null if invalid. + */ + protected Object parse(String value, Format formatter) { + + ParsePosition pos = new ParsePosition(0); + Object parsedValue = formatter.parseObject(value, pos); + if (pos.getErrorIndex() > -1) { + return null; + } + + if (isStrict() && pos.getIndex() < value.length()) { + return null; + } + + if (parsedValue != null) { + parsedValue = processParsedValue(parsedValue, formatter); + } + + return parsedValue; + + } + + /** + *

Process the parsed value, performing any further validation + * and type conversion required.

+ * + * @param value The parsed object created. + * @param formatter The Format used to parse the value with. + * @return The parsed value converted to the appropriate type + * if valid or null if invalid. + */ + protected abstract Object processParsedValue(Object value, Format formatter); + + /** + *

Returns a Format for the specified pattern + * and/or Locale.

+ * + * @param pattern The pattern used to validate the value against or + * null to use the default for the Locale. + * @param locale The locale to use for the currency format, system default if null. + * @return The NumberFormat to created. + */ + protected abstract Format getFormat(String pattern, Locale locale); + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractNumberValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractNumberValidator.java new file mode 100644 index 000000000..f8c6cce23 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractNumberValidator.java @@ -0,0 +1,277 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.DecimalFormatSymbols; +import java.text.Format; +import java.text.NumberFormat; +import java.text.DecimalFormat; +import java.util.Locale; + +/** + *

Abstract class for Number Validation.

+ * + *

This is a base class for building Number + * Validators using format parsing.

+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public abstract class AbstractNumberValidator extends AbstractFormatValidator { + + private static final long serialVersionUID = -3088817875906765463L; + + /** Standard NumberFormat type */ + public static final int STANDARD_FORMAT = 0; + + /** Currency NumberFormat type */ + public static final int CURRENCY_FORMAT = 1; + + /** Percent NumberFormat type */ + public static final int PERCENT_FORMAT = 2; + + private final boolean allowFractions; + private final int formatType; + + /** + * Construct an instance with specified strict + * and decimal parameters. + * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + * @param allowFractions true if fractions are + * allowed or false if integers only. + */ + public AbstractNumberValidator(boolean strict, int formatType, boolean allowFractions) { + super(strict); + this.allowFractions = allowFractions; + this.formatType = formatType; + } + + /** + *

Indicates whether the number being validated is + * a decimal or integer.

+ * + * @return true if decimals are allowed + * or false if the number is an integer. + */ + public boolean isAllowFractions() { + return allowFractions; + } + + /** + *

Indicates the type of NumberFormat created + * by this validator instance.

+ * + * @return the format type created. + */ + public int getFormatType() { + return formatType; + } + + /** + *

Validate using the specified Locale.

+ * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return true if the value is valid. + */ + @Override + public boolean isValid(String value, String pattern, Locale locale) { + Object parsedValue = parse(value, pattern, locale); + return (parsedValue == null ? false : true); + } + + /** + * Check if the value is within a specified range. + * + * @param value The value validation is being performed on. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(Number value, Number min, Number max) { + return (minValue(value, min) && maxValue(value, max)); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(Number value, Number min) { + if (isAllowFractions()) { + return (value.doubleValue() >= min.doubleValue()); + } + return (value.longValue() >= min.longValue()); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(Number value, Number max) { + if (isAllowFractions()) { + return (value.doubleValue() <= max.doubleValue()); + } + return (value.longValue() <= max.longValue()); + } + + /** + *

Parse the value using the specified pattern.

+ * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed value if valid or null if invalid. + */ + protected Object parse(String value, String pattern, Locale locale) { + + value = (value == null ? null : value.trim()); + if (value == null || value.length() == 0) { + return null; + } + Format formatter = getFormat(pattern, locale); + return parse(value, formatter); + + } + + /** + *

Process the parsed value, performing any further validation + * and type conversion required.

+ * + * @param value The parsed object created. + * @param formatter The Format used to parse the value with. + * @return The parsed value converted to the appropriate type + * if valid or null if invalid. + */ + @Override + protected abstract Object processParsedValue(Object value, Format formatter); + + /** + *

Returns a NumberFormat for the specified pattern + * and/or Locale.

+ * + * @param pattern The pattern used to validate the value against or + * null to use the default for the Locale. + * @param locale The locale to use for the currency format, system default if null. + * @return The NumberFormat to created. + */ + @Override + protected Format getFormat(String pattern, Locale locale) { + + NumberFormat formatter = null; + boolean usePattern = (pattern != null && pattern.length() > 0); + if (!usePattern) { + formatter = (NumberFormat)getFormat(locale); + } else if (locale == null) { + formatter = new DecimalFormat(pattern); + } else { + DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale); + formatter = new DecimalFormat(pattern, symbols); + } + + if (!isAllowFractions()) { + formatter.setParseIntegerOnly(true); + } + return formatter; + } + + /** + *

Returns the multiplier of the NumberFormat.

+ * + * @param format The NumberFormat to determine the + * multiplier of. + * @return The multiplying factor for the format.. + */ + protected int determineScale(NumberFormat format) { + if (!isStrict()) { + return -1; + } + if (!isAllowFractions() || format.isParseIntegerOnly()) { + return 0; + } + int minimumFraction = format.getMinimumFractionDigits(); + int maximumFraction = format.getMaximumFractionDigits(); + if (minimumFraction != maximumFraction) { + return -1; + } + int scale = minimumFraction; + if (format instanceof DecimalFormat) { + int multiplier = ((DecimalFormat)format).getMultiplier(); + if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber + scale += 2; // CHECKSTYLE IGNORE MagicNumber + } else if (multiplier == 1000) { // CHECKSTYLE IGNORE MagicNumber + scale += 3; // CHECKSTYLE IGNORE MagicNumber + } + } else if (formatType == PERCENT_FORMAT) { + scale += 2; // CHECKSTYLE IGNORE MagicNumber + } + return scale; + } + + /** + *

Returns a NumberFormat for the specified Locale.

+ * + * @param locale The locale a NumberFormat is required for, + * system default if null. + * @return The NumberFormat to created. + */ + protected Format getFormat(Locale locale) { + NumberFormat formatter = null; + switch (formatType) { + case CURRENCY_FORMAT: + if (locale == null) { + formatter = NumberFormat.getCurrencyInstance(); + } else { + formatter = NumberFormat.getCurrencyInstance(locale); + } + break; + case PERCENT_FORMAT: + if (locale == null) { + formatter = NumberFormat.getPercentInstance(); + } else { + formatter = NumberFormat.getPercentInstance(locale); + } + break; + default: + if (locale == null) { + formatter = NumberFormat.getInstance(); + } else { + formatter = NumberFormat.getInstance(locale); + } + if (!isAllowFractions()) { + formatter.setParseIntegerOnly(true); + } + break; + } + return formatter; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/BigDecimalValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/BigDecimalValidator.java new file mode 100644 index 000000000..d12eeb293 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/BigDecimalValidator.java @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.math.BigDecimal; +import java.text.Format; +import java.text.NumberFormat; +import java.util.Locale; + +/** + *

BigDecimal Validation and Conversion routines (java.math.BigDecimal).

+ * + *

This validator provides a number of methods for + * validating/converting a String value to + * a BigDecimal using java.text.NumberFormat + * to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted BigDecimal value.

+ * + *

Fraction/decimal values are automatically trimmed to the appropriate length.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform minimum, maximum and range checks:

+ *
    + *
  • minValue() checks whether the value is greater + * than or equal to a specified minimum.
  • + *
  • maxValue() checks whether the value is less + * than or equal to a specified maximum.
  • + *
  • isInRange() checks whether the value is within + * a specified range of values.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class BigDecimalValidator extends AbstractNumberValidator { + + private static final long serialVersionUID = -670320911490506772L; + + private static final BigDecimalValidator VALIDATOR = new BigDecimalValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the BigDecimalValidator. + */ + public static BigDecimalValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public BigDecimalValidator() { + this(true); + } + + /** + *

Construct an instance with the specified strict setting.

+ * + * @param strict true if strict + * Format parsing should be used. + */ + public BigDecimalValidator(boolean strict) { + this(strict, STANDARD_FORMAT, true); + } + + /** + *

Construct an instance with the specified strict setting + * and format type.

+ * + *

The formatType specified what type of + * NumberFormat is created - valid types + * are:

+ *
    + *
  • AbstractNumberValidator.STANDARD_FORMAT -to create + * standard number formats (the default).
  • + *
  • AbstractNumberValidator.CURRENCY_FORMAT -to create + * currency number formats.
  • + *
  • AbstractNumberValidator.PERCENT_FORMAT -to create + * percent number formats (the default).
  • + *
+ * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + * @param allowFractions true if fractions are + * allowed or false if integers only. + */ + protected BigDecimalValidator(boolean strict, int formatType, + boolean allowFractions) { + super(strict, formatType, allowFractions); + } + + /** + *

Validate/convert a BigDecimal using the default + * Locale. + * + * @param value The value validation is being performed on. + * @return The parsed BigDecimal if valid or null + * if invalid. + */ + public BigDecimal validate(String value) { + return (BigDecimal)parse(value, (String)null, (Locale)null); + } + + /** + *

Validate/convert a BigDecimal using the + * specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @return The parsed BigDecimal if valid or null if invalid. + */ + public BigDecimal validate(String value, String pattern) { + return (BigDecimal)parse(value, pattern, (Locale)null); + } + + /** + *

Validate/convert a BigDecimal using the + * specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the number format, system default if null. + * @return The parsed BigDecimal if valid or null if invalid. + */ + public BigDecimal validate(String value, Locale locale) { + return (BigDecimal)parse(value, (String)null, locale); + } + + /** + *

Validate/convert a BigDecimal using the + * specified pattern and/ or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed BigDecimal if valid or null if invalid. + */ + public BigDecimal validate(String value, String pattern, Locale locale) { + return (BigDecimal)parse(value, pattern, locale); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(BigDecimal value, double min, double max) { + return (value.doubleValue() >= min && value.doubleValue() <= max); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(BigDecimal value, double min) { + return (value.doubleValue() >= min); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(BigDecimal value, double max) { + return (value.doubleValue() <= max); + } + + /** + * Convert the parsed value to a BigDecimal. + * + * @param value The parsed Number object created. + * @param formatter The Format used to parse the value with. + * @return The parsed Number converted to a + * BigDecimal. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + BigDecimal decimal = null; + if (value instanceof Long) { + decimal = BigDecimal.valueOf(((Long)value).longValue()); + } else { + decimal = new BigDecimal(value.toString()); + } + + int scale = determineScale((NumberFormat)formatter); + if (scale >= 0) { + decimal = decimal.setScale(scale, BigDecimal.ROUND_DOWN); + } + + return decimal; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/BigIntegerValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/BigIntegerValidator.java new file mode 100644 index 000000000..32cdd5b06 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/BigIntegerValidator.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.validator.routines; + +import java.math.BigInteger; +import java.text.Format; +import java.util.Locale; + +/** + *

BigInteger Validation and Conversion routines (java.math.BigInteger).

+ * + *

This validator provides a number of methods for + * validating/converting a String value to + * a BigInteger using java.text.NumberFormat + * to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted BigInteger value.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform minimum, maximum and range checks:

+ *
    + *
  • minValue() checks whether the value is greater + * than or equal to a specified minimum.
  • + *
  • maxValue() checks whether the value is less + * than or equal to a specified maximum.
  • + *
  • isInRange() checks whether the value is within + * a specified range of values.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class BigIntegerValidator extends AbstractNumberValidator { + + private static final long serialVersionUID = 6713144356347139988L; + + private static final BigIntegerValidator VALIDATOR = new BigIntegerValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the BigIntegerValidator. + */ + public static BigIntegerValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public BigIntegerValidator() { + this(true, STANDARD_FORMAT); + } + + /** + *

Construct an instance with the specified strict setting + * and format type.

+ * + *

The formatType specified what type of + * NumberFormat is created - valid types + * are:

+ *
    + *
  • AbstractNumberValidator.STANDARD_FORMAT -to create + * standard number formats (the default).
  • + *
  • AbstractNumberValidator.CURRENCY_FORMAT -to create + * currency number formats.
  • + *
  • AbstractNumberValidator.PERCENT_FORMAT -to create + * percent number formats (the default).
  • + *
+ * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + */ + public BigIntegerValidator(boolean strict, int formatType) { + super(strict, formatType, false); + } + + /** + *

Validate/convert a BigInteger using the default + * Locale. + * + * @param value The value validation is being performed on. + * @return The parsed BigInteger if valid or null + * if invalid. + */ + public BigInteger validate(String value) { + return (BigInteger)parse(value, (String)null, (Locale)null); + } + + /** + *

Validate/convert a BigInteger using the + * specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed BigInteger if valid or null if invalid. + */ + public BigInteger validate(String value, String pattern) { + return (BigInteger)parse(value, pattern, (Locale)null); + } + + /** + *

Validate/convert a BigInteger using the + * specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the number format, system default if null. + * @return The parsed BigInteger if valid or null if invalid. + */ + public BigInteger validate(String value, Locale locale) { + return (BigInteger)parse(value, (String)null, locale); + } + + /** + *

Validate/convert a BigInteger using the + * specified pattern and/ or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed BigInteger if valid or null if invalid. + */ + public BigInteger validate(String value, String pattern, Locale locale) { + return (BigInteger)parse(value, pattern, locale); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(BigInteger value, long min, long max) { + return (value.longValue() >= min && value.longValue() <= max); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(BigInteger value, long min) { + return (value.longValue() >= min); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(BigInteger value, long max) { + return (value.longValue() <= max); + } + + /** + * Convert the parsed value to a BigInteger. + * + * @param value The parsed Number object created. + * @param formatter The Format used to parse the value with. + * @return The parsed Number converted to a + * BigInteger. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + return BigInteger.valueOf(((Number)value).longValue()); + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ByteValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ByteValidator.java new file mode 100644 index 000000000..fb638ff4f --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ByteValidator.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.Format; +import java.util.Locale; + +/** + *

Byte Validation and Conversion routines (java.lang.Byte).

+ * + *

This validator provides a number of methods for + * validating/converting a String value to + * a Byte using java.text.NumberFormat + * to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Byte value.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform minimum, maximum and range checks:

+ *
    + *
  • minValue() checks whether the value is greater + * than or equal to a specified minimum.
  • + *
  • maxValue() checks whether the value is less + * than or equal to a specified maximum.
  • + *
  • isInRange() checks whether the value is within + * a specified range of values.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class ByteValidator extends AbstractNumberValidator { + + private static final long serialVersionUID = 7001640945881854649L; + + private static final ByteValidator VALIDATOR = new ByteValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the ByteValidator. + */ + public static ByteValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public ByteValidator() { + this(true, STANDARD_FORMAT); + } + + /** + *

Construct an instance with the specified strict setting + * and format type.

+ * + *

The formatType specified what type of + * NumberFormat is created - valid types + * are:

+ *
    + *
  • AbstractNumberValidator.STANDARD_FORMAT -to create + * standard number formats (the default).
  • + *
  • AbstractNumberValidator.CURRENCY_FORMAT -to create + * currency number formats.
  • + *
  • AbstractNumberValidator.PERCENT_FORMAT -to create + * percent number formats (the default).
  • + *
+ * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + */ + public ByteValidator(boolean strict, int formatType) { + super(strict, formatType, false); + } + + /** + *

Validate/convert a Byte using the default + * Locale. + * + * @param value The value validation is being performed on. + * @return The parsed Byte if valid or null + * if invalid. + */ + public Byte validate(String value) { + return (Byte)parse(value, (String)null, (Locale)null); + } + + /** + *

Validate/convert a Byte using the + * specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed Byte if valid or null if invalid. + */ + public Byte validate(String value, String pattern) { + return (Byte)parse(value, pattern, (Locale)null); + } + + /** + *

Validate/convert a Byte using the + * specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the number format, system default if null. + * @return The parsed Byte if valid or null if invalid. + */ + public Byte validate(String value, Locale locale) { + return (Byte)parse(value, (String)null, locale); + } + + /** + *

Validate/convert a Byte using the + * specified pattern and/ or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Byte if valid or null if invalid. + */ + public Byte validate(String value, String pattern, Locale locale) { + return (Byte)parse(value, pattern, locale); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(byte value, byte min, byte max) { + return (value >= min && value <= max); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(Byte value, byte min, byte max) { + return isInRange(value.byteValue(), min, max); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(byte value, byte min) { + return (value >= min); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(Byte value, byte min) { + return minValue(value.byteValue(), min); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(byte value, byte max) { + return (value <= max); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(Byte value, byte max) { + return maxValue(value.byteValue(), max); + } + + /** + *

Perform further validation and convert the Number to + * a Byte.

+ * + * @param value The parsed Number object created. + * @param formatter The Format used to parse the value with. + * @return The parsed Number converted to a + * Byte if valid or null if invalid. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + + // Parsed value will be Long if it fits in a long and is not fractional + if (value instanceof Long) { + long longValue = ((Long)value).longValue(); + if (longValue >= Byte.MIN_VALUE && + longValue <= Byte.MAX_VALUE) { + return Byte.valueOf((byte)longValue); + } + } + return null; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CalendarValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CalendarValidator.java new file mode 100644 index 000000000..1e94114b2 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CalendarValidator.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.validator.routines; + +import java.text.DateFormat; +import java.text.Format; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + *

Calendar Validation and Conversion routines (java.util.Calendar).

+ * + *

This validator provides a number of methods for validating/converting + * a String date value to a java.util.Calendar using + * java.text.DateFormat to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

For each of the above mechanisms, conversion method (i.e the + * validate methods) implementations are provided which + * either use the default TimeZone or allow the + * TimeZone to be specified.

+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Calendar value.

+ * + *

Implementations of the validate() method are provided + * to create Calendar objects for different time zones + * if the system default is not appropriate.

+ * + *

Alternatively the CalendarValidator's adjustToTimeZone() method + * can be used to adjust the TimeZone of the Calendar + * object afterwards.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform various date comparison checks:

+ *
    + *
  • compareDates() compares the day, month and + * year of two calendars, returning 0, -1 or +1 indicating + * whether the first date is equal, before or after the second.
  • + *
  • compareWeeks() compares the week and + * year of two calendars, returning 0, -1 or +1 indicating + * whether the first week is equal, before or after the second.
  • + *
  • compareMonths() compares the month and + * year of two calendars, returning 0, -1 or +1 indicating + * whether the first month is equal, before or after the second.
  • + *
  • compareQuarters() compares the quarter and + * year of two calendars, returning 0, -1 or +1 indicating + * whether the first quarter is equal, before or after the second.
  • + *
  • compareYears() compares the + * year of two calendars, returning 0, -1 or +1 indicating + * whether the first year is equal, before or after the second.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using a specified pattern
  • + *
  • using the format for a specified Locale
  • + *
  • using the format for the default Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class CalendarValidator extends AbstractCalendarValidator { + + private static final long serialVersionUID = 9109652318762134167L; + + private static final CalendarValidator VALIDATOR = new CalendarValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the CalendarValidator. + */ + public static CalendarValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance with short + * date style. + */ + public CalendarValidator() { + this(true, DateFormat.SHORT); + } + + /** + * Construct an instance with the specified strict + * and date style parameters. + * + * @param strict true if strict + * Format parsing should be used. + * @param dateStyle the date style to use for Locale validation. + */ + public CalendarValidator(boolean strict, int dateStyle) { + super(strict, dateStyle, -1); + } + + /** + *

Validate/convert a Calendar using the default + * Locale and TimeZone. + * + * @param value The value validation is being performed on. + * @return The parsed Calendar if valid or null + * if invalid. + */ + public Calendar validate(String value) { + return (Calendar)parse(value, (String)null, (Locale)null, (TimeZone)null); + } + + /** + *

Validate/convert a Calendar using the specified + * TimeZone and default Locale. + * + * @param value The value validation is being performed on. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Calendar if valid or null + * if invalid. + */ + public Calendar validate(String value, TimeZone timeZone) { + return (Calendar)parse(value, (String)null, (Locale)null, timeZone); + } + + /** + *

Validate/convert a Calendar using the specified + * pattern and default TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, String pattern) { + return (Calendar)parse(value, pattern, (Locale)null, (TimeZone)null); + } + + /** + *

Validate/convert a Calendar using the specified + * pattern and TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, String pattern, TimeZone timeZone) { + return (Calendar)parse(value, pattern, (Locale)null, timeZone); + } + + /** + *

Validate/convert a Calendar using the specified + * Locale and default TimeZone. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, Locale locale) { + return (Calendar)parse(value, (String)null, locale, (TimeZone)null); + } + + /** + *

Validate/convert a Calendar using the specified + * Locale and TimeZone. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the date format, system default if null. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, Locale locale, TimeZone timeZone) { + return (Calendar)parse(value, (String)null, locale, timeZone); + } + + /** + *

Validate/convert a Calendar using the specified pattern + * and Locale and the default TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, String pattern, Locale locale) { + return (Calendar)parse(value, pattern, locale, (TimeZone)null); + } + + /** + *

Validate/convert a Calendar using the specified + * pattern, and Locale and TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, String pattern, Locale locale, TimeZone timeZone) { + return (Calendar)parse(value, pattern, locale, timeZone); + } + + /** + *

Adjusts a Calendar's value to a different TimeZone.

+ * + * @param value The value to adjust. + * @param timeZone The new time zone to use to adjust the Calendar to. + */ + public static void adjustToTimeZone(Calendar value, TimeZone timeZone) { + if (value.getTimeZone().hasSameRules(timeZone)) { + value.setTimeZone(timeZone); + } else { + int year = value.get(Calendar.YEAR); + int month = value.get(Calendar.MONTH); + int date = value.get(Calendar.DATE); + int hour = value.get(Calendar.HOUR_OF_DAY); + int minute = value.get(Calendar.MINUTE); + value.setTimeZone(timeZone); + value.set(year, month, date, hour, minute); + } + } + + /** + *

Compare Dates (day, month and year - not time).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @return Zero if the dates are equal, -1 if first + * date is less than the seconds and +1 if the first + * date is greater than. + */ + public int compareDates(Calendar value, Calendar compare) { + return compare(value, compare, Calendar.DATE); + } + + /** + *

Compare Weeks (week and year).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @return Zero if the weeks are equal, -1 if first + * parameter's week is less than the seconds and +1 if the first + * parameter's week is greater than. + */ + public int compareWeeks(Calendar value, Calendar compare) { + return compare(value, compare, Calendar.WEEK_OF_YEAR); + } + + /** + *

Compare Months (month and year).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @return Zero if the months are equal, -1 if first + * parameter's month is less than the seconds and +1 if the first + * parameter's month is greater than. + */ + public int compareMonths(Calendar value, Calendar compare) { + return compare(value, compare, Calendar.MONTH); + } + + /** + *

Compare Quarters (quarter and year).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to check the value against. + * @return Zero if the quarters are equal, -1 if first + * parameter's quarter is less than the seconds and +1 if the first + * parameter's quarter is greater than. + */ + public int compareQuarters(Calendar value, Calendar compare) { + return compareQuarters(value, compare, 1); + } + + /** + *

Compare Quarters (quarter and year).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @param monthOfFirstQuarter The month that the first quarter starts. + * @return Zero if the quarters are equal, -1 if first + * parameter's quarter is less than the seconds and +1 if the first + * parameter's quarter is greater than. + */ + @Override + public int compareQuarters(Calendar value, Calendar compare, int monthOfFirstQuarter) { + return super.compareQuarters(value, compare, monthOfFirstQuarter); + } + + /** + *

Compare Years.

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @return Zero if the years are equal, -1 if first + * parameter's year is less than the seconds and +1 if the first + * parameter's year is greater than. + */ + public int compareYears(Calendar value, Calendar compare) { + return compare(value, compare, Calendar.YEAR); + } + + /** + *

Convert the parsed Date to a Calendar.

+ * + * @param value The parsed Date object created. + * @param formatter The Format used to parse the value with. + * @return The parsed value converted to a Calendar. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + return ((DateFormat)formatter).getCalendar(); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CodeValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CodeValidator.java new file mode 100644 index 000000000..130e7c19d --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CodeValidator.java @@ -0,0 +1,283 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.io.Serializable; + +import org.apache.commons.validator.routines.checkdigit.CheckDigit; + +/** + * Generic Code Validation providing format, minimum/maximum + * length and {@link CheckDigit} validations. + *

+ * Performs the following validations on a code: + *

    + *
  • if the code is null, return null/false as appropriate
  • + *
  • trim the input. If the resulting code is empty, return null/false as appropriate
  • + *
  • Check the format of the code using a regular expression. (if specified)
  • + *
  • Check the minimum and maximum length (if specified) of the parsed code + * (i.e. parsed by the regular expression).
  • + *
  • Performs {@link CheckDigit} validation on the parsed code (if specified).
  • + *
  • The {@link #validate(String)} method returns the trimmed, parsed input (or null if validation failed)
  • + *
+ *

+ * Note + * The {@link #isValid(String)} method will return true if the input passes validation. + * Since this includes trimming as well as potentially dropping parts of the input, + * it is possible for a String to pass validation + * but fail the checkdigit test if passed directly to it (the check digit routines generally don't trim input + * nor do they generally check the format/length). + * To be sure that you are passing valid input to a method use {@link #validate(String)} as follows: + *

+ * Object valid = validator.validate(input); 
+ * if (valid != null) {
+ *    some_method(valid.toString());
+ * }
+ * 
+ *

+ * Configure the validator with the appropriate regular expression, minimum/maximum length + * and {@link CheckDigit} validator and then call one of the two validation + * methods provided:

+ *
    + *
  • boolean isValid(code)
  • + *
  • String validate(code)
  • + *
+ *

+ * Codes often include format characters - such as hyphens - to make them + * more easily human readable. These can be removed prior to length and check digit + * validation by specifying them as a non-capturing group in the regular + * expression (i.e. use the (?: ) notation). + *
+ * Or just avoid using parentheses except for the parts you want to capture + * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class CodeValidator implements Serializable { + + private static final long serialVersionUID = 446960910870938233L; + + private final RegexValidator regexValidator; + private final int minLength; + private final int maxLength; + private final CheckDigit checkdigit; + + /** + * Construct a code validator with a specified regular + * expression and {@link CheckDigit}. + * The RegexValidator validator is created to be case-sensitive + * + * @param regex The format regular expression + * @param checkdigit The check digit validation routine + */ + public CodeValidator(String regex, CheckDigit checkdigit) { + this(regex, -1, -1, checkdigit); + } + + /** + * Construct a code validator with a specified regular + * expression, length and {@link CheckDigit}. + * The RegexValidator validator is created to be case-sensitive + * + * @param regex The format regular expression. + * @param length The length of the code + * (sets the mimimum/maximum to the same) + * @param checkdigit The check digit validation routine + */ + public CodeValidator(String regex, int length, CheckDigit checkdigit) { + this(regex, length, length, checkdigit); + } + + /** + * Construct a code validator with a specified regular + * expression, minimum/maximum length and {@link CheckDigit} validation. + * The RegexValidator validator is created to be case-sensitive + * + * @param regex The regular expression + * @param minLength The minimum length of the code + * @param maxLength The maximum length of the code + * @param checkdigit The check digit validation routine + */ + public CodeValidator(String regex, int minLength, int maxLength, + CheckDigit checkdigit) { + if (regex != null && regex.length() > 0) { + this.regexValidator = new RegexValidator(regex); + } else { + this.regexValidator = null; + } + this.minLength = minLength; + this.maxLength = maxLength; + this.checkdigit = checkdigit; + } + + /** + * Construct a code validator with a specified regular expression, + * validator and {@link CheckDigit} validation. + * + * @param regexValidator The format regular expression validator + * @param checkdigit The check digit validation routine. + */ + public CodeValidator(RegexValidator regexValidator, CheckDigit checkdigit) { + this(regexValidator, -1, -1, checkdigit); + } + + /** + * Construct a code validator with a specified regular expression, + * validator, length and {@link CheckDigit} validation. + * + * @param regexValidator The format regular expression validator + * @param length The length of the code + * (sets the mimimum/maximum to the same value) + * @param checkdigit The check digit validation routine + */ + public CodeValidator(RegexValidator regexValidator, int length, CheckDigit checkdigit) { + this(regexValidator, length, length, checkdigit); + } + + /** + * Construct a code validator with a specified regular expression + * validator, minimum/maximum length and {@link CheckDigit} validation. + * + * @param regexValidator The format regular expression validator + * @param minLength The minimum length of the code + * @param maxLength The maximum length of the code + * @param checkdigit The check digit validation routine + */ + public CodeValidator(RegexValidator regexValidator, int minLength, int maxLength, + CheckDigit checkdigit) { + this.regexValidator = regexValidator; + this.minLength = minLength; + this.maxLength = maxLength; + this.checkdigit = checkdigit; + } + + /** + * Return the check digit validation routine. + *

+ * N.B. Optional, if not set no Check Digit + * validation will be performed on the code. + * + * @return The check digit validation routine + */ + public CheckDigit getCheckDigit() { + return checkdigit; + } + + /** + * Return the minimum length of the code. + *

+ * N.B. Optional, if less than zero the + * minimum length will not be checked. + * + * @return The minimum length of the code or + * -1 if the code has no minimum length + */ + public int getMinLength() { + return minLength; + } + + /** + * Return the maximum length of the code. + *

+ * N.B. Optional, if less than zero the + * maximum length will not be checked. + * + * @return The maximum length of the code or + * -1 if the code has no maximum length + */ + public int getMaxLength() { + return maxLength; + } + + /** + * Return the regular expression validator. + *

+ * N.B. Optional, if not set no regular + * expression validation will be performed on the code. + * + * @return The regular expression validator + */ + public RegexValidator getRegexValidator() { + return regexValidator; + } + + /** + * Validate the code returning either true + * or false. + *

+ * This calls {@link #validate(String)} and returns false + * if the return value is null, true otherwise. + *

+ * Note that {@link #validate(String)} trims the input + * and if there is a {@link RegexValidator} it may also + * change the input as part of the validation. + * + * @param input The code to validate + * @return true if valid, otherwise + * false + */ + public boolean isValid(String input) { + return (validate(input) != null); + } + + /** + * Validate the code returning either the valid code or + * null if invalid. + *

+ * Note that this method trims the input + * and if there is a {@link RegexValidator} it may also + * change the input as part of the validation. + * + * @param input The code to validate + * @return The code if valid, otherwise null + * if invalid + */ + public Object validate(String input) { + + if (input == null) { + return null; + } + + String code = input.trim(); + if (code.length() == 0) { + return null; + } + + // validate/reformat using regular expression + if (regexValidator != null) { + code = regexValidator.validate(code); + if (code == null) { + return null; + } + } + + // check the length (must be done after validate as that can change the code) + if ((minLength >= 0 && code.length() < minLength) || + (maxLength >= 0 && code.length() > maxLength)) { + return null; + } + + // validate the check digit + if (checkdigit != null && !checkdigit.isValid(code)) { + return null; + } + + return code; + + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java new file mode 100644 index 000000000..1f33c1e84 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java @@ -0,0 +1,517 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import org.apache.commons.validator.routines.checkdigit.CheckDigit; +import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; + +/** + * Perform credit card validations. + * + *

+ * By default, AMEX + VISA + MASTERCARD + DISCOVER card types are allowed. You can specify which + * cards should pass validation by configuring the validation options. For + * example, + *

+ * + *
+ * CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);
+ * 
+ * + *

+ * configures the validator to only pass American Express and Visa cards. + * If a card type is not directly supported by this class, you can create an + * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator} + * constructor along with any existing validators. For example: + *

+ * + *
+ * CreditCardValidator ccv = new CreditCardValidator(
+ *     new CodeValidator[] {
+ *         CreditCardValidator.AMEX_VALIDATOR,
+ *         CreditCardValidator.VISA_VALIDATOR,
+ *         new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY
+ * };
+ * 
+ * + *

+ * Alternatively you can define a validator using the {@link CreditCardRange} class. + * For example: + *

+ * + *
+ * CreditCardValidator ccv = new CreditCardValidator(
+ *    new CreditCardRange[]{
+ *        new CreditCardRange("300", "305", 14, 14), // Diners
+ *        new CreditCardRange("3095", null, 14, 14), // Diners
+ *        new CreditCardRange("36",   null, 14, 14), // Diners
+ *        new CreditCardRange("38",   "39", 14, 14), // Diners
+ *        new CreditCardRange("4",    null, new int[] {13, 16}), // VISA
+ *    }
+ * );
+ * 
+ * 
+ *

+ * This can be combined with a list of {@code CodeValidator}s + *

+ *

+ * More information can be found in Michael Gilleland's essay + * Anatomy of Credit Card Numbers. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public class CreditCardValidator implements Serializable { + + private static final long serialVersionUID = 5955978921148959496L; + + private static final int MIN_CC_LENGTH = 12; // minimum allowed length + + private static final int MAX_CC_LENGTH = 19; // maximum allowed length + + /** + * Class that represents a credit card range. + * @since 1.6 + */ + public static class CreditCardRange { + final String low; // e.g. 34 or 644 + final String high; // e.g. 34 or 65 + final int minLen; // e.g. 16 or -1 + final int maxLen; // e.g. 19 or -1 + final int lengths[]; // e.g. 16,18,19 + + /** + * Create a credit card range specifier for use in validation + * of the number syntax including the IIN range. + *

+ * The low and high parameters may be shorter than the length + * of an IIN (currently 6 digits) in which case subsequent digits + * are ignored and may range from 0-9. + *
+ * The low and high parameters may be different lengths. + * e.g. Discover "644" and "65". + *

+ * @param low the low digits of the IIN range + * @param high the high digits of the IIN range + * @param minLen the minimum length of the entire number + * @param maxLen the maximum length of the entire number + */ + public CreditCardRange(String low, String high, int minLen, int maxLen) { + this.low = low; + this.high = high; + this.minLen = minLen; + this.maxLen = maxLen; + this.lengths = null; + } + + /** + * Create a credit card range specifier for use in validation + * of the number syntax including the IIN range. + *

+ * The low and high parameters may be shorter than the length + * of an IIN (currently 6 digits) in which case subsequent digits + * are ignored and may range from 0-9. + *
+ * The low and high parameters may be different lengths. + * e.g. Discover "644" and "65". + *

+ * @param low the low digits of the IIN range + * @param high the high digits of the IIN range + * @param lengths array of valid lengths + */ + public CreditCardRange(String low, String high, int [] lengths) { + this.low = low; + this.high = high; + this.minLen = -1; + this.maxLen = -1; + this.lengths = lengths.clone(); + } + } + + /** + * Option specifying that no cards are allowed. This is useful if + * you want only custom card types to validate so you turn off the + * default cards with this option. + * + *
+     * 
+     * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
+     * v.addAllowedCardType(customType);
+     * v.isValid(aCardNumber);
+     * 
+     * 
+ */ + public static final long NONE = 0; + + /** + * Option specifying that American Express cards are allowed. + */ + public static final long AMEX = 1 << 0; + + /** + * Option specifying that Visa cards are allowed. + */ + public static final long VISA = 1 << 1; + + /** + * Option specifying that Mastercard cards are allowed. + */ + public static final long MASTERCARD = 1 << 2; + + /** + * Option specifying that Discover cards are allowed. + */ + public static final long DISCOVER = 1 << 3; // CHECKSTYLE IGNORE MagicNumber + + /** + * Option specifying that Diners cards are allowed. + */ + public static final long DINERS = 1 << 4; // CHECKSTYLE IGNORE MagicNumber + + /** + * Option specifying that VPay (Visa) cards are allowed. + * @since 1.5.0 + */ + public static final long VPAY = 1 << 5; // CHECKSTYLE IGNORE MagicNumber + + /** + * Option specifying that Mastercard cards (pre Oct 2016 only) are allowed. + * @deprecated for use until Oct 2016 only + */ + @Deprecated + public static final long MASTERCARD_PRE_OCT2016 = 1 << 6; // CHECKSTYLE IGNORE MagicNumber + + + /** + * The CreditCardTypes that are allowed to pass validation. + */ + private final List cardTypes = new ArrayList(); + + /** + * Luhn checkdigit validator for the card numbers. + */ + private static final CheckDigit LUHN_VALIDATOR = LuhnCheckDigit.LUHN_CHECK_DIGIT; + + /** + * American Express (Amex) Card Validator + *

+ * 34xxxx (15)
+ * 37xxxx (15)
+ */ + public static final CodeValidator AMEX_VALIDATOR = new CodeValidator("^(3[47]\\d{13})$", LUHN_VALIDATOR); + + /** + * Diners Card Validator + *

+ * 300xxx - 305xxx (14)
+ * 3095xx (14)
+ * 36xxxx (14)
+ * 38xxxx (14)
+ * 39xxxx (14)
+ */ + public static final CodeValidator DINERS_VALIDATOR = new CodeValidator("^(30[0-5]\\d{11}|3095\\d{10}|36\\d{12}|3[8-9]\\d{12})$", LUHN_VALIDATOR); + + /** + * Discover Card regular expressions + *

+ * 6011xx (16)
+ * 644xxx - 65xxxx (16)
+ */ + private static final RegexValidator DISCOVER_REGEX = new RegexValidator(new String[] {"^(6011\\d{12})$", "^(64[4-9]\\d{13})$", "^(65\\d{14})$"}); + + /** Discover Card Validator */ + public static final CodeValidator DISCOVER_VALIDATOR = new CodeValidator(DISCOVER_REGEX, LUHN_VALIDATOR); + + /** + * Mastercard regular expressions + *

+ * 2221xx - 2720xx (16)
+ * 51xxx - 55xxx (16)
+ */ + private static final RegexValidator MASTERCARD_REGEX = new RegexValidator( + new String[] { + "^(5[1-5]\\d{14})$", // 51 - 55 (pre Oct 2016) + // valid from October 2016 + "^(2221\\d{12})$", // 222100 - 222199 + "^(222[2-9]\\d{12})$",// 222200 - 222999 + "^(22[3-9]\\d{13})$", // 223000 - 229999 + "^(2[3-6]\\d{14})$", // 230000 - 269999 + "^(27[01]\\d{13})$", // 270000 - 271999 + "^(2720\\d{12})$", // 272000 - 272099 + }); + + /** Mastercard Card Validator */ + public static final CodeValidator MASTERCARD_VALIDATOR = new CodeValidator(MASTERCARD_REGEX, LUHN_VALIDATOR); + + /** + * Mastercard Card Validator (pre Oct 2016) + * @deprecated for use until Oct 2016 only + */ + @Deprecated + public static final CodeValidator MASTERCARD_VALIDATOR_PRE_OCT2016 = new CodeValidator("^(5[1-5]\\d{14})$", LUHN_VALIDATOR); + + /** + * Visa Card Validator + *

+ * 4xxxxx (13 or 16) + */ + public static final CodeValidator VISA_VALIDATOR = new CodeValidator("^(4)(\\d{12}|\\d{15})$", LUHN_VALIDATOR); + + /** VPay (Visa) Card Validator + *

+ * 4xxxxx (13-19) + * @since 1.5.0 + */ + public static final CodeValidator VPAY_VALIDATOR = new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR); + + /** + * Create a new CreditCardValidator with default options. + * The default options are: + * AMEX, VISA, MASTERCARD and DISCOVER + */ + public CreditCardValidator() { + this(AMEX + VISA + MASTERCARD + DISCOVER); + } + + /** + * Create a new CreditCardValidator with the specified options. + * @param options Pass in + * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that + * those are the only valid card types. + */ + public CreditCardValidator(long options) { + super(); + + if (isOn(options, VISA)) { + this.cardTypes.add(VISA_VALIDATOR); + } + + if (isOn(options, VPAY)) { + this.cardTypes.add(VPAY_VALIDATOR); + } + + if (isOn(options, AMEX)) { + this.cardTypes.add(AMEX_VALIDATOR); + } + + if (isOn(options, MASTERCARD)) { + this.cardTypes.add(MASTERCARD_VALIDATOR); + } + + if (isOn(options, MASTERCARD_PRE_OCT2016)) { + this.cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016); + } + + if (isOn(options, DISCOVER)) { + this.cardTypes.add(DISCOVER_VALIDATOR); + } + + if (isOn(options, DINERS)) { + this.cardTypes.add(DINERS_VALIDATOR); + } + } + + /** + * Create a new CreditCardValidator with the specified {@link CodeValidator}s. + * @param creditCardValidators Set of valid code validators + */ + public CreditCardValidator(CodeValidator[] creditCardValidators) { + if (creditCardValidators == null) { + throw new IllegalArgumentException("Card validators are missing"); + } + Collections.addAll(cardTypes, creditCardValidators); + } + + /** + * Create a new CreditCardValidator with the specified {@link CreditCardRange}s. + * @param creditCardRanges Set of valid code validators + * @since 1.6 + */ + public CreditCardValidator(CreditCardRange[] creditCardRanges) { + if (creditCardRanges == null) { + throw new IllegalArgumentException("Card ranges are missing"); + } + Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR)); + } + + /** + * Create a new CreditCardValidator with the specified {@link CodeValidator}s + * and {@link CreditCardRange}s. + *

+ * This can be used to combine predefined validators such as {@link #MASTERCARD_VALIDATOR} + * with additional validators using the simpler {@link CreditCardRange}s. + * @param creditCardValidators Set of valid code validators + * @param creditCardRanges Set of valid code validators + * @since 1.6 + */ + public CreditCardValidator(CodeValidator[] creditCardValidators, CreditCardRange[] creditCardRanges) { + if (creditCardValidators == null) { + throw new IllegalArgumentException("Card validators are missing"); + } + if (creditCardRanges == null) { + throw new IllegalArgumentException("Card ranges are missing"); + } + Collections.addAll(cardTypes, creditCardValidators); + Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR)); + } + + /** + * Create a new generic CreditCardValidator which validates the syntax and check digit only. + * Does not check the Issuer Identification Number (IIN) + * + * @param minLen minimum allowed length + * @param maxLen maximum allowed length + * @return the validator + * @since 1.6 + */ + public static CreditCardValidator genericCreditCardValidator(int minLen, int maxLen) { + return new CreditCardValidator(new CodeValidator[] {new CodeValidator("(\\d+)", minLen, maxLen, LUHN_VALIDATOR)}); + } + + /** + * Create a new generic CreditCardValidator which validates the syntax and check digit only. + * Does not check the Issuer Identification Number (IIN) + * + * @param length exact length + * @return the validator + * @since 1.6 + */ + public static CreditCardValidator genericCreditCardValidator(int length) { + return genericCreditCardValidator(length, length); + } + + /** + * Create a new generic CreditCardValidator which validates the syntax and check digit only. + * Does not check the Issuer Identification Number (IIN) + * + * @return the validator + * @since 1.6 + */ + public static CreditCardValidator genericCreditCardValidator() { + return genericCreditCardValidator(MIN_CC_LENGTH, MAX_CC_LENGTH); + } + + /** + * Checks if the field is a valid credit card number. + * @param card The card number to validate. + * @return Whether the card number is valid. + */ + public boolean isValid(String card) { + if (card == null || card.length() == 0) { + return false; + } + for (CodeValidator cardType : cardTypes) { + if (cardType.isValid(card)) { + return true; + } + } + return false; + } + + /** + * Checks if the field is a valid credit card number. + * @param card The card number to validate. + * @return The card number if valid or null + * if invalid. + */ + public Object validate(String card) { + if (card == null || card.length() == 0) { + return null; + } + Object result = null; + for (CodeValidator cardType : cardTypes) { + result = cardType.validate(card); + if (result != null) { + return result; + } + } + return null; + + } + + // package protected for unit test access + static boolean validLength(int valueLength, CreditCardRange range) { + if (range.lengths != null) { + for(int length : range.lengths) { + if (valueLength == length) { + return true; + } + } + return false; + } + return valueLength >= range.minLen && valueLength <= range.maxLen; + } + + // package protected for unit test access + static CodeValidator createRangeValidator(final CreditCardRange[] creditCardRanges, final CheckDigit digitCheck ) { + return new CodeValidator( + // must be numeric (rest of validation is done later) + new RegexValidator("(\\d+)") { + private static final long serialVersionUID = 1L; + private CreditCardRange[] ccr = creditCardRanges.clone(); + @Override + // must return full string + public String validate(String value) { + if (super.match(value) != null) { + int length = value.length(); + for(CreditCardRange range : ccr) { + if (validLength(length, range)) { + if (range.high == null) { // single prefix only + if (value.startsWith(range.low)) { + return value; + } + } else if (range.low.compareTo(value) <= 0 // no need to trim value here + && + // here we have to ignore digits beyond the prefix + range.high.compareTo(value.substring(0, range.high.length())) >= 0) { + return value; + } + } + } + } + return null; + } + @Override + public boolean isValid(String value) { + return validate(value) != null; + } + @Override + public String[] match(String value) { + return new String[]{validate(value)}; + } + }, digitCheck); + } + + /** + * Tests whether the given flag is on. If the flag is not a power of 2 + * (ie. 3) this tests whether the combination of flags is on. + * + * @param options The options specified. + * @param flag Flag value to check. + * + * @return whether the specified flag value is on. + */ + private boolean isOn(long options, long flag) { + return (options & flag) > 0; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CurrencyValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CurrencyValidator.java new file mode 100644 index 000000000..3dce07bd8 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CurrencyValidator.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.validator.routines; + +import java.text.DecimalFormat; +import java.text.Format; + +/** + *

Currency Validation and Conversion routines (java.math.BigDecimal).

+ * + *

This is one implementation of a currency validator that has the following features:

+ *
    + *
  • It is lenient about the presence of the currency symbol
  • + *
  • It converts the currency to a java.math.BigDecimal
  • + *
+ * + *

However any of the number validators can be used for currency validation. + * For example, if you wanted a currency validator that converts to a + * java.lang.Integer then you can simply instantiate an + * IntegerValidator with the appropriate format type:

+ * + *

... = new IntegerValidator(false, IntegerValidator.CURRENCY_FORMAT);

+ * + *

Pick the appropriate validator, depending on the type (e.g Float, Double, Integer, Long etc) + * you want the currency converted to. One thing to note - only the CurrencyValidator + * implements lenient behavior regarding the currency symbol.

+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class CurrencyValidator extends BigDecimalValidator { + + private static final long serialVersionUID = -4201640771171486514L; + + private static final CurrencyValidator VALIDATOR = new CurrencyValidator(); + + /** DecimalFormat's currency symbol */ + private static final char CURRENCY_SYMBOL = '\u00A4'; + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the CurrencyValidator. + */ + public static BigDecimalValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public CurrencyValidator() { + this(true, true); + } + + /** + * Construct an instance with the specified strict setting. + * + * @param strict true if strict + * Format parsing should be used. + * @param allowFractions true if fractions are + * allowed or false if integers only. + */ + public CurrencyValidator(boolean strict, boolean allowFractions) { + super(strict, CURRENCY_FORMAT, allowFractions); + } + + /** + *

Parse the value with the specified Format.

+ * + *

This implementation is lenient whether the currency symbol + * is present or not. The default NumberFormat + * behavior is for the parsing to "fail" if the currency + * symbol is missing. This method re-parses with a format + * without the currency symbol if it fails initially.

+ * + * @param value The value to be parsed. + * @param formatter The Format to parse the value with. + * @return The parsed value if valid or null if invalid. + */ + @Override + protected Object parse(String value, Format formatter) { + + // Initial parse of the value + Object parsedValue = super.parse(value, formatter); + if (parsedValue != null || !(formatter instanceof DecimalFormat)) { + return parsedValue; + } + + // Re-parse using a pattern without the currency symbol + DecimalFormat decimalFormat = (DecimalFormat)formatter; + String pattern = decimalFormat.toPattern(); + if (pattern.indexOf(CURRENCY_SYMBOL) >= 0) { + StringBuilder buffer = new StringBuilder(pattern.length()); + for (int i = 0; i < pattern.length(); i++) { + if (pattern.charAt(i) != CURRENCY_SYMBOL) { + buffer.append(pattern.charAt(i)); + } + } + decimalFormat.applyPattern(buffer.toString()); + parsedValue = super.parse(value, decimalFormat); + } + return parsedValue; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DateValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DateValidator.java new file mode 100644 index 000000000..60a6b4338 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DateValidator.java @@ -0,0 +1,350 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.DateFormat; +import java.text.Format; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + *

Date Validation and Conversion routines (java.util.Date).

+ * + *

This validator provides a number of methods for validating/converting + * a String date value to a java.util.Date using + * java.text.DateFormat to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

For each of the above mechanisms, conversion method (i.e the + * validate methods) implementations are provided which + * either use the default TimeZone or allow the + * TimeZone to be specified.

+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Date value.

+ * + *

Implementations of the validate() method are provided + * to create Date objects for different time zones + * if the system default is not appropriate.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform various date comparison checks:

+ *
    + *
  • compareDates() compares the day, month and + * year of two dates, returning 0, -1 or +1 indicating + * whether the first date is equal, before or after the second.
  • + *
  • compareWeeks() compares the week and + * year of two dates, returning 0, -1 or +1 indicating + * whether the first week is equal, before or after the second.
  • + *
  • compareMonths() compares the month and + * year of two dates, returning 0, -1 or +1 indicating + * whether the first month is equal, before or after the second.
  • + *
  • compareQuarters() compares the quarter and + * year of two dates, returning 0, -1 or +1 indicating + * whether the first quarter is equal, before or after the second.
  • + *
  • compareYears() compares the + * year of two dates, returning 0, -1 or +1 indicating + * whether the first year is equal, before or after the second.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using a specified pattern
  • + *
  • using the format for a specified Locale
  • + *
  • using the format for the default Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class DateValidator extends AbstractCalendarValidator { + + private static final long serialVersionUID = -3966328400469953190L; + + private static final DateValidator VALIDATOR = new DateValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the DateValidator. + */ + public static DateValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance with short + * date style. + */ + public DateValidator() { + this(true, DateFormat.SHORT); + } + + /** + * Construct an instance with the specified strict + * and date style parameters. + * + * @param strict true if strict + * Format parsing should be used. + * @param dateStyle the date style to use for Locale validation. + */ + public DateValidator(boolean strict, int dateStyle) { + super(strict, dateStyle, -1); + } + + /** + *

Validate/convert a Date using the default + * Locale and TimeZone. + * + * @param value The value validation is being performed on. + * @return The parsed Date if valid or null + * if invalid. + */ + public Date validate(String value) { + return (Date)parse(value, (String)null, (Locale)null, (TimeZone)null); + } + + /** + *

Validate/convert a Date using the specified + * TimeZone and default Locale. + * + * @param value The value validation is being performed on. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Date if valid or null if invalid. + */ + public Date validate(String value, TimeZone timeZone) { + return (Date)parse(value, (String)null, (Locale)null, timeZone); + } + + /** + *

Validate/convert a Date using the specified + * pattern and default TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @return The parsed Date if valid or null if invalid. + */ + public Date validate(String value, String pattern) { + return (Date)parse(value, pattern, (Locale)null, (TimeZone)null); + } + + /** + *

Validate/convert a Date using the specified + * pattern and TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Date if valid or null if invalid. + */ + public Date validate(String value, String pattern, TimeZone timeZone) { + return (Date)parse(value, pattern, (Locale)null, timeZone); + } + + /** + *

Validate/convert a Date using the specified + * Locale and default TimeZone. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Date if valid or null if invalid. + */ + public Date validate(String value, Locale locale) { + return (Date)parse(value, (String)null, locale, (TimeZone)null); + } + + /** + *

Validate/convert a Date using the specified + * Locale and TimeZone. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the date format, system default if null. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Date if valid or null if invalid. + */ + public Date validate(String value, Locale locale, TimeZone timeZone) { + return (Date)parse(value, (String)null, locale, timeZone); + } + + /** + *

Validate/convert a Date using the specified pattern + * and Locale and the default TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Date if valid or null if invalid. + */ + public Date validate(String value, String pattern, Locale locale) { + return (Date)parse(value, pattern, locale, (TimeZone)null); + } + + /** + *

Validate/convert a Date using the specified + * pattern, and Locale and TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Date if valid or null if invalid. + */ + public Date validate(String value, String pattern, Locale locale, TimeZone timeZone) { + return (Date)parse(value, pattern, locale, timeZone); + } + + /** + *

Compare Dates (day, month and year - not time).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @param timeZone The Time Zone used to compare the dates, system default if null. + * @return Zero if the dates are equal, -1 if first + * date is less than the seconds and +1 if the first + * date is greater than. + */ + public int compareDates(Date value, Date compare, TimeZone timeZone) { + Calendar calendarValue = getCalendar(value, timeZone); + Calendar calendarCompare = getCalendar(compare, timeZone); + return compare(calendarValue, calendarCompare, Calendar.DATE); + } + + /** + *

Compare Weeks (week and year).

+ * + * @param value The Date value to check. + * @param compare The Date to compare the value to. + * @param timeZone The Time Zone used to compare the dates, system default if null. + * @return Zero if the weeks are equal, -1 if first + * parameter's week is less than the seconds and +1 if the first + * parameter's week is greater than. + */ + public int compareWeeks(Date value, Date compare, TimeZone timeZone) { + Calendar calendarValue = getCalendar(value, timeZone); + Calendar calendarCompare = getCalendar(compare, timeZone); + return compare(calendarValue, calendarCompare, Calendar.WEEK_OF_YEAR); + } + + /** + *

Compare Months (month and year).

+ * + * @param value The Date value to check. + * @param compare The Date to compare the value to. + * @param timeZone The Time Zone used to compare the dates, system default if null. + * @return Zero if the months are equal, -1 if first + * parameter's month is less than the seconds and +1 if the first + * parameter's month is greater than. + */ + public int compareMonths(Date value, Date compare, TimeZone timeZone) { + Calendar calendarValue = getCalendar(value, timeZone); + Calendar calendarCompare = getCalendar(compare, timeZone); + return compare(calendarValue, calendarCompare, Calendar.MONTH); + } + + /** + *

Compare Quarters (quarter and year).

+ * + * @param value The Date value to check. + * @param compare The Date to compare the value to. + * @param timeZone The Time Zone used to compare the dates, system default if null. + * @return Zero if the months are equal, -1 if first + * parameter's quarter is less than the seconds and +1 if the first + * parameter's quarter is greater than. + */ + public int compareQuarters(Date value, Date compare, TimeZone timeZone) { + return compareQuarters(value, compare, timeZone, 1); + } + + /** + *

Compare Quarters (quarter and year).

+ * + * @param value The Date value to check. + * @param compare The Date to compare the value to. + * @param timeZone The Time Zone used to compare the dates, system default if null. + * @param monthOfFirstQuarter The month that the first quarter starts. + * @return Zero if the quarters are equal, -1 if first + * parameter's quarter is less than the seconds and +1 if the first + * parameter's quarter is greater than. + */ + public int compareQuarters(Date value, Date compare, TimeZone timeZone, int monthOfFirstQuarter) { + Calendar calendarValue = getCalendar(value, timeZone); + Calendar calendarCompare = getCalendar(compare, timeZone); + return super.compareQuarters(calendarValue, calendarCompare, monthOfFirstQuarter); + } + + /** + *

Compare Years.

+ * + * @param value The Date value to check. + * @param compare The Date to compare the value to. + * @param timeZone The Time Zone used to compare the dates, system default if null. + * @return Zero if the years are equal, -1 if first + * parameter's year is less than the seconds and +1 if the first + * parameter's year is greater than. + */ + public int compareYears(Date value, Date compare, TimeZone timeZone) { + Calendar calendarValue = getCalendar(value, timeZone); + Calendar calendarCompare = getCalendar(compare, timeZone); + return compare(calendarValue, calendarCompare, Calendar.YEAR); + } + + /** + *

Returns the parsed Date unchanged.

+ * + * @param value The parsed Date object created. + * @param formatter The Format used to parse the value with. + * @return The parsed value converted to a Calendar. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + return value; + } + + /** + *

Convert a Date to a Calendar.

+ * + * @param value The date value to be converted. + * @return The converted Calendar. + */ + private Calendar getCalendar(Date value, TimeZone timeZone) { + + Calendar calendar = null; + if (timeZone != null) { + calendar = Calendar.getInstance(timeZone); + } else { + calendar = Calendar.getInstance(); + } + calendar.setTime(value); + return calendar; + + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DomainValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DomainValidator.java new file mode 100644 index 000000000..8a6d594f3 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DomainValidator.java @@ -0,0 +1,2117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.net.IDN; +import java.util.Arrays; +import java.util.Locale; + +/** + *

Domain name validation routines.

+ * + *

+ * This validator provides methods for validating Internet domain names + * and top-level domains. + *

+ * + *

Domain names are evaluated according + * to the standards RFC1034, + * section 3, and RFC1123, + * section 2.1. No accommodation is provided for the specialized needs of + * other applications; if the domain name has been URL-encoded, for example, + * validation will fail even though the equivalent plaintext version of the + * same name would have passed. + *

+ * + *

+ * Validation is also provided for top-level domains (TLDs) as defined and + * maintained by the Internet Assigned Numbers Authority (IANA): + *

+ * + *
    + *
  • {@link #isValidInfrastructureTld} - validates infrastructure TLDs + * (.arpa, etc.)
  • + *
  • {@link #isValidGenericTld} - validates generic TLDs + * (.com, .org, etc.)
  • + *
  • {@link #isValidCountryCodeTld} - validates country code TLDs + * (.us, .uk, .cn, etc.)
  • + *
+ * + *

+ * (NOTE: This class does not provide IP address lookup for domain names or + * methods to ensure that a given domain name matches a specific IP; see + * {@link java.net.InetAddress} for that functionality.) + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public class DomainValidator implements Serializable { + + /** Maximum allowable length ({@value}) of a domain name */ + private static final int MAX_DOMAIN_LENGTH = 253; + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + private static final long serialVersionUID = -4407125112880174009L; + + // Regular expression strings for hostnames (derived from RFC2396 and RFC 1123) + + // RFC2396: domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + // Max 63 characters + private static final String DOMAIN_LABEL_REGEX = "\\p{Alnum}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?"; + + // RFC2396 toplabel = alpha | alpha *( alphanum | "-" ) alphanum + // Max 63 characters + private static final String TOP_LABEL_REGEX = "\\p{Alpha}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?"; + + // RFC2396 hostname = *( domainlabel "." ) toplabel [ "." ] + // Note that the regex currently requires both a domain label and a top level label, whereas + // the RFC does not. This is because the regex is used to detect if a TLD is present. + // If the match fails, input is checked against DOMAIN_LABEL_REGEX (hostnameRegex) + // RFC1123 sec 2.1 allows hostnames to start with a digit + private static final String DOMAIN_NAME_REGEX = + "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")\\.?$"; + + private final boolean allowLocal; + + /** + * Singleton instance of this validator, which + * doesn't consider local addresses as valid. + */ + private static final DomainValidator DOMAIN_VALIDATOR = new DomainValidator(false); + + /** + * Singleton instance of this validator, which does + * consider local addresses valid. + */ + private static final DomainValidator DOMAIN_VALIDATOR_WITH_LOCAL = new DomainValidator(true); + + /** + * The above instances must only be returned via the getInstance() methods. + * This is to ensure that the override data arrays are properly protected. + */ + + /** + * RegexValidator for matching domains. + */ + private final RegexValidator domainRegex = + new RegexValidator(DOMAIN_NAME_REGEX); + /** + * RegexValidator for matching a local hostname + */ + // RFC1123 sec 2.1 allows hostnames to start with a digit + private final RegexValidator hostnameRegex = + new RegexValidator(DOMAIN_LABEL_REGEX); + + /** + * Returns the singleton instance of this validator. It + * will not consider local addresses as valid. + * @return the singleton instance of this validator + */ + public static synchronized DomainValidator getInstance() { + inUse = true; + return DOMAIN_VALIDATOR; + } + + /** + * Returns the singleton instance of this validator, + * with local validation as required. + * @param allowLocal Should local addresses be considered valid? + * @return the singleton instance of this validator + */ + public static synchronized DomainValidator getInstance(boolean allowLocal) { + inUse = true; + if(allowLocal) { + return DOMAIN_VALIDATOR_WITH_LOCAL; + } + return DOMAIN_VALIDATOR; + } + + /** + * Private constructor. + * This does not set the inUse flag - that is done by getInstance. + * This is to allow the static shared instances to be created. + */ + private DomainValidator(boolean allowLocal) { + this.allowLocal = allowLocal; + } + + /** + * Returns true if the specified String parses + * as a valid domain name with a recognized top-level domain. + * The parsing is case-insensitive. + * @param domain the parameter to check for domain name syntax + * @return true if the parameter is a valid domain name + */ + public boolean isValid(String domain) { + if (domain == null) { + return false; + } + domain = unicodeToASCII(domain); + // hosts must be equally reachable via punycode and Unicode + // Unicode is never shorter than punycode, so check punycode + // if domain did not convert, then it will be caught by ASCII + // checks in the regexes below + if (domain.length() > MAX_DOMAIN_LENGTH) { + return false; + } + String[] groups = domainRegex.match(domain); + if (groups != null && groups.length > 0) { + return isValidTld(groups[0]); + } + return allowLocal && hostnameRegex.isValid(domain); + } + + // package protected for unit test access + // must agree with isValid() above + final boolean isValidDomainSyntax(String domain) { + if (domain == null) { + return false; + } + domain = unicodeToASCII(domain); + // hosts must be equally reachable via punycode and Unicode + // Unicode is never shorter than punycode, so check punycode + // if domain did not convert, then it will be caught by ASCII + // checks in the regexes below + if (domain.length() > MAX_DOMAIN_LENGTH) { + return false; + } + String[] groups = domainRegex.match(domain); + return (groups != null && groups.length > 0) + || hostnameRegex.isValid(domain); + } + + /** + * Returns true if the specified String matches any + * IANA-defined top-level domain. Leading dots are ignored if present. + * The search is case-insensitive. + *

+ * If allowLocal is true, the TLD is checked using {@link #isValidLocalTld(String)}. + * The TLD is then checked against {@link #isValidInfrastructureTld(String)}, + * {@link #isValidGenericTld(String)} and {@link #isValidCountryCodeTld(String)} + * @param tld the parameter to check for TLD status, not null + * @return true if the parameter is a TLD + */ + public boolean isValidTld(String tld) { + if(allowLocal && isValidLocalTld(tld)) { + return true; + } + return isValidInfrastructureTld(tld) + || isValidGenericTld(tld) + || isValidCountryCodeTld(tld); + } + + /** + * Returns true if the specified String matches any + * IANA-defined infrastructure top-level domain. Leading dots are + * ignored if present. The search is case-insensitive. + * @param iTld the parameter to check for infrastructure TLD status, not null + * @return true if the parameter is an infrastructure TLD + */ + public boolean isValidInfrastructureTld(String iTld) { + final String key = chompLeadingDot(unicodeToASCII(iTld).toLowerCase(Locale.ENGLISH)); + return arrayContains(INFRASTRUCTURE_TLDS, key); + } + + /** + * Returns true if the specified String matches any + * IANA-defined generic top-level domain. Leading dots are ignored + * if present. The search is case-insensitive. + * @param gTld the parameter to check for generic TLD status, not null + * @return true if the parameter is a generic TLD + */ + public boolean isValidGenericTld(String gTld) { + final String key = chompLeadingDot(unicodeToASCII(gTld).toLowerCase(Locale.ENGLISH)); + return (arrayContains(GENERIC_TLDS, key) || arrayContains(genericTLDsPlus, key)) + && !arrayContains(genericTLDsMinus, key); + } + + /** + * Returns true if the specified String matches any + * IANA-defined country code top-level domain. Leading dots are + * ignored if present. The search is case-insensitive. + * @param ccTld the parameter to check for country code TLD status, not null + * @return true if the parameter is a country code TLD + */ + public boolean isValidCountryCodeTld(String ccTld) { + final String key = chompLeadingDot(unicodeToASCII(ccTld).toLowerCase(Locale.ENGLISH)); + return (arrayContains(COUNTRY_CODE_TLDS, key) || arrayContains(countryCodeTLDsPlus, key)) + && !arrayContains(countryCodeTLDsMinus, key); + } + + /** + * Returns true if the specified String matches any + * widely used "local" domains (localhost or localdomain). Leading dots are + * ignored if present. The search is case-insensitive. + * @param lTld the parameter to check for local TLD status, not null + * @return true if the parameter is an local TLD + */ + public boolean isValidLocalTld(String lTld) { + final String key = chompLeadingDot(unicodeToASCII(lTld).toLowerCase(Locale.ENGLISH)); + return arrayContains(LOCAL_TLDS, key); + } + + private String chompLeadingDot(String str) { + if (str.startsWith(".")) { + return str.substring(1); + } + return str; + } + + // --------------------------------------------- + // ----- TLDs defined by IANA + // ----- Authoritative and comprehensive list at: + // ----- http://data.iana.org/TLD/tlds-alpha-by-domain.txt + + // Note that the above list is in UPPER case. + // The code currently converts strings to lower case (as per the tables below) + + // IANA also provide an HTML list at http://www.iana.org/domains/root/db + // Note that this contains several country code entries which are NOT in + // the text file. These all have the "Not assigned" in the "Sponsoring Organisation" column + // For example (as of 2015-01-02): + // .bl country-code Not assigned + // .um country-code Not assigned + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static final String[] INFRASTRUCTURE_TLDS = new String[] { + "arpa", // internet infrastructure + }; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static final String[] GENERIC_TLDS = new String[] { + // Taken from Version 2020062100, Last Updated Sun Jun 21 07:07:01 2020 UTC + "aaa", // aaa American Automobile Association, Inc. + "aarp", // aarp AARP + "abarth", // abarth Fiat Chrysler Automobiles N.V. + "abb", // abb ABB Ltd + "abbott", // abbott Abbott Laboratories, Inc. + "abbvie", // abbvie AbbVie Inc. + "abc", // abc Disney Enterprises, Inc. + "able", // able Able Inc. + "abogado", // abogado Top Level Domain Holdings Limited + "abudhabi", // abudhabi Abu Dhabi Systems and Information Centre + "academy", // academy Half Oaks, LLC + "accenture", // accenture Accenture plc + "accountant", // accountant dot Accountant Limited + "accountants", // accountants Knob Town, LLC + "aco", // aco ACO Severin Ahlmann GmbH & Co. KG +// "active", // active The Active Network, Inc + "actor", // actor United TLD Holdco Ltd. + "adac", // adac Allgemeiner Deutscher Automobil-Club e.V. (ADAC) + "ads", // ads Charleston Road Registry Inc. + "adult", // adult ICM Registry AD LLC + "aeg", // aeg Aktiebolaget Electrolux + "aero", // aero Societe Internationale de Telecommunications Aeronautique (SITA INC USA) + "aetna", // aetna Aetna Life Insurance Company + "afamilycompany", // afamilycompany Johnson Shareholdings, Inc. + "afl", // afl Australian Football League + "africa", // africa ZA Central Registry NPC trading as Registry.Africa + "agakhan", // agakhan Fondation Aga Khan (Aga Khan Foundation) + "agency", // agency Steel Falls, LLC + "aig", // aig American International Group, Inc. + "aigo", // aigo aigo Digital Technology Co,Ltd. + "airbus", // airbus Airbus S.A.S. + "airforce", // airforce United TLD Holdco Ltd. + "airtel", // airtel Bharti Airtel Limited + "akdn", // akdn Fondation Aga Khan (Aga Khan Foundation) + "alfaromeo", // alfaromeo Fiat Chrysler Automobiles N.V. + "alibaba", // alibaba Alibaba Group Holding Limited + "alipay", // alipay Alibaba Group Holding Limited + "allfinanz", // allfinanz Allfinanz Deutsche Vermögensberatung Aktiengesellschaft + "allstate", // allstate Allstate Fire and Casualty Insurance Company + "ally", // ally Ally Financial Inc. + "alsace", // alsace REGION D ALSACE + "alstom", // alstom ALSTOM + "amazon", // amazon Amazon Registry Services, Inc. + "americanexpress", // americanexpress American Express Travel Related Services Company, Inc. + "americanfamily", // americanfamily AmFam, Inc. + "amex", // amex American Express Travel Related Services Company, Inc. + "amfam", // amfam AmFam, Inc. + "amica", // amica Amica Mutual Insurance Company + "amsterdam", // amsterdam Gemeente Amsterdam + "analytics", // analytics Campus IP LLC + "android", // android Charleston Road Registry Inc. + "anquan", // anquan QIHOO 360 TECHNOLOGY CO. LTD. + "anz", // anz Australia and New Zealand Banking Group Limited + "aol", // aol AOL Inc. + "apartments", // apartments June Maple, LLC + "app", // app Charleston Road Registry Inc. + "apple", // apple Apple Inc. + "aquarelle", // aquarelle Aquarelle.com + "arab", // arab League of Arab States + "aramco", // aramco Aramco Services Company + "archi", // archi STARTING DOT LIMITED + "army", // army United TLD Holdco Ltd. + "art", // art UK Creative Ideas Limited + "arte", // arte Association Relative à la Télévision Européenne G.E.I.E. + "asda", // asda Wal-Mart Stores, Inc. + "asia", // asia DotAsia Organisation Ltd. + "associates", // associates Baxter Hill, LLC + "athleta", // athleta The Gap, Inc. + "attorney", // attorney United TLD Holdco, Ltd + "auction", // auction United TLD HoldCo, Ltd. + "audi", // audi AUDI Aktiengesellschaft + "audible", // audible Amazon Registry Services, Inc. + "audio", // audio Uniregistry, Corp. + "auspost", // auspost Australian Postal Corporation + "author", // author Amazon Registry Services, Inc. + "auto", // auto Uniregistry, Corp. + "autos", // autos DERAutos, LLC + "avianca", // avianca Aerovias del Continente Americano S.A. Avianca + "aws", // aws Amazon Registry Services, Inc. + "axa", // axa AXA SA + "azure", // azure Microsoft Corporation + "baby", // baby Johnson & Johnson Services, Inc. + "baidu", // baidu Baidu, Inc. + "banamex", // banamex Citigroup Inc. + "bananarepublic", // bananarepublic The Gap, Inc. + "band", // band United TLD Holdco, Ltd + "bank", // bank fTLD Registry Services, LLC + "bar", // bar Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable + "barcelona", // barcelona Municipi de Barcelona + "barclaycard", // barclaycard Barclays Bank PLC + "barclays", // barclays Barclays Bank PLC + "barefoot", // barefoot Gallo Vineyards, Inc. + "bargains", // bargains Half Hallow, LLC + "baseball", // baseball MLB Advanced Media DH, LLC + "basketball", // basketball Fédération Internationale de Basketball (FIBA) + "bauhaus", // bauhaus Werkhaus GmbH + "bayern", // bayern Bayern Connect GmbH + "bbc", // bbc British Broadcasting Corporation + "bbt", // bbt BB&T Corporation + "bbva", // bbva BANCO BILBAO VIZCAYA ARGENTARIA, S.A. + "bcg", // bcg The Boston Consulting Group, Inc. + "bcn", // bcn Municipi de Barcelona + "beats", // beats Beats Electronics, LLC + "beauty", // beauty L'Oréal + "beer", // beer Top Level Domain Holdings Limited + "bentley", // bentley Bentley Motors Limited + "berlin", // berlin dotBERLIN GmbH & Co. KG + "best", // best BestTLD Pty Ltd + "bestbuy", // bestbuy BBY Solutions, Inc. + "bet", // bet Afilias plc + "bharti", // bharti Bharti Enterprises (Holding) Private Limited + "bible", // bible American Bible Society + "bid", // bid dot Bid Limited + "bike", // bike Grand Hollow, LLC + "bing", // bing Microsoft Corporation + "bingo", // bingo Sand Cedar, LLC + "bio", // bio STARTING DOT LIMITED + "biz", // biz Neustar, Inc. + "black", // black Afilias Limited + "blackfriday", // blackfriday Uniregistry, Corp. +// "blanco", // blanco BLANCO GmbH + Co KG + "blockbuster", // blockbuster Dish DBS Corporation + "blog", // blog Knock Knock WHOIS There, LLC + "bloomberg", // bloomberg Bloomberg IP Holdings LLC + "blue", // blue Afilias Limited + "bms", // bms Bristol-Myers Squibb Company + "bmw", // bmw Bayerische Motoren Werke Aktiengesellschaft +// "bnl", // bnl Banca Nazionale del Lavoro + "bnpparibas", // bnpparibas BNP Paribas + "boats", // boats DERBoats, LLC + "boehringer", // boehringer Boehringer Ingelheim International GmbH + "bofa", // bofa NMS Services, Inc. + "bom", // bom Núcleo de Informação e Coordenação do Ponto BR - NIC.br + "bond", // bond Bond University Limited + "boo", // boo Charleston Road Registry Inc. + "book", // book Amazon Registry Services, Inc. + "booking", // booking Booking.com B.V. +// "boots", // boots THE BOOTS COMPANY PLC + "bosch", // bosch Robert Bosch GMBH + "bostik", // bostik Bostik SA + "boston", // boston Boston TLD Management, LLC + "bot", // bot Amazon Registry Services, Inc. + "boutique", // boutique Over Galley, LLC + "box", // box NS1 Limited + "bradesco", // bradesco Banco Bradesco S.A. + "bridgestone", // bridgestone Bridgestone Corporation + "broadway", // broadway Celebrate Broadway, Inc. + "broker", // broker DOTBROKER REGISTRY LTD + "brother", // brother Brother Industries, Ltd. + "brussels", // brussels DNS.be vzw + "budapest", // budapest Top Level Domain Holdings Limited + "bugatti", // bugatti Bugatti International SA + "build", // build Plan Bee LLC + "builders", // builders Atomic Madison, LLC + "business", // business Spring Cross, LLC + "buy", // buy Amazon Registry Services, INC + "buzz", // buzz DOTSTRATEGY CO. + "bzh", // bzh Association www.bzh + "cab", // cab Half Sunset, LLC + "cafe", // cafe Pioneer Canyon, LLC + "cal", // cal Charleston Road Registry Inc. + "call", // call Amazon Registry Services, Inc. + "calvinklein", // calvinklein PVH gTLD Holdings LLC + "cam", // cam AC Webconnecting Holding B.V. + "camera", // camera Atomic Maple, LLC + "camp", // camp Delta Dynamite, LLC + "cancerresearch", // cancerresearch Australian Cancer Research Foundation + "canon", // canon Canon Inc. + "capetown", // capetown ZA Central Registry NPC trading as ZA Central Registry + "capital", // capital Delta Mill, LLC + "capitalone", // capitalone Capital One Financial Corporation + "car", // car Cars Registry Limited + "caravan", // caravan Caravan International, Inc. + "cards", // cards Foggy Hollow, LLC + "care", // care Goose Cross, LLC + "career", // career dotCareer LLC + "careers", // careers Wild Corner, LLC + "cars", // cars Uniregistry, Corp. +// "cartier", // cartier Richemont DNS Inc. + "casa", // casa Top Level Domain Holdings Limited + "case", // case CNH Industrial N.V. + "caseih", // caseih CNH Industrial N.V. + "cash", // cash Delta Lake, LLC + "casino", // casino Binky Sky, LLC + "cat", // cat Fundacio puntCAT + "catering", // catering New Falls. LLC + "catholic", // catholic Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) + "cba", // cba COMMONWEALTH BANK OF AUSTRALIA + "cbn", // cbn The Christian Broadcasting Network, Inc. + "cbre", // cbre CBRE, Inc. + "cbs", // cbs CBS Domains Inc. + "ceb", // ceb The Corporate Executive Board Company + "center", // center Tin Mill, LLC + "ceo", // ceo CEOTLD Pty Ltd + "cern", // cern European Organization for Nuclear Research ("CERN") + "cfa", // cfa CFA Institute + "cfd", // cfd DOTCFD REGISTRY LTD + "chanel", // chanel Chanel International B.V. + "channel", // channel Charleston Road Registry Inc. + "charity", // charity Corn Lake, LLC + "chase", // chase JPMorgan Chase & Co. + "chat", // chat Sand Fields, LLC + "cheap", // cheap Sand Cover, LLC + "chintai", // chintai CHINTAI Corporation +// "chloe", // chloe Richemont DNS Inc. (Not assigned) + "christmas", // christmas Uniregistry, Corp. + "chrome", // chrome Charleston Road Registry Inc. +// "chrysler", // chrysler FCA US LLC. + "church", // church Holly Fileds, LLC + "cipriani", // cipriani Hotel Cipriani Srl + "circle", // circle Amazon Registry Services, Inc. + "cisco", // cisco Cisco Technology, Inc. + "citadel", // citadel Citadel Domain LLC + "citi", // citi Citigroup Inc. + "citic", // citic CITIC Group Corporation + "city", // city Snow Sky, LLC + "cityeats", // cityeats Lifestyle Domain Holdings, Inc. + "claims", // claims Black Corner, LLC + "cleaning", // cleaning Fox Shadow, LLC + "click", // click Uniregistry, Corp. + "clinic", // clinic Goose Park, LLC + "clinique", // clinique The Estée Lauder Companies Inc. + "clothing", // clothing Steel Lake, LLC + "cloud", // cloud ARUBA S.p.A. + "club", // club .CLUB DOMAINS, LLC + "clubmed", // clubmed Club Méditerranée S.A. + "coach", // coach Koko Island, LLC + "codes", // codes Puff Willow, LLC + "coffee", // coffee Trixy Cover, LLC + "college", // college XYZ.COM LLC + "cologne", // cologne NetCologne Gesellschaft für Telekommunikation mbH + "com", // com VeriSign Global Registry Services + "comcast", // comcast Comcast IP Holdings I, LLC + "commbank", // commbank COMMONWEALTH BANK OF AUSTRALIA + "community", // community Fox Orchard, LLC + "company", // company Silver Avenue, LLC + "compare", // compare iSelect Ltd + "computer", // computer Pine Mill, LLC + "comsec", // comsec VeriSign, Inc. + "condos", // condos Pine House, LLC + "construction", // construction Fox Dynamite, LLC + "consulting", // consulting United TLD Holdco, LTD. + "contact", // contact Top Level Spectrum, Inc. + "contractors", // contractors Magic Woods, LLC + "cooking", // cooking Top Level Domain Holdings Limited + "cookingchannel", // cookingchannel Lifestyle Domain Holdings, Inc. + "cool", // cool Koko Lake, LLC + "coop", // coop DotCooperation LLC + "corsica", // corsica Collectivité Territoriale de Corse + "country", // country Top Level Domain Holdings Limited + "coupon", // coupon Amazon Registry Services, Inc. + "coupons", // coupons Black Island, LLC + "courses", // courses OPEN UNIVERSITIES AUSTRALIA PTY LTD + "cpa", // cpa American Institute of Certified Public Accountants + "credit", // credit Snow Shadow, LLC + "creditcard", // creditcard Binky Frostbite, LLC + "creditunion", // creditunion CUNA Performance Resources, LLC + "cricket", // cricket dot Cricket Limited + "crown", // crown Crown Equipment Corporation + "crs", // crs Federated Co-operatives Limited + "cruise", // cruise Viking River Cruises (Bermuda) Ltd. + "cruises", // cruises Spring Way, LLC + "csc", // csc Alliance-One Services, Inc. + "cuisinella", // cuisinella SALM S.A.S. + "cymru", // cymru Nominet UK + "cyou", // cyou Beijing Gamease Age Digital Technology Co., Ltd. + "dabur", // dabur Dabur India Limited + "dad", // dad Charleston Road Registry Inc. + "dance", // dance United TLD Holdco Ltd. + "data", // data Dish DBS Corporation + "date", // date dot Date Limited + "dating", // dating Pine Fest, LLC + "datsun", // datsun NISSAN MOTOR CO., LTD. + "day", // day Charleston Road Registry Inc. + "dclk", // dclk Charleston Road Registry Inc. + "dds", // dds Minds + Machines Group Limited + "deal", // deal Amazon Registry Services, Inc. + "dealer", // dealer Dealer Dot Com, Inc. + "deals", // deals Sand Sunset, LLC + "degree", // degree United TLD Holdco, Ltd + "delivery", // delivery Steel Station, LLC + "dell", // dell Dell Inc. + "deloitte", // deloitte Deloitte Touche Tohmatsu + "delta", // delta Delta Air Lines, Inc. + "democrat", // democrat United TLD Holdco Ltd. + "dental", // dental Tin Birch, LLC + "dentist", // dentist United TLD Holdco, Ltd + "desi", // desi Desi Networks LLC + "design", // design Top Level Design, LLC + "dev", // dev Charleston Road Registry Inc. + "dhl", // dhl Deutsche Post AG + "diamonds", // diamonds John Edge, LLC + "diet", // diet Uniregistry, Corp. + "digital", // digital Dash Park, LLC + "direct", // direct Half Trail, LLC + "directory", // directory Extra Madison, LLC + "discount", // discount Holly Hill, LLC + "discover", // discover Discover Financial Services + "dish", // dish Dish DBS Corporation + "diy", // diy Lifestyle Domain Holdings, Inc. + "dnp", // dnp Dai Nippon Printing Co., Ltd. + "docs", // docs Charleston Road Registry Inc. + "doctor", // doctor Brice Trail, LLC +// "dodge", // dodge FCA US LLC. + "dog", // dog Koko Mill, LLC +// "doha", // doha Communications Regulatory Authority (CRA) + "domains", // domains Sugar Cross, LLC +// "doosan", // doosan Doosan Corporation (retired) + "dot", // dot Dish DBS Corporation + "download", // download dot Support Limited + "drive", // drive Charleston Road Registry Inc. + "dtv", // dtv Dish DBS Corporation + "dubai", // dubai Dubai Smart Government Department + "duck", // duck Johnson Shareholdings, Inc. + "dunlop", // dunlop The Goodyear Tire & Rubber Company +// "duns", // duns The Dun & Bradstreet Corporation + "dupont", // dupont E. I. du Pont de Nemours and Company + "durban", // durban ZA Central Registry NPC trading as ZA Central Registry + "dvag", // dvag Deutsche Vermögensberatung Aktiengesellschaft DVAG + "dvr", // dvr Hughes Satellite Systems Corporation + "earth", // earth Interlink Co., Ltd. + "eat", // eat Charleston Road Registry Inc. + "eco", // eco Big Room Inc. + "edeka", // edeka EDEKA Verband kaufmännischer Genossenschaften e.V. + "edu", // edu EDUCAUSE + "education", // education Brice Way, LLC + "email", // email Spring Madison, LLC + "emerck", // emerck Merck KGaA + "energy", // energy Binky Birch, LLC + "engineer", // engineer United TLD Holdco Ltd. + "engineering", // engineering Romeo Canyon + "enterprises", // enterprises Snow Oaks, LLC +// "epost", // epost Deutsche Post AG + "epson", // epson Seiko Epson Corporation + "equipment", // equipment Corn Station, LLC + "ericsson", // ericsson Telefonaktiebolaget L M Ericsson + "erni", // erni ERNI Group Holding AG + "esq", // esq Charleston Road Registry Inc. + "estate", // estate Trixy Park, LLC + // "esurance", // esurance Esurance Insurance Company (not assigned as at Version 2020062100) + "etisalat", // etisalat Emirates Telecommunic + "eurovision", // eurovision European Broadcasting Union (EBU) + "eus", // eus Puntueus Fundazioa + "events", // events Pioneer Maple, LLC +// "everbank", // everbank EverBank + "exchange", // exchange Spring Falls, LLC + "expert", // expert Magic Pass, LLC + "exposed", // exposed Victor Beach, LLC + "express", // express Sea Sunset, LLC + "extraspace", // extraspace Extra Space Storage LLC + "fage", // fage Fage International S.A. + "fail", // fail Atomic Pipe, LLC + "fairwinds", // fairwinds FairWinds Partners, LLC + "faith", // faith dot Faith Limited + "family", // family United TLD Holdco Ltd. + "fan", // fan Asiamix Digital Ltd + "fans", // fans Asiamix Digital Limited + "farm", // farm Just Maple, LLC + "farmers", // farmers Farmers Insurance Exchange + "fashion", // fashion Top Level Domain Holdings Limited + "fast", // fast Amazon Registry Services, Inc. + "fedex", // fedex Federal Express Corporation + "feedback", // feedback Top Level Spectrum, Inc. + "ferrari", // ferrari Fiat Chrysler Automobiles N.V. + "ferrero", // ferrero Ferrero Trading Lux S.A. + "fiat", // fiat Fiat Chrysler Automobiles N.V. + "fidelity", // fidelity Fidelity Brokerage Services LLC + "fido", // fido Rogers Communications Canada Inc. + "film", // film Motion Picture Domain Registry Pty Ltd + "final", // final Núcleo de Informação e Coordenação do Ponto BR - NIC.br + "finance", // finance Cotton Cypress, LLC + "financial", // financial Just Cover, LLC + "fire", // fire Amazon Registry Services, Inc. + "firestone", // firestone Bridgestone Corporation + "firmdale", // firmdale Firmdale Holdings Limited + "fish", // fish Fox Woods, LLC + "fishing", // fishing Top Level Domain Holdings Limited + "fit", // fit Minds + Machines Group Limited + "fitness", // fitness Brice Orchard, LLC + "flickr", // flickr Yahoo! Domain Services Inc. + "flights", // flights Fox Station, LLC + "flir", // flir FLIR Systems, Inc. + "florist", // florist Half Cypress, LLC + "flowers", // flowers Uniregistry, Corp. +// "flsmidth", // flsmidth FLSmidth A/S retired 2016-07-22 + "fly", // fly Charleston Road Registry Inc. + "foo", // foo Charleston Road Registry Inc. + "food", // food Lifestyle Domain Holdings, Inc. + "foodnetwork", // foodnetwork Lifestyle Domain Holdings, Inc. + "football", // football Foggy Farms, LLC + "ford", // ford Ford Motor Company + "forex", // forex DOTFOREX REGISTRY LTD + "forsale", // forsale United TLD Holdco, LLC + "forum", // forum Fegistry, LLC + "foundation", // foundation John Dale, LLC + "fox", // fox FOX Registry, LLC + "free", // free Amazon Registry Services, Inc. + "fresenius", // fresenius Fresenius Immobilien-Verwaltungs-GmbH + "frl", // frl FRLregistry B.V. + "frogans", // frogans OP3FT + "frontdoor", // frontdoor Lifestyle Domain Holdings, Inc. + "frontier", // frontier Frontier Communications Corporation + "ftr", // ftr Frontier Communications Corporation + "fujitsu", // fujitsu Fujitsu Limited + "fujixerox", // fujixerox Xerox DNHC LLC + "fun", // fun DotSpace, Inc. + "fund", // fund John Castle, LLC + "furniture", // furniture Lone Fields, LLC + "futbol", // futbol United TLD Holdco, Ltd. + "fyi", // fyi Silver Tigers, LLC + "gal", // gal Asociación puntoGAL + "gallery", // gallery Sugar House, LLC + "gallo", // gallo Gallo Vineyards, Inc. + "gallup", // gallup Gallup, Inc. + "game", // game Uniregistry, Corp. + "games", // games United TLD Holdco Ltd. + "gap", // gap The Gap, Inc. + "garden", // garden Top Level Domain Holdings Limited + "gay", // gay Top Level Design, LLC + "gbiz", // gbiz Charleston Road Registry Inc. + "gdn", // gdn Joint Stock Company "Navigation-information systems" + "gea", // gea GEA Group Aktiengesellschaft + "gent", // gent COMBELL GROUP NV/SA + "genting", // genting Resorts World Inc. Pte. Ltd. + "george", // george Wal-Mart Stores, Inc. + "ggee", // ggee GMO Internet, Inc. + "gift", // gift Uniregistry, Corp. + "gifts", // gifts Goose Sky, LLC + "gives", // gives United TLD Holdco Ltd. + "giving", // giving Giving Limited + "glade", // glade Johnson Shareholdings, Inc. + "glass", // glass Black Cover, LLC + "gle", // gle Charleston Road Registry Inc. + "global", // global Dot Global Domain Registry Limited + "globo", // globo Globo Comunicação e Participações S.A + "gmail", // gmail Charleston Road Registry Inc. + "gmbh", // gmbh Extra Dynamite, LLC + "gmo", // gmo GMO Internet, Inc. + "gmx", // gmx 1&1 Mail & Media GmbH + "godaddy", // godaddy Go Daddy East, LLC + "gold", // gold June Edge, LLC + "goldpoint", // goldpoint YODOBASHI CAMERA CO.,LTD. + "golf", // golf Lone Falls, LLC + "goo", // goo NTT Resonant Inc. +// "goodhands", // goodhands Allstate Fire and Casualty Insurance Company + "goodyear", // goodyear The Goodyear Tire & Rubber Company + "goog", // goog Charleston Road Registry Inc. + "google", // google Charleston Road Registry Inc. + "gop", // gop Republican State Leadership Committee, Inc. + "got", // got Amazon Registry Services, Inc. + "gov", // gov General Services Administration Attn: QTDC, 2E08 (.gov Domain Registration) + "grainger", // grainger Grainger Registry Services, LLC + "graphics", // graphics Over Madison, LLC + "gratis", // gratis Pioneer Tigers, LLC + "green", // green Afilias Limited + "gripe", // gripe Corn Sunset, LLC + "grocery", // grocery Wal-Mart Stores, Inc. + "group", // group Romeo Town, LLC + "guardian", // guardian The Guardian Life Insurance Company of America + "gucci", // gucci Guccio Gucci S.p.a. + "guge", // guge Charleston Road Registry Inc. + "guide", // guide Snow Moon, LLC + "guitars", // guitars Uniregistry, Corp. + "guru", // guru Pioneer Cypress, LLC + "hair", // hair L'Oreal + "hamburg", // hamburg Hamburg Top-Level-Domain GmbH + "hangout", // hangout Charleston Road Registry Inc. + "haus", // haus United TLD Holdco, LTD. + "hbo", // hbo HBO Registry Services, Inc. + "hdfc", // hdfc HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED + "hdfcbank", // hdfcbank HDFC Bank Limited + "health", // health DotHealth, LLC + "healthcare", // healthcare Silver Glen, LLC + "help", // help Uniregistry, Corp. + "helsinki", // helsinki City of Helsinki + "here", // here Charleston Road Registry Inc. + "hermes", // hermes Hermes International + "hgtv", // hgtv Lifestyle Domain Holdings, Inc. + "hiphop", // hiphop Uniregistry, Corp. + "hisamitsu", // hisamitsu Hisamitsu Pharmaceutical Co.,Inc. + "hitachi", // hitachi Hitachi, Ltd. + "hiv", // hiv dotHIV gemeinnuetziger e.V. + "hkt", // hkt PCCW-HKT DataCom Services Limited + "hockey", // hockey Half Willow, LLC + "holdings", // holdings John Madison, LLC + "holiday", // holiday Goose Woods, LLC + "homedepot", // homedepot Homer TLC, Inc. + "homegoods", // homegoods The TJX Companies, Inc. + "homes", // homes DERHomes, LLC + "homesense", // homesense The TJX Companies, Inc. + "honda", // honda Honda Motor Co., Ltd. +// "honeywell", // honeywell Honeywell GTLD LLC + "horse", // horse Top Level Domain Holdings Limited + "hospital", // hospital Ruby Pike, LLC + "host", // host DotHost Inc. + "hosting", // hosting Uniregistry, Corp. + "hot", // hot Amazon Registry Services, Inc. + "hoteles", // hoteles Travel Reservations SRL + "hotels", // hotels Booking.com B.V. + "hotmail", // hotmail Microsoft Corporation + "house", // house Sugar Park, LLC + "how", // how Charleston Road Registry Inc. + "hsbc", // hsbc HSBC Holdings PLC +// "htc", // htc HTC corporation (Not assigned) + "hughes", // hughes Hughes Satellite Systems Corporation + "hyatt", // hyatt Hyatt GTLD, L.L.C. + "hyundai", // hyundai Hyundai Motor Company + "ibm", // ibm International Business Machines Corporation + "icbc", // icbc Industrial and Commercial Bank of China Limited + "ice", // ice IntercontinentalExchange, Inc. + "icu", // icu One.com A/S + "ieee", // ieee IEEE Global LLC + "ifm", // ifm ifm electronic gmbh +// "iinet", // iinet Connect West Pty. Ltd. (Retired) + "ikano", // ikano Ikano S.A. + "imamat", // imamat Fondation Aga Khan (Aga Khan Foundation) + "imdb", // imdb Amazon Registry Services, Inc. + "immo", // immo Auburn Bloom, LLC + "immobilien", // immobilien United TLD Holdco Ltd. + "inc", // inc Intercap Holdings Inc. + "industries", // industries Outer House, LLC + "infiniti", // infiniti NISSAN MOTOR CO., LTD. + "info", // info Afilias Limited + "ing", // ing Charleston Road Registry Inc. + "ink", // ink Top Level Design, LLC + "institute", // institute Outer Maple, LLC + "insurance", // insurance fTLD Registry Services LLC + "insure", // insure Pioneer Willow, LLC + "int", // int Internet Assigned Numbers Authority + "intel", // intel Intel Corporation + "international", // international Wild Way, LLC + "intuit", // intuit Intuit Administrative Services, Inc. + "investments", // investments Holly Glen, LLC + "ipiranga", // ipiranga Ipiranga Produtos de Petroleo S.A. + "irish", // irish Dot-Irish LLC +// "iselect", // iselect iSelect Ltd + "ismaili", // ismaili Fondation Aga Khan (Aga Khan Foundation) + "ist", // ist Istanbul Metropolitan Municipality + "istanbul", // istanbul Istanbul Metropolitan Municipality / Medya A.S. + "itau", // itau Itau Unibanco Holding S.A. + "itv", // itv ITV Services Limited + "iveco", // iveco CNH Industrial N.V. +// "iwc", // iwc Richemont DNS Inc. + "jaguar", // jaguar Jaguar Land Rover Ltd + "java", // java Oracle Corporation + "jcb", // jcb JCB Co., Ltd. + "jcp", // jcp JCP Media, Inc. + "jeep", // jeep FCA US LLC. + "jetzt", // jetzt New TLD Company AB + "jewelry", // jewelry Wild Bloom, LLC + "jio", // jio Affinity Names, Inc. +// "jlc", // jlc Richemont DNS Inc. + "jll", // jll Jones Lang LaSalle Incorporated + "jmp", // jmp Matrix IP LLC + "jnj", // jnj Johnson & Johnson Services, Inc. + "jobs", // jobs Employ Media LLC + "joburg", // joburg ZA Central Registry NPC trading as ZA Central Registry + "jot", // jot Amazon Registry Services, Inc. + "joy", // joy Amazon Registry Services, Inc. + "jpmorgan", // jpmorgan JPMorgan Chase & Co. + "jprs", // jprs Japan Registry Services Co., Ltd. + "juegos", // juegos Uniregistry, Corp. + "juniper", // juniper JUNIPER NETWORKS, INC. + "kaufen", // kaufen United TLD Holdco Ltd. + "kddi", // kddi KDDI CORPORATION + "kerryhotels", // kerryhotels Kerry Trading Co. Limited + "kerrylogistics", // kerrylogistics Kerry Trading Co. Limited + "kerryproperties", // kerryproperties Kerry Trading Co. Limited + "kfh", // kfh Kuwait Finance House + "kia", // kia KIA MOTORS CORPORATION + "kim", // kim Afilias Limited + "kinder", // kinder Ferrero Trading Lux S.A. + "kindle", // kindle Amazon Registry Services, Inc. + "kitchen", // kitchen Just Goodbye, LLC + "kiwi", // kiwi DOT KIWI LIMITED + "koeln", // koeln NetCologne Gesellschaft für Telekommunikation mbH + "komatsu", // komatsu Komatsu Ltd. + "kosher", // kosher Kosher Marketing Assets LLC + "kpmg", // kpmg KPMG International Cooperative (KPMG International Genossenschaft) + "kpn", // kpn Koninklijke KPN N.V. + "krd", // krd KRG Department of Information Technology + "kred", // kred KredTLD Pty Ltd + "kuokgroup", // kuokgroup Kerry Trading Co. Limited + "kyoto", // kyoto Academic Institution: Kyoto Jyoho Gakuen + "lacaixa", // lacaixa CAIXA D'ESTALVIS I PENSIONS DE BARCELONA +// "ladbrokes", // ladbrokes LADBROKES INTERNATIONAL PLC + "lamborghini", // lamborghini Automobili Lamborghini S.p.A. + "lamer", // lamer The Estée Lauder Companies Inc. + "lancaster", // lancaster LANCASTER + "lancia", // lancia Fiat Chrysler Automobiles N.V. +// "lancome", // lancome L'Oréal + "land", // land Pine Moon, LLC + "landrover", // landrover Jaguar Land Rover Ltd + "lanxess", // lanxess LANXESS Corporation + "lasalle", // lasalle Jones Lang LaSalle Incorporated + "lat", // lat ECOM-LAC Federación de Latinoamérica y el Caribe para Internet y el Comercio Electrónico + "latino", // latino Dish DBS Corporation + "latrobe", // latrobe La Trobe University + "law", // law Minds + Machines Group Limited + "lawyer", // lawyer United TLD Holdco, Ltd + "lds", // lds IRI Domain Management, LLC + "lease", // lease Victor Trail, LLC + "leclerc", // leclerc A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc + "lefrak", // lefrak LeFrak Organization, Inc. + "legal", // legal Blue Falls, LLC + "lego", // lego LEGO Juris A/S + "lexus", // lexus TOYOTA MOTOR CORPORATION + "lgbt", // lgbt Afilias Limited +// "liaison", // liaison Liaison Technologies, Incorporated + "lidl", // lidl Schwarz Domains und Services GmbH & Co. KG + "life", // life Trixy Oaks, LLC + "lifeinsurance", // lifeinsurance American Council of Life Insurers + "lifestyle", // lifestyle Lifestyle Domain Holdings, Inc. + "lighting", // lighting John McCook, LLC + "like", // like Amazon Registry Services, Inc. + "lilly", // lilly Eli Lilly and Company + "limited", // limited Big Fest, LLC + "limo", // limo Hidden Frostbite, LLC + "lincoln", // lincoln Ford Motor Company + "linde", // linde Linde Aktiengesellschaft + "link", // link Uniregistry, Corp. + "lipsy", // lipsy Lipsy Ltd + "live", // live United TLD Holdco Ltd. + "living", // living Lifestyle Domain Holdings, Inc. + "lixil", // lixil LIXIL Group Corporation + "llc", // llc Afilias plc + "llp", // llp Dot Registry LLC + "loan", // loan dot Loan Limited + "loans", // loans June Woods, LLC + "locker", // locker Dish DBS Corporation + "locus", // locus Locus Analytics LLC + "loft", // loft Annco, Inc. + "lol", // lol Uniregistry, Corp. + "london", // london Dot London Domains Limited + "lotte", // lotte Lotte Holdings Co., Ltd. + "lotto", // lotto Afilias Limited + "love", // love Merchant Law Group LLP + "lpl", // lpl LPL Holdings, Inc. + "lplfinancial", // lplfinancial LPL Holdings, Inc. + "ltd", // ltd Over Corner, LLC + "ltda", // ltda InterNetX Corp. + "lundbeck", // lundbeck H. Lundbeck A/S + "lupin", // lupin LUPIN LIMITED + "luxe", // luxe Top Level Domain Holdings Limited + "luxury", // luxury Luxury Partners LLC + "macys", // macys Macys, Inc. + "madrid", // madrid Comunidad de Madrid + "maif", // maif Mutuelle Assurance Instituteur France (MAIF) + "maison", // maison Victor Frostbite, LLC + "makeup", // makeup L'Oréal + "man", // man MAN SE + "management", // management John Goodbye, LLC + "mango", // mango PUNTO FA S.L. + "map", // map Charleston Road Registry Inc. + "market", // market Unitied TLD Holdco, Ltd + "marketing", // marketing Fern Pass, LLC + "markets", // markets DOTMARKETS REGISTRY LTD + "marriott", // marriott Marriott Worldwide Corporation + "marshalls", // marshalls The TJX Companies, Inc. + "maserati", // maserati Fiat Chrysler Automobiles N.V. + "mattel", // mattel Mattel Sites, Inc. + "mba", // mba Lone Hollow, LLC +// "mcd", // mcd McDonald’s Corporation (Not assigned) +// "mcdonalds", // mcdonalds McDonald’s Corporation (Not assigned) + "mckinsey", // mckinsey McKinsey Holdings, Inc. + "med", // med Medistry LLC + "media", // media Grand Glen, LLC + "meet", // meet Afilias Limited + "melbourne", // melbourne The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation + "meme", // meme Charleston Road Registry Inc. + "memorial", // memorial Dog Beach, LLC + "men", // men Exclusive Registry Limited + "menu", // menu Wedding TLD2, LLC +// "meo", // meo PT Comunicacoes S.A. + "merckmsd", // merckmsd MSD Registry Holdings, Inc. + "metlife", // metlife MetLife Services and Solutions, LLC + "miami", // miami Top Level Domain Holdings Limited + "microsoft", // microsoft Microsoft Corporation + "mil", // mil DoD Network Information Center + "mini", // mini Bayerische Motoren Werke Aktiengesellschaft + "mint", // mint Intuit Administrative Services, Inc. + "mit", // mit Massachusetts Institute of Technology + "mitsubishi", // mitsubishi Mitsubishi Corporation + "mlb", // mlb MLB Advanced Media DH, LLC + "mls", // mls The Canadian Real Estate Association + "mma", // mma MMA IARD + "mobi", // mobi Afilias Technologies Limited dba dotMobi + "mobile", // mobile Dish DBS Corporation +// "mobily", // mobily GreenTech Consultancy Company W.L.L. + "moda", // moda United TLD Holdco Ltd. + "moe", // moe Interlink Co., Ltd. + "moi", // moi Amazon Registry Services, Inc. + "mom", // mom Uniregistry, Corp. + "monash", // monash Monash University + "money", // money Outer McCook, LLC + "monster", // monster Monster Worldwide, Inc. +// "montblanc", // montblanc Richemont DNS Inc. (Not assigned) +// "mopar", // mopar FCA US LLC. + "mormon", // mormon IRI Domain Management, LLC ("Applicant") + "mortgage", // mortgage United TLD Holdco, Ltd + "moscow", // moscow Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) + "moto", // moto Motorola Trademark Holdings, LLC + "motorcycles", // motorcycles DERMotorcycles, LLC + "mov", // mov Charleston Road Registry Inc. + "movie", // movie New Frostbite, LLC +// "movistar", // movistar Telefónica S.A. + "msd", // msd MSD Registry Holdings, Inc. + "mtn", // mtn MTN Dubai Limited +// "mtpc", // mtpc Mitsubishi Tanabe Pharma Corporation (Retired) + "mtr", // mtr MTR Corporation Limited + "museum", // museum Museum Domain Management Association + "mutual", // mutual Northwestern Mutual MU TLD Registry, LLC +// "mutuelle", // mutuelle Fédération Nationale de la Mutualité Française (Retired) + "nab", // nab National Australia Bank Limited +// "nadex", // nadex Nadex Domains, Inc + "nagoya", // nagoya GMO Registry, Inc. + "name", // name VeriSign Information Services, Inc. + "nationwide", // nationwide Nationwide Mutual Insurance Company + "natura", // natura NATURA COSMÉTICOS S.A. + "navy", // navy United TLD Holdco Ltd. + "nba", // nba NBA REGISTRY, LLC + "nec", // nec NEC Corporation + "net", // net VeriSign Global Registry Services + "netbank", // netbank COMMONWEALTH BANK OF AUSTRALIA + "netflix", // netflix Netflix, Inc. + "network", // network Trixy Manor, LLC + "neustar", // neustar NeuStar, Inc. + "new", // new Charleston Road Registry Inc. + "newholland", // newholland CNH Industrial N.V. + "news", // news United TLD Holdco Ltd. + "next", // next Next plc + "nextdirect", // nextdirect Next plc + "nexus", // nexus Charleston Road Registry Inc. + "nfl", // nfl NFL Reg Ops LLC + "ngo", // ngo Public Interest Registry + "nhk", // nhk Japan Broadcasting Corporation (NHK) + "nico", // nico DWANGO Co., Ltd. + "nike", // nike NIKE, Inc. + "nikon", // nikon NIKON CORPORATION + "ninja", // ninja United TLD Holdco Ltd. + "nissan", // nissan NISSAN MOTOR CO., LTD. + "nissay", // nissay Nippon Life Insurance Company + "nokia", // nokia Nokia Corporation + "northwesternmutual", // northwesternmutual Northwestern Mutual Registry, LLC + "norton", // norton Symantec Corporation + "now", // now Amazon Registry Services, Inc. + "nowruz", // nowruz Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "nowtv", // nowtv Starbucks (HK) Limited + "nra", // nra NRA Holdings Company, INC. + "nrw", // nrw Minds + Machines GmbH + "ntt", // ntt NIPPON TELEGRAPH AND TELEPHONE CORPORATION + "nyc", // nyc The City of New York by and through the New York City Department of Information Technology & Telecommunications + "obi", // obi OBI Group Holding SE & Co. KGaA + "observer", // observer Top Level Spectrum, Inc. + "off", // off Johnson Shareholdings, Inc. + "office", // office Microsoft Corporation + "okinawa", // okinawa BusinessRalliart inc. + "olayan", // olayan Crescent Holding GmbH + "olayangroup", // olayangroup Crescent Holding GmbH + "oldnavy", // oldnavy The Gap, Inc. + "ollo", // ollo Dish DBS Corporation + "omega", // omega The Swatch Group Ltd + "one", // one One.com A/S + "ong", // ong Public Interest Registry + "onl", // onl I-REGISTRY Ltd., Niederlassung Deutschland + "online", // online DotOnline Inc. + "onyourside", // onyourside Nationwide Mutual Insurance Company + "ooo", // ooo INFIBEAM INCORPORATION LIMITED + "open", // open American Express Travel Related Services Company, Inc. + "oracle", // oracle Oracle Corporation + "orange", // orange Orange Brand Services Limited + "org", // org Public Interest Registry (PIR) + "organic", // organic Afilias Limited +// "orientexpress", // orientexpress Orient Express (retired 2017-04-11) + "origins", // origins The Estée Lauder Companies Inc. + "osaka", // osaka Interlink Co., Ltd. + "otsuka", // otsuka Otsuka Holdings Co., Ltd. + "ott", // ott Dish DBS Corporation + "ovh", // ovh OVH SAS + "page", // page Charleston Road Registry Inc. +// "pamperedchef", // pamperedchef The Pampered Chef, Ltd. (Not assigned) + "panasonic", // panasonic Panasonic Corporation +// "panerai", // panerai Richemont DNS Inc. + "paris", // paris City of Paris + "pars", // pars Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "partners", // partners Magic Glen, LLC + "parts", // parts Sea Goodbye, LLC + "party", // party Blue Sky Registry Limited + "passagens", // passagens Travel Reservations SRL + "pay", // pay Amazon Registry Services, Inc. + "pccw", // pccw PCCW Enterprises Limited + "pet", // pet Afilias plc + "pfizer", // pfizer Pfizer Inc. + "pharmacy", // pharmacy National Association of Boards of Pharmacy + "phd", // phd Charleston Road Registry Inc. + "philips", // philips Koninklijke Philips N.V. + "phone", // phone Dish DBS Corporation + "photo", // photo Uniregistry, Corp. + "photography", // photography Sugar Glen, LLC + "photos", // photos Sea Corner, LLC + "physio", // physio PhysBiz Pty Ltd +// "piaget", // piaget Richemont DNS Inc. + "pics", // pics Uniregistry, Corp. + "pictet", // pictet Pictet Europe S.A. + "pictures", // pictures Foggy Sky, LLC + "pid", // pid Top Level Spectrum, Inc. + "pin", // pin Amazon Registry Services, Inc. + "ping", // ping Ping Registry Provider, Inc. + "pink", // pink Afilias Limited + "pioneer", // pioneer Pioneer Corporation + "pizza", // pizza Foggy Moon, LLC + "place", // place Snow Galley, LLC + "play", // play Charleston Road Registry Inc. + "playstation", // playstation Sony Computer Entertainment Inc. + "plumbing", // plumbing Spring Tigers, LLC + "plus", // plus Sugar Mill, LLC + "pnc", // pnc PNC Domain Co., LLC + "pohl", // pohl Deutsche Vermögensberatung Aktiengesellschaft DVAG + "poker", // poker Afilias Domains No. 5 Limited + "politie", // politie Politie Nederland + "porn", // porn ICM Registry PN LLC + "post", // post Universal Postal Union + "pramerica", // pramerica Prudential Financial, Inc. + "praxi", // praxi Praxi S.p.A. + "press", // press DotPress Inc. + "prime", // prime Amazon Registry Services, Inc. + "pro", // pro Registry Services Corporation dba RegistryPro + "prod", // prod Charleston Road Registry Inc. + "productions", // productions Magic Birch, LLC + "prof", // prof Charleston Road Registry Inc. + "progressive", // progressive Progressive Casualty Insurance Company + "promo", // promo Afilias plc + "properties", // properties Big Pass, LLC + "property", // property Uniregistry, Corp. + "protection", // protection XYZ.COM LLC + "pru", // pru Prudential Financial, Inc. + "prudential", // prudential Prudential Financial, Inc. + "pub", // pub United TLD Holdco Ltd. + "pwc", // pwc PricewaterhouseCoopers LLP + "qpon", // qpon dotCOOL, Inc. + "quebec", // quebec PointQuébec Inc + "quest", // quest Quest ION Limited + "qvc", // qvc QVC, Inc. + "racing", // racing Premier Registry Limited + "radio", // radio European Broadcasting Union (EBU) + "raid", // raid Johnson Shareholdings, Inc. + "read", // read Amazon Registry Services, Inc. + "realestate", // realestate dotRealEstate LLC + "realtor", // realtor Real Estate Domains LLC + "realty", // realty Fegistry, LLC + "recipes", // recipes Grand Island, LLC + "red", // red Afilias Limited + "redstone", // redstone Redstone Haute Couture Co., Ltd. + "redumbrella", // redumbrella Travelers TLD, LLC + "rehab", // rehab United TLD Holdco Ltd. + "reise", // reise Foggy Way, LLC + "reisen", // reisen New Cypress, LLC + "reit", // reit National Association of Real Estate Investment Trusts, Inc. + "reliance", // reliance Reliance Industries Limited + "ren", // ren Beijing Qianxiang Wangjing Technology Development Co., Ltd. + "rent", // rent XYZ.COM LLC + "rentals", // rentals Big Hollow,LLC + "repair", // repair Lone Sunset, LLC + "report", // report Binky Glen, LLC + "republican", // republican United TLD Holdco Ltd. + "rest", // rest Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable + "restaurant", // restaurant Snow Avenue, LLC + "review", // review dot Review Limited + "reviews", // reviews United TLD Holdco, Ltd. + "rexroth", // rexroth Robert Bosch GMBH + "rich", // rich I-REGISTRY Ltd., Niederlassung Deutschland + "richardli", // richardli Pacific Century Asset Management (HK) Limited + "ricoh", // ricoh Ricoh Company, Ltd. + "rightathome", // rightathome Johnson Shareholdings, Inc. + "ril", // ril Reliance Industries Limited + "rio", // rio Empresa Municipal de Informática SA - IPLANRIO + "rip", // rip United TLD Holdco Ltd. + "rmit", // rmit Royal Melbourne Institute of Technology + "rocher", // rocher Ferrero Trading Lux S.A. + "rocks", // rocks United TLD Holdco, LTD. + "rodeo", // rodeo Top Level Domain Holdings Limited + "rogers", // rogers Rogers Communications Canada Inc. + "room", // room Amazon Registry Services, Inc. + "rsvp", // rsvp Charleston Road Registry Inc. + "rugby", // rugby World Rugby Strategic Developments Limited + "ruhr", // ruhr regiodot GmbH & Co. KG + "run", // run Snow Park, LLC + "rwe", // rwe RWE AG + "ryukyu", // ryukyu BusinessRalliart inc. + "saarland", // saarland dotSaarland GmbH + "safe", // safe Amazon Registry Services, Inc. + "safety", // safety Safety Registry Services, LLC. + "sakura", // sakura SAKURA Internet Inc. + "sale", // sale United TLD Holdco, Ltd + "salon", // salon Outer Orchard, LLC + "samsclub", // samsclub Wal-Mart Stores, Inc. + "samsung", // samsung SAMSUNG SDS CO., LTD + "sandvik", // sandvik Sandvik AB + "sandvikcoromant", // sandvikcoromant Sandvik AB + "sanofi", // sanofi Sanofi + "sap", // sap SAP AG +// "sapo", // sapo PT Comunicacoes S.A. + "sarl", // sarl Delta Orchard, LLC + "sas", // sas Research IP LLC + "save", // save Amazon Registry Services, Inc. + "saxo", // saxo Saxo Bank A/S + "sbi", // sbi STATE BANK OF INDIA + "sbs", // sbs SPECIAL BROADCASTING SERVICE CORPORATION + "sca", // sca SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) + "scb", // scb The Siam Commercial Bank Public Company Limited ("SCB") + "schaeffler", // schaeffler Schaeffler Technologies AG & Co. KG + "schmidt", // schmidt SALM S.A.S. + "scholarships", // scholarships Scholarships.com, LLC + "school", // school Little Galley, LLC + "schule", // schule Outer Moon, LLC + "schwarz", // schwarz Schwarz Domains und Services GmbH & Co. KG + "science", // science dot Science Limited + "scjohnson", // scjohnson Johnson Shareholdings, Inc. + // "scor", // scor SCOR SE (not assigned as at Version 2020062100) + "scot", // scot Dot Scot Registry Limited + "search", // search Charleston Road Registry Inc. + "seat", // seat SEAT, S.A. (Sociedad Unipersonal) + "secure", // secure Amazon Registry Services, Inc. + "security", // security XYZ.COM LLC + "seek", // seek Seek Limited + "select", // select iSelect Ltd + "sener", // sener Sener Ingeniería y Sistemas, S.A. + "services", // services Fox Castle, LLC + "ses", // ses SES + "seven", // seven Seven West Media Ltd + "sew", // sew SEW-EURODRIVE GmbH & Co KG + "sex", // sex ICM Registry SX LLC + "sexy", // sexy Uniregistry, Corp. + "sfr", // sfr Societe Francaise du Radiotelephone - SFR + "shangrila", // shangrila Shangri‐La International Hotel Management Limited + "sharp", // sharp Sharp Corporation + "shaw", // shaw Shaw Cablesystems G.P. + "shell", // shell Shell Information Technology International Inc + "shia", // shia Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "shiksha", // shiksha Afilias Limited + "shoes", // shoes Binky Galley, LLC + "shop", // shop GMO Registry, Inc. + "shopping", // shopping Over Keep, LLC + "shouji", // shouji QIHOO 360 TECHNOLOGY CO. LTD. + "show", // show Snow Beach, LLC + "showtime", // showtime CBS Domains Inc. + "shriram", // shriram Shriram Capital Ltd. + "silk", // silk Amazon Registry Services, Inc. + "sina", // sina Sina Corporation + "singles", // singles Fern Madison, LLC + "site", // site DotSite Inc. + "ski", // ski STARTING DOT LIMITED + "skin", // skin L'Oréal + "sky", // sky Sky International AG + "skype", // skype Microsoft Corporation + "sling", // sling Hughes Satellite Systems Corporation + "smart", // smart Smart Communications, Inc. (SMART) + "smile", // smile Amazon Registry Services, Inc. + "sncf", // sncf SNCF (Société Nationale des Chemins de fer Francais) + "soccer", // soccer Foggy Shadow, LLC + "social", // social United TLD Holdco Ltd. + "softbank", // softbank SoftBank Group Corp. + "software", // software United TLD Holdco, Ltd + "sohu", // sohu Sohu.com Limited + "solar", // solar Ruby Town, LLC + "solutions", // solutions Silver Cover, LLC + "song", // song Amazon Registry Services, Inc. + "sony", // sony Sony Corporation + "soy", // soy Charleston Road Registry Inc. + "space", // space DotSpace Inc. +// "spiegel", // spiegel SPIEGEL-Verlag Rudolf Augstein GmbH & Co. KG + "sport", // sport Global Association of International Sports Federations (GAISF) + "spot", // spot Amazon Registry Services, Inc. + "spreadbetting", // spreadbetting DOTSPREADBETTING REGISTRY LTD + "srl", // srl InterNetX Corp. +// "srt", // srt FCA US LLC. + "stada", // stada STADA Arzneimittel AG + "staples", // staples Staples, Inc. + "star", // star Star India Private Limited +// "starhub", // starhub StarHub Limited + "statebank", // statebank STATE BANK OF INDIA + "statefarm", // statefarm State Farm Mutual Automobile Insurance Company +// "statoil", // statoil Statoil ASA + "stc", // stc Saudi Telecom Company + "stcgroup", // stcgroup Saudi Telecom Company + "stockholm", // stockholm Stockholms kommun + "storage", // storage Self Storage Company LLC + "store", // store DotStore Inc. + "stream", // stream dot Stream Limited + "studio", // studio United TLD Holdco Ltd. + "study", // study OPEN UNIVERSITIES AUSTRALIA PTY LTD + "style", // style Binky Moon, LLC + "sucks", // sucks Vox Populi Registry Ltd. + "supplies", // supplies Atomic Fields, LLC + "supply", // supply Half Falls, LLC + "support", // support Grand Orchard, LLC + "surf", // surf Top Level Domain Holdings Limited + "surgery", // surgery Tin Avenue, LLC + "suzuki", // suzuki SUZUKI MOTOR CORPORATION + "swatch", // swatch The Swatch Group Ltd + "swiftcover", // swiftcover Swiftcover Insurance Services Limited + "swiss", // swiss Swiss Confederation + "sydney", // sydney State of New South Wales, Department of Premier and Cabinet + "symantec", // symantec Symantec Corporation + "systems", // systems Dash Cypress, LLC + "tab", // tab Tabcorp Holdings Limited + "taipei", // taipei Taipei City Government + "talk", // talk Amazon Registry Services, Inc. + "taobao", // taobao Alibaba Group Holding Limited + "target", // target Target Domain Holdings, LLC + "tatamotors", // tatamotors Tata Motors Ltd + "tatar", // tatar LLC "Coordination Center of Regional Domain of Tatarstan Republic" + "tattoo", // tattoo Uniregistry, Corp. + "tax", // tax Storm Orchard, LLC + "taxi", // taxi Pine Falls, LLC + "tci", // tci Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "tdk", // tdk TDK Corporation + "team", // team Atomic Lake, LLC + "tech", // tech Dot Tech LLC + "technology", // technology Auburn Falls, LLC + "tel", // tel Telnic Ltd. +// "telecity", // telecity TelecityGroup International Limited +// "telefonica", // telefonica Telefónica S.A. + "temasek", // temasek Temasek Holdings (Private) Limited + "tennis", // tennis Cotton Bloom, LLC + "teva", // teva Teva Pharmaceutical Industries Limited + "thd", // thd Homer TLC, Inc. + "theater", // theater Blue Tigers, LLC + "theatre", // theatre XYZ.COM LLC + "tiaa", // tiaa Teachers Insurance and Annuity Association of America + "tickets", // tickets Accent Media Limited + "tienda", // tienda Victor Manor, LLC + "tiffany", // tiffany Tiffany and Company + "tips", // tips Corn Willow, LLC + "tires", // tires Dog Edge, LLC + "tirol", // tirol punkt Tirol GmbH + "tjmaxx", // tjmaxx The TJX Companies, Inc. + "tjx", // tjx The TJX Companies, Inc. + "tkmaxx", // tkmaxx The TJX Companies, Inc. + "tmall", // tmall Alibaba Group Holding Limited + "today", // today Pearl Woods, LLC + "tokyo", // tokyo GMO Registry, Inc. + "tools", // tools Pioneer North, LLC + "top", // top Jiangsu Bangning Science & Technology Co.,Ltd. + "toray", // toray Toray Industries, Inc. + "toshiba", // toshiba TOSHIBA Corporation + "total", // total Total SA + "tours", // tours Sugar Station, LLC + "town", // town Koko Moon, LLC + "toyota", // toyota TOYOTA MOTOR CORPORATION + "toys", // toys Pioneer Orchard, LLC + "trade", // trade Elite Registry Limited + "trading", // trading DOTTRADING REGISTRY LTD + "training", // training Wild Willow, LLC + "travel", // travel Tralliance Registry Management Company, LLC. + "travelchannel", // travelchannel Lifestyle Domain Holdings, Inc. + "travelers", // travelers Travelers TLD, LLC + "travelersinsurance", // travelersinsurance Travelers TLD, LLC + "trust", // trust Artemis Internet Inc + "trv", // trv Travelers TLD, LLC + "tube", // tube Latin American Telecom LLC + "tui", // tui TUI AG + "tunes", // tunes Amazon Registry Services, Inc. + "tushu", // tushu Amazon Registry Services, Inc. + "tvs", // tvs T V SUNDRAM IYENGAR & SONS PRIVATE LIMITED + "ubank", // ubank National Australia Bank Limited + "ubs", // ubs UBS AG +// "uconnect", // uconnect FCA US LLC. + "unicom", // unicom China United Network Communications Corporation Limited + "university", // university Little Station, LLC + "uno", // uno Dot Latin LLC + "uol", // uol UBN INTERNET LTDA. + "ups", // ups UPS Market Driver, Inc. + "vacations", // vacations Atomic Tigers, LLC + "vana", // vana Lifestyle Domain Holdings, Inc. + "vanguard", // vanguard The Vanguard Group, Inc. + "vegas", // vegas Dot Vegas, Inc. + "ventures", // ventures Binky Lake, LLC + "verisign", // verisign VeriSign, Inc. + "versicherung", // versicherung dotversicherung-registry GmbH + "vet", // vet United TLD Holdco, Ltd + "viajes", // viajes Black Madison, LLC + "video", // video United TLD Holdco, Ltd + "vig", // vig VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe + "viking", // viking Viking River Cruises (Bermuda) Ltd. + "villas", // villas New Sky, LLC + "vin", // vin Holly Shadow, LLC + "vip", // vip Minds + Machines Group Limited + "virgin", // virgin Virgin Enterprises Limited + "visa", // visa Visa Worldwide Pte. Limited + "vision", // vision Koko Station, LLC +// "vista", // vista Vistaprint Limited +// "vistaprint", // vistaprint Vistaprint Limited + "viva", // viva Saudi Telecom Company + "vivo", // vivo Telefonica Brasil S.A. + "vlaanderen", // vlaanderen DNS.be vzw + "vodka", // vodka Top Level Domain Holdings Limited + "volkswagen", // volkswagen Volkswagen Group of America Inc. + "volvo", // volvo Volvo Holding Sverige Aktiebolag + "vote", // vote Monolith Registry LLC + "voting", // voting Valuetainment Corp. + "voto", // voto Monolith Registry LLC + "voyage", // voyage Ruby House, LLC + "vuelos", // vuelos Travel Reservations SRL + "wales", // wales Nominet UK + "walmart", // walmart Wal-Mart Stores, Inc. + "walter", // walter Sandvik AB + "wang", // wang Zodiac Registry Limited + "wanggou", // wanggou Amazon Registry Services, Inc. +// "warman", // warman Weir Group IP Limited + "watch", // watch Sand Shadow, LLC + "watches", // watches Richemont DNS Inc. + "weather", // weather The Weather Channel, LLC + "weatherchannel", // weatherchannel The Weather Channel, LLC + "webcam", // webcam dot Webcam Limited + "weber", // weber Saint-Gobain Weber SA + "website", // website DotWebsite Inc. + "wed", // wed Atgron, Inc. + "wedding", // wedding Top Level Domain Holdings Limited + "weibo", // weibo Sina Corporation + "weir", // weir Weir Group IP Limited + "whoswho", // whoswho Who's Who Registry + "wien", // wien punkt.wien GmbH + "wiki", // wiki Top Level Design, LLC + "williamhill", // williamhill William Hill Organization Limited + "win", // win First Registry Limited + "windows", // windows Microsoft Corporation + "wine", // wine June Station, LLC + "winners", // winners The TJX Companies, Inc. + "wme", // wme William Morris Endeavor Entertainment, LLC + "wolterskluwer", // wolterskluwer Wolters Kluwer N.V. + "woodside", // woodside Woodside Petroleum Limited + "work", // work Top Level Domain Holdings Limited + "works", // works Little Dynamite, LLC + "world", // world Bitter Fields, LLC + "wow", // wow Amazon Registry Services, Inc. + "wtc", // wtc World Trade Centers Association, Inc. + "wtf", // wtf Hidden Way, LLC + "xbox", // xbox Microsoft Corporation + "xerox", // xerox Xerox DNHC LLC + "xfinity", // xfinity Comcast IP Holdings I, LLC + "xihuan", // xihuan QIHOO 360 TECHNOLOGY CO. LTD. + "xin", // xin Elegant Leader Limited + "xn--11b4c3d", // कॉम VeriSign Sarl + "xn--1ck2e1b", // セール Amazon Registry Services, Inc. + "xn--1qqw23a", // 佛山 Guangzhou YU Wei Information Technology Co., Ltd. + "xn--30rr7y", // 慈善 Excellent First Limited + "xn--3bst00m", // 集团 Eagle Horizon Limited + "xn--3ds443g", // 在线 TLD REGISTRY LIMITED + "xn--3oq18vl8pn36a", // 大众汽车 Volkswagen (China) Investment Co., Ltd. + "xn--3pxu8k", // 点看 VeriSign Sarl + "xn--42c2d9a", // คอม VeriSign Sarl + "xn--45q11c", // 八卦 Zodiac Scorpio Limited + "xn--4gbrim", // موقع Suhub Electronic Establishment + "xn--55qw42g", // 公益 China Organizational Name Administration Center + "xn--55qx5d", // 公司 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center) + "xn--5su34j936bgsg", // 香格里拉 Shangri‐La International Hotel Management Limited + "xn--5tzm5g", // 网站 Global Website TLD Asia Limited + "xn--6frz82g", // 移动 Afilias Limited + "xn--6qq986b3xl", // 我爱你 Tycoon Treasure Limited + "xn--80adxhks", // москва Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) + "xn--80aqecdr1a", // католик Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) + "xn--80asehdb", // онлайн CORE Association + "xn--80aswg", // сайт CORE Association + "xn--8y0a063a", // 联通 China United Network Communications Corporation Limited + "xn--90ae", // бг Imena.BG Plc (NAMES.BG Plc) + "xn--9dbq2a", // קום VeriSign Sarl + "xn--9et52u", // 时尚 RISE VICTORY LIMITED + "xn--9krt00a", // 微博 Sina Corporation + "xn--b4w605ferd", // 淡马锡 Temasek Holdings (Private) Limited + "xn--bck1b9a5dre4c", // ファッション Amazon Registry Services, Inc. + "xn--c1avg", // орг Public Interest Registry + "xn--c2br7g", // नेट VeriSign Sarl + "xn--cck2b3b", // ストア Amazon Registry Services, Inc. + "xn--cckwcxetd", // アマゾン Amazon Registry Services, Inc. + "xn--cg4bki", // 삼성 SAMSUNG SDS CO., LTD + "xn--czr694b", // 商标 HU YI GLOBAL INFORMATION RESOURCES(HOLDING) COMPANY.HONGKONG LIMITED + "xn--czrs0t", // 商店 Wild Island, LLC + "xn--czru2d", // 商城 Zodiac Aquarius Limited + "xn--d1acj3b", // дети The Foundation for Network Initiatives “The Smart Internet” + "xn--eckvdtc9d", // ポイント Amazon Registry Services, Inc. + "xn--efvy88h", // 新闻 Xinhua News Agency Guangdong Branch 新华通讯社广东分社 +// "xn--estv75g", // 工行 Industrial and Commercial Bank of China Limited + "xn--fct429k", // 家電 Amazon Registry Services, Inc. + "xn--fhbei", // كوم VeriSign Sarl + "xn--fiq228c5hs", // 中文网 TLD REGISTRY LIMITED + "xn--fiq64b", // 中信 CITIC Group Corporation + "xn--fjq720a", // 娱乐 Will Bloom, LLC + "xn--flw351e", // 谷歌 Charleston Road Registry Inc. + "xn--fzys8d69uvgm", // 電訊盈科 PCCW Enterprises Limited + "xn--g2xx48c", // 购物 Minds + Machines Group Limited + "xn--gckr3f0f", // クラウド Amazon Registry Services, Inc. + "xn--gk3at1e", // 通販 Amazon Registry Services, Inc. + "xn--hxt814e", // 网店 Zodiac Libra Limited + "xn--i1b6b1a6a2e", // संगठन Public Interest Registry + "xn--imr513n", // 餐厅 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG LIMITED + "xn--io0a7i", // 网络 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center) + "xn--j1aef", // ком VeriSign Sarl + "xn--jlq480n2rg", // 亚马逊 Amazon Registry Services, Inc. + "xn--jlq61u9w7b", // 诺基亚 Nokia Corporation + "xn--jvr189m", // 食品 Amazon Registry Services, Inc. + "xn--kcrx77d1x4a", // 飞利浦 Koninklijke Philips N.V. + "xn--kpu716f", // 手表 Richemont DNS Inc. + "xn--kput3i", // 手机 Beijing RITT-Net Technology Development Co., Ltd + "xn--mgba3a3ejt", // ارامكو Aramco Services Company + "xn--mgba7c0bbn0a", // العليان Crescent Holding GmbH + "xn--mgbaakc7dvf", // اتصالات Emirates Telecommunications Corporation (trading as Etisalat) + "xn--mgbab2bd", // بازار CORE Association +// "xn--mgbb9fbpob", // موبايلي GreenTech Consultancy Company W.L.L. + "xn--mgbca7dzdo", // ابوظبي Abu Dhabi Systems and Information Centre + "xn--mgbi4ecexp", // كاثوليك Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) + "xn--mgbt3dhd", // همراه Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "xn--mk1bu44c", // 닷컴 VeriSign Sarl + "xn--mxtq1m", // 政府 Net-Chinese Co., Ltd. + "xn--ngbc5azd", // شبكة International Domain Registry Pty. Ltd. + "xn--ngbe9e0a", // بيتك Kuwait Finance House + "xn--ngbrx", // عرب League of Arab States + "xn--nqv7f", // 机构 Public Interest Registry + "xn--nqv7fs00ema", // 组织机构 Public Interest Registry + "xn--nyqy26a", // 健康 Stable Tone Limited + "xn--otu796d", // 招聘 Dot Trademark TLD Holding Company Limited + "xn--p1acf", // рус Rusnames Limited + "xn--pbt977c", // 珠宝 Richemont DNS Inc. + "xn--pssy2u", // 大拿 VeriSign Sarl + "xn--q9jyb4c", // みんな Charleston Road Registry Inc. + "xn--qcka1pmc", // グーグル Charleston Road Registry Inc. + "xn--rhqv96g", // 世界 Stable Tone Limited + "xn--rovu88b", // 書籍 Amazon EU S.à r.l. + "xn--ses554g", // 网址 KNET Co., Ltd + "xn--t60b56a", // 닷넷 VeriSign Sarl + "xn--tckwe", // コム VeriSign Sarl + "xn--tiq49xqyj", // 天主教 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) + "xn--unup4y", // 游戏 Spring Fields, LLC + "xn--vermgensberater-ctb", // VERMöGENSBERATER Deutsche Vermögensberatung Aktiengesellschaft DVAG + "xn--vermgensberatung-pwb", // VERMöGENSBERATUNG Deutsche Vermögensberatung Aktiengesellschaft DVAG + "xn--vhquv", // 企业 Dash McCook, LLC + "xn--vuq861b", // 信息 Beijing Tele-info Network Technology Co., Ltd. + "xn--w4r85el8fhu5dnra", // 嘉里大酒店 Kerry Trading Co. Limited + "xn--w4rs40l", // 嘉里 Kerry Trading Co. Limited + "xn--xhq521b", // 广东 Guangzhou YU Wei Information Technology Co., Ltd. + "xn--zfr164b", // 政务 China Organizational Name Administration Center +// "xperia", // xperia Sony Mobile Communications AB + "xxx", // xxx ICM Registry LLC + "xyz", // xyz XYZ.COM LLC + "yachts", // yachts DERYachts, LLC + "yahoo", // yahoo Yahoo! Domain Services Inc. + "yamaxun", // yamaxun Amazon Registry Services, Inc. + "yandex", // yandex YANDEX, LLC + "yodobashi", // yodobashi YODOBASHI CAMERA CO.,LTD. + "yoga", // yoga Top Level Domain Holdings Limited + "yokohama", // yokohama GMO Registry, Inc. + "you", // you Amazon Registry Services, Inc. + "youtube", // youtube Charleston Road Registry Inc. + "yun", // yun QIHOO 360 TECHNOLOGY CO. LTD. + "zappos", // zappos Amazon Registry Services, Inc. + "zara", // zara Industria de Diseño Textil, S.A. (INDITEX, S.A.) + "zero", // zero Amazon Registry Services, Inc. + "zip", // zip Charleston Road Registry Inc. +// "zippo", // zippo Zadco Company + "zone", // zone Outer Falls, LLC + "zuerich", // zuerich Kanton Zürich (Canton of Zurich) +}; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static final String[] COUNTRY_CODE_TLDS = new String[] { + // Taken from Version 2020051000, Last Updated Sun May 10 07:07:01 2020 UTC + "ac", // Ascension Island + "ad", // Andorra + "ae", // United Arab Emirates + "af", // Afghanistan + "ag", // Antigua and Barbuda + "ai", // Anguilla + "al", // Albania + "am", // Armenia +// "an", // Netherlands Antilles (retired) + "ao", // Angola + "aq", // Antarctica + "ar", // Argentina + "as", // American Samoa + "at", // Austria + "au", // Australia (includes Ashmore and Cartier Islands and Coral Sea Islands) + "aw", // Aruba + "ax", // Åland + "az", // Azerbaijan + "ba", // Bosnia and Herzegovina + "bb", // Barbados + "bd", // Bangladesh + "be", // Belgium + "bf", // Burkina Faso + "bg", // Bulgaria + "bh", // Bahrain + "bi", // Burundi + "bj", // Benin + "bm", // Bermuda + "bn", // Brunei Darussalam + "bo", // Bolivia + "br", // Brazil + "bs", // Bahamas + "bt", // Bhutan + "bv", // Bouvet Island + "bw", // Botswana + "by", // Belarus + "bz", // Belize + "ca", // Canada + "cc", // Cocos (Keeling) Islands + "cd", // Democratic Republic of the Congo (formerly Zaire) + "cf", // Central African Republic + "cg", // Republic of the Congo + "ch", // Switzerland + "ci", // Côte d'Ivoire + "ck", // Cook Islands + "cl", // Chile + "cm", // Cameroon + "cn", // China, mainland + "co", // Colombia + "cr", // Costa Rica + "cu", // Cuba + "cv", // Cape Verde + "cw", // Curaçao + "cx", // Christmas Island + "cy", // Cyprus + "cz", // Czech Republic + "de", // Germany + "dj", // Djibouti + "dk", // Denmark + "dm", // Dominica + "do", // Dominican Republic + "dz", // Algeria + "ec", // Ecuador + "ee", // Estonia + "eg", // Egypt + "er", // Eritrea + "es", // Spain + "et", // Ethiopia + "eu", // European Union + "fi", // Finland + "fj", // Fiji + "fk", // Falkland Islands + "fm", // Federated States of Micronesia + "fo", // Faroe Islands + "fr", // France + "ga", // Gabon + "gb", // Great Britain (United Kingdom) + "gd", // Grenada + "ge", // Georgia + "gf", // French Guiana + "gg", // Guernsey + "gh", // Ghana + "gi", // Gibraltar + "gl", // Greenland + "gm", // The Gambia + "gn", // Guinea + "gp", // Guadeloupe + "gq", // Equatorial Guinea + "gr", // Greece + "gs", // South Georgia and the South Sandwich Islands + "gt", // Guatemala + "gu", // Guam + "gw", // Guinea-Bissau + "gy", // Guyana + "hk", // Hong Kong + "hm", // Heard Island and McDonald Islands + "hn", // Honduras + "hr", // Croatia (Hrvatska) + "ht", // Haiti + "hu", // Hungary + "id", // Indonesia + "ie", // Ireland (Éire) + "il", // Israel + "im", // Isle of Man + "in", // India + "io", // British Indian Ocean Territory + "iq", // Iraq + "ir", // Iran + "is", // Iceland + "it", // Italy + "je", // Jersey + "jm", // Jamaica + "jo", // Jordan + "jp", // Japan + "ke", // Kenya + "kg", // Kyrgyzstan + "kh", // Cambodia (Khmer) + "ki", // Kiribati + "km", // Comoros + "kn", // Saint Kitts and Nevis + "kp", // North Korea + "kr", // South Korea + "kw", // Kuwait + "ky", // Cayman Islands + "kz", // Kazakhstan + "la", // Laos (currently being marketed as the official domain for Los Angeles) + "lb", // Lebanon + "lc", // Saint Lucia + "li", // Liechtenstein + "lk", // Sri Lanka + "lr", // Liberia + "ls", // Lesotho + "lt", // Lithuania + "lu", // Luxembourg + "lv", // Latvia + "ly", // Libya + "ma", // Morocco + "mc", // Monaco + "md", // Moldova + "me", // Montenegro + "mg", // Madagascar + "mh", // Marshall Islands + "mk", // Republic of Macedonia + "ml", // Mali + "mm", // Myanmar + "mn", // Mongolia + "mo", // Macau + "mp", // Northern Mariana Islands + "mq", // Martinique + "mr", // Mauritania + "ms", // Montserrat + "mt", // Malta + "mu", // Mauritius + "mv", // Maldives + "mw", // Malawi + "mx", // Mexico + "my", // Malaysia + "mz", // Mozambique + "na", // Namibia + "nc", // New Caledonia + "ne", // Niger + "nf", // Norfolk Island + "ng", // Nigeria + "ni", // Nicaragua + "nl", // Netherlands + "no", // Norway + "np", // Nepal + "nr", // Nauru + "nu", // Niue + "nz", // New Zealand + "om", // Oman + "pa", // Panama + "pe", // Peru + "pf", // French Polynesia With Clipperton Island + "pg", // Papua New Guinea + "ph", // Philippines + "pk", // Pakistan + "pl", // Poland + "pm", // Saint-Pierre and Miquelon + "pn", // Pitcairn Islands + "pr", // Puerto Rico + "ps", // Palestinian territories (PA-controlled West Bank and Gaza Strip) + "pt", // Portugal + "pw", // Palau + "py", // Paraguay + "qa", // Qatar + "re", // Réunion + "ro", // Romania + "rs", // Serbia + "ru", // Russia + "rw", // Rwanda + "sa", // Saudi Arabia + "sb", // Solomon Islands + "sc", // Seychelles + "sd", // Sudan + "se", // Sweden + "sg", // Singapore + "sh", // Saint Helena + "si", // Slovenia + "sj", // Svalbard and Jan Mayen Islands Not in use (Norwegian dependencies; see .no) + "sk", // Slovakia + "sl", // Sierra Leone + "sm", // San Marino + "sn", // Senegal + "so", // Somalia + "sr", // Suriname + "ss", // ss National Communication Authority (NCA) + "st", // São Tomé and Príncipe + "su", // Soviet Union (deprecated) + "sv", // El Salvador + "sx", // Sint Maarten + "sy", // Syria + "sz", // Swaziland + "tc", // Turks and Caicos Islands + "td", // Chad + "tf", // French Southern and Antarctic Lands + "tg", // Togo + "th", // Thailand + "tj", // Tajikistan + "tk", // Tokelau + "tl", // East Timor (deprecated old code) + "tm", // Turkmenistan + "tn", // Tunisia + "to", // Tonga +// "tp", // East Timor (Retired) + "tr", // Turkey + "tt", // Trinidad and Tobago + "tv", // Tuvalu + "tw", // Taiwan, Republic of China + "tz", // Tanzania + "ua", // Ukraine + "ug", // Uganda + "uk", // United Kingdom + "us", // United States of America + "uy", // Uruguay + "uz", // Uzbekistan + "va", // Vatican City State + "vc", // Saint Vincent and the Grenadines + "ve", // Venezuela + "vg", // British Virgin Islands + "vi", // U.S. Virgin Islands + "vn", // Vietnam + "vu", // Vanuatu + "wf", // Wallis and Futuna + "ws", // Samoa (formerly Western Samoa) + "xn--2scrj9c", // ಭಾರತ National Internet eXchange of India + "xn--3e0b707e", // 한국 KISA (Korea Internet & Security Agency) + "xn--3hcrj9c", // ଭାରତ National Internet eXchange of India + "xn--45br5cyl", // ভাৰত National Internet eXchange of India + "xn--45brj9c", // ভারত National Internet Exchange of India + "xn--54b7fta0cc", // বাংলা Posts and Telecommunications Division + "xn--80ao21a", // қаз Association of IT Companies of Kazakhstan + "xn--90a3ac", // срб Serbian National Internet Domain Registry (RNIDS) + "xn--90ais", // ??? Reliable Software Inc. + "xn--clchc0ea0b2g2a9gcd", // சிங்கப்பூர் Singapore Network Information Centre (SGNIC) Pte Ltd + "xn--d1alf", // мкд Macedonian Academic Research Network Skopje + "xn--e1a4c", // ею EURid vzw/asbl + "xn--fiqs8s", // 中国 China Internet Network Information Center + "xn--fiqz9s", // 中國 China Internet Network Information Center + "xn--fpcrj9c3d", // భారత్ National Internet Exchange of India + "xn--fzc2c9e2c", // ලංකා LK Domain Registry + "xn--gecrj9c", // ભારત National Internet Exchange of India + "xn--h2breg3eve", // भारतम् National Internet eXchange of India + "xn--h2brj9c", // भारत National Internet Exchange of India + "xn--h2brj9c8c", // भारोत National Internet eXchange of India + "xn--j1amh", // укр Ukrainian Network Information Centre (UANIC), Inc. + "xn--j6w193g", // 香港 Hong Kong Internet Registration Corporation Ltd. + "xn--kprw13d", // 台湾 Taiwan Network Information Center (TWNIC) + "xn--kpry57d", // 台灣 Taiwan Network Information Center (TWNIC) + "xn--l1acc", // мон Datacom Co.,Ltd + "xn--lgbbat1ad8j", // الجزائر CERIST + "xn--mgb9awbf", // عمان Telecommunications Regulatory Authority (TRA) + "xn--mgba3a4f16a", // ایران Institute for Research in Fundamental Sciences (IPM) + "xn--mgbaam7a8h", // امارات Telecommunications Regulatory Authority (TRA) + "xn--mgbah1a3hjkrd", // موريتانيا Université de Nouakchott Al Aasriya + "xn--mgbai9azgqp6j", // پاکستان National Telecommunication Corporation + "xn--mgbayh7gpa", // الاردن National Information Technology Center (NITC) + "xn--mgbbh1a", // بارت National Internet eXchange of India + "xn--mgbbh1a71e", // بھارت National Internet Exchange of India + "xn--mgbc0a9azcg", // المغرب Agence Nationale de Réglementation des Télécommunications (ANRT) + "xn--mgbcpq6gpa1a", // البحرين Telecommunications Regulatory Authority (TRA) + "xn--mgberp4a5d4ar", // السعودية Communications and Information Technology Commission + "xn--mgbgu82a", // ڀارت National Internet eXchange of India + "xn--mgbpl2fh", // ????? Sudan Internet Society + "xn--mgbtx2b", // عراق Communications and Media Commission (CMC) + "xn--mgbx4cd0ab", // مليسيا MYNIC Berhad + "xn--mix891f", // 澳門 Bureau of Telecommunications Regulation (DSRT) + "xn--node", // გე Information Technologies Development Center (ITDC) + "xn--o3cw4h", // ไทย Thai Network Information Center Foundation + "xn--ogbpf8fl", // سورية National Agency for Network Services (NANS) + "xn--p1ai", // рф Coordination Center for TLD RU + "xn--pgbs0dh", // تونس Agence Tunisienne d'Internet + "xn--q7ce6a", // ລາວ Lao National Internet Center (LANIC) + "xn--qxa6a", // ευ EURid vzw/asbl + "xn--qxam", // ελ ICS-FORTH GR + "xn--rvc1e0am3e", // ഭാരതം National Internet eXchange of India + "xn--s9brj9c", // ਭਾਰਤ National Internet Exchange of India + "xn--wgbh1c", // مصر National Telecommunication Regulatory Authority - NTRA + "xn--wgbl6a", // قطر Communications Regulatory Authority + "xn--xkc2al3hye2a", // இலங்கை LK Domain Registry + "xn--xkc2dl3a5ee0h", // இந்தியா National Internet Exchange of India + "xn--y9a3aq", // ??? Internet Society + "xn--yfro4i67o", // 新加坡 Singapore Network Information Centre (SGNIC) Pte Ltd + "xn--ygbi2ammx", // فلسطين Ministry of Telecom & Information Technology (MTIT) + "ye", // Yemen + "yt", // Mayotte + "za", // South Africa + "zm", // Zambia + "zw", // Zimbabwe + }; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static final String[] LOCAL_TLDS = new String[] { + "localdomain", // Also widely used as localhost.localdomain + "localhost", // RFC2606 defined + }; + + // Additional arrays to supplement or override the built in ones. + // The PLUS arrays are valid keys, the MINUS arrays are invalid keys + + /* + * This field is used to detect whether the getInstance has been called. + * After this, the method updateTLDOverride is not allowed to be called. + * This field does not need to be volatile since it is only accessed from + * synchronized methods. + */ + private static boolean inUse = false; + + /* + * These arrays are mutable. + * They can only be updated by the updateTLDOverride method, and readers must first get an instance + * using the getInstance methods which are all (now) synchronised. + * The only other access is via getTLDEntries which is now synchronised. + */ + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] countryCodeTLDsPlus = EMPTY_STRING_ARRAY; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] genericTLDsPlus = EMPTY_STRING_ARRAY; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] countryCodeTLDsMinus = EMPTY_STRING_ARRAY; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] genericTLDsMinus = EMPTY_STRING_ARRAY; + + /** + * enum used by {@link DomainValidator#updateTLDOverride(ArrayType, String[])} + * to determine which override array to update / fetch + * @since 1.5.0 + * @since 1.5.1 made public and added read-only array references + */ + public enum ArrayType { + /** Update (or get a copy of) the GENERIC_TLDS_PLUS table containing additonal generic TLDs */ + GENERIC_PLUS, + /** Update (or get a copy of) the GENERIC_TLDS_MINUS table containing deleted generic TLDs */ + GENERIC_MINUS, + /** Update (or get a copy of) the COUNTRY_CODE_TLDS_PLUS table containing additonal country code TLDs */ + COUNTRY_CODE_PLUS, + /** Update (or get a copy of) the COUNTRY_CODE_TLDS_MINUS table containing deleted country code TLDs */ + COUNTRY_CODE_MINUS, + /** Get a copy of the generic TLDS table */ + GENERIC_RO, + /** Get a copy of the country code table */ + COUNTRY_CODE_RO, + /** Get a copy of the infrastructure table */ + INFRASTRUCTURE_RO, + /** Get a copy of the local table */ + LOCAL_RO + ; + }; + + // For use by unit test code only + static synchronized void clearTLDOverrides() { + inUse = false; + countryCodeTLDsPlus = EMPTY_STRING_ARRAY; + countryCodeTLDsMinus = EMPTY_STRING_ARRAY; + genericTLDsPlus = EMPTY_STRING_ARRAY; + genericTLDsMinus = EMPTY_STRING_ARRAY; + } + /** + * Update one of the TLD override arrays. + * This must only be done at program startup, before any instances are accessed using getInstance. + *

+ * For example: + *

+ * {@code DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"apache"})} + *

+ * To clear an override array, provide an empty array. + * + * @param table the table to update, see {@link DomainValidator.ArrayType} + * Must be one of the following + *

    + *
  • COUNTRY_CODE_MINUS
  • + *
  • COUNTRY_CODE_PLUS
  • + *
  • GENERIC_MINUS
  • + *
  • GENERIC_PLUS
  • + *
+ * @param tlds the array of TLDs, must not be null + * @throws IllegalStateException if the method is called after getInstance + * @throws IllegalArgumentException if one of the read-only tables is requested + * @since 1.5.0 + */ + public static synchronized void updateTLDOverride(ArrayType table, String [] tlds) { + if (inUse) { + throw new IllegalStateException("Can only invoke this method before calling getInstance"); + } + String [] copy = new String[tlds.length]; + // Comparisons are always done with lower-case entries + for (int i = 0; i < tlds.length; i++) { + copy[i] = tlds[i].toLowerCase(Locale.ENGLISH); + } + Arrays.sort(copy); + switch(table) { + case COUNTRY_CODE_MINUS: + countryCodeTLDsMinus = copy; + break; + case COUNTRY_CODE_PLUS: + countryCodeTLDsPlus = copy; + break; + case GENERIC_MINUS: + genericTLDsMinus = copy; + break; + case GENERIC_PLUS: + genericTLDsPlus = copy; + break; + case COUNTRY_CODE_RO: + case GENERIC_RO: + case INFRASTRUCTURE_RO: + case LOCAL_RO: + throw new IllegalArgumentException("Cannot update the table: " + table); + default: + throw new IllegalArgumentException("Unexpected enum value: " + table); + } + } + + /** + * Get a copy of the internal array. + * @param table the array type (any of the enum values) + * @return a copy of the array + * @throws IllegalArgumentException if the table type is unexpected (should not happen) + * @since 1.5.1 + */ + public static synchronized String [] getTLDEntries(ArrayType table) { + final String array[]; + switch(table) { + case COUNTRY_CODE_MINUS: + array = countryCodeTLDsMinus; + break; + case COUNTRY_CODE_PLUS: + array = countryCodeTLDsPlus; + break; + case GENERIC_MINUS: + array = genericTLDsMinus; + break; + case GENERIC_PLUS: + array = genericTLDsPlus; + break; + case GENERIC_RO: + array = GENERIC_TLDS; + break; + case COUNTRY_CODE_RO: + array = COUNTRY_CODE_TLDS; + break; + case INFRASTRUCTURE_RO: + array = INFRASTRUCTURE_TLDS; + break; + case LOCAL_RO: + array = LOCAL_TLDS; + break; + default: + throw new IllegalArgumentException("Unexpected enum value: " + table); + } + return Arrays.copyOf(array, array.length); // clone the array + } + + /** + * Converts potentially Unicode input to punycode. + * If conversion fails, returns the original input. + * + * @param input the string to convert, not null + * @return converted input, or original input if conversion fails + */ + // Needed by UrlValidator + static String unicodeToASCII(String input) { + if (isOnlyASCII(input)) { // skip possibly expensive processing + return input; + } + try { + final String ascii = IDN.toASCII(input); + if (IDNBUGHOLDER.IDN_TOASCII_PRESERVES_TRAILING_DOTS) { + return ascii; + } + final int length = input.length(); + if (length == 0) {// check there is a last character + return input; + } + // RFC3490 3.1. 1) + // Whenever dots are used as label separators, the following + // characters MUST be recognized as dots: U+002E (full stop), U+3002 + // (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 + // (halfwidth ideographic full stop). + char lastChar = input.charAt(length-1);// fetch original last char + switch(lastChar) { + case '\u002E': // "." full stop + case '\u3002': // ideographic full stop + case '\uFF0E': // fullwidth full stop + case '\uFF61': // halfwidth ideographic full stop + return ascii + "."; // restore the missing stop + default: + return ascii; + } + } catch (IllegalArgumentException e) { // input is not valid + return input; + } + } + + private static class IDNBUGHOLDER { + private static boolean keepsTrailingDot() { + final String input = "a."; // must be a valid name + return input.equals(IDN.toASCII(input)); + } + private static final boolean IDN_TOASCII_PRESERVES_TRAILING_DOTS = keepsTrailingDot(); + } + + /* + * Check if input contains only ASCII + * Treats null as all ASCII + */ + private static boolean isOnlyASCII(String input) { + if (input == null) { + return true; + } + for(int i=0; i < input.length(); i++) { + if (input.charAt(i) > 0x7F) { // CHECKSTYLE IGNORE MagicNumber + return false; + } + } + return true; + } + + /** + * Check if a sorted array contains the specified key + * + * @param sortedArray the array to search + * @param key the key to find + * @return {@code true} if the array contains the key + */ + private static boolean arrayContains(String[] sortedArray, String key) { + return Arrays.binarySearch(sortedArray, key) >= 0; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DoubleValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DoubleValidator.java new file mode 100644 index 000000000..d24a3d835 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DoubleValidator.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.Format; +import java.util.Locale; + +/** + *

Double Validation and Conversion routines (java.lang.Double).

+ * + *

This validator provides a number of methods for + * validating/converting a String value to + * a Double using java.text.NumberFormat + * to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Double value.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform minimum, maximum and range checks:

+ *
    + *
  • minValue() checks whether the value is greater + * than or equal to a specified minimum.
  • + *
  • maxValue() checks whether the value is less + * than or equal to a specified maximum.
  • + *
  • isInRange() checks whether the value is within + * a specified range of values.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class DoubleValidator extends AbstractNumberValidator { + + private static final long serialVersionUID = 5867946581318211330L; + + private static final DoubleValidator VALIDATOR = new DoubleValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the DoubleValidator. + */ + public static DoubleValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public DoubleValidator() { + this(true, STANDARD_FORMAT); + } + + /** + *

Construct an instance with the specified strict setting + * and format type.

+ * + *

The formatType specified what type of + * NumberFormat is created - valid types + * are:

+ *
    + *
  • AbstractNumberValidator.STANDARD_FORMAT -to create + * standard number formats (the default).
  • + *
  • AbstractNumberValidator.CURRENCY_FORMAT -to create + * currency number formats.
  • + *
  • AbstractNumberValidator.PERCENT_FORMAT -to create + * percent number formats (the default).
  • + *
+ * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + */ + public DoubleValidator(boolean strict, int formatType) { + super(strict, formatType, true); + } + + /** + *

Validate/convert a Double using the default + * Locale. + * + * @param value The value validation is being performed on. + * @return The parsed Double if valid or null + * if invalid. + */ + public Double validate(String value) { + return (Double)parse(value, (String)null, (Locale)null); + } + + /** + *

Validate/convert a Double using the + * specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed BigDecimal if valid or null if invalid. + */ + public Double validate(String value, String pattern) { + return (Double)parse(value, pattern, (Locale)null); + } + + /** + *

Validate/convert a Double using the + * specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the number format, system default if null. + * @return The parsed Double if valid or null if invalid. + */ + public Double validate(String value, Locale locale) { + return (Double)parse(value, (String)null, locale); + } + + /** + *

Validate/convert a Double using the + * specified pattern and/ or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Double if valid or null if invalid. + */ + public Double validate(String value, String pattern, Locale locale) { + return (Double)parse(value, pattern, locale); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(double value, double min, double max) { + return (value >= min && value <= max); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(Double value, double min, double max) { + return isInRange(value.doubleValue(), min, max); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(double value, double min) { + return (value >= min); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(Double value, double min) { + return minValue(value.doubleValue(), min); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(double value, double max) { + return (value <= max); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(Double value, double max) { + return maxValue(value.doubleValue(), max); + } + + /** + * Convert the parsed value to a Double. + * + * @param value The parsed Number object created. + * @param formatter The Format used to parse the value with. + * @return The validated/converted Double value if valid + * or null if invalid. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + + if (value instanceof Double) { + return value; + } + return Double.valueOf(((Number)value).doubleValue()); + + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/EmailValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/EmailValidator.java new file mode 100644 index 000000000..51337d950 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/EmailValidator.java @@ -0,0 +1,223 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + *

Perform email validations.

+ *

+ * Based on a script by Sandeep V. Tamhankar + * http://javascript.internet.com + *

+ *

+ * This implementation is not guaranteed to catch all possible errors in an email address. + *

. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class EmailValidator implements Serializable { + + private static final long serialVersionUID = 1705927040799295880L; + + private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; + private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]"; + private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")"; + private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; + + private static final String EMAIL_REGEX = "^(.+)@(\\S+)$"; + private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; + private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$"; + + private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); + private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); + private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); + + private static final int MAX_USERNAME_LEN = 64; + + private final boolean allowLocal; + private final boolean allowTld; + + /** + * Singleton instance of this class, which + * doesn't consider local addresses as valid. + */ + private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false); + + /** + * Singleton instance of this class, which + * doesn't consider local addresses as valid. + */ + private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true); + + /** + * Singleton instance of this class, which does + * consider local addresses valid. + */ + private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false); + + + /** + * Singleton instance of this class, which does + * consider local addresses valid. + */ + private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true); + + /** + * Returns the Singleton instance of this validator. + * + * @return singleton instance of this validator. + */ + public static EmailValidator getInstance() { + return EMAIL_VALIDATOR; + } + + /** + * Returns the Singleton instance of this validator, + * with local validation as required. + * + * @param allowLocal Should local addresses be considered valid? + * @param allowTld Should TLDs be allowed? + * @return singleton instance of this validator + */ + public static EmailValidator getInstance(boolean allowLocal, boolean allowTld) { + if(allowLocal) { + if (allowTld) { + return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD; + } else { + return EMAIL_VALIDATOR_WITH_LOCAL; + } + } else { + if (allowTld) { + return EMAIL_VALIDATOR_WITH_TLD; + } else { + return EMAIL_VALIDATOR; + } + } + } + + /** + * Returns the Singleton instance of this validator, + * with local validation as required. + * + * @param allowLocal Should local addresses be considered valid? + * @return singleton instance of this validator + */ + public static EmailValidator getInstance(boolean allowLocal) { + return getInstance(allowLocal, false); + } + + /** + * Protected constructor for subclasses to use. + * + * @param allowLocal Should local addresses be considered valid? + * @param allowTld Should TLDs be allowed? + */ + protected EmailValidator(boolean allowLocal, boolean allowTld) { + super(); + this.allowLocal = allowLocal; + this.allowTld = allowTld; + } + + /** + * Protected constructor for subclasses to use. + * + * @param allowLocal Should local addresses be considered valid? + */ + protected EmailValidator(boolean allowLocal) { + super(); + this.allowLocal = allowLocal; + this.allowTld = false; + } + + /** + *

Checks if a field has a valid e-mail address.

+ * + * @param email The value validation is being performed on. A null + * value is considered invalid. + * @return true if the email address is valid. + */ + public boolean isValid(String email) { + if (email == null) { + return false; + } + + if (email.endsWith(".")) { // check this first - it's cheap! + return false; + } + + // Check the whole email address structure + Matcher emailMatcher = EMAIL_PATTERN.matcher(email); + if (!emailMatcher.matches()) { + return false; + } + + if (!isValidUser(emailMatcher.group(1))) { + return false; + } + + if (!isValidDomain(emailMatcher.group(2))) { + return false; + } + + return true; + } + + /** + * Returns true if the domain component of an email address is valid. + * + * @param domain being validated, may be in IDN format + * @return true if the email address's domain is valid. + */ + protected boolean isValidDomain(String domain) { + // see if domain is an IP address in brackets + Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); + + if (ipDomainMatcher.matches()) { + InetAddressValidator inetAddressValidator = + InetAddressValidator.getInstance(); + return inetAddressValidator.isValid(ipDomainMatcher.group(1)); + } + // Domain is symbolic name + DomainValidator domainValidator = + DomainValidator.getInstance(allowLocal); + if (allowTld) { + return domainValidator.isValid(domain) || (!domain.startsWith(".") && domainValidator.isValidTld(domain)); + } else { + return domainValidator.isValid(domain); + } + } + + /** + * Returns true if the user component of an email address is valid. + * + * @param user being validated + * @return true if the user name is valid. + */ + protected boolean isValidUser(String user) { + + if (user == null || user.length() > MAX_USERNAME_LEN) { + return false; + } + + return USER_PATTERN.matcher(user).matches(); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/FloatValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/FloatValidator.java new file mode 100644 index 000000000..612aa952a --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/FloatValidator.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.Format; +import java.util.Locale; + +/** + *

Float Validation and Conversion routines (java.lang.Float).

+ * + *

This validator provides a number of methods for + * validating/converting a String value to + * a Float using java.text.NumberFormat + * to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Float value.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform minimum, maximum and range checks:

+ *
    + *
  • minValue() checks whether the value is greater + * than or equal to a specified minimum.
  • + *
  • maxValue() checks whether the value is less + * than or equal to a specified maximum.
  • + *
  • isInRange() checks whether the value is within + * a specified range of values.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class FloatValidator extends AbstractNumberValidator { + + private static final long serialVersionUID = -4513245432806414267L; + + private static final FloatValidator VALIDATOR = new FloatValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the FloatValidator. + */ + public static FloatValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public FloatValidator() { + this(true, STANDARD_FORMAT); + } + + /** + *

Construct an instance with the specified strict setting + * and format type.

+ * + *

The formatType specified what type of + * NumberFormat is created - valid types + * are:

+ *
    + *
  • AbstractNumberValidator.STANDARD_FORMAT -to create + * standard number formats (the default).
  • + *
  • AbstractNumberValidator.CURRENCY_FORMAT -to create + * currency number formats.
  • + *
  • AbstractNumberValidator.PERCENT_FORMAT -to create + * percent number formats (the default).
  • + *
+ * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + */ + public FloatValidator(boolean strict, int formatType) { + super(strict, formatType, true); + } + + /** + *

Validate/convert a Float using the default + * Locale. + * + * @param value The value validation is being performed on. + * @return The parsed Float if valid or null + * if invalid. + */ + public Float validate(String value) { + return (Float)parse(value, (String)null, (Locale)null); + } + + /** + *

Validate/convert a Float using the + * specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed Float if valid or null if invalid. + */ + public Float validate(String value, String pattern) { + return (Float)parse(value, pattern, (Locale)null); + } + + /** + *

Validate/convert a Float using the + * specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the number format, system default if null. + * @return The parsed Float if valid or null if invalid. + */ + public Float validate(String value, Locale locale) { + return (Float)parse(value, (String)null, locale); + } + + /** + *

Validate/convert a Float using the + * specified pattern and/ or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Float if valid or null if invalid. + */ + public Float validate(String value, String pattern, Locale locale) { + return (Float)parse(value, pattern, locale); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(float value, float min, float max) { + return (value >= min && value <= max); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(Float value, float min, float max) { + return isInRange(value.floatValue(), min, max); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(float value, float min) { + return (value >= min); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(Float value, float min) { + return minValue(value.floatValue(), min); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(float value, float max) { + return (value <= max); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(Float value, float max) { + return maxValue(value.floatValue(), max); + } + + /** + *

Perform further validation and convert the Number to + * a Float.

+ * + * @param value The parsed Number object created. + * @param formatter The Format used to parse the value with. + * @return The parsed Number converted to a + * Float if valid or null if invalid. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + + double doubleValue = ((Number)value).doubleValue(); + + if (doubleValue > 0) { + if (doubleValue < Float.MIN_VALUE) { + return null; + } + if (doubleValue > Float.MAX_VALUE) { + return null; + } + } else if (doubleValue < 0){ + double posDouble = doubleValue * -1; + if (posDouble < Float.MIN_VALUE) { + return null; + } + if (posDouble > Float.MAX_VALUE) { + return null; + } + } + + return Float.valueOf((float)doubleValue); + + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/IBANValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/IBANValidator.java new file mode 100644 index 000000000..5624fde9b --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/IBANValidator.java @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit; + +/** + * IBAN Validator. + * @since 1.5.0 + */ +public class IBANValidator { + + private final Map formatValidators; + + /** + * The validation class + */ + public static class Validator { + /* + * The minimum length does not appear to be defined by the standard. + * Norway is currently the shortest at 15. + * + * There is no standard for BBANs; they vary between countries. + * But a BBAN must consist of a branch id and account number. + * Each of these must be at least 2 chars (generally more) so an absolute minimum is + * 4 characters for the BBAN and 8 for the IBAN. + */ + private static final int MIN_LEN = 8; + private static final int MAX_LEN = 34; // defined by [3] + final String countryCode; + final RegexValidator validator; + final int lengthOfIBAN; // used to avoid unnecessary regex matching + + /** + * Creates the validator + * @param cc the country code + * @param len the length of the IBAN + * @param format the regex to use to check the format + */ + public Validator(String cc, int len, String format) { + if (!(cc.length() == 2 && Character.isUpperCase(cc.charAt(0)) && Character.isUpperCase(cc.charAt(1)))) { + throw new IllegalArgumentException("Invalid country Code; must be exactly 2 upper-case characters"); + } + if (len > MAX_LEN || len < MIN_LEN) { + throw new IllegalArgumentException("Invalid length parameter, must be in range "+MIN_LEN+" to "+MAX_LEN+" inclusive: " +len); + } + if (!format.startsWith(cc)) { + throw new IllegalArgumentException("countryCode '"+cc+"' does not agree with format: " + format); + } + this.countryCode = cc; + this.lengthOfIBAN = len; + this.validator = new RegexValidator(format); + } + } + + /* + * Wikipedia [1] says that only uppercase is allowed. + * The SWIFT PDF file [2] implies that lower case is allowed. + * However there are no examples using lower-case. + * Unfortunately the relevant ISO documents (ISO 13616-1) are not available for free. + * The IBANCheckDigit code treats upper and lower case the same, + * so any case validation has to be done in this class. + * + * Note: the European Payments council has a document [3] which includes a description + * of the IBAN. Section 5 clearly states that only upper case is allowed. + * Also the maximum length is 34 characters (including the country code), + * and the length is fixed for each country. + * + * It looks like lower-case is permitted in BBANs, but they must be converted to + * upper case for IBANs. + * + * [1] https://en.wikipedia.org/wiki/International_Bank_Account_Number + * [2] http://www.swift.com/dsp/resources/documents/IBAN_Registry.pdf (404) + * => https://www.swift.com/sites/default/files/resources/iban_registry.pdf + * The above is an old version (62, Jan 2016) + * As at May 2020, the current IBAN standards are located at: + * https://www.swift.com/standards/data-standards/iban + * [3] http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf + */ + + private static final Validator[] DEFAULT_FORMATS = { + new Validator("AD", 24, "AD\\d{10}[A-Z0-9]{12}" ), // Andorra + new Validator("AE", 23, "AE\\d{21}" ), // United Arab Emirates (The) + new Validator("AL", 28, "AL\\d{10}[A-Z0-9]{16}" ), // Albania + new Validator("AT", 20, "AT\\d{18}" ), // Austria + new Validator("AZ", 28, "AZ\\d{2}[A-Z]{4}[A-Z0-9]{20}" ), // Azerbaijan + new Validator("BA", 20, "BA\\d{18}" ), // Bosnia and Herzegovina + new Validator("BE", 16, "BE\\d{14}" ), // Belgium + new Validator("BG", 22, "BG\\d{2}[A-Z]{4}\\d{6}[A-Z0-9]{8}" ), // Bulgaria + new Validator("BH", 22, "BH\\d{2}[A-Z]{4}[A-Z0-9]{14}" ), // Bahrain + new Validator("BR", 29, "BR\\d{25}[A-Z]{1}[A-Z0-9]{1}" ), // Brazil + new Validator("BY", 28, "BY\\d{2}[A-Z0-9]{4}\\d{4}[A-Z0-9]{16}" ), // Republic of Belarus + new Validator("CH", 21, "CH\\d{7}[A-Z0-9]{12}" ), // Switzerland + new Validator("CR", 22, "CR\\d{20}" ), // Costa Rica + new Validator("CY", 28, "CY\\d{10}[A-Z0-9]{16}" ), // Cyprus + new Validator("CZ", 24, "CZ\\d{22}" ), // Czechia + new Validator("DE", 22, "DE\\d{20}" ), // Germany + new Validator("DK", 18, "DK\\d{16}" ), // Denmark + new Validator("DO", 28, "DO\\d{2}[A-Z0-9]{4}\\d{20}" ), // Dominican Republic + new Validator("EE", 20, "EE\\d{18}" ), // Estonia + new Validator("EG", 29, "EG\\d{27}" ), // Egypt + new Validator("ES", 24, "ES\\d{22}" ), // Spain + new Validator("FI", 18, "FI\\d{16}" ), // Finland + new Validator("FO", 18, "FO\\d{16}" ), // Faroe Islands + new Validator("FR", 27, "FR\\d{12}[A-Z0-9]{11}\\d{2}" ), // France + new Validator("GB", 22, "GB\\d{2}[A-Z]{4}\\d{14}" ), // United Kingdom + new Validator("GE", 22, "GE\\d{2}[A-Z]{2}\\d{16}" ), // Georgia + new Validator("GI", 23, "GI\\d{2}[A-Z]{4}[A-Z0-9]{15}" ), // Gibraltar + new Validator("GL", 18, "GL\\d{16}" ), // Greenland + new Validator("GR", 27, "GR\\d{9}[A-Z0-9]{16}" ), // Greece + new Validator("GT", 28, "GT\\d{2}[A-Z0-9]{24}" ), // Guatemala + new Validator("HR", 21, "HR\\d{19}" ), // Croatia + new Validator("HU", 28, "HU\\d{26}" ), // Hungary + new Validator("IE", 22, "IE\\d{2}[A-Z]{4}\\d{14}" ), // Ireland + new Validator("IL", 23, "IL\\d{21}" ), // Israel + new Validator("IQ", 23, "IQ\\d{2}[A-Z]{4}\\d{15}" ), // Iraq + new Validator("IS", 26, "IS\\d{24}" ), // Iceland + new Validator("IT", 27, "IT\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}" ), // Italy + new Validator("JO", 30, "JO\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}" ), // Jordan + new Validator("KW", 30, "KW\\d{2}[A-Z]{4}[A-Z0-9]{22}" ), // Kuwait + new Validator("KZ", 20, "KZ\\d{5}[A-Z0-9]{13}" ), // Kazakhstan + new Validator("LB", 28, "LB\\d{6}[A-Z0-9]{20}" ), // Lebanon + new Validator("LC", 32, "LC\\d{2}[A-Z]{4}[A-Z0-9]{24}" ), // Saint Lucia + new Validator("LI", 21, "LI\\d{7}[A-Z0-9]{12}" ), // Liechtenstein + new Validator("LT", 20, "LT\\d{18}" ), // Lithuania + new Validator("LU", 20, "LU\\d{5}[A-Z0-9]{13}" ), // Luxembourg + new Validator("LV", 21, "LV\\d{2}[A-Z]{4}[A-Z0-9]{13}" ), // Latvia + new Validator("MC", 27, "MC\\d{12}[A-Z0-9]{11}\\d{2}" ), // Monaco + new Validator("MD", 24, "MD\\d{2}[A-Z0-9]{20}" ), // Moldova + new Validator("ME", 22, "ME\\d{20}" ), // Montenegro + new Validator("MK", 19, "MK\\d{5}[A-Z0-9]{10}\\d{2}" ), // Macedonia + new Validator("MR", 27, "MR\\d{25}" ), // Mauritania + new Validator("MT", 31, "MT\\d{2}[A-Z]{4}\\d{5}[A-Z0-9]{18}" ), // Malta + new Validator("MU", 30, "MU\\d{2}[A-Z]{4}\\d{19}[A-Z]{3}" ), // Mauritius + new Validator("NL", 18, "NL\\d{2}[A-Z]{4}\\d{10}" ), // Netherlands (The) + new Validator("NO", 15, "NO\\d{13}" ), // Norway + new Validator("PK", 24, "PK\\d{2}[A-Z]{4}[A-Z0-9]{16}" ), // Pakistan + new Validator("PL", 28, "PL\\d{26}" ), // Poland + new Validator("PS", 29, "PS\\d{2}[A-Z]{4}[A-Z0-9]{21}" ), // Palestine, State of + new Validator("PT", 25, "PT\\d{23}" ), // Portugal + new Validator("QA", 29, "QA\\d{2}[A-Z]{4}[A-Z0-9]{21}" ), // Qatar + new Validator("RO", 24, "RO\\d{2}[A-Z]{4}[A-Z0-9]{16}" ), // Romania + new Validator("RS", 22, "RS\\d{20}" ), // Serbia + new Validator("SA", 24, "SA\\d{4}[A-Z0-9]{18}" ), // Saudi Arabia + new Validator("SC", 31, "SC\\d{2}[A-Z]{4}\\d{20}[A-Z]{3}" ), // Seychelles + new Validator("SE", 24, "SE\\d{22}" ), // Sweden + new Validator("SI", 19, "SI\\d{17}" ), // Slovenia + new Validator("SK", 24, "SK\\d{22}" ), // Slovakia + new Validator("SM", 27, "SM\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}" ), // San Marino + new Validator("ST", 25, "ST\\d{23}" ), // Sao Tome and Principe + new Validator("SV", 28, "SV\\d{2}[A-Z]{4}\\d{20}" ), // El Salvador + new Validator("TL", 23, "TL\\d{21}" ), // Timor-Leste + new Validator("TN", 24, "TN\\d{22}" ), // Tunisia + new Validator("TR", 26, "TR\\d{8}[A-Z0-9]{16}" ), // Turkey + new Validator("UA", 29, "UA\\d{8}[A-Z0-9]{19}" ), // Ukraine + new Validator("VA", 22, "VA\\d{20}" ), // Vatican City State + new Validator("VG", 24, "VG\\d{2}[A-Z]{4}\\d{16}" ), // Virgin Islands + new Validator("XK", 20, "XK\\d{18}" ), // Kosovo + }; + + /** The singleton instance which uses the default formats */ + public static final IBANValidator DEFAULT_IBAN_VALIDATOR = new IBANValidator(); + + /** + * Return a singleton instance of the IBAN validator using the default formats + * + * @return A singleton instance of the ISBN validator + */ + public static IBANValidator getInstance() { + return DEFAULT_IBAN_VALIDATOR; + } + + /** + * Create a default IBAN validator. + */ + public IBANValidator() { + this(DEFAULT_FORMATS); + } + + /** + * Create an IBAN validator from the specified map of IBAN formats. + * + * @param formatMap map of IBAN formats + */ + public IBANValidator(Validator[] formatMap) { + this.formatValidators = createValidators(formatMap); + } + + private Map createValidators(Validator[] formatMap) { + Map m = new ConcurrentHashMap(); + for(Validator v : formatMap) { + m.put(v.countryCode, v); + } + return m; + } + + /** + * Validate an IBAN Code + * + * @param code The value validation is being performed on + * @return true if the value is valid + */ + public boolean isValid(String code) { + Validator formatValidator = getValidator(code); + if (formatValidator == null || code.length() != formatValidator.lengthOfIBAN || !formatValidator.validator.isValid(code)) { + return false; + } + return IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(code); + } + + /** + * Does the class have the required validator? + * + * @param code the code to check + * @return true if there is a validator + */ + public boolean hasValidator(String code) { + return getValidator(code) != null; + } + + /** + * Gets a copy of the default Validators. + * + * @return a copy of the default Validator array + */ + public Validator[] getDefaultValidators() { + return Arrays.copyOf(DEFAULT_FORMATS, DEFAULT_FORMATS.length); + } + + /** + * Get the Validator for a given IBAN + * + * @param code a string starting with the ISO country code (e.g. an IBAN) + * + * @return the validator or {@code null} if there is not one registered. + */ + public Validator getValidator(String code) { + if (code == null || code.length() < 2) { // ensure we can extract the code + return null; + } + String key = code.substring(0, 2); + return formatValidators.get(key); + } + + /** + * Installs a validator. + * Will replace any existing entry which has the same countryCode + * + * @param validator the instance to install. + * @return the previous Validator, or {@code null} if there was none + * @throws IllegalStateException if an attempt is made to modify the singleton validator + */ + public Validator setValidator(Validator validator) { + if (this == DEFAULT_IBAN_VALIDATOR) { + throw new IllegalStateException("The singleton validator cannot be modified"); + } + return formatValidators.put(validator.countryCode, validator); + } + + /** + * Installs a validator. + * Will replace any existing entry which has the same countryCode. + * + * @param countryCode the country code + * @param length the length of the IBAN. Must be ≥ 8 and ≤ 32. + * If the length is < 0, the validator is removed, and the format is not used. + * @param format the format of the IBAN (as a regular expression) + * @return the previous Validator, or {@code null} if there was none + * @throws IllegalArgumentException if there is a problem + * @throws IllegalStateException if an attempt is made to modify the singleton validator + */ + public Validator setValidator(String countryCode, int length, String format) { + if (this == DEFAULT_IBAN_VALIDATOR) { + throw new IllegalStateException("The singleton validator cannot be modified"); + } + if (length < 0) { + return formatValidators.remove(countryCode); + } + return setValidator(new Validator(countryCode, length, format)); + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISBNValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISBNValidator.java new file mode 100644 index 000000000..0f8036efb --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISBNValidator.java @@ -0,0 +1,271 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.io.Serializable; +import org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit; +import org.apache.commons.validator.routines.checkdigit.ISBN10CheckDigit; +import org.apache.commons.validator.routines.checkdigit.CheckDigitException; + +/** + * ISBN-10 and ISBN-13 Code Validation. + *

+ * This validator validates the code is either a valid ISBN-10 + * (using a {@link CodeValidator} with the {@link ISBN10CheckDigit}) + * or a valid ISBN-13 code (using a {@link CodeValidator} with the + * the {@link EAN13CheckDigit} routine). + *

+ * The validate() methods return the ISBN code with formatting + * characters removed if valid or null if invalid. + *

+ * This validator also provides the facility to convert ISBN-10 codes to + * ISBN-13 if the convert property is true. + *

+ * From 1st January 2007 the book industry will start to use a new 13 digit + * ISBN number (rather than this 10 digit ISBN number). ISBN-13 codes are + * EAN + * codes, for more information see:

+ * + * + * + *

ISBN-13s are either prefixed with 978 or 979. 978 prefixes are only assigned + * to the ISBN agency. 979 prefixes may be assigned to ISBNs or ISMNs + * (International + * Standard Music Numbers). + *

    + *
  • 979-0 are assigned to the ISMN agency
  • + *
  • 979-10, 979-11, 979-12 are assigned to the ISBN agency
  • + *
+ * All other 979 prefixed EAN-13 numbers have not yet been assigned to an agency. The + * validator validates all 13 digit codes with 978 or 979 prefixes. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class ISBNValidator implements Serializable { + + private static final int ISBN_10_LEN = 10; + + private static final long serialVersionUID = 4319515687976420405L; + + private static final String SEP = "(?:\\-|\\s)"; + private static final String GROUP = "(\\d{1,5})"; + private static final String PUBLISHER = "(\\d{1,7})"; + private static final String TITLE = "(\\d{1,6})"; + + /** + * ISBN-10 consists of 4 groups of numbers separated by either dashes (-) + * or spaces. The first group is 1-5 characters, second 1-7, third 1-6, + * and fourth is 1 digit or an X. + */ + static final String ISBN10_REGEX = + "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9X])))$"; + + /** + * ISBN-13 consists of 5 groups of numbers separated by either dashes (-) + * or spaces. The first group is 978 or 979, the second group is + * 1-5 characters, third 1-7, fourth 1-6, and fifth is 1 digit. + */ + static final String ISBN13_REGEX = + "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9])))$"; + + /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */ + private static final ISBNValidator ISBN_VALIDATOR = new ISBNValidator(); + + /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */ + private static final ISBNValidator ISBN_VALIDATOR_NO_CONVERT = new ISBNValidator(false); + + + /** ISBN-10 Code Validator */ + private final CodeValidator isbn10Validator = new CodeValidator(ISBN10_REGEX, 10, ISBN10CheckDigit.ISBN10_CHECK_DIGIT); + + /** ISBN-13 Code Validator */ + private final CodeValidator isbn13Validator = new CodeValidator(ISBN13_REGEX, 13, EAN13CheckDigit.EAN13_CHECK_DIGIT); + + private final boolean convert; + + /** + * Return a singleton instance of the ISBN validator which + * converts ISBN-10 codes to ISBN-13. + * + * @return A singleton instance of the ISBN validator. + */ + public static ISBNValidator getInstance() { + return ISBN_VALIDATOR; + } + + /** + * Return a singleton instance of the ISBN validator specifying + * whether ISBN-10 codes should be converted to ISBN-13. + * + * @param convert true if valid ISBN-10 codes + * should be converted to ISBN-13 codes or false + * if valid ISBN-10 codes should be returned unchanged. + * @return A singleton instance of the ISBN validator. + */ + public static ISBNValidator getInstance(boolean convert) { + return (convert ? ISBN_VALIDATOR : ISBN_VALIDATOR_NO_CONVERT); + } + + /** + * Construct an ISBN validator which converts ISBN-10 codes + * to ISBN-13. + */ + public ISBNValidator() { + this(true); + } + + /** + * Construct an ISBN validator indicating whether + * ISBN-10 codes should be converted to ISBN-13. + * + * @param convert true if valid ISBN-10 codes + * should be converted to ISBN-13 codes or false + * if valid ISBN-10 codes should be returned unchanged. + */ + public ISBNValidator(boolean convert) { + this.convert = convert; + } + + /** + * Check the code is either a valid ISBN-10 or ISBN-13 code. + * + * @param code The code to validate. + * @return true if a valid ISBN-10 or + * ISBN-13 code, otherwise false. + */ + public boolean isValid(String code) { + return (isValidISBN13(code) || isValidISBN10(code)); + } + + /** + * Check the code is a valid ISBN-10 code. + * + * @param code The code to validate. + * @return true if a valid ISBN-10 + * code, otherwise false. + */ + public boolean isValidISBN10(String code) { + return isbn10Validator.isValid(code); + } + + /** + * Check the code is a valid ISBN-13 code. + * + * @param code The code to validate. + * @return true if a valid ISBN-13 + * code, otherwise false. + */ + public boolean isValidISBN13(String code) { + return isbn13Validator.isValid(code); + } + + /** + * Check the code is either a valid ISBN-10 or ISBN-13 code. + *

+ * If valid, this method returns the ISBN code with + * formatting characters removed (i.e. space or hyphen). + *

+ * Converts an ISBN-10 codes to ISBN-13 if + * convertToISBN13 is true. + * + * @param code The code to validate. + * @return A valid ISBN code if valid, otherwise null. + */ + public String validate(String code) { + String result = validateISBN13(code); + if (result == null) { + result = validateISBN10(code); + if (result != null && convert) { + result = convertToISBN13(result); + } + } + return result; + } + + /** + * Check the code is a valid ISBN-10 code. + *

+ * If valid, this method returns the ISBN-10 code with + * formatting characters removed (i.e. space or hyphen). + * + * @param code The code to validate. + * @return A valid ISBN-10 code if valid, + * otherwise null. + */ + public String validateISBN10(String code) { + Object result = isbn10Validator.validate(code); + return (result == null ? null : result.toString()); + } + + /** + * Check the code is a valid ISBN-13 code. + *

+ * If valid, this method returns the ISBN-13 code with + * formatting characters removed (i.e. space or hyphen). + * + * @param code The code to validate. + * @return A valid ISBN-13 code if valid, + * otherwise null. + */ + public String validateISBN13(String code) { + Object result = isbn13Validator.validate(code); + return (result == null ? null : result.toString()); + } + + /** + * Convert an ISBN-10 code to an ISBN-13 code. + *

+ * This method requires a valid ISBN-10 with NO formatting + * characters. + * + * @param isbn10 The ISBN-10 code to convert + * @return A converted ISBN-13 code or null + * if the ISBN-10 code is not valid + */ + public String convertToISBN13(String isbn10) { + + if (isbn10 == null) { + return null; + } + + String input = isbn10.trim(); + if (input.length() != ISBN_10_LEN) { + throw new IllegalArgumentException("Invalid length " + input.length() + " for '" + input + "'"); + } + + // Calculate the new ISBN-13 code (drop the original checkdigit) + String isbn13 = "978" + input.substring(0, ISBN_10_LEN - 1); + try { + String checkDigit = isbn13Validator.getCheckDigit().calculate(isbn13); + isbn13 += checkDigit; + return isbn13; + } catch (CheckDigitException e) { + throw new IllegalArgumentException("Check digit error for '" + input + "' - " + e.getMessage()); + } + + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISINValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISINValidator.java new file mode 100644 index 000000000..165fc756a --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISINValidator.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.validator.routines; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Locale; + +import org.apache.commons.validator.routines.checkdigit.ISINCheckDigit; + +/** + * ISIN (International Securities Identifying Number) validation. + * + *

+ * ISIN Numbers are 12 character alphanumeric codes used to identify Securities. + *

+ * + *

+ * ISINs consist of two alphabetic characters, + * which are the ISO 3166-1 alpha-2 code for the issuing country, + * nine alpha-numeric characters (the National Securities Identifying Number, or NSIN, which identifies the security), + * and one numerical check digit. + * They are 12 characters in length. + *

+ * + *

+ * See Wikipedia - ISIN + * for more details. + *

+ * + * @since 1.7 + */ +public class ISINValidator implements Serializable { + + private static final long serialVersionUID = -5964391439144260936L; + + private static final String ISIN_REGEX = "([A-Z]{2}[A-Z0-9]{9}[0-9])"; + + private static final CodeValidator VALIDATOR = new CodeValidator(ISIN_REGEX, 12, ISINCheckDigit.ISIN_CHECK_DIGIT); + + /** ISIN Code Validator (no countryCode check) */ + private static final ISINValidator ISIN_VALIDATOR_FALSE = new ISINValidator(false); + + /** ISIN Code Validator (with countryCode check) */ + private static final ISINValidator ISIN_VALIDATOR_TRUE = new ISINValidator(true); + + private static final String [] CCODES = Locale.getISOCountries(); + + private static final String [] SPECIALS = { + "EZ", // http://www.anna-web.org/standards/isin-iso-6166/ + "XS", // https://www.isin.org/isin/ + }; + + static { + Arrays.sort(CCODES); // we cannot assume the codes are sorted + Arrays.sort(SPECIALS); // Just in case ... + } + + private final boolean checkCountryCode; + + /** + * Return a singleton instance of the ISIN validator + * @param checkCountryCode whether to check the country-code prefix or not + * @return A singleton instance of the appropriate ISIN validator. + */ + public static ISINValidator getInstance(boolean checkCountryCode) { + return checkCountryCode ? ISIN_VALIDATOR_TRUE : ISIN_VALIDATOR_FALSE; + } + + private ISINValidator(boolean checkCountryCode) { + this.checkCountryCode = checkCountryCode; + } + + /** + * Check the code is a valid ISIN code after any transformation + * by the validate routine. + * @param code The code to validate. + * @return true if a valid ISIN + * code, otherwise false. + */ + public boolean isValid(String code) { + final boolean valid = VALIDATOR.isValid(code); + if (valid && checkCountryCode) { + return checkCode(code.substring(0,2)); + } + return valid; + } + + /** + * Check the code is valid ISIN code. + * + * @param code The code to validate. + * @return A valid ISIN code if valid, otherwise null. + */ + public Object validate(String code) { + final Object validate = VALIDATOR.validate(code); + if (validate != null && checkCountryCode) { + return checkCode(code.substring(0,2)) ? validate : null; + } + return validate; + } + + private boolean checkCode(String code) { + return Arrays.binarySearch(CCODES, code) >= 0 + || + Arrays.binarySearch(SPECIALS, code) >= 0 + ; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISSNValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISSNValidator.java new file mode 100644 index 000000000..1bbd39199 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISSNValidator.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.io.Serializable; + +import org.apache.commons.validator.routines.checkdigit.CheckDigitException; +import org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit; +import org.apache.commons.validator.routines.checkdigit.ISSNCheckDigit; + +/** + * International Standard Serial Number (ISSN) + * is an eight-digit serial number used to + * uniquely identify a serial publication. + *
+ * The format is:
+ * 
+ * ISSN dddd-dddC
+ * where:
+ * d = decimal digit (0-9)
+ * C = checksum (0-9 or X)
+ * 
+ * The checksum is formed by adding the first 7 digits multiplied by
+ * the position in the entire number (counting from the right).
+ * 
+ * For example, abcd-efg would be 8a + 7b + 6c + 5d + 4e +3f +2g.
+ * The check digit is modulus 11, where the value 10 is represented by 'X'
+ * For example:
+ * ISSN 0317-8471
+ * ISSN 1050-124X
+ *
+ * This class strips off the 'ISSN ' prefix if it is present before passing
+ * the remainder to the checksum routine.
+ * 
+ * 
+ *

+ * Note: the {@link #isValid(String)} and {@link #validate(String)} methods strip off any leading + * or trailing spaces before doing the validation. + * To ensure that only a valid code (without 'ISSN ' prefix) is passed to a method, + * use the following code: + *

+ * Object valid = validator.validate(input); 
+ * if (valid != null) {
+ *    some_method(valid.toString());
+ * }
+ * 
+ * @since 1.5.0 + */ +public class ISSNValidator implements Serializable { + + private static final long serialVersionUID = 4319515687976420405L; + + private static final String ISSN_REGEX = "(?:ISSN )?(\\d{4})-(\\d{3}[0-9X])$"; // We don't include the '-' in the code, so it is 8 chars + + private static final int ISSN_LEN = 8; + + private static final String ISSN_PREFIX = "977"; + + private static final String EAN_ISSN_REGEX = "^(977)(?:(\\d{10}))$"; + + private static final int EAN_ISSN_LEN = 13; + + private static final CodeValidator VALIDATOR = new CodeValidator(ISSN_REGEX, ISSN_LEN, ISSNCheckDigit.ISSN_CHECK_DIGIT); + + private static final CodeValidator EAN_VALIDATOR = new CodeValidator(EAN_ISSN_REGEX, EAN_ISSN_LEN, EAN13CheckDigit.EAN13_CHECK_DIGIT); + + /** ISSN Code Validator */ + private static final ISSNValidator ISSN_VALIDATOR = new ISSNValidator(); + + /** + * Return a singleton instance of the ISSN validator + * + * @return A singleton instance of the ISSN validator. + */ + public static ISSNValidator getInstance() { + return ISSN_VALIDATOR; + } + + /** + * Check the code is a valid EAN code. + *

+ * If valid, this method returns the EAN code + * + * @param code The code to validate. + * @return A valid EAN code if valid, otherwise null. + */ + public Object validateEan(String code) { + return EAN_VALIDATOR.validate(code); + } + + /** + * Check the code is a valid ISSN code after any transformation + * by the validate routine. + * @param code The code to validate. + * @return true if a valid ISSN + * code, otherwise false. + */ + public boolean isValid(String code) { + return VALIDATOR.isValid(code); + } + + /** + * Check the code is valid ISSN code. + *

+ * If valid, this method returns the ISSN code with + * the 'ISSN ' prefix removed (if it was present) + * + * @param code The code to validate. + * @return A valid ISSN code if valid, otherwise null. + */ + public Object validate(String code) { + return VALIDATOR.validate(code); + } + + /** + * Convert an ISSN code to an EAN-13 code. + *

+ * This method requires a valid ISSN code. + * It may contain a leading 'ISSN ' prefix, + * as the input is passed through the {@link #validate(String)} + * method. + * + * @param issn The ISSN code to convert + * @param suffix the two digit suffix, e.g. "00" + * @return A converted EAN-13 code or null + * if the input ISSN code is not valid + */ + public String convertToEAN13(String issn, String suffix) { + + if (suffix == null || !suffix.matches("\\d\\d")) { + throw new IllegalArgumentException("Suffix must be two digits: '" + suffix + "'"); + } + + Object result = validate(issn); + if (result == null) { + return null; + } + + // Calculate the new EAN-13 code + final String input = result.toString(); + String ean13 = ISSN_PREFIX + input.substring(0, input.length() -1) + suffix; + try { + String checkDigit = EAN13CheckDigit.EAN13_CHECK_DIGIT.calculate(ean13); + ean13 += checkDigit; + return ean13; + } catch (CheckDigitException e) { // Should not happen + throw new IllegalArgumentException("Check digit error for '" + ean13 + "' - " + e.getMessage()); + } + + } + + /** + * Extract an ISSN code from an ISSN-EAN-13 code. + *

+ * This method requires a valid ISSN-EAN-13 code with NO formatting + * characters. + * That is a 13 digit EAN-13 code with the '977' prefix + * + * @param ean13 The ISSN code to convert + * @return A valid ISSN code or null + * if the input ISSN EAN-13 code is not valid + */ + public String extractFromEAN13(String ean13) { + String input = ean13.trim(); + if (input.length() != EAN_ISSN_LEN ) { + throw new IllegalArgumentException("Invalid length " + input.length() + " for '" + input + "'"); + } + if (!input.startsWith(ISSN_PREFIX)) { + throw new IllegalArgumentException("Prefix must be " + ISSN_PREFIX + " to contain an ISSN: '" + ean13 + "'"); + } + Object result = validateEan(input); + if (result == null) { + return null; + } + // Calculate the ISSN code + input = result.toString(); + try { + //CHECKSTYLE:OFF: MagicNumber + String issnBase = input.substring(3,10); // TODO: how to derive these + //CHECKSTYLE:ON: MagicNumber + String checkDigit = ISSNCheckDigit.ISSN_CHECK_DIGIT.calculate(issnBase); + String issn = issnBase + checkDigit; + return issn; + } catch (CheckDigitException e) { // Should not happen + throw new IllegalArgumentException("Check digit error for '" + ean13 + "' - " + e.getMessage()); + } + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/InetAddressValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/InetAddressValidator.java new file mode 100644 index 000000000..e6134fcb4 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/InetAddressValidator.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *

InetAddress validation and conversion routines (java.net.InetAddress).

+ * + *

This class provides methods to validate a candidate IP address. + * + *

+ * This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} method. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public class InetAddressValidator implements Serializable { + + private static final int IPV4_MAX_OCTET_VALUE = 255; + + private static final int MAX_UNSIGNED_SHORT = 0xffff; + + private static final int BASE_16 = 16; + + private static final long serialVersionUID = -919201640201914789L; + + private static final String IPV4_REGEX = + "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"; + + // Max number of hex groups (separated by :) in an IPV6 address + private static final int IPV6_MAX_HEX_GROUPS = 8; + + // Max hex digits in each IPv6 group + private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; + + /** + * Singleton instance of this class. + */ + private static final InetAddressValidator VALIDATOR = new InetAddressValidator(); + + /** IPv4 RegexValidator */ + private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX); + + /** + * Returns the singleton instance of this validator. + * @return the singleton instance of this validator + */ + public static InetAddressValidator getInstance() { + return VALIDATOR; + } + + /** + * Checks if the specified string is a valid IP address. + * @param inetAddress the string to validate + * @return true if the string validates as an IP address + */ + public boolean isValid(String inetAddress) { + return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress); + } + + /** + * Validates an IPv4 address. Returns true if valid. + * @param inet4Address the IPv4 address to validate + * @return true if the argument contains a valid IPv4 address + */ + public boolean isValidInet4Address(String inet4Address) { + // verify that address conforms to generic IPv4 format + String[] groups = ipv4Validator.match(inet4Address); + + if (groups == null) { + return false; + } + + // verify that address subgroups are legal + for (String ipSegment : groups) { + if (ipSegment == null || ipSegment.length() == 0) { + return false; + } + + int iIpSegment = 0; + + try { + iIpSegment = Integer.parseInt(ipSegment); + } catch(NumberFormatException e) { + return false; + } + + if (iIpSegment > IPV4_MAX_OCTET_VALUE) { + return false; + } + + if (ipSegment.length() > 1 && ipSegment.startsWith("0")) { + return false; + } + + } + + return true; + } + + /** + * Validates an IPv6 address. Returns true if valid. + * @param inet6Address the IPv6 address to validate + * @return true if the argument contains a valid IPv6 address + * + * @since 1.4.1 + */ + public boolean isValidInet6Address(String inet6Address) { + boolean containsCompressedZeroes = inet6Address.contains("::"); + if (containsCompressedZeroes && (inet6Address.indexOf("::") != inet6Address.lastIndexOf("::"))) { + return false; + } + if ((inet6Address.startsWith(":") && !inet6Address.startsWith("::")) + || (inet6Address.endsWith(":") && !inet6Address.endsWith("::"))) { + return false; + } + String[] octets = inet6Address.split(":"); + if (containsCompressedZeroes) { + List octetList = new ArrayList(Arrays.asList(octets)); + if (inet6Address.endsWith("::")) { + // String.split() drops ending empty segments + octetList.add(""); + } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) { + octetList.remove(0); + } + octets = octetList.toArray(new String[octetList.size()]); + } + if (octets.length > IPV6_MAX_HEX_GROUPS) { + return false; + } + int validOctets = 0; + int emptyOctets = 0; // consecutive empty chunks + for (int index = 0; index < octets.length; index++) { + String octet = octets[index]; + if (octet.length() == 0) { + emptyOctets++; + if (emptyOctets > 1) { + return false; + } + } else { + emptyOctets = 0; + // Is last chunk an IPv4 address? + if (index == octets.length - 1 && octet.contains(".")) { + if (!isValidInet4Address(octet)) { + return false; + } + validOctets += 2; + continue; + } + if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) { + return false; + } + int octetInt = 0; + try { + octetInt = Integer.parseInt(octet, BASE_16); + } catch (NumberFormatException e) { + return false; + } + if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) { + return false; + } + } + validOctets++; + } + if (validOctets > IPV6_MAX_HEX_GROUPS || (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes)) { + return false; + } + return true; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/IntegerValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/IntegerValidator.java new file mode 100644 index 000000000..eab42bd50 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/IntegerValidator.java @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.Format; +import java.util.Locale; + +/** + *

Integer Validation and Conversion routines (java.lang.Integer).

+ * + *

This validator provides a number of methods for + * validating/converting a String value to + * a Integer using java.text.NumberFormat + * to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Integer value.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform minimum, maximum and range checks:

+ *
    + *
  • minValue() checks whether the value is greater + * than or equal to a specified minimum.
  • + *
  • maxValue() checks whether the value is less + * than or equal to a specified maximum.
  • + *
  • isInRange() checks whether the value is within + * a specified range of values.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class IntegerValidator extends AbstractNumberValidator { + + private static final long serialVersionUID = 422081746310306596L; + + private static final IntegerValidator VALIDATOR = new IntegerValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the IntegerValidator. + */ + public static IntegerValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public IntegerValidator() { + this(true, STANDARD_FORMAT); + } + + /** + *

Construct an instance with the specified strict setting + * and format type.

+ * + *

The formatType specified what type of + * NumberFormat is created - valid types + * are:

+ *
    + *
  • AbstractNumberValidator.STANDARD_FORMAT -to create + * standard number formats (the default).
  • + *
  • AbstractNumberValidator.CURRENCY_FORMAT -to create + * currency number formats.
  • + *
  • AbstractNumberValidator.PERCENT_FORMAT -to create + * percent number formats (the default).
  • + *
+ * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + */ + public IntegerValidator(boolean strict, int formatType) { + super(strict, formatType, false); + } + + /** + *

Validate/convert an Integer using the default + * Locale. + * + * @param value The value validation is being performed on. + * @return The parsed Integer if valid or null + * if invalid. + */ + public Integer validate(String value) { + return (Integer)parse(value, (String)null, (Locale)null); + } + + /** + *

Validate/convert an Integer using the + * specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed Integer if valid or null if invalid. + */ + public Integer validate(String value, String pattern) { + return (Integer)parse(value, pattern, (Locale)null); + } + + /** + *

Validate/convert an Integer using the + * specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the number format, system default if null. + * @return The parsed Integer if valid or null if invalid. + */ + public Integer validate(String value, Locale locale) { + return (Integer)parse(value, (String)null, locale); + } + + /** + *

Validate/convert a Integer using the + * specified pattern and/ or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Integer if valid or null if invalid. + */ + public Integer validate(String value, String pattern, Locale locale) { + return (Integer)parse(value, pattern, locale); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(int value, int min, int max) { + return (value >= min && value <= max); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(Integer value, int min, int max) { + return isInRange(value.intValue(), min, max); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(int value, int min) { + return (value >= min); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(Integer value, int min) { + return minValue(value.intValue(), min); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(int value, int max) { + return (value <= max); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(Integer value, int max) { + return maxValue(value.intValue(), max); + } + + /** + *

Perform further validation and convert the Number to + * an Integer.

+ * + * @param value The parsed Number object created. + * @param formatter The Format used to parse the value with. + * @return The parsed Number converted to an + * Integer if valid or null if invalid. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + + // Parsed value will be Long if it fits in a long and is not fractional + if (value instanceof Long) { + long longValue = ((Long)value).longValue(); + if (longValue >= Integer.MIN_VALUE && + longValue <= Integer.MAX_VALUE) { + return Integer.valueOf((int)longValue); + } + } + return null; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/LongValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/LongValidator.java new file mode 100644 index 000000000..9aa965724 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/LongValidator.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.Format; +import java.util.Locale; + +/** + *

Long Validation and Conversion routines (java.lang.Long).

+ * + *

This validator provides a number of methods for + * validating/converting a String value to + * a Long using java.text.NumberFormat + * to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Long value.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform minimum, maximum and range checks:

+ *
    + *
  • minValue() checks whether the value is greater + * than or equal to a specified minimum.
  • + *
  • maxValue() checks whether the value is less + * than or equal to a specified maximum.
  • + *
  • isInRange() checks whether the value is within + * a specified range of values.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using a specified pattern
  • + *
  • using the format for a specified Locale
  • + *
  • using the format for the default Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class LongValidator extends AbstractNumberValidator { + + private static final long serialVersionUID = -5117231731027866098L; + + private static final LongValidator VALIDATOR = new LongValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the LongValidator. + */ + public static LongValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public LongValidator() { + this(true, STANDARD_FORMAT); + } + + /** + *

Construct an instance with the specified strict setting + * and format type.

+ * + *

The formatType specified what type of + * NumberFormat is created - valid types + * are:

+ *
    + *
  • AbstractNumberValidator.STANDARD_FORMAT -to create + * standard number formats (the default).
  • + *
  • AbstractNumberValidator.CURRENCY_FORMAT -to create + * currency number formats.
  • + *
  • AbstractNumberValidator.PERCENT_FORMAT -to create + * percent number formats (the default).
  • + *
+ * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + */ + public LongValidator(boolean strict, int formatType) { + super(strict, formatType, false); + } + + /** + *

Validate/convert a Long using the default + * Locale. + * + * @param value The value validation is being performed on. + * @return The parsed Long if valid or null + * if invalid. + */ + public Long validate(String value) { + return (Long)parse(value, (String)null, (Locale)null); + } + + /** + *

Validate/convert a Long using the + * specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed Long if valid or null if invalid. + */ + public Long validate(String value, String pattern) { + return (Long)parse(value, pattern, (Locale)null); + } + + /** + *

Validate/convert a Long using the + * specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the number format, system default if null. + * @return The parsed Long if valid or null if invalid. + */ + public Long validate(String value, Locale locale) { + return (Long)parse(value, (String)null, locale); + } + + /** + *

Validate/convert a Long using the + * specified pattern and/ or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Long if valid or null if invalid. + */ + public Long validate(String value, String pattern, Locale locale) { + return (Long)parse(value, pattern, locale); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(long value, long min, long max) { + return (value >= min && value <= max); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(Long value, long min, long max) { + return isInRange(value.longValue(), min, max); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(long value, long min) { + return (value >= min); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(Long value, long min) { + return minValue(value.longValue(), min); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(long value, long max) { + return (value <= max); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(Long value, long max) { + return maxValue(value.longValue(), max); + } + + /** + * Convert the parsed value to a Long. + * + * @param value The parsed Number object created. + * @param formatter The Format used to parse the value with. + * @return The parsed Number converted to a + * Long. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + + // Parsed value will be Long if it fits in a long and is not fractional + if (value instanceof Long) { + return value; + } + return null; + + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/PercentValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/PercentValidator.java new file mode 100644 index 000000000..05dd1ded1 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/PercentValidator.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.validator.routines; + +import java.text.DecimalFormat; +import java.text.Format; +import java.math.BigDecimal; + +/** + *

Percentage Validation and Conversion routines (java.math.BigDecimal).

+ * + *

This is one implementation of a percent validator that has the following features:

+ *
    + *
  • It is lenient about the presence of the percent symbol
  • + *
  • It converts the percent to a java.math.BigDecimal
  • + *
+ * + *

However any of the number validators can be used for percent validation. + * For example, if you wanted a percent validator that converts to a + * java.lang.Float then you can simply instantiate an + * FloatValidator with the appropriate format type:

+ * + *

... = new FloatValidator(false, FloatValidator.PERCENT_FORMAT);

+ * + *

Pick the appropriate validator, depending on the type (i.e Float, Double or BigDecimal) + * you want the percent converted to. Please note, it makes no sense to use + * one of the validators that doesn't handle fractions (i.e. byte, short, integer, long + * and BigInteger) since percentages are converted to fractions (i.e 50% is + * converted to 0.5).

+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class PercentValidator extends BigDecimalValidator { + + private static final long serialVersionUID = -3508241924961535772L; + + private static final PercentValidator VALIDATOR = new PercentValidator(); + + /** DecimalFormat's percent (thousand multiplier) symbol */ + private static final char PERCENT_SYMBOL = '%'; + + private static final BigDecimal POINT_ZERO_ONE = new BigDecimal("0.01"); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the PercentValidator. + */ + public static BigDecimalValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public PercentValidator() { + this(true); + } + + /** + * Construct an instance with the specified strict setting. + * + * @param strict true if strict + * Format parsing should be used. + */ + public PercentValidator(boolean strict) { + super(strict, PERCENT_FORMAT, true); + } + + /** + *

Parse the value with the specified Format.

+ * + *

This implementation is lenient whether the currency symbol + * is present or not. The default NumberFormat + * behavior is for the parsing to "fail" if the currency + * symbol is missing. This method re-parses with a format + * without the currency symbol if it fails initially.

+ * + * @param value The value to be parsed. + * @param formatter The Format to parse the value with. + * @return The parsed value if valid or null if invalid. + */ + @Override + protected Object parse(String value, Format formatter) { + + // Initial parse of the value + BigDecimal parsedValue = (BigDecimal)super.parse(value, formatter); + if (parsedValue != null || !(formatter instanceof DecimalFormat)) { + return parsedValue; + } + + // Re-parse using a pattern without the percent symbol + DecimalFormat decimalFormat = (DecimalFormat)formatter; + String pattern = decimalFormat.toPattern(); + if (pattern.indexOf(PERCENT_SYMBOL) >= 0) { + StringBuilder buffer = new StringBuilder(pattern.length()); + for (int i = 0; i < pattern.length(); i++) { + if (pattern.charAt(i) != PERCENT_SYMBOL) { + buffer.append(pattern.charAt(i)); + } + } + decimalFormat.applyPattern(buffer.toString()); + parsedValue = (BigDecimal)super.parse(value, decimalFormat); + + // If parsed OK, divide by 100 to get percent + if (parsedValue != null) { + parsedValue = parsedValue.multiply(POINT_ZERO_ONE); + } + + } + return parsedValue; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/RegexValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/RegexValidator.java new file mode 100644 index 000000000..3214b4eb6 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/RegexValidator.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +/** + * Regular Expression validation (using JDK 1.4+ regex support). + *

+ * Construct the validator either for a single regular expression or a set (array) of + * regular expressions. By default validation is case sensitive but constructors + * are provided to allow case in-sensitive validation. For example to create + * a validator which does case in-sensitive validation for a set of regular + * expressions: + *

+ *
+ * 
+ * String[] regexs = new String[] {...};
+ * RegexValidator validator = new RegexValidator(regexs, false);
+ * 
+ * 
+ * + *
    + *
  • Validate true or false:
  • + *
  • + *
      + *
    • boolean valid = validator.isValid(value);
    • + *
    + *
  • + *
  • Validate returning an aggregated String of the matched groups:
  • + *
  • + *
      + *
    • String result = validator.validate(value);
    • + *
    + *
  • + *
  • Validate returning the matched groups:
  • + *
  • + *
      + *
    • String[] result = validator.match(value);
    • + *
    + *
  • + *
+ * + * Note that patterns are matched against the entire input. + * + *

+ * Cached instances pre-compile and re-use {@link Pattern}(s) - which according + * to the {@link Pattern} API are safe to use in a multi-threaded environment. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public class RegexValidator implements Serializable { + + private static final long serialVersionUID = -8832409930574867162L; + + private final Pattern[] patterns; + + /** + * Construct a case sensitive validator for a single + * regular expression. + * + * @param regex The regular expression this validator will + * validate against + */ + public RegexValidator(String regex) { + this(regex, true); + } + + /** + * Construct a validator for a single regular expression + * with the specified case sensitivity. + * + * @param regex The regular expression this validator will + * validate against + * @param caseSensitive when true matching is case + * sensitive, otherwise matching is case in-sensitive + */ + public RegexValidator(String regex, boolean caseSensitive) { + this(new String[] {regex}, caseSensitive); + } + + /** + * Construct a case sensitive validator that matches any one + * of the set of regular expressions. + * + * @param regexs The set of regular expressions this validator will + * validate against + */ + public RegexValidator(String[] regexs) { + this(regexs, true); + } + + /** + * Construct a validator that matches any one of the set of regular + * expressions with the specified case sensitivity. + * + * @param regexs The set of regular expressions this validator will + * validate against + * @param caseSensitive when true matching is case + * sensitive, otherwise matching is case in-sensitive + */ + public RegexValidator(String[] regexs, boolean caseSensitive) { + if (regexs == null || regexs.length == 0) { + throw new IllegalArgumentException("Regular expressions are missing"); + } + patterns = new Pattern[regexs.length]; + int flags = (caseSensitive ? 0: Pattern.CASE_INSENSITIVE); + for (int i = 0; i < regexs.length; i++) { + if (regexs[i] == null || regexs[i].length() == 0) { + throw new IllegalArgumentException("Regular expression[" + i + "] is missing"); + } + patterns[i] = Pattern.compile(regexs[i], flags); + } + } + + /** + * Validate a value against the set of regular expressions. + * + * @param value The value to validate. + * @return true if the value is valid + * otherwise false. + */ + public boolean isValid(String value) { + if (value == null) { + return false; + } + for (int i = 0; i < patterns.length; i++) { + if (patterns[i].matcher(value).matches()) { + return true; + } + } + return false; + } + + /** + * Validate a value against the set of regular expressions + * returning the array of matched groups. + * + * @param value The value to validate. + * @return String array of the groups matched if + * valid or null if invalid + */ + public String[] match(String value) { + if (value == null) { + return null; + } + for (int i = 0; i < patterns.length; i++) { + Matcher matcher = patterns[i].matcher(value); + if (matcher.matches()) { + int count = matcher.groupCount(); + String[] groups = new String[count]; + for (int j = 0; j < count; j++) { + groups[j] = matcher.group(j+1); + } + return groups; + } + } + return null; + } + + + /** + * Validate a value against the set of regular expressions + * returning a String value of the aggregated groups. + * + * @param value The value to validate. + * @return Aggregated String value comprised of the + * groups matched if valid or null if invalid + */ + public String validate(String value) { + if (value == null) { + return null; + } + for (int i = 0; i < patterns.length; i++) { + Matcher matcher = patterns[i].matcher(value); + if (matcher.matches()) { + int count = matcher.groupCount(); + if (count == 1) { + return matcher.group(1); + } + StringBuilder buffer = new StringBuilder(); + for (int j = 0; j < count; j++) { + String component = matcher.group(j+1); + if (component != null) { + buffer.append(component); + } + } + return buffer.toString(); + } + } + return null; + } + + /** + * Provide a String representation of this validator. + * @return A String representation of this validator + */ + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("RegexValidator{"); + for (int i = 0; i < patterns.length; i++) { + if (i > 0) { + buffer.append(","); + } + buffer.append(patterns[i].pattern()); + } + buffer.append("}"); + return buffer.toString(); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ShortValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ShortValidator.java new file mode 100644 index 000000000..80517b285 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ShortValidator.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.validator.routines; + +import java.text.Format; +import java.util.Locale; + +/** + *

Short Validation and Conversion routines (java.lang.Short).

+ * + *

This validator provides a number of methods for + * validating/converting a String value to + * a Short using java.text.NumberFormat + * to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Short value.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform minimum, maximum and range checks:

+ *
    + *
  • minValue() checks whether the value is greater + * than or equal to a specified minimum.
  • + *
  • maxValue() checks whether the value is less + * than or equal to a specified maximum.
  • + *
  • isInRange() checks whether the value is within + * a specified range of values.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class ShortValidator extends AbstractNumberValidator { + + private static final long serialVersionUID = -5227510699747787066L; + + private static final ShortValidator VALIDATOR = new ShortValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the ShortValidator. + */ + public static ShortValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance. + */ + public ShortValidator() { + this(true, STANDARD_FORMAT); + } + + /** + *

Construct an instance with the specified strict setting + * and format type.

+ * + *

The formatType specified what type of + * NumberFormat is created - valid types + * are:

+ *
    + *
  • AbstractNumberValidator.STANDARD_FORMAT -to create + * standard number formats (the default).
  • + *
  • AbstractNumberValidator.CURRENCY_FORMAT -to create + * currency number formats.
  • + *
  • AbstractNumberValidator.PERCENT_FORMAT -to create + * percent number formats (the default).
  • + *
+ * + * @param strict true if strict + * Format parsing should be used. + * @param formatType The NumberFormat type to + * create for validation, default is STANDARD_FORMAT. + */ + public ShortValidator(boolean strict, int formatType) { + super(strict, formatType, false); + } + + /** + *

Validate/convert a Short using the default + * Locale. + * + * @param value The value validation is being performed on. + * @return The parsed Short if valid or null + * if invalid. + */ + public Short validate(String value) { + return (Short)parse(value, (String)null, (Locale)null); + } + + /** + *

Validate/convert a Short using the + * specified pattern. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed Short if valid or null if invalid. + */ + public Short validate(String value, String pattern) { + return (Short)parse(value, pattern, (Locale)null); + } + + /** + *

Validate/convert a Short using the + * specified Locale. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the number format, system default if null. + * @return The parsed Short if valid or null if invalid. + */ + public Short validate(String value, Locale locale) { + return (Short)parse(value, (String)null, locale); + } + + /** + *

Validate/convert a Short using the + * specified pattern and/ or Locale. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Short if valid or null if invalid. + */ + public Short validate(String value, String pattern, Locale locale) { + return (Short)parse(value, pattern, locale); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(short value, short min, short max) { + return (value >= min && value <= max); + } + + /** + * Check if the value is within a specified range. + * + * @param value The Number value to check. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return true if the value is within the + * specified range. + */ + public boolean isInRange(Short value, short min, short max) { + return isInRange(value.shortValue(), min, max); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(short value, short min) { + return (value >= min); + } + + /** + * Check if the value is greater than or equal to a minimum. + * + * @param value The value validation is being performed on. + * @param min The minimum value. + * @return true if the value is greater than + * or equal to the minimum. + */ + public boolean minValue(Short value, short min) { + return minValue(value.shortValue(), min); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(short value, short max) { + return (value <= max); + } + + /** + * Check if the value is less than or equal to a maximum. + * + * @param value The value validation is being performed on. + * @param max The maximum value. + * @return true if the value is less than + * or equal to the maximum. + */ + public boolean maxValue(Short value, short max) { + return maxValue(value.shortValue(), max); + } + + /** + *

Perform further validation and convert the Number to + * a Short.

+ * + * @param value The parsed Number object created. + * @param formatter The Format used to parse the value with. + * @return The parsed Number converted to a + * Short if valid or null if invalid. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + + long longValue = ((Number)value).longValue(); + + if (longValue < Short.MIN_VALUE || + longValue > Short.MAX_VALUE) { + return null; + } + return Short.valueOf((short)longValue); + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/TimeValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/TimeValidator.java new file mode 100644 index 000000000..007e18c79 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/TimeValidator.java @@ -0,0 +1,285 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.DateFormat; +import java.text.Format; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + *

Time Validation and Conversion routines (java.util.Calendar).

+ * + *

This validator provides a number of methods for validating/converting + * a String time value to a java.util.Calendar using + * java.text.DateFormat to parse either:

+ *
    + *
  • using the default format for the default Locale
  • + *
  • using a specified pattern with the default Locale
  • + *
  • using the default format for a specified Locale
  • + *
  • using a specified pattern with a specified Locale
  • + *
+ * + *

For each of the above mechanisms, conversion method (i.e the + * validate methods) implementations are provided which + * either use the default TimeZone or allow the + * TimeZone to be specified.

+ * + *

Use one of the isValid() methods to just validate or + * one of the validate() methods to validate and receive a + * converted Calendar value for the time.

+ * + *

Implementations of the validate() method are provided + * to create Calendar objects for different time zones + * if the system default is not appropriate.

+ * + *

Alternatively the CalendarValidator's adjustToTimeZone() method + * can be used to adjust the TimeZone of the Calendar + * object afterwards.

+ * + *

Once a value has been successfully converted the following + * methods can be used to perform various time comparison checks:

+ *
    + *
  • compareTime() compares the hours, minutes, seconds + * and milliseconds of two calendars, returning 0, -1 or +1 indicating + * whether the first time is equal, before or after the second.
  • + *
  • compareSeconds() compares the hours, minutes and + * seconds of two times, returning 0, -1 or +1 indicating + * whether the first is equal to, before or after the second.
  • + *
  • compareMinutes() compares the hours and minutes + * two times, returning 0, -1 or +1 indicating + * whether the first is equal to, before or after the second.
  • + *
  • compareHours() compares the hours + * of two times, returning 0, -1 or +1 indicating + * whether the first is equal to, before or after the second.
  • + *
+ * + *

So that the same mechanism used for parsing an input value + * for validation can be used to format output, corresponding + * format() methods are also provided. That is you can + * format either:

+ *
    + *
  • using a specified pattern
  • + *
  • using the format for a specified Locale
  • + *
  • using the format for the default Locale
  • + *
+ * + * @version $Revision$ + * @since Validator 1.3.0 + */ +public class TimeValidator extends AbstractCalendarValidator { + + private static final long serialVersionUID = 3494007492269691581L; + + private static final TimeValidator VALIDATOR = new TimeValidator(); + + /** + * Return a singleton instance of this validator. + * @return A singleton instance of the TimeValidator. + */ + public static TimeValidator getInstance() { + return VALIDATOR; + } + + /** + * Construct a strict instance with short + * time style. + */ + public TimeValidator() { + this(true, DateFormat.SHORT); + } + + /** + * Construct an instance with the specified strict + * and time style parameters. + * + * @param strict true if strict + * Format parsing should be used. + * @param timeStyle the time style to use for Locale validation. + */ + public TimeValidator(boolean strict, int timeStyle) { + super(strict, -1, timeStyle); + } + + /** + *

Validate/convert a time using the default Locale + * and TimeZone. + * + * @param value The value validation is being performed on. + * @return The parsed Calendar if valid or null + * if invalid. + */ + public Calendar validate(String value) { + return (Calendar)parse(value, (String)null, (Locale)null, (TimeZone)null); + } + + /** + *

Validate/convert a time using the specified TimeZone + * and default Locale. + * + * @param value The value validation is being performed on. + * @param timeZone The Time Zone used to parse the time, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, TimeZone timeZone) { + return (Calendar)parse(value, (String)null, (Locale)null, timeZone); + } + + /** + *

Validate/convert a time using the specified pattern and + * default TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, String pattern) { + return (Calendar)parse(value, pattern, (Locale)null, (TimeZone)null); + } + + /** + *

Validate/convert a time using the specified pattern + * and TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against. + * @param timeZone The Time Zone used to parse the time, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, String pattern, TimeZone timeZone) { + return (Calendar)parse(value, pattern, (Locale)null, timeZone); + } + + /** + *

Validate/convert a time using the specified Locale + * default TimeZone. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the time format, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, Locale locale) { + return (Calendar)parse(value, (String)null, locale, (TimeZone)null); + } + + /** + *

Validate/convert a time using the specified specified Locale + * and TimeZone. + * + * @param value The value validation is being performed on. + * @param locale The locale to use for the time format, system default if null. + * @param timeZone The Time Zone used to parse the time, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, Locale locale, TimeZone timeZone) { + return (Calendar)parse(value, (String)null, locale, timeZone); + } + + /** + *

Validate/convert a time using the specified pattern and Locale + * and the default TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, String pattern, Locale locale) { + return (Calendar)parse(value, pattern, locale, (TimeZone)null); + } + + /** + *

Validate/convert a time using the specified pattern, Locale + * and TimeZone. + * + * @param value The value validation is being performed on. + * @param pattern The pattern used to validate the value against, or the + * default for the Locale if null. + * @param locale The locale to use for the date format, system default if null. + * @param timeZone The Time Zone used to parse the date, system default if null. + * @return The parsed Calendar if valid or null if invalid. + */ + public Calendar validate(String value, String pattern, Locale locale, TimeZone timeZone) { + return (Calendar)parse(value, pattern, locale, timeZone); + } + + /** + *

Compare Times (hour, minute, second and millisecond - not date).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @return Zero if the hours are equal, -1 if first + * time is less than the seconds and +1 if the first + * time is greater than. + */ + public int compareTime(Calendar value, Calendar compare) { + return compareTime(value, compare, Calendar.MILLISECOND); + } + + /** + *

Compare Seconds (hours, minutes and seconds).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @return Zero if the hours are equal, -1 if first + * parameter's seconds are less than the seconds and +1 if the first + * parameter's seconds are greater than. + */ + public int compareSeconds(Calendar value, Calendar compare) { + return compareTime(value, compare, Calendar.SECOND); + } + + /** + *

Compare Minutes (hours and minutes).

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @return Zero if the hours are equal, -1 if first + * parameter's minutes are less than the seconds and +1 if the first + * parameter's minutes are greater than. + */ + public int compareMinutes(Calendar value, Calendar compare) { + return compareTime(value, compare, Calendar.MINUTE); + } + + /** + *

Compare Hours.

+ * + * @param value The Calendar value to check. + * @param compare The Calendar to compare the value to. + * @return Zero if the hours are equal, -1 if first + * parameter's hour is less than the seconds and +1 if the first + * parameter's hour is greater than. + */ + public int compareHours(Calendar value, Calendar compare) { + return compareTime(value, compare, Calendar.HOUR_OF_DAY); + } + + /** + *

Convert the parsed Date to a Calendar.

+ * + * @param value The parsed Date object created. + * @param formatter The Format used to parse the value with. + * @return The parsed value converted to a Calendar. + */ + @Override + protected Object processParsedValue(Object value, Format formatter) { + return ((DateFormat)formatter).getCalendar(); + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/UrlValidator.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/UrlValidator.java new file mode 100644 index 000000000..b26d95840 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/UrlValidator.java @@ -0,0 +1,576 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + *

URL Validation routines.

+ * Behavior of validation is modified by passing in options: + *
    + *
  • ALLOW_2_SLASHES - [FALSE] Allows double '/' characters in the path + * component.
  • + *
  • NO_FRAGMENT- [FALSE] By default fragments are allowed, if this option is + * included then fragments are flagged as illegal.
  • + *
  • ALLOW_ALL_SCHEMES - [FALSE] By default only http, https, and ftp are + * considered valid schemes. Enabling this option will let any scheme pass validation.
  • + *
+ * + *

Originally based in on php script by Debbie Dyer, validation.php v1.2b, Date: 03/07/02, + * http://javascript.internet.com. However, this validation now bears little resemblance + * to the php original.

+ *
+ *   Example of usage:
+ *   Construct a UrlValidator with valid schemes of "http", and "https".
+ *
+ *    String[] schemes = {"http","https"}.
+ *    UrlValidator urlValidator = new UrlValidator(schemes);
+ *    if (urlValidator.isValid("ftp://foo.bar.com/")) {
+ *       System.out.println("url is valid");
+ *    } else {
+ *       System.out.println("url is invalid");
+ *    }
+ *
+ *    prints "url is invalid"
+ *   If instead the default constructor is used.
+ *
+ *    UrlValidator urlValidator = new UrlValidator();
+ *    if (urlValidator.isValid("ftp://foo.bar.com/")) {
+ *       System.out.println("url is valid");
+ *    } else {
+ *       System.out.println("url is invalid");
+ *    }
+ *
+ *   prints out "url is valid"
+ *  
+ * + * @see + * + * Uniform Resource Identifiers (URI): Generic Syntax + * + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class UrlValidator implements Serializable { + + private static final long serialVersionUID = 7557161713937335013L; + + private static final int MAX_UNSIGNED_16_BIT_INT = 0xFFFF; // port max + + /** + * Allows all validly formatted schemes to pass validation instead of + * supplying a set of valid schemes. + */ + public static final long ALLOW_ALL_SCHEMES = 1 << 0; + + /** + * Allow two slashes in the path component of the URL. + */ + public static final long ALLOW_2_SLASHES = 1 << 1; + + /** + * Enabling this options disallows any URL fragments. + */ + public static final long NO_FRAGMENTS = 1 << 2; + + /** + * Allow local URLs, such as http://localhost/ or http://machine/ . + * This enables a broad-brush check, for complex local machine name + * validation requirements you should create your validator with + * a {@link RegexValidator} instead ({@link #UrlValidator(RegexValidator, long)}) + */ + public static final long ALLOW_LOCAL_URLS = 1 << 3; // CHECKSTYLE IGNORE MagicNumber + + /** + * This expression derived/taken from the BNF for URI (RFC2396). + */ + private static final String URL_REGEX = + "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"; + // 12 3 4 5 6 7 8 9 + private static final Pattern URL_PATTERN = Pattern.compile(URL_REGEX); + + /** + * Schema/Protocol (ie. http:, ftp:, file:, etc). + */ + private static final int PARSE_URL_SCHEME = 2; + + /** + * Includes hostname/ip and port number. + */ + private static final int PARSE_URL_AUTHORITY = 4; + + private static final int PARSE_URL_PATH = 5; + + private static final int PARSE_URL_QUERY = 7; + + private static final int PARSE_URL_FRAGMENT = 9; + + /** + * Protocol scheme (e.g. http, ftp, https). + */ + private static final String SCHEME_REGEX = "^\\p{Alpha}[\\p{Alnum}\\+\\-\\.]*"; + private static final Pattern SCHEME_PATTERN = Pattern.compile(SCHEME_REGEX); + + // Drop numeric, and "+-." for now + // TODO does not allow for optional userinfo. + // Validation of character set is done by isValidAuthority + private static final String AUTHORITY_CHARS_REGEX = "\\p{Alnum}\\-\\."; // allows for IPV4 but not IPV6 + private static final String IPV6_REGEX = "[0-9a-fA-F:]+"; // do this as separate match because : could cause ambiguity with port prefix + + // userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + // We assume that password has the same valid chars as user info + private static final String USERINFO_CHARS_REGEX = "[a-zA-Z0-9%-._~!$&'()*+,;=]"; + // since neither ':' nor '@' are allowed chars, we don't need to use non-greedy matching + private static final String USERINFO_FIELD_REGEX = + USERINFO_CHARS_REGEX + "+" + // At least one character for the name + "(?::" + USERINFO_CHARS_REGEX + "*)?@"; // colon and password may be absent + private static final String AUTHORITY_REGEX = + "(?:\\[("+IPV6_REGEX+")\\]|(?:(?:"+USERINFO_FIELD_REGEX+")?([" + AUTHORITY_CHARS_REGEX + "]*)))(?::(\\d*))?(.*)?"; + // 1 e.g. user:pass@ 2 3 4 + private static final Pattern AUTHORITY_PATTERN = Pattern.compile(AUTHORITY_REGEX); + + private static final int PARSE_AUTHORITY_IPV6 = 1; + + private static final int PARSE_AUTHORITY_HOST_IP = 2; // excludes userinfo, if present + + private static final int PARSE_AUTHORITY_PORT = 3; // excludes leading colon + + /** + * Should always be empty. The code currently allows spaces. + */ + private static final int PARSE_AUTHORITY_EXTRA = 4; + + private static final String PATH_REGEX = "^(/[-\\w:@&?=+,.!/~*'%$_;\\(\\)]*)?$"; + private static final Pattern PATH_PATTERN = Pattern.compile(PATH_REGEX); + + private static final String QUERY_REGEX = "^(\\S*)$"; + private static final Pattern QUERY_PATTERN = Pattern.compile(QUERY_REGEX); + + /** + * Holds the set of current validation options. + */ + private final long options; + + /** + * The set of schemes that are allowed to be in a URL. + */ + private final Set allowedSchemes; // Must be lower-case + + /** + * Regular expressions used to manually validate authorities if IANA + * domain name validation isn't desired. + */ + private final RegexValidator authorityValidator; + + /** + * If no schemes are provided, default to this set. + */ + private static final String[] DEFAULT_SCHEMES = {"http", "https", "ftp"}; // Must be lower-case + + /** + * Singleton instance of this class with default schemes and options. + */ + private static final UrlValidator DEFAULT_URL_VALIDATOR = new UrlValidator(); + + /** + * Returns the singleton instance of this class with default schemes and options. + * @return singleton instance with default schemes and options + */ + public static UrlValidator getInstance() { + return DEFAULT_URL_VALIDATOR; + } + + /** + * Create a UrlValidator with default properties. + */ + public UrlValidator() { + this(null); + } + + /** + * Behavior of validation is modified by passing in several strings options: + * @param schemes Pass in one or more url schemes to consider valid, passing in + * a null will default to "http,https,ftp" being valid. + * If a non-null schemes is specified then all valid schemes must + * be specified. Setting the ALLOW_ALL_SCHEMES option will + * ignore the contents of schemes. + */ + public UrlValidator(String[] schemes) { + this(schemes, 0L); + } + + /** + * Initialize a UrlValidator with the given validation options. + * @param options The options should be set using the public constants declared in + * this class. To set multiple options you simply add them together. For example, + * ALLOW_2_SLASHES + NO_FRAGMENTS enables both of those options. + */ + public UrlValidator(long options) { + this(null, null, options); + } + + /** + * Behavior of validation is modified by passing in options: + * @param schemes The set of valid schemes. Ignored if the ALLOW_ALL_SCHEMES option is set. + * @param options The options should be set using the public constants declared in + * this class. To set multiple options you simply add them together. For example, + * ALLOW_2_SLASHES + NO_FRAGMENTS enables both of those options. + */ + public UrlValidator(String[] schemes, long options) { + this(schemes, null, options); + } + + /** + * Initialize a UrlValidator with the given validation options. + * @param authorityValidator Regular expression validator used to validate the authority part + * This allows the user to override the standard set of domains. + * @param options Validation options. Set using the public constants of this class. + * To set multiple options, simply add them together: + *

ALLOW_2_SLASHES + NO_FRAGMENTS

+ * enables both of those options. + */ + public UrlValidator(RegexValidator authorityValidator, long options) { + this(null, authorityValidator, options); + } + + /** + * Customizable constructor. Validation behavior is modifed by passing in options. + * @param schemes the set of valid schemes. Ignored if the ALLOW_ALL_SCHEMES option is set. + * @param authorityValidator Regular expression validator used to validate the authority part + * @param options Validation options. Set using the public constants of this class. + * To set multiple options, simply add them together: + *

ALLOW_2_SLASHES + NO_FRAGMENTS

+ * enables both of those options. + */ + public UrlValidator(String[] schemes, RegexValidator authorityValidator, long options) { + this.options = options; + + if (isOn(ALLOW_ALL_SCHEMES)) { + allowedSchemes = Collections.emptySet(); + } else { + if (schemes == null) { + schemes = DEFAULT_SCHEMES; + } + allowedSchemes = new HashSet(schemes.length); + for(int i=0; i < schemes.length; i++) { + allowedSchemes.add(schemes[i].toLowerCase(Locale.ENGLISH)); + } + } + + this.authorityValidator = authorityValidator; + } + + /** + *

Checks if a field has a valid url address.

+ * + * Note that the method calls #isValidAuthority() + * which checks that the domain is valid. + * + * @param value The value validation is being performed on. A null + * value is considered invalid. + * @return true if the url is valid. + */ + public boolean isValid(String value) { + if (value == null) { + return false; + } + + // Check the whole url address structure + Matcher urlMatcher = URL_PATTERN.matcher(value); + if (!urlMatcher.matches()) { + return false; + } + + String scheme = urlMatcher.group(PARSE_URL_SCHEME); + if (!isValidScheme(scheme)) { + return false; + } + + String authority = urlMatcher.group(PARSE_URL_AUTHORITY); + if ("file".equals(scheme) && (authority == null || "".equals(authority))) {// Special case - file: allows an empty authority + return true; // this is a local file - nothing more to do here + } else if ("file".equals(scheme) && authority != null && authority.contains(":")) { + return false; + } else { + // Validate the authority + if (!isValidAuthority(authority)) { + return false; + } + } + + if (!isValidPath(urlMatcher.group(PARSE_URL_PATH))) { + return false; + } + + if (!isValidQuery(urlMatcher.group(PARSE_URL_QUERY))) { + return false; + } + + if (!isValidFragment(urlMatcher.group(PARSE_URL_FRAGMENT))) { + return false; + } + + return true; + } + + /** + * Validate scheme. If schemes[] was initialized to a non null, + * then only those schemes are allowed. + * Otherwise the default schemes are "http", "https", "ftp". + * Matching is case-blind. + * @param scheme The scheme to validate. A null value is considered + * invalid. + * @return true if valid. + */ + protected boolean isValidScheme(String scheme) { + if (scheme == null) { + return false; + } + + // TODO could be removed if external schemes were checked in the ctor before being stored + if (!SCHEME_PATTERN.matcher(scheme).matches()) { + return false; + } + + if (isOff(ALLOW_ALL_SCHEMES) && !allowedSchemes.contains(scheme.toLowerCase(Locale.ENGLISH))) { + return false; + } + + return true; + } + + /** + * Returns true if the authority is properly formatted. An authority is the combination + * of hostname and port. A null authority value is considered invalid. + * Note: this implementation validates the domain unless a RegexValidator was provided. + * If a RegexValidator was supplied and it matches, then the authority is regarded + * as valid with no further checks, otherwise the method checks against the + * AUTHORITY_PATTERN and the DomainValidator (ALLOW_LOCAL_URLS) + * @param authority Authority value to validate, alllows IDN + * @return true if authority (hostname and port) is valid. + */ + protected boolean isValidAuthority(String authority) { + if (authority == null) { + return false; + } + + // check manual authority validation if specified + if (authorityValidator != null && authorityValidator.isValid(authority)) { + return true; + } + // convert to ASCII if possible + final String authorityASCII = DomainValidator.unicodeToASCII(authority); + + Matcher authorityMatcher = AUTHORITY_PATTERN.matcher(authorityASCII); + if (!authorityMatcher.matches()) { + return false; + } + + // We have to process IPV6 separately because that is parsed in a different group + String ipv6 = authorityMatcher.group(PARSE_AUTHORITY_IPV6); + if (ipv6 != null) { + InetAddressValidator inetAddressValidator = InetAddressValidator.getInstance(); + if (!inetAddressValidator.isValidInet6Address(ipv6)) { + return false; + } + } else { + String hostLocation = authorityMatcher.group(PARSE_AUTHORITY_HOST_IP); + // check if authority is hostname or IP address: + // try a hostname first since that's much more likely + DomainValidator domainValidator = DomainValidator.getInstance(isOn(ALLOW_LOCAL_URLS)); + if (!domainValidator.isValid(hostLocation)) { + // try an IPv4 address + InetAddressValidator inetAddressValidator = InetAddressValidator.getInstance(); + if (!inetAddressValidator.isValidInet4Address(hostLocation)) { + // isn't IPv4, so the URL is invalid + return false; + } + } + String port = authorityMatcher.group(PARSE_AUTHORITY_PORT); + if (port != null && port.length() > 0) { + try { + int iPort = Integer.parseInt(port); + if (iPort < 0 || iPort > MAX_UNSIGNED_16_BIT_INT) { + return false; + } + } catch (NumberFormatException nfe) { + return false; // this can happen for big numbers + } + } + } + + String extra = authorityMatcher.group(PARSE_AUTHORITY_EXTRA); + if (extra != null && extra.trim().length() > 0){ + return false; + } + + return true; + } + + /** + * Returns true if the path is valid. A null value is considered invalid. + * @param path Path value to validate. + * @return true if path is valid. + */ + protected boolean isValidPath(String path) { + if (path == null) { + return false; + } + + if (!PATH_PATTERN.matcher(path).matches()) { + return false; + } + + try { + // Don't omit host otherwise leading path may be taken as host if it starts with // + URI uri = new URI(null,"localhost",path,null); + String norm = uri.normalize().getPath(); + if (norm.startsWith("/../") // Trying to go via the parent dir + || norm.equals("/..")) { // Trying to go to the parent dir + return false; + } + } catch (URISyntaxException e) { + return false; + } + + int slash2Count = countToken("//", path); + if (isOff(ALLOW_2_SLASHES) && (slash2Count > 0)) { + return false; + } + + return true; + } + + /** + * Returns true if the query is null or it's a properly formatted query string. + * @param query Query value to validate. + * @return true if query is valid. + */ + protected boolean isValidQuery(String query) { + if (query == null) { + return true; + } + + return QUERY_PATTERN.matcher(query).matches(); + } + + /** + * Returns true if the given fragment is null or fragments are allowed. + * @param fragment Fragment value to validate. + * @return true if fragment is valid. + */ + protected boolean isValidFragment(String fragment) { + if (fragment == null) { + return true; + } + + return isOff(NO_FRAGMENTS); + } + + /** + * Returns the number of times the token appears in the target. + * @param token Token value to be counted. + * @param target Target value to count tokens in. + * @return the number of tokens. + */ + protected int countToken(String token, String target) { + int tokenIndex = 0; + int count = 0; + while (tokenIndex != -1) { + tokenIndex = target.indexOf(token, tokenIndex); + if (tokenIndex > -1) { + tokenIndex++; + count++; + } + } + return count; + } + + /** + * Tests whether the given flag is on. If the flag is not a power of 2 + * (ie. 3) this tests whether the combination of flags is on. + * + * @param flag Flag value to check. + * + * @return whether the specified flag value is on. + */ + private boolean isOn(long flag) { + return (options & flag) > 0; + } + + /** + * Tests whether the given flag is off. If the flag is not a power of 2 + * (ie. 3) this tests whether the combination of flags is off. + * + * @param flag Flag value to check. + * + * @return whether the specified flag value is off. + */ + private boolean isOff(long flag) { + return (options & flag) == 0; + } + + // Unit test access to pattern matcher + Matcher matchURL(String value) { + return URL_PATTERN.matcher(value); + } + + /** + * Validator for checking URL parsing + * @param args - URLs to validate + */ + public static void main(String[] args) { + UrlValidator val = new UrlValidator(new String[] { "file", "http", "https" }, UrlValidator.ALLOW_LOCAL_URLS); + for(String arg: args) { + Matcher m = val.matchURL(arg); + if (m.matches()) { + System.out.printf("%s has %d parts%n",arg,m.groupCount()); + for(int i=1;i ABA Number (or Routing Transit Number (RTN)) Check Digit + * calculation/validation. + * + *

+ * ABA Numbers (or Routing Transit Numbers) are a nine digit numeric code used + * to identify American financial institutions for things such as checks or deposits + * (ABA stands for the American Bankers Association). + *

+ * + * Check digit calculation is based on modulus 10 with digits being weighted + * based on their position (from right to left) as follows: + * + *
    + *
  • Digits 1, 4 and & 7 are weighted 1
  • + *
  • Digits 2, 5 and & 8 are weighted 7
  • + *
  • Digits 3, 6 and & 9 are weighted 3
  • + *
+ * + *

+ * For further information see + * Wikipedia - + * Routing transit number. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class ABANumberCheckDigit extends ModulusCheckDigit { + + private static final long serialVersionUID = -8255937433810380145L; + + /** Singleton Routing Transit Number Check Digit instance */ + public static final CheckDigit ABAN_CHECK_DIGIT = new ABANumberCheckDigit(); + + /** weighting given to digits depending on their right position */ + private static final int[] POSITION_WEIGHT = new int[] {3, 1, 7}; + + /** + * Construct a modulus 10 Check Digit routine for ABA Numbers. + */ + public ABANumberCheckDigit() { + super(10); // CHECKSTYLE IGNORE MagicNumber + } + + /** + * Calculates the weighted value of a character in the + * code at a specified position. + *

+ * ABA Routing numbers are weighted in the following manner: + *


+     *     left position: 1  2  3  4  5  6  7  8  9
+     *            weight: 3  7  1  3  7  1  3  7  1
+     * 
+ * + * @param charValue The numeric value of the character. + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The positionof the character in the code, counting from right to left + * @return The weighted value of the character. + */ + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) { + int weight = POSITION_WEIGHT[rightPos % 3]; // CHECKSTYLE IGNORE MagicNumber + return charValue * weight; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CUSIPCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CUSIPCheckDigit.java new file mode 100644 index 000000000..fd97efaeb --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CUSIPCheckDigit.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +/** + * Modulus 10 CUSIP (North American Securities) Check Digit calculation/validation. + * + *

+ * CUSIP Numbers are 9 character alphanumeric codes used + * to identify North American Securities. + *

+ * + *

+ * Check digit calculation uses the Modulus 10 Double Add Double technique + * with every second digit being weighted by 2. Alphabetic characters are + * converted to numbers by their position in the alphabet starting with A being 10. + * Weighted numbers greater than ten are treated as two separate numbers. + *

+ * + *

+ * See Wikipedia - CUSIP + * for more details. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class CUSIPCheckDigit extends ModulusCheckDigit { + + private static final long serialVersionUID = 666941918490152456L; + + /** Singleton CUSIP Check Digit instance */ + public static final CheckDigit CUSIP_CHECK_DIGIT = new CUSIPCheckDigit(); + + /** weighting given to digits depending on their right position */ + private static final int[] POSITION_WEIGHT = new int[] {2, 1}; + + /** + * Construct an CUSIP Indetifier Check Digit routine. + */ + public CUSIPCheckDigit() { + super(10); // CHECKSTYLE IGNORE MagicNumber + } + + /** + * Convert a character at a specified position to an integer value. + * + * @param character The character to convert + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The position of the character in the code, counting from right to left + * @return The integer value of the character + * @throws CheckDigitException if character is not alphanumeric + */ + @Override + protected int toInt(char character, int leftPos, int rightPos) + throws CheckDigitException { + int charValue = Character.getNumericValue(character); + // the final character is only allowed to reach 9 + final int charMax = rightPos == 1 ? 9 : 35; // CHECKSTYLE IGNORE MagicNumber + if (charValue < 0 || charValue > charMax) { + throw new CheckDigitException("Invalid Character[" + + leftPos + "," + rightPos + "] = '" + charValue + "' out of range 0 to " + charMax); + } + return charValue; + } + + /** + *

Calculates the weighted value of a charcter in the + * code at a specified position.

+ * + *

For CUSIP (from right to left) odd digits are weighted + * with a factor of one and even digits with a factor + * of two. Weighted values > 9, have 9 subtracted

+ * + * @param charValue The numeric value of the character. + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The positionof the character in the code, counting from right to left + * @return The weighted value of the character. + */ + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) { + int weight = POSITION_WEIGHT[rightPos % 2]; + int weightedValue = (charValue * weight); + return ModulusCheckDigit.sumDigits(weightedValue); + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CheckDigit.java new file mode 100644 index 000000000..bea38cd11 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CheckDigit.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +/** + * Check Digit calculation and validation. + *

+ * The logic for validating check digits has previously been + * embedded within the logic for specific code validation, which + * includes other validations such as verifying the format + * or length of a code. {@link CheckDigit} provides for separating out + * the check digit calculation logic enabling it to be more easily + * tested and reused. + *

+ *

+ * Although Commons Validator is primarily concerned with validation, + * {@link CheckDigit} also defines behavior for calculating/generating check + * digits, since it makes sense that users will want to (re-)use the + * same logic for both. The {@link org.apache.commons.validator.routines.ISBNValidator} + * makes specific use of this feature by providing the facility to validate ISBN-10 codes + * and then convert them to the new ISBN-13 standard. + *

+ *

+ * CheckDigit is used by the new generic @link CodeValidator} implementation. + *

+ * + *

Implementations

+ * See the + * Package Summary for a full + * list of implementations provided within Commons Validator. + * + * @see org.apache.commons.validator.routines.CodeValidator + * @version $Revision$ + * @since Validator 1.4 + */ +public interface CheckDigit { + + /** + * Calculates the Check Digit for a code. + * + * @param code The code to calculate the Check Digit for. + * The string must not include the check digit + * @return The calculated Check Digit + * @throws CheckDigitException if an error occurs. + */ + String calculate(String code) throws CheckDigitException; + + /** + * Validates the check digit for the code. + * + * @param code The code to validate, the string must include the check digit. + * @return true if the check digit is valid, otherwise + * false. + */ + boolean isValid(String code); + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CheckDigitException.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CheckDigitException.java new file mode 100644 index 000000000..cbbae666a --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CheckDigitException.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.validator.routines.checkdigit; + +/** + * Check Digit calculation/validation error. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class CheckDigitException extends Exception { + + private static final long serialVersionUID = -3519894732624685477L; + + /** + * Construct an Exception with no message. + */ + public CheckDigitException() { + } + + /** + * Construct an Exception with a message. + * + * @param msg The error message. + */ + public CheckDigitException(String msg) { + super(msg); + } + + /** + * Construct an Exception with a message and + * the underlying cause. + * + * @param msg The error message. + * @param cause The underlying cause of the error + */ + public CheckDigitException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/EAN13CheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/EAN13CheckDigit.java new file mode 100644 index 000000000..ca029a692 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/EAN13CheckDigit.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +/** + * Modulus 10 EAN-13 / UPC / ISBN-13 Check Digit + * calculation/validation. + *

+ * Check digit calculation is based on modulus 10 with digits in + * an odd position (from right to left) being weighted 1 and even + * position digits being weighted 3. + *

+ * For further information see: + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class EAN13CheckDigit extends ModulusCheckDigit { + + private static final long serialVersionUID = 1726347093230424107L; + + /** Singleton EAN-13 Check Digit instance */ + public static final CheckDigit EAN13_CHECK_DIGIT = new EAN13CheckDigit(); + + /** weighting given to digits depending on their right position */ + private static final int[] POSITION_WEIGHT = new int[] {3, 1}; + + /** + * Construct a modulus 10 Check Digit routine for EAN/UPC. + */ + public EAN13CheckDigit() { + super(10); // CHECKSTYLE IGNORE MagicNumber + } + + /** + *

Calculates the weighted value of a character in the + * code at a specified position.

+ * + *

For EAN-13 (from right to left) odd digits are weighted + * with a factor of one and even digits with a factor + * of three.

+ * + * @param charValue The numeric value of the character. + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The positionof the character in the code, counting from right to left + * @return The weighted value of the character. + */ + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) { + int weight = POSITION_WEIGHT[rightPos % 2]; + return charValue * weight; + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/IBANCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/IBANCheckDigit.java new file mode 100644 index 000000000..b1106345d --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/IBANCheckDigit.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.validator.routines.checkdigit; + +import java.io.Serializable; + +/** + * IBAN (International Bank Account Number) Check Digit calculation/validation. + *

+ * This routine is based on the ISO 7064 Mod 97,10 check digit calculation routine. + *

+ * The two check digit characters in a IBAN number are the third and fourth characters + * in the code. For check digit calculation/validation the first four characters are moved + * to the end of the code. + * So CCDDnnnnnnn becomes nnnnnnnCCDD (where + * CC is the country code and DD is the check digit). For + * check digit calculation the check digit value should be set to zero (i.e. + * CC00nnnnnnn in this example. + *

+ * Note: the class does not check the format of the IBAN number, only the check digits. + *

+ * For further information see + * Wikipedia - + * IBAN number. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class IBANCheckDigit implements CheckDigit, Serializable { + + private static final int MIN_CODE_LEN = 5; + + private static final long serialVersionUID = -3600191725934382801L; + + private static final int MAX_ALPHANUMERIC_VALUE = 35; // Character.getNumericValue('Z') + + /** Singleton IBAN Number Check Digit instance */ + public static final CheckDigit IBAN_CHECK_DIGIT = new IBANCheckDigit(); + + private static final long MAX = 999999999; + + private static final long MODULUS = 97; + + /** + * Construct Check Digit routine for IBAN Numbers. + */ + public IBANCheckDigit() { + } + + /** + * Validate the check digit of an IBAN code. + * + * @param code The code to validate + * @return true if the check digit is valid, otherwise + * false + */ + @Override + public boolean isValid(String code) { + if (code == null || code.length() < MIN_CODE_LEN) { + return false; + } + String check = code.substring(2,4); // CHECKSTYLE IGNORE MagicNumber + if ("00".equals(check) || "01".equals(check) || "99".equals(check)) { + return false; + } + try { + int modulusResult = calculateModulus(code); + return (modulusResult == 1); + } catch (CheckDigitException ex) { + return false; + } + } + + /** + * Calculate the Check Digit for an IBAN code. + *

+ * Note: The check digit is the third and fourth + * characters and is set to the value "00". + * + * @param code The code to calculate the Check Digit for + * @return The calculated Check Digit as 2 numeric decimal characters, e.g. "42" + * @throws CheckDigitException if an error occurs calculating + * the check digit for the specified code + */ + @Override + public String calculate(String code) throws CheckDigitException { + if (code == null || code.length() < MIN_CODE_LEN) { + throw new CheckDigitException("Invalid Code length=" + + (code == null ? 0 : code.length())); + } + code = code.substring(0, 2) + "00" + code.substring(4); // CHECKSTYLE IGNORE MagicNumber + int modulusResult = calculateModulus(code); + int charValue = (98 - modulusResult); // CHECKSTYLE IGNORE MagicNumber + String checkDigit = Integer.toString(charValue); + return (charValue > 9 ? checkDigit : "0" + checkDigit); // CHECKSTYLE IGNORE MagicNumber + } + + /** + * Calculate the modulus for a code. + * + * @param code The code to calculate the modulus for. + * @return The modulus value + * @throws CheckDigitException if an error occurs calculating the modulus + * for the specified code + */ + private int calculateModulus(String code) throws CheckDigitException { + String reformattedCode = code.substring(4) + code.substring(0, 4); // CHECKSTYLE IGNORE MagicNumber + long total = 0; + for (int i = 0; i < reformattedCode.length(); i++) { + int charValue = Character.getNumericValue(reformattedCode.charAt(i)); + if (charValue < 0 || charValue > MAX_ALPHANUMERIC_VALUE) { + throw new CheckDigitException("Invalid Character[" + + i + "] = '" + charValue + "'"); + } + total = (charValue > 9 ? total * 100 : total * 10) + charValue; // CHECKSTYLE IGNORE MagicNumber + if (total > MAX) { + total = total % MODULUS; + } + } + return (int)(total % MODULUS); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISBN10CheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISBN10CheckDigit.java new file mode 100644 index 000000000..b59934fc8 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISBN10CheckDigit.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +/** + * Modulus 11 ISBN-10 Check Digit calculation/validation. + *

+ * ISBN-10 Numbers are a numeric code except for the last (check) digit + * which can have a value of "X". + *

+ * Check digit calculation is based on modulus 11 with digits being weighted + * based by their position, from right to left with the first digit being weighted + * 1, the second 2 and so on. If the check digit is calculated as "10" it is converted + * to "X". + *

+ * N.B. From 1st January 2007 the book industry will start to use a new 13 digit + * ISBN number (rather than this 10 digit ISBN number) which uses the EAN-13 / UPC + * (see {@link EAN13CheckDigit}) standard. + *

+ * For further information see: + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class ISBN10CheckDigit extends ModulusCheckDigit { + + private static final long serialVersionUID = 8000855044504864964L; + + /** Singleton ISBN-10 Check Digit instance */ + public static final CheckDigit ISBN10_CHECK_DIGIT = new ISBN10CheckDigit(); + + /** + * Construct a modulus 11 Check Digit routine for ISBN-10. + */ + public ISBN10CheckDigit() { + super(11); // CHECKSTYLE IGNORE MagicNumber + } + + /** + * Calculates the weighted value of a charcter in the + * code at a specified position. + * + *

For ISBN-10 (from right to left) digits are weighted + * by their position.

+ * + * @param charValue The numeric value of the character. + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The positionof the character in the code, counting from right to left + * @return The weighted value of the character. + */ + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) { + return charValue * rightPos; + } + + /** + *

Convert a character at a specified position to an + * integer value.

+ * + *

Character 'X' check digit converted to 10.

+ * + * @param character The character to convert. + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The position of the character in the code, counting from right to left + * @return The integer value of the character. + * @throws CheckDigitException if an error occurs. + */ + @Override + protected int toInt(char character, int leftPos, int rightPos) + throws CheckDigitException { + if (rightPos == 1 && character == 'X') { + return 10; // CHECKSTYLE IGNORE MagicNumber + } + return super.toInt(character, leftPos, rightPos); + } + + /** + *

Convert an integer value to a character at a specified position.

+ * + *

Value '10' for position 1 (check digit) converted to 'X'.

+ * + * @param charValue The integer value of the character. + * @return The converted character. + * @throws CheckDigitException if an error occurs. + */ + @Override + protected String toCheckDigit(int charValue) + throws CheckDigitException { + if (charValue == 10) { // CHECKSTYLE IGNORE MagicNumber + return "X"; + } + return super.toCheckDigit(charValue); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISBNCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISBNCheckDigit.java new file mode 100644 index 000000000..02f0efd81 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISBNCheckDigit.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.validator.routines.checkdigit; + +import java.io.Serializable; + +/** + * Combined ISBN-10 / ISBN-13 Check Digit calculation/validation. + *

+ * This implementation validates/calculates ISBN check digits + * based on the length of the code passed to it - delegating + * either to the {@link ISBNCheckDigit#ISBN10_CHECK_DIGIT} or the + * {@link ISBNCheckDigit#ISBN13_CHECK_DIGIT} routines to perform the actual + * validation/calculation. + *

+ * N.B. From 1st January 2007 the book industry will start to use a new 13 digit + * ISBN number (rather than this 10 digit ISBN number) which uses the EAN-13 / UPC + * standard. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class ISBNCheckDigit implements CheckDigit, Serializable { + + private static final long serialVersionUID = 1391849166205184558L; + + /** Singleton ISBN-10 Check Digit instance */ + public static final CheckDigit ISBN10_CHECK_DIGIT = ISBN10CheckDigit.ISBN10_CHECK_DIGIT; + + /** Singleton ISBN-13 Check Digit instance */ + public static final CheckDigit ISBN13_CHECK_DIGIT = EAN13CheckDigit.EAN13_CHECK_DIGIT; + + /** Singleton combined ISBN-10 / ISBN-13 Check Digit instance */ + public static final CheckDigit ISBN_CHECK_DIGIT = new ISBNCheckDigit(); + + /** + * Calculate an ISBN-10 or ISBN-13 check digit, depending + * on the length of the code. + *

+ * If the length of the code is 9, it is treated as an ISBN-10 + * code or if the length of the code is 12, it is treated as an ISBN-13 + * code. + * + * @param code The ISBN code to validate (should have a length of + * 9 or 12) + * @return The ISBN-10 check digit if the length is 9 or an ISBN-13 + * check digit if the length is 12. + * @throws CheckDigitException if the code is missing, or an invalid + * length (i.e. not 9 or 12) or if there is an error calculating the + * check digit. + */ + @Override + public String calculate(String code) throws CheckDigitException { + if (code == null || code.length() == 0) { + throw new CheckDigitException("ISBN Code is missing"); + } else if (code.length() == 9) { // CHECKSTYLE IGNORE MagicNumber + return ISBN10_CHECK_DIGIT.calculate(code); + } else if (code.length() == 12) { // CHECKSTYLE IGNORE MagicNumber + return ISBN13_CHECK_DIGIT.calculate(code); + } else { + throw new CheckDigitException("Invalid ISBN Length = " + code.length()); + } + } + + /** + *

Validate an ISBN-10 or ISBN-13 check digit, depending + * on the length of the code.

+ *

+ * If the length of the code is 10, it is treated as an ISBN-10 + * code or ff the length of the code is 13, it is treated as an ISBN-13 + * code. + * + * @param code The ISBN code to validate (should have a length of + * 10 or 13) + * @return true if the code has a length of 10 and is + * a valid ISBN-10 check digit or the code has a length of 13 and is + * a valid ISBN-13 check digit - otherwise false. + */ + @Override + public boolean isValid(String code) { + if (code == null) { + return false; + } else if (code.length() == 10) { // CHECKSTYLE IGNORE MagicNumber + return ISBN10_CHECK_DIGIT.isValid(code); + } else if (code.length() == 13) { // CHECKSTYLE IGNORE MagicNumber + return ISBN13_CHECK_DIGIT.isValid(code); + } else { + return false; + } + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISINCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISINCheckDigit.java new file mode 100644 index 000000000..357725c7d --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISINCheckDigit.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +/** + * Modulus 10 ISIN (International Securities Identifying Number) Check Digit calculation/validation. + * + *

+ * ISIN Numbers are 12 character alphanumeric codes used + * to identify Securities. + *

+ * + *

+ * Check digit calculation uses the Modulus 10 Double Add Double technique + * with every second digit being weighted by 2. Alphabetic characters are + * converted to numbers by their position in the alphabet starting with A being 10. + * Weighted numbers greater than ten are treated as two separate numbers. + *

+ * + *

+ * See Wikipedia - ISIN + * for more details. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class ISINCheckDigit extends ModulusCheckDigit { + + private static final long serialVersionUID = -1239211208101323599L; + + private static final int MAX_ALPHANUMERIC_VALUE = 35; // Character.getNumericValue('Z') + + /** Singleton ISIN Check Digit instance */ + public static final CheckDigit ISIN_CHECK_DIGIT = new ISINCheckDigit(); + + /** weighting given to digits depending on their right position */ + private static final int[] POSITION_WEIGHT = new int[] {2, 1}; + + /** + * Construct an ISIN Indetifier Check Digit routine. + */ + public ISINCheckDigit() { + super(10); // CHECKSTYLE IGNORE MagicNumber + } + + /** + * Calculate the modulus for an ISIN code. + * + * @param code The code to calculate the modulus for. + * @param includesCheckDigit Whether the code includes the Check Digit or not. + * @return The modulus value + * @throws CheckDigitException if an error occurs calculating the modulus + * for the specified code + */ + @Override + protected int calculateModulus(String code, boolean includesCheckDigit) throws CheckDigitException { + StringBuilder transformed = new StringBuilder(code.length() * 2); + if (includesCheckDigit) { + char checkDigit = code.charAt(code.length()-1); // fetch the last character + if (!Character.isDigit(checkDigit)){ + throw new CheckDigitException("Invalid checkdigit["+ checkDigit+ "] in " + code); + } + } + for (int i = 0; i < code.length(); i++) { + int charValue = Character.getNumericValue(code.charAt(i)); + if (charValue < 0 || charValue > MAX_ALPHANUMERIC_VALUE) { + throw new CheckDigitException("Invalid Character[" + + (i + 1) + "] = '" + charValue + "'"); + } + // this converts alphanumerics to two digits + // so there is no need to overload toInt() + transformed.append(charValue); + } + return super.calculateModulus(transformed.toString(), includesCheckDigit); + } + + /** + *

Calculates the weighted value of a charcter in the + * code at a specified position.

+ * + *

For Luhn (from right to left) odd digits are weighted + * with a factor of one and even digits with a factor + * of two. Weighted values > 9, have 9 subtracted

+ * + * @param charValue The numeric value of the character. + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The position of the character in the code, counting from right to left + * @return The weighted value of the character. + */ + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) { + int weight = POSITION_WEIGHT[rightPos % 2]; + int weightedValue = (charValue * weight); + return ModulusCheckDigit.sumDigits(weightedValue); + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISSNCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISSNCheckDigit.java new file mode 100644 index 000000000..a8c274176 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISSNCheckDigit.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +/** + * International Standard Serial Number (ISSN) + * is an eight-digit serial number used to + * uniquely identify a serial publication. + *
 
+ * The format is:
+ * 
+ * ISSN dddd-dddC
+ * where:
+ * d = decimal digit (0-9)
+ * C = checksum (0-9 or X)
+ * 
+ * The checksum is formed by adding the first 7 digits multiplied by
+ * the position in the entire number (counting from the right).
+ * For example, abcd-efg would be 8a + 7b + 6c + 5d + 4e +3f +2g.
+ * The check digit is modulus 11, where the value 10 is represented by 'X'
+ * For example:
+ * ISSN 0317-8471
+ * ISSN 1050-124X
+ * 
+ *

+ * Note: This class expects the input to be numeric only, + * with all formatting removed. + * For example: + *

+ * 03178471
+ * 1050124X
+ * 
+ * @since 1.5.0 + */ +public final class ISSNCheckDigit extends ModulusCheckDigit { + + + private static final long serialVersionUID = 1L; + + /** Singleton ISSN Check Digit instance */ + public static final CheckDigit ISSN_CHECK_DIGIT = new ISSNCheckDigit(); + + /** + * Creates the instance using a checkdigit modulus of 11 + */ + public ISSNCheckDigit() { + super(11); // CHECKSTYLE IGNORE MagicNumber + } + + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) throws CheckDigitException { + return charValue * (9 - leftPos); // CHECKSTYLE IGNORE MagicNumber + } + + @Override + protected String toCheckDigit(int charValue) throws CheckDigitException { + if (charValue == 10) { // CHECKSTYLE IGNORE MagicNumber + return "X"; + } + return super.toCheckDigit(charValue); + } + + @Override + protected int toInt(char character, int leftPos, int rightPos) + throws CheckDigitException { + if (rightPos == 1 && character == 'X') { + return 10; // CHECKSTYLE IGNORE MagicNumber + } + return super.toInt(character, leftPos, rightPos); + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/LuhnCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/LuhnCheckDigit.java new file mode 100644 index 000000000..4eb1f6dc8 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/LuhnCheckDigit.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.validator.routines.checkdigit; + +/** + * Modulus 10 Luhn Check Digit calculation/validation. + * + * Luhn check digits are used, for example, by: + * + * Check digit calculation is based on modulus 10 with digits in + * an odd position (from right to left) being weighted 1 and even + * position digits being weighted 2 (weighted values greater than 9 have 9 subtracted). + * + *

+ * See Wikipedia + * for more details. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class LuhnCheckDigit extends ModulusCheckDigit { + + private static final long serialVersionUID = -2976900113942875999L; + + /** Singleton Luhn Check Digit instance */ + public static final CheckDigit LUHN_CHECK_DIGIT = new LuhnCheckDigit(); + + /** weighting given to digits depending on their right position */ + private static final int[] POSITION_WEIGHT = new int[] {2, 1}; + + /** + * Construct a modulus 10 Luhn Check Digit routine. + */ + public LuhnCheckDigit() { + super(10); // CHECKSTYLE IGNORE MagicNumber + } + + /** + *

Calculates the weighted value of a charcter in the + * code at a specified position.

+ * + *

For Luhn (from right to left) odd digits are weighted + * with a factor of one and even digits with a factor + * of two. Weighted values > 9, have 9 subtracted

+ * + * @param charValue The numeric value of the character. + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The positionof the character in the code, counting from right to left + * @return The weighted value of the character. + */ + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) { + int weight = POSITION_WEIGHT[rightPos % 2]; // CHECKSTYLE IGNORE MagicNumber + int weightedValue = charValue * weight; + return weightedValue > 9 ? (weightedValue - 9) : weightedValue; // CHECKSTYLE IGNORE MagicNumber + } +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ModulusCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ModulusCheckDigit.java new file mode 100644 index 000000000..ab73e7bc8 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ModulusCheckDigit.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +import java.io.Serializable; + +/** + * Abstract Modulus Check digit calculation/validation. + *

+ * Provides a base class for building modulus Check + * Digit routines. + *

+ * This implementation only handles single-digit numeric codes, such as + * EAN-13. For alphanumeric codes such as EAN-128 you + * will need to implement/override the toInt() and + * toChar() methods. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public abstract class ModulusCheckDigit implements CheckDigit, Serializable { + + private static final long serialVersionUID = 2948962251251528941L; + + // N.B. The modulus can be > 10 provided that the implementing class overrides toCheckDigit and toInt + // (for example as in ISBN10CheckDigit) + private final int modulus; + + /** + * Construct a {@link CheckDigit} routine for a specified modulus. + * + * @param modulus The modulus value to use for the check digit calculation + */ + public ModulusCheckDigit(int modulus) { + this.modulus = modulus; + } + + /** + * Return the modulus value this check digit routine is based on. + * + * @return The modulus value this check digit routine is based on + */ + public int getModulus() { + return modulus; + } + + /** + * Validate a modulus check digit for a code. + * + * @param code The code to validate + * @return true if the check digit is valid, otherwise + * false + */ + @Override + public boolean isValid(String code) { + if (code == null || code.length() == 0) { + return false; + } + try { + int modulusResult = calculateModulus(code, true); + return (modulusResult == 0); + } catch (CheckDigitException ex) { + return false; + } + } + + /** + * Calculate a modulus Check Digit for a code which does not yet have one. + * + * @param code The code for which to calculate the Check Digit; + * the check digit should not be included + * @return The calculated Check Digit + * @throws CheckDigitException if an error occurs calculating the check digit + */ + @Override + public String calculate(String code) throws CheckDigitException { + if (code == null || code.length() == 0) { + throw new CheckDigitException("Code is missing"); + } + int modulusResult = calculateModulus(code, false); + int charValue = (modulus - modulusResult) % modulus; + return toCheckDigit(charValue); + } + + /** + * Calculate the modulus for a code. + * + * @param code The code to calculate the modulus for. + * @param includesCheckDigit Whether the code includes the Check Digit or not. + * @return The modulus value + * @throws CheckDigitException if an error occurs calculating the modulus + * for the specified code + */ + protected int calculateModulus(String code, boolean includesCheckDigit) throws CheckDigitException { + int total = 0; + for (int i = 0; i < code.length(); i++) { + int lth = code.length() + (includesCheckDigit ? 0 : 1); + int leftPos = i + 1; + int rightPos = lth - i; + int charValue = toInt(code.charAt(i), leftPos, rightPos); + total += weightedValue(charValue, leftPos, rightPos); + } + if (total == 0) { + throw new CheckDigitException("Invalid code, sum is zero"); + } + return total % modulus; + } + + /** + * Calculates the weighted value of a character in the + * code at a specified position. + *

+ * Some modulus routines weight the value of a character + * depending on its position in the code (e.g. ISBN-10), while + * others use different weighting factors for odd/even positions + * (e.g. EAN or Luhn). Implement the appropriate mechanism + * required by overriding this method. + * + * @param charValue The numeric value of the character + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The positionof the character in the code, counting from right to left + * @return The weighted value of the character + * @throws CheckDigitException if an error occurs calculating + * the weighted value + */ + protected abstract int weightedValue(int charValue, int leftPos, int rightPos) + throws CheckDigitException; + + + /** + * Convert a character at a specified position to an integer value. + *

+ * Note: this implementation only handlers numeric values + * For non-numeric characters, override this method to provide + * character-->integer conversion. + * + * @param character The character to convert + * @param leftPos The position of the character in the code, counting from left to right (for identifiying the position in the string) + * @param rightPos The position of the character in the code, counting from right to left (not used here) + * @return The integer value of the character + * @throws CheckDigitException if character is non-numeric + */ + protected int toInt(char character, int leftPos, int rightPos) + throws CheckDigitException { + if (Character.isDigit(character)) { + return Character.getNumericValue(character); + } + throw new CheckDigitException("Invalid Character[" + + leftPos + "] = '" + character + "'"); + } + + /** + * Convert an integer value to a check digit. + *

+ * Note: this implementation only handles single-digit numeric values + * For non-numeric characters, override this method to provide + * integer-->character conversion. + * + * @param charValue The integer value of the character + * @return The converted character + * @throws CheckDigitException if integer character value + * doesn't represent a numeric character + */ + protected String toCheckDigit(int charValue) + throws CheckDigitException { + if (charValue >= 0 && charValue <= 9) { // CHECKSTYLE IGNORE MagicNumber + return Integer.toString(charValue); + } + throw new CheckDigitException("Invalid Check Digit Value =" + + + charValue); + } + + /** + * Add together the individual digits in a number. + * + * @param number The number whose digits are to be added + * @return The sum of the digits + */ + public static int sumDigits(int number) { + int total = 0; + int todo = number; + while (todo > 0) { + total += todo % 10; // CHECKSTYLE IGNORE MagicNumber + todo = todo / 10; // CHECKSTYLE IGNORE MagicNumber + } + return total; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ModulusTenCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ModulusTenCheckDigit.java new file mode 100644 index 000000000..50e261832 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ModulusTenCheckDigit.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +import java.util.Arrays; + +import org.apache.commons.validator.routines.CodeValidator; + +/** + * General Modulus 10 Check Digit calculation/validation. + * + *

How if Works

+ *

+ * This implementation calculates/validates the check digit in the following + * way: + *

    + *
  • Converting each character to an integer value using + * Character.getNumericValue(char) - negative integer values from + * that method are invalid.
  • + *
  • Calculating a weighted value by multiplying the character's + * integer value by a weighting factor. The weighting factor is + * selected from the configured postitionWeight array based on its + * position. The postitionWeight values are used either + * left-to-right (when useRightPos=false) or right-to-left (when + * useRightPos=true).
  • + *
  • If sumWeightedDigits=true, the weighted value is + * re-calculated by summing its digits.
  • + *
  • The weighted values of each character are totalled.
  • + *
  • The total modulo 10 will be zero for a code with a valid Check Digit.
  • + *
+ *

Limitations

+ *

+ * This implementation has the following limitations: + *

    + *
  • It assumes the last character in the code is the Check Digit and + * validates that it is a numeric character.
  • + *
  • The only limitation on valid characters are those that + * Character.getNumericValue(char) returns a positive value. If, + * for example, the code should only contain numbers, this implementation does + * not check that.
  • + *
  • There are no checks on code length.
  • + *
+ *

+ * Note: This implementation can be combined with the + * {@link CodeValidator} in order to ensure the length and characters are valid. + * + *

Example Usage

+ *

+ * This implementation was added after a number of Modulus 10 routines and these + * are shown re-implemented using this routine below: + * + *

+ * ABA Number Check Digit Routine (equivalent of + * {@link ABANumberCheckDigit}). Weighting factors are [1, 7, 3] + * applied from right to left. + * + *

+ * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 7, 3 }, true);
+ * 
+ * + *

+ * CUSIP Check Digit Routine (equivalent of {@link CUSIPCheckDigit}). + * Weighting factors are [1, 2] applied from right to left and the + * digits of the weighted value are summed. + * + *

+ * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 2 }, true, true);
+ * 
+ * + *

+ * EAN-13 / UPC Check Digit Routine (equivalent of + * {@link EAN13CheckDigit}). Weighting factors are [1, 3] applied + * from right to left. + * + *

+ * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 3 }, true);
+ * 
+ * + *

+ * Luhn Check Digit Routine (equivalent of {@link LuhnCheckDigit}). + * Weighting factors are [1, 2] applied from right to left and the + * digits of the weighted value are summed. + * + *

+ * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 2 }, true, true);
+ * 
+ * + *

+ * SEDOL Check Digit Routine (equivalent of {@link SedolCheckDigit}). + * Weighting factors are [1, 3, 1, 7, 3, 9, 1] applied from left to + * right. + * + *

+ * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 3, 1, 7, 3, 9, 1 });
+ * 
+ * + * @since Validator 1.6 + * @version $Revision: 1739356 $ + */ +public final class ModulusTenCheckDigit extends ModulusCheckDigit { + + private static final long serialVersionUID = -3752929983453368497L; + + private final int[] postitionWeight; + private final boolean useRightPos; + private final boolean sumWeightedDigits; + + /** + * Construct a modulus 10 Check Digit routine with the specified weighting + * from left to right. + * + * @param postitionWeight the weighted values to apply based on the + * character position + */ + public ModulusTenCheckDigit(int[] postitionWeight) { + this(postitionWeight, false, false); + } + + /** + * Construct a modulus 10 Check Digit routine with the specified weighting, + * indicating whether its from the left or right. + * + * @param postitionWeight the weighted values to apply based on the + * character position + * @param useRightPos true if use positionWeights from right to + * left + */ + public ModulusTenCheckDigit(int[] postitionWeight, boolean useRightPos) { + this(postitionWeight, useRightPos, false); + } + + /** + * Construct a modulus 10 Check Digit routine with the specified weighting, + * indicating whether its from the left or right and whether the weighted + * digits should be summed. + * + * @param postitionWeight the weighted values to apply based on the + * character position + * @param useRightPos true if use positionWeights from right to + * left + * @param sumWeightedDigits true if sum the digits of the + * weighted value + */ + public ModulusTenCheckDigit(int[] postitionWeight, boolean useRightPos, boolean sumWeightedDigits) { + super(10); // CHECKSTYLE IGNORE MagicNumber + this.postitionWeight = Arrays.copyOf(postitionWeight, postitionWeight.length); + this.useRightPos = useRightPos; + this.sumWeightedDigits = sumWeightedDigits; + } + + /** + * Validate a modulus check digit for a code. + *

+ * Note: assumes last digit is the check digit + * + * @param code The code to validate + * @return true if the check digit is valid, otherwise + * false + */ + @Override + public boolean isValid(String code) { + if (code == null || code.length() == 0) { + return false; + } + if (!Character.isDigit(code.charAt(code.length() - 1))) { + return false; + } + + return super.isValid(code); + } + + /** + * Convert a character at a specified position to an integer value. + *

+ * Note: this implementation only handlers values that + * Character.getNumericValue(char) returns a non-negative number. + * + * @param character The character to convert + * @param leftPos The position of the character in the code, counting from + * left to right (for identifying the position in the string) + * @param rightPos The position of the character in the code, counting from + * right to left (not used here) + * @return The integer value of the character + * @throws CheckDigitException if Character.getNumericValue(char) returns a + * negative number + */ + @Override + protected int toInt(char character, int leftPos, int rightPos) throws CheckDigitException { + int num = Character.getNumericValue(character); + if (num < 0) { + throw new CheckDigitException("Invalid Character[" + leftPos + "] = '" + character + "'"); + } + return num; + } + + /** + * Calculates the weighted value of a character in the code at a + * specified position. + * + * @param charValue The numeric value of the character. + * @param leftPos The position of the character in the code, counting from + * left to right + * @param rightPos The position of the character in the code, counting from + * right to left + * @return The weighted value of the character. + */ + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) { + int pos = useRightPos ? rightPos : leftPos; + int weight = postitionWeight[(pos - 1) % postitionWeight.length]; + int weightedValue = charValue * weight; + if (sumWeightedDigits) { + weightedValue = ModulusCheckDigit.sumDigits(weightedValue); + } + return weightedValue; + } + + /** + * Return a string representation of this implementation. + * + * @return a string representation + */ + @Override + public String toString() { + return getClass().getSimpleName() + "[postitionWeight=" + Arrays.toString(postitionWeight) + ", useRightPos=" + + useRightPos + ", sumWeightedDigits=" + sumWeightedDigits + "]"; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/SedolCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/SedolCheckDigit.java new file mode 100644 index 000000000..788aafd4d --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/SedolCheckDigit.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +/** + * Modulus 10 SEDOL (UK Securities) Check Digit calculation/validation. + * + *

+ * SEDOL Numbers are 7 character alphanumeric codes used + * to identify UK Securities (SEDOL stands for Stock Exchange Daily Official List). + *

+ *

+ * Check digit calculation is based on modulus 10 with digits being weighted + * based on their position, from left to right, as follows: + *

+ *

+ *      position:  1  2  3  4  5  6  7
+ *     weighting:  1  3  1  7  3  9  1
+ * 
+ *

+ * See Wikipedia - SEDOL + * for more details. + *

+ * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class SedolCheckDigit extends ModulusCheckDigit { + + private static final long serialVersionUID = -8976881621148878443L; + + private static final int MAX_ALPHANUMERIC_VALUE = 35; // Character.getNumericValue('Z') + + /** Singleton SEDOL check digit instance */ + public static final CheckDigit SEDOL_CHECK_DIGIT = new SedolCheckDigit(); + + /** weighting given to digits depending on their right position */ + private static final int[] POSITION_WEIGHT = new int[] {1, 3, 1, 7, 3, 9, 1}; + + /** + * Construct a modulus 11 Check Digit routine for ISBN-10. + */ + public SedolCheckDigit() { + super(10); // CHECKSTYLE IGNORE MagicNumber + } + + /** + * Calculate the modulus for an SEDOL code. + * + * @param code The code to calculate the modulus for. + * @param includesCheckDigit Whether the code includes the Check Digit or not. + * @return The modulus value + * @throws CheckDigitException if an error occurs calculating the modulus + * for the specified code + */ + @Override + protected int calculateModulus(String code, boolean includesCheckDigit) throws CheckDigitException { + if (code.length() > POSITION_WEIGHT.length) { + throw new CheckDigitException("Invalid Code Length = " + code.length()); + } + return super.calculateModulus(code, includesCheckDigit); + } + + /** + * Calculates the weighted value of a charcter in the + * code at a specified position. + * + * @param charValue The numeric value of the character. + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The positionof the character in the code, counting from right to left + * @return The weighted value of the character. + */ + @Override + protected int weightedValue(int charValue, int leftPos, int rightPos) { + return charValue * POSITION_WEIGHT[leftPos - 1]; + } + + /** + * Convert a character at a specified position to an integer value. + * + * @param character The character to convert + * @param leftPos The position of the character in the code, counting from left to right + * @param rightPos The positionof the character in the code, counting from right to left + * @return The integer value of the character + * @throws CheckDigitException if character is not alphanumeric + */ + @Override + protected int toInt(char character, int leftPos, int rightPos) + throws CheckDigitException { + int charValue = Character.getNumericValue(character); + // the check digit is only allowed to reach 9 + final int charMax = rightPos == 1 ? 9 : MAX_ALPHANUMERIC_VALUE; // CHECKSTYLE IGNORE MagicNumber + if (charValue < 0 || charValue > charMax) { + throw new CheckDigitException("Invalid Character[" + + leftPos + "," + rightPos + "] = '" + charValue + "' out of range 0 to " + charMax); + } + return charValue; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/VerhoeffCheckDigit.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/VerhoeffCheckDigit.java new file mode 100644 index 000000000..b4e5c6650 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/VerhoeffCheckDigit.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.validator.routines.checkdigit; + +import java.io.Serializable; + +/** + * Verhoeff (Dihedral) Check Digit calculation/validation. + *

+ * Check digit calculation for numeric codes using a + * Dihedral Group + * of order 10. + *

+ * See Wikipedia + * - Verhoeff algorithm for more details. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public final class VerhoeffCheckDigit implements CheckDigit, Serializable { + + private static final long serialVersionUID = 4138993995483695178L; + + /** Singleton Verhoeff Check Digit instance */ + public static final CheckDigit VERHOEFF_CHECK_DIGIT = new VerhoeffCheckDigit(); + + /** D - multiplication table */ + private static final int[][] D_TABLE = new int[][] { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + {1, 2, 3, 4, 0, 6, 7, 8, 9, 5}, + {2, 3, 4, 0, 1, 7, 8, 9, 5, 6}, + {3, 4, 0, 1, 2, 8, 9, 5, 6, 7}, + {4, 0, 1, 2, 3, 9, 5, 6, 7, 8}, + {5, 9, 8, 7, 6, 0, 4, 3, 2, 1}, + {6, 5, 9, 8, 7, 1, 0, 4, 3, 2}, + {7, 6, 5, 9, 8, 2, 1, 0, 4, 3}, + {8, 7, 6, 5, 9, 3, 2, 1, 0, 4}, + {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}}; + + /** P - permutation table */ + private static final int[][] P_TABLE = new int[][] { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + {1, 5, 7, 6, 2, 8, 3, 0, 9, 4}, + {5, 8, 0, 3, 7, 9, 6, 1, 4, 2}, + {8, 9, 1, 6, 0, 4, 3, 5, 2, 7}, + {9, 4, 5, 3, 1, 2, 6, 8, 7, 0}, + {4, 2, 8, 6, 5, 7, 3, 9, 0, 1}, + {2, 7, 9, 3, 8, 0, 6, 4, 1, 5}, + {7, 0, 4, 6, 9, 1, 3, 2, 5, 8}}; + + /** inv: inverse table */ + private static final int[] INV_TABLE = new int[] + {0, 4, 3, 2, 1, 5, 6, 7, 8, 9}; + + + /** + * Validate the Verhoeff Check Digit for a code. + * + * @param code The code to validate + * @return true if the check digit is valid, + * otherwise false + */ + @Override + public boolean isValid(String code) { + if (code == null || code.length() == 0) { + return false; + } + try { + return (calculateChecksum(code, true) == 0); + } catch (CheckDigitException e) { + return false; + } + } + + /** + * Calculate a Verhoeff Check Digit for a code. + * + * @param code The code to calculate the Check Digit for + * @return The calculated Check Digit + * @throws CheckDigitException if an error occurs calculating + * the check digit for the specified code + */ + @Override + public String calculate(String code) throws CheckDigitException { + if (code == null || code.length() == 0) { + throw new CheckDigitException("Code is missing"); + } + int checksum = calculateChecksum(code, false); + return Integer.toString(INV_TABLE[checksum]); + } + + /** + * Calculate the checksum. + * + * @param code The code to calculate the checksum for. + * @param includesCheckDigit Whether the code includes the Check Digit or not. + * @return The checksum value + * @throws CheckDigitException if the code contains an invalid character (i.e. not numeric) + */ + private int calculateChecksum(String code, boolean includesCheckDigit) throws CheckDigitException { + int checksum = 0; + for (int i = 0; i < code.length(); i++) { + int idx = code.length() - (i + 1); + int num = Character.getNumericValue(code.charAt(idx)); + if (num < 0 || num > 9) { // CHECKSTYLE IGNORE MagicNumber + throw new CheckDigitException("Invalid Character[" + + i + "] = '" + ((int)code.charAt(idx)) + "'"); + } + int pos = includesCheckDigit ? i : i + 1; + checksum = D_TABLE[checksum][P_TABLE[pos % 8][num]]; // CHECKSTYLE IGNORE MagicNumber + } + return checksum; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/package.html b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/package.html new file mode 100644 index 000000000..ffaf91bfc --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/package.html @@ -0,0 +1,29 @@ + + + +Package Documentation for org.apache.commons.validator.routines.checkdigit Package + + + +

This package contains Check Digit validation/calculation routines.

+

Note that these do not validate the input for length or syntax.
+ Such validation is performed by the org.apache.commons.validator.routines.XYZValidator classes. +

+ + + diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/package.html b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/package.html new file mode 100644 index 000000000..bfdecda5d --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/package.html @@ -0,0 +1,835 @@ + + + +Package Documentation for org.apache.commons.validator.routines Package + + +

This package contains independant validation routines.

+

Table of Contents

+ + + + +

1. Overview

+

+ Commons Validator serves two purposes: +

+
    +
  • To provide standard, independent validation routines/functions.
  • +
  • To provide a mini framework for Validation.
  • +
+

+ This package has been created, since version 1.3.0, in an attempt to clearly + separate these two concerns and is the location for the standard, independent + validation routines/functions in Commons Validator. +

+ +

+ The contents of this package have no dependencies on the framework aspect of + Commons Validator and can be used on their own. +

+ + +

2. Date and Time Validators

+ + +

2.1 Overview

+

+ The date and time validators either validate according to a specified format + or use a standard format for a specified Locale. +

+
    +
  • Date Validator - validates dates + converting to a java.util.Date type.
  • +
  • Calendar Validator - validates dates + converting to a java.util.Calendar type.
  • +
  • Time Validator - validates times + converting to a java.util.Calendar type.
  • +
+ + +

2.2 Validating a Date Value

+

+ You can either use one of the isValid() methods to just determine + if a date is valid, or use one of the validate() methods to + validate a date and convert it to a java.util.Date... +

+
+      // Get the Date validator
+      DateValidator validator = DateValidator.getInstance();
+
+      // Validate/Convert the date
+      Date fooDate = validator.validate(fooString, "dd/MM/yyyy");
+      if (fooDate == null) {
+          // error...not a valid date
+          return;
+      }
+
+ +

The following methods are provided to validate a date/time (return a boolean result): +

+
    +
  • isValid(value)
  • +
  • isValid(value, pattern)
  • +
  • isValid(value, Locale)
  • +
  • isValid(value, pattern, Locale)
  • +
+

The following methods are provided to validate a date/time and convert it to either a + java.util.Date or java.util.Calendar: +

+
    +
  • validate(value)
  • +
  • validate(value, pattern)
  • +
  • validate(value, Locale)
  • +
  • validate(value, pattern, Locale)
  • +
+ + +

2.3 Formatting

+

+ Formatting and validating are two sides of the same coin. Typically + input values which are converted from Strings according to a + specified format also have to be rendered for output in + the same format. These validators provide the mechanism for formatting from + date/time objects to Strings. The following methods are provided to format + date/time values as Strings: +

+
    +
  • format(date/calendar)
  • +
  • format(date/calendar, pattern)
  • +
  • format(date/calendar, Locale)
  • +
  • format(date/calendar, pattern, Locale)
  • +
+ + +

2.4 Time Zones

+

+ If the date being parsed relates to a different time zone than the + system default, you can specify the TimeZone to use when + validating/converting: +

+
+      // Get the GMT time zone
+      TimeZone GMT = TimeZone.getInstance("GMT");
+
+      // Validate/Convert the date using GMT
+      Date fooDate = validator.validate(fooString, "dd/MM/yyyy", GMT);
+
+ +

The following Time Zone flavours of the Validation/Conversion methods + are provided:

+
    +
  • validate(value, TimeZone)
  • +
  • validate(value, pattern, TimeZone)
  • +
  • validate(value, Locale, TimeZone)
  • +
  • validate(value, pattern, Locale, TimeZone)
  • +
+ + +

2.5 Comparing Dates and Times

+

+ As well as validating that a value is a valid date or time, these validators + also provide date comparison functions. The DateValidator + and CalendarValidator provide functions for comparing years, + quarters, months, weeks and dates and the TimeValidator provides + functions for comparing hours, minutes, seconds and milliseconds. + For example, to check that a date is in the current month, you could use + the compareMonths() method, which compares the year and month + components of a date: +

+
+      // Check if the date is in the current month 
+      int compare = validator.compareMonths(fooDate, new Date(), null); 
+      if (compare == 0) { 
+          // do current month processing
+          return;
+      }
+
+      // Check if the date is in the previous quarter
+      compare = validator.compareQuarters(fooDate, new Date(), null);
+      if (compare < 0) {
+          // do previous quarter processing
+          return;
+      }
+
+      // Check if the date is in the next year
+      compare = validator.compareYears(fooDate, new Date(), null);
+      if (compare > 0) {
+          // do next year processing
+          return;
+      }
+
+ + + +

3 Numeric Validators

+ + +

3.1 Overview

+

+ The numeric validators either validate according to a specified format + or use a standard format for a specified Locale or use + a custom format for a specified Locale. +

+ + + +

3.2 Validating a Numeric Value

+

+ You can either use one of the isValid() methods to just determine + if a number is valid, or use one of the validate() methods to + validate a number and convert it to an appropriate type. +

+

+ The following example validates an integer against a custom pattern + for the German locale. Please note the format is specified using + the standard symbols for java.text.DecimalFormat so although + the decimal separator is indicated as a period (".") in the format, the + validator will check using the German decimal separator - which is a comma (","). +

+
+      // Get the Integer validator
+      IntegerValidator validator = IntegerValidator.getInstance();
+
+      // Validate/Convert the number
+      Integer fooInteger = validator.validate(fooString, "#,##0.00", Locale.GERMAN);
+      if (fooInteger == null) {
+          // error...not a valid Integer
+          return;
+      }
+
+

The following methods are provided to validate a number (return a boolean result):

+
    +
  • isValid(value)
  • +
  • isValid(value, pattern)
  • +
  • isValid(value, Locale)
  • +
  • isValid(value, pattern, Locale)
  • +
+ +

The following methods are provided to validate a number and convert it one of + the java.lang.Number implementations:

+
    +
  • validate(value)
  • +
  • validate(value, pattern)
  • +
  • validate(value, Locale)
  • +
  • validate(value, pattern, Locale)
  • +
+ + +

3.3 Formatting

+

+ Formatting and validating are two sides of the same coin. Typically + input values which are converted from Strings according to a + specified format also have to be rendered for output in + the same format. These validators provide the mechanism for formatting from + numeric objects to Strings. The following methods are provided to format + numeric values as Strings: +

+
    +
  • format(number)
  • +
  • format(number, pattern)
  • +
  • format(number, Locale)
  • +
  • format(number, pattern, Locale)
  • +
+ + +

3.4 Comparing Numbers

+

+ As well as validating that a value is a valid number, these validators + also provide functions for validating the minimum, maximum + and range of a value. +

+
+      // Check the number is between 25 and 75
+      if (validator.isInRange(fooInteger, 25, 75) {
+          // valid...in the specified range
+          return;
+      }
+
+ + +

3.5 Currency Validation

+

+ A default Currency Validator + implementation is provided, although all the numeric validators + support currency validation. The default implementation converts + currency amounts to a java.math.BigDecimal and additionally + it provides lenient currency symbol validation. That is, currency + amounts are valid with or without the currency symbol. +

+
+      BigDecimalValidator validator = CurrencyValidator.getInstance();
+
+      BigDecimal fooAmount = validator.validate("$12,500.00", Locale.US);
+      if (fooAmount == null) {
+          // error...not a valid currency amount
+          return;
+      }
+
+      // Check the amount is a minimum of $1,000
+      if (validator.minValue(fooAmount, 1000) {
+          // valid...in the specified range
+          return;
+      }
+
+ +

+ If, for example, you want to use the Integer + Validator to validate a currency, then you can simply create a + new instance with the appropriate format style. Note that + the other validators do not support the lenient currency symbol + validation. +

+ +
+      IntegerValidator validator = 
+          new IntegerValidator(true, IntegerValidator.CURRENCY_FORMAT);
+
+      String pattern = "#,###" + '\u00A4' + '\u00A4';  // Use international symbol
+
+      Integer fooAmount = validator.validate("10.100EUR", pattern, Locale.GERMAN);
+      if (fooAmount == null) {
+          // error...not a valid currency amount
+          return;
+      }
+
+ + +

3.6 Percent Validation

+

+ A default Percent Validator + implementation is provided, although the Float, + Double and BigDecimal validators also support + percent validation. The default implementation converts + percent amounts to a java.math.BigDecimal and additionally + it provides lenient percent symbol validation. That is, percent + amounts are valid with or without the percent symbol. +

+ +
+      BigDecimalValidator validator = PercentValidator.getInstance();
+
+      BigDecimal fooPercent = validator.validate("20%", Locale.US);
+      if (fooPercent == null) {
+          // error...not a valid percent
+          return;
+      }
+
+      // Check the percent is between 10% and 90%
+      if (validator.isInRange(fooPercent, 0.1, 0.9) {
+          // valid...in the specified range
+          return;
+      }
+
+ +

+ If, for example, you want to use the Float + Validator to validate a percent, then you can simply create a + new instance with the appropriate format style. Note that + the other validators do not support the lenient percent symbol + validation. +

+ +
+      FloatValidator validator = 
+          new FloatValidator(true, FloatValidator.PERCENT_FORMAT);
+
+      Float fooPercent = validator.validate("20%", "###%");
+      if (fooPercent == null) {
+          // error...not a valid percent
+          return;
+      }
+
+
+

+ Note: in theory the other numeric validators besides + Float, Double and BigDecimal (i.e. Byte, + Short, Integer, Long and BigInteger) + also support percent validation. However, since they don't allow fractions + they will only work with percentages greater than 100%. +

+ + +

4. Other Validators

+ + +

4.1 Overview

+

+ This section lists other available validators. +

+ + + +

4.2 Regular Expression Validation

+

+ Regular expression validation can be done either by using the static + methods provied by RegexValidator or + by creating a new instance, which caches and re-uses compiled Patterns. +

+
    +
  • Method Flavours - three flavours of validation metods are provided:
  • +
  • +
      +
    • isValid() methods return true/false to indicate + whether validation was successful.
    • +
    • validate() methods return a String + value of the matched groups aggregated together or + null if invalid.
    • +
    • match() methods return a String array + of the matched groups or null if invalid.
    • +
    +
  • +
  • Case Sensitivity - matching can be done in either a case + sensitive or case in-sensitive way.
  • +
  • Multiple Expressions - instances of the + RegexValidator + can be created to either match against a single regular expression + or set (String array) of regular expressions.
  • +
+

+ Below is an example of using one of the static methods to validate, + matching in a case insensitive manner and returning a String + of the matched groups (which doesn't include the hyphen). +

+
+      // set up the parameters
+      boolean caseSensitive   = false;
+      String regex            = "^([A-Z]*)(?:\\-)([A-Z]*)$";
+
+      // validate - result should be a String of value "abcdef"
+      String result = RegexValidator.validate("abc-def", regex, caseSensitive);
+
+
+ +

The following static methods are provided for regular expression validation: +

+
    +
  • isValid(value, regex)
  • +
  • isValid(value, regex, caseSensitive)
  • +
  • validate(value, regex)
  • +
  • validate(value, regex, caseSensitive)
  • +
  • match(value, regex)
  • +
  • match(value, regex, caseSensitive)
  • +
+

+ Below is an example of creating an instance of + RegexValidator matching in a case insensitive + manner against a set of regular expressions: +

+
+      // set up the parameters
+      boolean caseSensitive = false;
+      String regex1   = "^([A-Z]*)(?:\\-)([A-Z]*)*$"
+      String regex2   = "^([A-Z]*)$";
+      String[] regexs = new String[] {regex1, regex1};
+
+      // Create the validator
+      RegexValidator validator = new RegexValidator(regexs, caseSensitive);
+
+      // Validate true/false
+      boolean valid = validator.isValid("abc-def");
+
+      // Validate and return a String
+      String result = validator.validate("abc-def");
+
+      // Validate and return a String[]
+      String[] groups = validator.match("abc-def");
+
+
+

See the + RegexValidator javadoc for a full list + of the available constructors. +

+ + +

4.3 Check Digit validation/calculation

+

+ CheckDigit defines a new + type for the calculation and validation of check digits with the + following methods: +

+
    +
  • isValid(code) - validates the check digit of a code, + returning true or false.
  • +
  • calculate(code) - calulates the check digit for a code + returning the check digit character.
  • +
+

+ The following implementations are provided: +

+ +

+ The following examples show validating the check digit of a code: +

+
+
+      // Luhn check digit validation
+      boolean valid = LuhnCheckDigit.INSTANCE.isValid(code);
+
+      // EAN / UPC / ISBN-13 check digit validation
+      boolean valid = EAN13CheckDigit.INSTANCE.isValid(code);
+
+      // ISBN-10 check digit validation
+      boolean valid = ISBNCheckDigit.ISBN10.isValid(code);
+      boolean valid = ISBN10CheckDigit.INSTANCE.isValid(code);
+
+      // ISBN-13 check digit validation
+      boolean valid = ISBNCheckDigit.ISBN13.isValid(code);
+
+      // ISBN-10 or ISBN-13 check digit validation
+      boolean valid = ISBNCheckDigit.ISBN.isValid(code);
+
+
+

+ The following examples show calulating the check digit of a code: +

+
+
+      // Luhn check digit validation
+      char checkdigit = LuhnCheckDigit.INSTANCE.calculate(code);
+
+      // EAN / UPC / ISBN-13 check digit validation
+      char checkdigit = EAN13CheckDigit.INSTANCE.calculate(code);
+
+      // ISBN-10 check digit validation
+      char checkdigit = ISBNCheckDigit.ISBN10.isValid(code);
+      char checkdigit = ISBN10CheckDigit.INSTANCE.calculate(code);
+
+      // ISBN-13 check digit validation
+      char checkdigit = ISBNCheckDigit.ISBN13.calculate(code);
+
+      // ISBN-10 or ISBN-13 check digit validation
+      char checkdigit = ISBNCheckDigit.ISBN.calculate(code);
+
+
+ + + +

4.4 General Code validation

+

+ CodeValidator provides a generic + implementation for validating codes. It performs the following + validations on a code: +

+
    +
  • Format - the format of the code is validated using + a regular expression (see RegexValidator).
  • +
  • Length - the minimum/maximum length of the code is + checked - after being parsed by the regular expression - with which + format characters can be removed with the use of + non-capturing groups.
  • +
  • Check Digit - a CheckDigit + routine checks that code's check digit is valid.
  • +
+

+ For example to create a validator to validate EAN-13 codes (numeric, + with a length of 13): +

+
+
+      // Create an EAN-13 code validator
+      CodeValidator validator = new CodeValidator("^[0-9]*$", 13, EAN13CheckDigit.INSTANCE);
+
+      // Validate an EAN-13 code
+      if (!validator.isValid(code)) {
+          ... // invalid
+      }
+
+
+ + +

4.5 ISBN validation

+

+ ISBNValidator provides ISBN-10 + and ISBN-13 validation and can optionally convert + ISBN-10 codes to ISBN-13. +

+
    +
  • ISBN-10 - validates using a + CodeValidator with the + ISBN10CheckDigit + routine.
  • +
  • +
      +
    • isValidISBN10(value) - returns a boolean
    • +
    • validateISBN10(value) - returns a reformatted ISBN-10 code
    • +
    +
  • +
  • ISBN-13 - validates using a + CodeValidator with the + EAN13CheckDigit + routine.
  • +
  • +
      +
    • isValidISBN13(value) - returns a boolean
    • +
    • validateISBN13(value) - returns a reformatted ISBN-13 code
    • +
    +
  • +
  • ISBN-10 and ISBN-13 - validates codes are either + valid ISBN-10 or valid ISBN-13 - optionally can convert ISBN-10 codes to ISBN-13.
  • +
  • +
      +
    • isValid(value) - returns a boolean
    • +
    • validate(value) - returns a reformatted ISBN code + (converts ISBN-10 to ISBN-13 if the convert option is true).
    • +
    +
  • +
+

+ For example to validate +

+
+
+      // Validate an ISBN-10 or ISBN-13 code
+      if (!ISBNValidator.getInstance().isValid(code)) {
+          ... // invalid
+      }
+
+      // Validate an ISBN-10 or ISBN-13 code (converting to ISBN-13)
+      String code = ISBNValidator.getInstance().validate(code);
+
+      // Validate an ISBN-10 or ISBN-13 code (not converting)
+      String code = ISBNValidator.getInstance(false).validate(code);
+
+
+ + +

4.6 IP Address Validation

+ +

+ InetAddressValidator provides + IPv4 address validation. +

+

+ For example: +

+
+
+      // Get an InetAddressValidator
+      InetAddressValidator validator = InetAddressValidator.getInstance();
+
+      // Validate an IPv4 address
+      if (!validator.isValid(candidateInetAddress)) {
+          ... // invalid
+      }
+
+
+ + +

4.7 Email Address Validation

+ +

+ EmailValidator provides email address + validation according to RFC 822 standards. +

+

+ For example: +

+
+      // Get an EmailValidator
+      EmailValidator validator = EmailValidator.getInstance();
+
+      // Validate an email address
+      boolean isAddressValid = validator.isValid("user@apache.org");
+
+      // Validate a variable containing an email address
+      if (!validator.isValid(addressFromUserForm)) {
+          webController.sendRedirect(ERROR_REDIRECT, "Email address isn't valid");
+          // etc.
+      }
+
+ + +

4.8 URL Validation

+ +

+ UrlValidator provides URL validation by + checking the scheme, authority, path, query, and fragment in turn. Clients + may specify valid schemes to be used in validating in addition to or instead of + the default values (HTTP, HTTPS, FTP). The UrlValidator also supports options + that change the parsing rules; for example, the ALLOW_2_SLASHES option instructs + the Validator to allow consecutive slash characters in the path component, which + is considered an error by default. + + For more information on the available options, see the UrlValidator documentation. +

+

+ For example: +

+
+      // Get an UrlValidator
+      UrlValidator defaultValidator = new UrlValidator(); // default schemes
+      if (defaultValidator.isValid("http://www.apache.org")) {
+          ... // valid
+      }
+      if (!defaultValidator.isValid("http//www.oops.com")) {
+          ... // invalid
+      }
+
+      // Get an UrlValidator with custom schemes
+      String[] customSchemes = { "sftp", "scp", "https" };
+      UrlValidator customValidator = new UrlValidator(customSchemes);
+      if (!customValidator.isValid("http://www.apache.org")) {
+          ... // invalid due to insecure protocol
+      }
+
+      // Get an UrlValidator that allows double slashes in the path
+      UrlValidator doubleSlashValidator = new UrlValidator(UrlValidator.ALLOW_2_SLASHES);
+      if (doubleSlashValidator.isValid("http://www.apache.org//projects")) {
+          ... // valid only in this Validator instance
+      }
+
+ + +

4.9 Domain Name Validation

+ +

+ DomainValidator provides validation of Internet + domain names as specified by RFC1034/RFC1123 and according to the IANA-recognized + list of top-level domains (TLDs). Clients may validate an entire domain name, a + TLD of any category, or a TLD within a specific category. +

+

+ For example: +

+
+      // Get a DomainValidator
+      DomainValidator validator = DomainValidator.getInstance();
+
+      // Validate a domain name
+      if (validator.isValid("www.apache.org")) {
+          ... // valid
+      }
+      if (!validator.isValid("www.apache.wrong")) {
+          ... // invalid
+      }
+
+      // Validate a TLD
+      if (validator.isValidTld(".com")) {
+          ... // valid
+      }
+      if (validator.isValidTld("org")) {
+          ... // valid, the leading dot is optional
+      }
+      if (validator.isValidTld(".us")) {
+          ... // valid, country code TLDs are also accepted
+      }
+
+      // Validate TLDs in categories
+      if (validator.isValidGenericTld(".name")) {
+          ... // valid
+      }
+      if (!validator.isValidGenericTld(".uk")) {
+          ... // invalid, .uk is a country code TLD
+      }
+      if (!validator.isValidCountryCodeTld(".info")) {
+          ... // invalid, .info is a generic TLD
+      }
+
+ + diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/Flags.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/Flags.java new file mode 100644 index 000000000..21ba4a021 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/Flags.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.util; + +import java.io.Serializable; + +/** + * Represents a collection of 64 boolean (on/off) flags. Individual flags + * are represented by powers of 2. For example,
+ * Flag 1 = 1
+ * Flag 2 = 2
+ * Flag 3 = 4
+ * Flag 4 = 8

+ * or using shift operator to make numbering easier:
+ * Flag 1 = 1 << 0
+ * Flag 2 = 1 << 1
+ * Flag 3 = 1 << 2
+ * Flag 4 = 1 << 3
+ * + *

+ * There cannot be a flag with a value of 3 because that represents Flag 1 + * and Flag 2 both being on/true. + *

+ * + * @version $Revision$ + */ +public class Flags implements Serializable, Cloneable { + + private static final long serialVersionUID = 8481587558770237995L; + + /** + * Represents the current flag state. + */ + private long flags = 0; + + /** + * Create a new Flags object. + */ + public Flags() { + super(); + } + + /** + * Initialize a new Flags object with the given flags. + * + * @param flags collection of boolean flags to represent. + */ + public Flags(long flags) { + super(); + this.flags = flags; + } + + /** + * Returns the current flags. + * + * @return collection of boolean flags represented. + */ + public long getFlags() { + return this.flags; + } + + /** + * Tests whether the given flag is on. If the flag is not a power of 2 + * (ie. 3) this tests whether the combination of flags is on. + * + * @param flag Flag value to check. + * + * @return whether the specified flag value is on. + */ + public boolean isOn(long flag) { + return (this.flags & flag) == flag; + } + + /** + * Tests whether the given flag is off. If the flag is not a power of 2 + * (ie. 3) this tests whether the combination of flags is off. + * + * @param flag Flag value to check. + * + * @return whether the specified flag value is off. + */ + public boolean isOff(long flag) { + return (this.flags & flag) == 0; + } + + /** + * Turns on the given flag. If the flag is not a power of 2 (ie. 3) this + * turns on multiple flags. + * + * @param flag Flag value to turn on. + */ + public void turnOn(long flag) { + this.flags |= flag; + } + + /** + * Turns off the given flag. If the flag is not a power of 2 (ie. 3) this + * turns off multiple flags. + * + * @param flag Flag value to turn off. + */ + public void turnOff(long flag) { + this.flags &= ~flag; + } + + /** + * Turn off all flags. + */ + public void turnOffAll() { + this.flags = 0; + } + + /** + * Turn off all flags. This is a synonym for turnOffAll(). + * @since Validator 1.1.1 + */ + public void clear() { + this.flags = 0; + } + + /** + * Turn on all 64 flags. + */ + public void turnOnAll() { + this.flags = 0xFFFFFFFFFFFFFFFFl; + } + + /** + * Clone this Flags object. + * + * @return a copy of this object. + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch(CloneNotSupportedException e) { + throw new RuntimeException("Couldn't clone Flags object."); + } + } + + /** + * Tests if two Flags objects are in the same state. + * @param obj object being tested + * @see java.lang.Object#equals(java.lang.Object) + * + * @return whether the objects are equal. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Flags)) { + return false; + } + + if (obj == this) { + return true; + } + + Flags f = (Flags) obj; + + return this.flags == f.flags; + } + + /** + * The hash code is based on the current state of the flags. + * @see java.lang.Object#hashCode() + * + * @return the hash code for this object. + */ + @Override + public int hashCode() { + return (int) this.flags; + } + + /** + * Returns a 64 length String with the first flag on the right and the + * 64th flag on the left. A 1 indicates the flag is on, a 0 means it's + * off. + * + * @return string representation of this object. + */ + @Override + public String toString() { + StringBuilder bin = new StringBuilder(Long.toBinaryString(this.flags)); + for (int i = 64 - bin.length(); i > 0; i--) { // CHECKSTYLE IGNORE MagicNumber + bin.insert(0, "0"); + } + return bin.toString(); + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/ValidatorUtils.java b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/ValidatorUtils.java new file mode 100644 index 000000000..b0539bc66 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/ValidatorUtils.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.util; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.collections.FastHashMap; // DEPRECATED +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.validator.Arg; +import org.apache.commons.validator.Msg; +import org.apache.commons.validator.Var; + +/** + * Basic utility methods. + *

+ * The use of FastHashMap is deprecated and will be replaced in a future + * release. + *

+ * + * @version $Revision$ + */ +public class ValidatorUtils { + + private static final Log LOG = LogFactory.getLog(ValidatorUtils.class); + + /** + *

Replace part of a String with another value.

+ * + * @param value String to perform the replacement on. + * @param key The name of the constant. + * @param replaceValue The value of the constant. + * + * @return The modified value. + */ + public static String replace(String value, String key, String replaceValue) { + + if (value == null || key == null || replaceValue == null) { + return value; + } + + int pos = value.indexOf(key); + + if (pos < 0) { + return value; + } + + int length = value.length(); + int start = pos; + int end = pos + key.length(); + + if (length == key.length()) { + value = replaceValue; + + } else if (end == length) { + value = value.substring(0, start) + replaceValue; + + } else { + value = + value.substring(0, start) + + replaceValue + + replace(value.substring(end), key, replaceValue); + } + + return value; + } + + /** + * Convenience method for getting a value from a bean property as a + * String. If the property is a String[] or + * Collection and it is empty, an empty String + * "" is returned. Otherwise, property.toString() is returned. This method + * may return null if there was an error retrieving the + * property. + * + * @param bean The bean object. + * @param property The name of the property to access. + * + * @return The value of the property. + */ + public static String getValueAsString(Object bean, String property) { + Object value = null; + + try { + value = PropertyUtils.getProperty(bean, property); + + } catch(IllegalAccessException e) { + LOG.error(e.getMessage(), e); + } catch(InvocationTargetException e) { + LOG.error(e.getMessage(), e); + } catch(NoSuchMethodException e) { + LOG.error(e.getMessage(), e); + } + + if (value == null) { + return null; + } + + if (value instanceof String[]) { + return ((String[]) value).length > 0 ? value.toString() : ""; + + } else if (value instanceof Collection) { + return ((Collection) value).isEmpty() ? "" : value.toString(); + + } else { + return value.toString(); + } + + } + + /** + * Makes a deep copy of a FastHashMap if the values + * are Msg, Arg, + * or Var. Otherwise it is a shallow copy. + * + * @param map FastHashMap to copy. + * @return FastHashMap A copy of the FastHashMap that was + * passed in. + * @deprecated This method is not part of Validator's public API. Validator + * will use it internally until FastHashMap references are removed. Use + * copyMap() instead. + */ + @Deprecated + public static FastHashMap copyFastHashMap(FastHashMap map) { + FastHashMap results = new FastHashMap(); + + @SuppressWarnings("unchecked") // FastHashMap is not generic + Iterator> i = map.entrySet().iterator(); + while (i.hasNext()) { + Entry entry = i.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + + if (value instanceof Msg) { + results.put(key, ((Msg) value).clone()); + } else if (value instanceof Arg) { + results.put(key, ((Arg) value).clone()); + } else if (value instanceof Var) { + results.put(key, ((Var) value).clone()); + } else { + results.put(key, value); + } + } + + results.setFast(true); + return results; + } + + /** + * Makes a deep copy of a Map if the values are + * Msg, Arg, or Var. Otherwise, + * it is a shallow copy. + * + * @param map The source Map to copy. + * + * @return A copy of the Map that was passed in. + */ + public static Map copyMap(Map map) { + Map results = new HashMap(); + + Iterator> i = map.entrySet().iterator(); + while (i.hasNext()) { + Entry entry = i.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + + if (value instanceof Msg) { + results.put(key, ((Msg) value).clone()); + } else if (value instanceof Arg) { + results.put(key, ((Arg) value).clone()); + } else if (value instanceof Var) { + results.put(key, ((Var) value).clone()); + } else { + results.put(key, value); + } + } + return results; + } + +} diff --git a/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/package.html b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/package.html new file mode 100644 index 000000000..ce4613baa --- /dev/null +++ b/Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/package.html @@ -0,0 +1,26 @@ + + + +Package Documentation for org.apache.commons.validator.util Package + + +

+This package contains utility classes used by Commons Validator. +

+ + diff --git a/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/digester-rules.xml b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/digester-rules.xml new file mode 100644 index 000000000..50fde7796 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/digester-rules.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_0.dtd b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_0.dtd new file mode 100644 index 000000000..6dfb90b28 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_0.dtd @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_0_1.dtd b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_0_1.dtd new file mode 100644 index 000000000..f5211690b --- /dev/null +++ b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_0_1.dtd @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_1.dtd b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_1.dtd new file mode 100644 index 000000000..671e74094 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_1.dtd @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_1_3.dtd b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_1_3.dtd new file mode 100644 index 000000000..8dce2b888 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_1_3.dtd @@ -0,0 +1,328 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_2_0.dtd b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_2_0.dtd new file mode 100644 index 000000000..58d5adc4d --- /dev/null +++ b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_2_0.dtd @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_3_0.dtd b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_3_0.dtd new file mode 100644 index 000000000..f644aeb59 --- /dev/null +++ b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_3_0.dtd @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_4_0.dtd b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_4_0.dtd new file mode 100644 index 000000000..b446ab71a --- /dev/null +++ b/Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_4_0.dtd @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/site/resources/download_validator.cgi b/Java-base/commons-validator/src/src/site/resources/download_validator.cgi new file mode 100755 index 000000000..495cde12d --- /dev/null +++ b/Java-base/commons-validator/src/src/site/resources/download_validator.cgi @@ -0,0 +1,4 @@ +#!/bin/sh +# Just call the standard mirrors.cgi script. It will use download.html +# as the input template. +exec /www/www.apache.org/dyn/mirrors/mirrors.cgi $* \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/site/resources/images/logo.png b/Java-base/commons-validator/src/src/site/resources/images/logo.png new file mode 100644 index 000000000..9ed617e29 Binary files /dev/null and b/Java-base/commons-validator/src/src/site/resources/images/logo.png differ diff --git a/Java-base/commons-validator/src/src/site/site.xml b/Java-base/commons-validator/src/src/site/site.xml new file mode 100644 index 000000000..0b65f7e9d --- /dev/null +++ b/Java-base/commons-validator/src/src/site/site.xml @@ -0,0 +1,51 @@ + + + + + Commons Validator + /images/logo.png + /index.html + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/site/xdoc/building.xml b/Java-base/commons-validator/src/src/site/xdoc/building.xml new file mode 100644 index 000000000..f0a90bcab --- /dev/null +++ b/Java-base/commons-validator/src/src/site/xdoc/building.xml @@ -0,0 +1,75 @@ + + + + + Building + Commons Documentation Team + + + +
+

+ Commons Validator uses Maven or + Ant as a build system. +

+
+ +
+

+ To build a jar file, change into Validator's root directory and run + maven jar. + The result will be in the "target" subdirectory. +

+

+ To build the Javadocs, run maven javadoc. + The result will be in "target/docs/apidocs". +

+

+ To build the full website, run maven site. + + The result will be in "target/docs". +

+

+ Further details can be found in the + commons build instructions. +

+
+ +
+

+ To build a jar file and the javadocs, change into Validator's root directory + and run ant dist. + The result will be in the "dist" subdirectory. +

+
+ + +
+

+ Nightly Builds + are built once a day from the current SVN HEAD. These are provided purely for test purposes and are NOT + official releases of the Apache Software Foundation - Released versions of Commons Validator are + available here. +

+
+ + + +
diff --git a/Java-base/commons-validator/src/src/site/xdoc/community.xml b/Java-base/commons-validator/src/src/site/xdoc/community.xml new file mode 100644 index 000000000..51b810d1d --- /dev/null +++ b/Java-base/commons-validator/src/src/site/xdoc/community.xml @@ -0,0 +1,50 @@ + + + + + + Community + + + +
+

+ The Apache Wiki is a Wiki run by Apache for the Apache community. The Validator Wiki home page is + here. +

+

+ Anyone is welcome to create new content about Validator providing that they + observe the usual rules of netiquette. +

+
+ +
+

+ Struts Console is a free + standalone Java Swing application for managing + Struts related configuration files, including Commons Validator config files. + Struts Console also plugs into several popular Java IDEs for seamless + management of config files from one central development tool. +

+ +
+ +
+ diff --git a/Java-base/commons-validator/src/src/site/xdoc/download_validator.xml b/Java-base/commons-validator/src/src/site/xdoc/download_validator.xml new file mode 100644 index 000000000..ec80f4bac --- /dev/null +++ b/Java-base/commons-validator/src/src/site/xdoc/download_validator.xml @@ -0,0 +1,154 @@ + + + + + + Download Apache Commons Validator + Apache Commons Documentation Team + + +
+ +

+ We recommend you use a mirror to download our release + builds, but you must verify the integrity of + the downloaded files using signatures downloaded from our main + distribution directories. Recent releases (48 hours) may not yet + be available from all the mirrors. +

+ +

+ You are currently using [preferred]. If you + encounter a problem with this mirror, please select another + mirror. If all mirrors are failing, there are backup + mirrors (at the end of the mirrors list) that should be + available. +

+ [if-any logo][end] +

+ + +

+ Other mirrors: + + +

+ + +

+ It is essential that you + verify the integrity + of downloaded files, preferably using the PGP signature (*.asc files); + failing that using the MD5 hash (*.md5 checksum files). +

+

+ The KEYS + file contains the public PGP keys used by Apache Commons developers + to sign releases. +

+
+
+
+ + + + + + + + + + + + +
commons-validator-1.6-bin.tar.gzmd5pgp
commons-validator-1.6-bin.zipmd5pgp
+
+ + + + + + + + + + + + +
commons-validator-1.6-src.tar.gzmd5pgp
commons-validator-1.6-src.zipmd5pgp
+
+
+
+

+ Older releases can be obtained from the archives. +

+ +
+ +
diff --git a/Java-base/commons-validator/src/src/site/xdoc/index.xml b/Java-base/commons-validator/src/src/site/xdoc/index.xml new file mode 100644 index 000000000..8616296a3 --- /dev/null +++ b/Java-base/commons-validator/src/src/site/xdoc/index.xml @@ -0,0 +1,123 @@ + + + + + + + Commons Validator + + + + +
+ +

+ A common issue when receiving data either electronically or from + user input is verifying the integrity of the data. This work is + repetitive and becomes even more complicated when different sets + of validation rules need to be applied to the same set of data based + on locale. Error messages may also vary by locale. + This package addresses some of these issues to + speed development and maintenance of validation rules. +

+
+ +
+

See the Downloads + page for current/previous releases. + For details of whats new in each version see the Release Notes. + Community Notes on + release are maintained on the Apache Commons Wiki. +

+
+ +
+ +

Validator provides two distinct sets of functionality:

+
    +
  1. A configurable (typically XML) validation engine
  2. +
  3. Reusable "primitive" validation methods
  4. +
+ +

+ Your validation methods are plugged into the engine and + executed against your data. Often, these methods use + resources specific to one application or framework so Commons + Validator doesn't directly provide pluggable validator actions. + However, it does have a set of common validation methods + (email addresses, dates, URLs, etc.) that help in creating + pluggable actions. +

+
+ + +

In order to use the Validator, the following basic steps are required:

+
    +
  • + Create a new instance of the + org.apache.commons.validator.Validator class. Currently + Validator instances may be safely reused if the current + ValidatorResources are the same, as long as you have completed any + previous validation, and you do not try to utilize a particular + Validator instance from more than one thread at a time. +
  • +
  • + Add any resources needed to perform the validations, such as the + JavaBean to validate. +
  • +
  • + Call the validate method on + org.apache.commons.validator.Validator. +
  • +
+
+ + +

+ The package Javadoc has useful information: +

+ +

+ See the subversion repository for the latest source code. +

+
+ +
+ +
+

+ The commons mailing lists act as the main support forum. + The user list is suitable for most library usage queries. + The dev list is intended for the development discussion. + Please remember that the lists are shared between all commons components, + so prefix your email by [validator]. +

+ +

+ Issues may be reported via ASF JIRA. +

+
+ + + +
diff --git a/Java-base/commons-validator/src/src/site/xdoc/issue-tracking.xml b/Java-base/commons-validator/src/src/site/xdoc/issue-tracking.xml new file mode 100644 index 000000000..fb3259200 --- /dev/null +++ b/Java-base/commons-validator/src/src/site/xdoc/issue-tracking.xml @@ -0,0 +1,102 @@ + + + + + + Commons Validator Issue tracking + Commons Documentation Team + + + +
+

+ Commons Validator uses ASF JIRA for tracking issues. + See the Commons Validator JIRA project page. +

+ +

+ To use JIRA you may need to create an account + (if you have previously created/updated Commons issues using Bugzilla an account will have been automatically + created and you can use the Forgot Password + page to get a new password). +

+ +

+ If you would like to report a bug, or raise an enhancement request with + Commons Validator please do the following: +

    +
  1. Search existing open bugs. + If you find your issue listed then please add a comment with your details.
  2. +
  3. Search the mailing list archive(s). + You may find your issue or idea has already been discussed.
  4. +
  5. Decide if your issue is a bug or an enhancement.
  6. +
  7. Submit either a bug report + or enhancement request.
  8. +
+

+ +

+ Please also remember these points: +

    +
  • the more information you provide, the better we can help you
  • +
  • test cases are vital, particularly for any proposed enhancements
  • +
  • the developers of Commons Validator are all unpaid volunteers
  • +
+

+ +

+ For more information on subversion and creating patches see the + Apache Contributors Guide. +

+ +

+ You may also find these links useful: +

+

+
+ +
diff --git a/Java-base/commons-validator/src/src/site/xdoc/mail-lists.xml b/Java-base/commons-validator/src/src/site/xdoc/mail-lists.xml new file mode 100644 index 000000000..e0b1075e6 --- /dev/null +++ b/Java-base/commons-validator/src/src/site/xdoc/mail-lists.xml @@ -0,0 +1,202 @@ + + + + + + Commons Validator Mailing Lists + Commons Documentation Team + + + +
+

+ Commons Validator shares mailing lists with all the other + Commons Components. + To make it easier for people to only read messages related to components they are interested in, + the convention in Commons is to prefix the subject line of messages with the component's name, + for example: +

    +
  • [validator] Problem with the ...
  • +
+

+

+ Questions related to the usage of Commons Validator should be posted to the + User List. +
+ The Developer List + is for questions and discussion related to the development of Commons Validator. +
+ Please do not cross-post; developers are also subscribed to the user list. +

+

+ Note: please don't send patches or attachments to any of the mailing lists. + Patches are best handled via the Issue Tracking system. + Otherwise, please upload the file to a public server and include the URL in the mail. +

+
+ +
+

+ Please prefix the subject line of any messages for Commons Validator + with [validator] - thanks! +
+
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSubscribeUnsubscribePostArchiveOther Archives
+ Commons User List +

+ Questions on using Commons Validator. +

+
SubscribeUnsubscribePostmail-archives.apache.orgmarkmail.org
+ www.mail-archive.com
+ news.gmane.org +
+ Commons Developer List +

+ Discussion of development of Commons Validator. +

+
SubscribeUnsubscribePostmail-archives.apache.orgmarkmail.org
+ www.mail-archive.com
+ news.gmane.org +
+ Commons Issues List +

+ Only for e-mails automatically generated by the issue tracking system. +

+
SubscribeUnsubscriberead onlymail-archives.apache.orgmarkmail.org
+ www.mail-archive.com +
+ Commons Commits List +

+ Only for e-mails automatically generated by the source control sytem. +

+
SubscribeUnsubscriberead onlymail-archives.apache.orgmarkmail.org
+ www.mail-archive.com +
+ +
+
+

+ Other mailing lists which you may find useful include: +

+ + + + + + + + + + + + + + + + + + +
NameSubscribeUnsubscribePostArchiveOther Archives
+ Apache Announce List +

+ General announcements of Apache project releases. +

+
SubscribeUnsubscriberead onlymail-archives.apache.orgmarkmail.org
+ old.nabble.com
+ www.mail-archive.com
+ news.gmane.org +
+ +
+ +
diff --git a/Java-base/commons-validator/src/src/site/xdoc/tasks.xml b/Java-base/commons-validator/src/src/site/xdoc/tasks.xml new file mode 100644 index 000000000..c3737121c --- /dev/null +++ b/Java-base/commons-validator/src/src/site/xdoc/tasks.xml @@ -0,0 +1,63 @@ + + + + + + TODO + + + +
+

+ The following is a list of items that need to be completed in + Validator. Contributions are welcome! +

+ +
    +
  • + Change the validation.xml file semantics to match a more general "bean" validation usage. + Currently, the <form-validation>, <formset>, <form>, and <field> elements + require a form-centric view of validations. Changing these to <bean-validation> or <validator-config>, + <beans>, <bean>, and <property> respectively would allow Validator to be used more easily in + non-form based environments. See the 2.0 DTD proposal for specifics. +
  • +
  • + The above changes to validation.xml could only apply to Validator's native configuration format. We + could add a ValidatorResources constructor that accepts a digester-rules file to allow parsing any + XML format into Validator configuration objects. This would allow higher level frameworks like Struts + to use configuration semantics specific to their domain. +
  • +
  • + ValidatorException is only thrown to indicate configuration and programmer errors + yet is a checked exception. ValidatorException should be converted to a RuntimeException to match its + real purpose. Furthermore, the exception handling for pluggable validations (ValidatorActions) + is not well defined or documented. RuntimeExceptions thrown from ValidatorActions should be propogated + out of the Validator framework as is because they indicate programmer error. Checked exceptions thrown + from a ValidatorAction should stop validation and be propogated out of the framework for handling as these + indicate an unrecoverable system failure. Validation method implementation becomes easier because they + can throw SQLException, IOException, etc. instead of wrapping the exception or defining it as a rule failure. + This allows clients to reliably distinguish between a normal validation failure (invalid data) and exceptional + conditions. +
  • +
+ +
+ +
diff --git a/Java-base/commons-validator/src/src/site/xdoc/validator_2_0_0_proposal.dtd b/Java-base/commons-validator/src/src/site/xdoc/validator_2_0_0_proposal.dtd new file mode 100644 index 000000000..c5428fe10 --- /dev/null +++ b/Java-base/commons-validator/src/src/site/xdoc/validator_2_0_0_proposal.dtd @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/AbstractCommonTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/AbstractCommonTest.java new file mode 100644 index 000000000..da2791c72 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/AbstractCommonTest.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.validator; + +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.xml.sax.SAXException; + +/** + * Consolidates reading in XML config file into parent class. + * + * @version $Revision$ + */ +abstract public class AbstractCommonTest extends TestCase { + + /** + * Resources used for validation tests. + */ + protected ValidatorResources resources = null; + + public AbstractCommonTest(String string) { + super(string); + } + + /** + * Load ValidatorResources from + * validator-numeric.xml. + */ + protected void loadResources(String file) throws IOException, SAXException { + // Load resources + InputStream in = null; + + try { + in = this.getClass().getResourceAsStream(file); + resources = new ValidatorResources(in); + } finally { + if (in != null) { + in.close(); + } + } + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/AbstractNumberTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/AbstractNumberTest.java new file mode 100644 index 000000000..0b2f6b71a --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/AbstractNumberTest.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; + +import org.xml.sax.SAXException; + +/** + * Abstracts number unit tests methods. + * + * @version $Revision$ + */ +abstract public class AbstractNumberTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected String FORM_KEY; + + /** + * The key used to retrieve the validator action. + */ + protected String ACTION; + + + public AbstractNumberTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-numeric.xml. + */ + @Override + protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("TestNumber-config.xml"); + } + + @Override + protected void tearDown() { + } + + /** + * Tests the number validation. + */ + public void testNumber() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("0"); + valueTest(info, true); + } + + /** + * Tests the float validation failure. + */ + public void testNumberFailure() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + valueTest(info, false); + } + + /** + * Utlity class to run a test on a value. + * + * @param info Value to run test on. + * @param passed Whether or not the test is expected to pass. + */ + protected void valueTest(Object info, boolean passed) throws ValidatorException { + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, info); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult result = results.getValidatorResult("value"); + + assertNotNull(ACTION + " value ValidatorResult should not be null.", result); + assertTrue(ACTION + " value ValidatorResult should contain the '" + ACTION + "' action.", result.containsAction(ACTION)); + assertTrue(ACTION + " value ValidatorResult for the '" + ACTION + "' action should have " + (passed ? "passed" : "failed") + ".", (passed ? result.isValid(ACTION) : !result.isValid(ACTION))); + } + + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ByteTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ByteTest.java new file mode 100644 index 000000000..63ac3ed2a --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ByteTest.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + + + +/** + * Performs Validation Test for byte validations. + * + * @version $Revision$ + */ +public class ByteTest extends AbstractNumberTest { + + public ByteTest(String name) { + super(name); + ACTION = "byte"; + FORM_KEY = "byteForm"; + } + + /** + * Tests the byte validation. + */ + public void testByte() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("0"); + + valueTest(info, true); + } + + /** + * Tests the byte validation. + */ + public void testByteMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Byte.valueOf(Byte.MIN_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the byte validation. + */ + public void testByteMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Byte.valueOf(Byte.MAX_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the byte validation failure. + */ + public void testByteFailure() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + valueTest(info, false); + } + + /** + * Tests the byte validation failure. + */ + public void testByteBeyondMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Byte.MIN_VALUE + "1"); + + valueTest(info, false); + } + + /** + * Tests the byte validation failure. + */ + public void testByteBeyondMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Byte.MAX_VALUE + "1"); + + valueTest(info, false); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/CreditCardValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/CreditCardValidatorTest.java new file mode 100644 index 000000000..fed4c505a --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/CreditCardValidatorTest.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.validator; + +import junit.framework.TestCase; + +/** + * Test the CreditCardValidator class. + * + * @version $Revision$ + * @deprecated this test can be removed when the deprecated class is removed + */ +@Deprecated +public class CreditCardValidatorTest extends TestCase { + + private static final String VALID_VISA = "4417123456789113"; + private static final String VALID_SHORT_VISA = "4222222222222"; + private static final String VALID_AMEX = "378282246310005"; + private static final String VALID_MASTERCARD = "5105105105105100"; + private static final String VALID_DISCOVER = "6011000990139424"; + private static final String VALID_DINERS = "30569309025904"; + + /** + * Constructor for CreditCardValidatorTest. + */ + public CreditCardValidatorTest(String name) { + super(name); + } + + public void testIsValid() { + CreditCardValidator ccv = new CreditCardValidator(); + + assertFalse(ccv.isValid(null)); + assertFalse(ccv.isValid("")); + assertFalse(ccv.isValid("123456789012")); // too short + assertFalse(ccv.isValid("12345678901234567890")); // too long + assertFalse(ccv.isValid("4417123456789112")); + assertFalse(ccv.isValid("4417q23456w89113")); + assertTrue(ccv.isValid(VALID_VISA)); + assertTrue(ccv.isValid(VALID_SHORT_VISA)); + assertTrue(ccv.isValid(VALID_AMEX)); + assertTrue(ccv.isValid(VALID_MASTERCARD)); + assertTrue(ccv.isValid(VALID_DISCOVER)); + + // disallow Visa so it should fail even with good number + ccv = new CreditCardValidator(CreditCardValidator.AMEX); + assertFalse(ccv.isValid("4417123456789113")); + } + + public void testAddAllowedCardType() { + CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.NONE); + // Turned off all cards so even valid numbers should fail + assertFalse(ccv.isValid(VALID_VISA)); + assertFalse(ccv.isValid(VALID_AMEX)); + assertFalse(ccv.isValid(VALID_MASTERCARD)); + assertFalse(ccv.isValid(VALID_DISCOVER)); + + // test our custom type + ccv.addAllowedCardType(new DinersClub()); + assertTrue(ccv.isValid(VALID_DINERS)); + } + + /** + * Test a custom implementation of CreditCardType. + */ + private class DinersClub implements CreditCardValidator.CreditCardType { + private static final String PREFIX = "300,301,302,303,304,305,"; + @Override + public boolean matches(String card) { + String prefix = card.substring(0, 3) + ","; + return ((PREFIX.contains(prefix)) && (card.length() == 14)); + } + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/CustomValidatorResourcesTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/CustomValidatorResourcesTest.java new file mode 100644 index 000000000..f1ee2ee7d --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/CustomValidatorResourcesTest.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.validator; + +import java.io.InputStream; + +import junit.framework.TestCase; + +/** + * Test custom ValidatorResources. + * + * @version $Revision$ + */ +public class CustomValidatorResourcesTest extends TestCase { + + /** + * Construct a test case with the specified name. + * @param name Name of the test + */ + public CustomValidatorResourcesTest(String name) { + super(name); + } + + /** + * Set up. + */ + @Override + protected void setUp() { + } + + /** + * Tear Down + */ + @Override + protected void tearDown() { + } + + /** + * Test creating a custom validator resources. + */ + public void testCustomResources() { + // Load resources + InputStream in = null; + try { + in = this.getClass().getResourceAsStream("TestNumber-config.xml"); + } catch(Exception e) { + fail("Error loading resources: " + e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch(Exception e) { + } + } + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/DateTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/DateTest.java new file mode 100644 index 000000000..79539d354 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/DateTest.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; +import java.util.Locale; + +import org.xml.sax.SAXException; + +/** + * Abstracts date unit tests methods. + * + * @version $Revision$ + */ +public class DateTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected String FORM_KEY = "dateForm"; + + /** + * The key used to retrieve the validator action. + */ + protected String ACTION = "date"; + + + public DateTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-numeric.xml. + */ + @Override + protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("DateTest-config.xml"); + } + + /** + * Tests the date validation. + */ + public void testValidDate() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("12/01/2005"); + valueTest(info, true); + } + + /** + * Tests the date validation. + */ + public void testInvalidDate() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("12/01as/2005"); + valueTest(info, false); + } + + + /** + * Utlity class to run a test on a value. + * + * @param info Value to run test on. + * @param passed Whether or not the test is expected to pass. + */ + protected void valueTest(Object info, boolean passed) throws ValidatorException { + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, info); + validator.setParameter(Validator.LOCALE_PARAM, Locale.US); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult result = results.getValidatorResult("value"); + + assertNotNull(ACTION + " value ValidatorResult should not be null.", result); + assertTrue(ACTION + " value ValidatorResult should contain the '" + ACTION + "' action.", result.containsAction(ACTION)); + assertTrue(ACTION + " value ValidatorResult for the '" + ACTION + "' action should have " + (passed ? "passed" : "failed") + ".", (passed ? result.isValid(ACTION) : !result.isValid(ACTION))); + } + + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/DoubleTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/DoubleTest.java new file mode 100644 index 000000000..9e907ab36 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/DoubleTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + + +/** + * Performs Validation Test for double validations. + * + * @version $Revision$ + */ +public class DoubleTest extends AbstractNumberTest { + + public DoubleTest(String name) { + super(name); + ACTION = "double"; + FORM_KEY = "doubleForm"; + } + + + /** + * Tests the double validation. + */ + public void testDouble() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("0"); + + valueTest(info, true); + } + + /** + * Tests the double validation. + */ + public void testDoubleMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Double.valueOf(Double.MIN_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the double validation. + */ + public void testDoubleMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Double.valueOf(Double.MAX_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the double validation failure. + */ + public void testDoubleFailure() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + valueTest(info, false); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/EmailTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/EmailTest.java new file mode 100644 index 000000000..1216bd83a --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/EmailTest.java @@ -0,0 +1,449 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; + +import org.xml.sax.SAXException; + +/** + * Performs Validation Test for e-mail validations. + * + * + * @version $Revision$ + * @deprecated to be removed when target class is removed + */ +@Deprecated +public class EmailTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "emailForm"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "email"; + + + public EmailTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-regexp.xml. + */ + @Override +protected void setUp() throws IOException, SAXException { + loadResources("EmailTest-config.xml"); + } + + /** + * Tests the e-mail validation. + */ + public void testEmail() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + info.setValue("jsmith@apache.org"); + valueTest(info, true); + } + + /** + * Tests the email validation with numeric domains. + */ + public void testEmailWithNumericAddress() throws ValidatorException { + ValueBean info = new ValueBean(); + info.setValue("someone@[216.109.118.76]"); + valueTest(info, true); + info.setValue("someone@yahoo.com"); + valueTest(info, true); + } + + /** + * Tests the e-mail validation. + */ + public void testEmailExtension() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + info.setValue("jsmith@apache.org"); + valueTest(info, true); + + info.setValue("jsmith@apache.com"); + valueTest(info, true); + + info.setValue("jsmith@apache.net"); + valueTest(info, true); + + info.setValue("jsmith@apache.info"); + valueTest(info, true); + + info.setValue("jsmith@apache."); + valueTest(info, false); + + info.setValue("jsmith@apache.c"); + valueTest(info, false); + + info.setValue("someone@yahoo.museum"); + valueTest(info, true); + + info.setValue("someone@yahoo.mu-seum"); + valueTest(info, false); + } + + /** + *

Tests the e-mail validation with a dash in + * the address.

+ */ + public void testEmailWithDash() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + info.setValue("andy.noble@data-workshop.com"); + valueTest(info, true); + + info.setValue("andy-noble@data-workshop.-com"); + valueTest(info, false); + info.setValue("andy-noble@data-workshop.c-om"); + valueTest(info,false); + info.setValue("andy-noble@data-workshop.co-m"); + valueTest(info, false); + + + } + + /** + * Tests the e-mail validation with a dot at the end of + * the address. + */ + public void testEmailWithDotEnd() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + info.setValue("andy.noble@data-workshop.com."); + valueTest(info, false); + + } + + /** + * Tests the e-mail validation with an RCS-noncompliant character in + * the address. + */ + public void testEmailWithBogusCharacter() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + info.setValue("andy.noble@\u008fdata-workshop.com"); + valueTest(info, false); + + // The ' character is valid in an email username. + info.setValue("andy.o'reilly@data-workshop.com"); + valueTest(info, true); + + // But not in the domain name. + info.setValue("andy@o'reilly.data-workshop.com"); + valueTest(info, false); + + info.setValue("foo+bar@i.am.not.in.us.example.com"); + valueTest(info, true); + } + + /** + * Tests the email validation with commas. + */ + public void testEmailWithCommas() throws ValidatorException { + ValueBean info = new ValueBean(); + info.setValue("joeblow@apa,che.org"); + valueTest(info, false); + info.setValue("joeblow@apache.o,rg"); + valueTest(info, false); + info.setValue("joeblow@apache,org"); + valueTest(info, false); + + } + + /** + * Tests the email validation with spaces. + */ + public void testEmailWithSpaces() throws ValidatorException { + ValueBean info = new ValueBean(); + info.setValue("joeblow @apache.org"); + valueTest(info, false); + info.setValue("joeblow@ apache.org"); + valueTest(info, false); + info.setValue(" joeblow@apache.org"); + valueTest(info, false); + info.setValue("joeblow@apache.org "); + valueTest(info, false); + info.setValue("joe blow@apache.org "); + valueTest(info, false); + info.setValue("joeblow@apa che.org "); + valueTest(info, false); + info.setValue("\"joe blow\"@apache.org"); + valueTest(info, true); + + } + + /** + * Tests the email validation with ascii control characters. + * (i.e. Ascii chars 0 - 31 and 127) + */ + public void testEmailWithControlChars() { + EmailValidator validator = new EmailValidator(); + for (char c = 0; c < 32; c++) { + assertFalse("Test control char " + ((int)c), validator.isValid("foo" + c + "bar@domain.com")); + } + assertFalse("Test control char 127", validator.isValid("foo" + ((char)127) + "bar@domain.com")); + } + + /** + * Tests the e-mail validation with a user at a TLD + */ + public void testEmailAtTLD() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + info.setValue("m@de"); + valueTest(info, false); + + org.apache.commons.validator.routines.EmailValidator validator = + org.apache.commons.validator.routines.EmailValidator.getInstance(true, true); + boolean result = validator.isValid("m@de"); + assertTrue("Result should have been true", result); + + } + + /** + * Test that @localhost and @localhost.localdomain + * addresses aren't declared valid by default + */ + public void testEmailLocalhost() throws ValidatorException { + ValueBean info = new ValueBean(); + info.setValue("joe@localhost"); + valueTest(info, false); + info.setValue("joe@localhost.localdomain"); + valueTest(info, false); + } + + /** + * Write this test according to parts of RFC, as opposed to the type of character + * that is being tested. + * + *

FIXME: This test fails so disable it with a leading _ for 1.1.4 release. + * The real solution is to fix the email parsing. + * + * @throws ValidatorException + */ + public void _testEmailUserName() throws ValidatorException { + ValueBean info = new ValueBean(); + info.setValue("joe1blow@apache.org"); + valueTest(info, true); + info.setValue("joe$blow@apache.org"); + valueTest(info, true); + info.setValue("joe-@apache.org"); + valueTest(info, true); + info.setValue("joe_@apache.org"); + valueTest(info, true); + + //UnQuoted Special characters are invalid + + info.setValue("joe.@apache.org"); + valueTest(info, false); + info.setValue("joe+@apache.org"); + valueTest(info, false); + info.setValue("joe!@apache.org"); + valueTest(info, false); + info.setValue("joe*@apache.org"); + valueTest(info, false); + info.setValue("joe'@apache.org"); + valueTest(info, false); + info.setValue("joe(@apache.org"); + valueTest(info, false); + info.setValue("joe)@apache.org"); + valueTest(info, false); + info.setValue("joe,@apache.org"); + valueTest(info, false); + info.setValue("joe%45@apache.org"); + valueTest(info, false); + info.setValue("joe;@apache.org"); + valueTest(info, false); + info.setValue("joe?@apache.org"); + valueTest(info, false); + info.setValue("joe&@apache.org"); + valueTest(info, false); + info.setValue("joe=@apache.org"); + valueTest(info, false); + + //Quoted Special characters are valid + info.setValue("\"joe.\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe+\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe!\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe*\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe'\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe(\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe)\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe,\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe%45\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe;\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe?\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe&\"@apache.org"); + valueTest(info, true); + info.setValue("\"joe=\"@apache.org"); + valueTest(info, true); + + } + + /** + * These test values derive directly from RFC 822 & + * Mail::RFC822::Address & RFC::RFC822::Address perl test.pl + * For traceability don't combine these test values with other tests. + */ + ResultPair[] testEmailFromPerl = { + new ResultPair("abigail@example.com", true), + new ResultPair("abigail@example.com ", true), + new ResultPair(" abigail@example.com", true), + new ResultPair("abigail @example.com ", true), + new ResultPair("*@example.net", true), + new ResultPair("\"\\\"\"@foo.bar", true), + new ResultPair("fred&barny@example.com", true), + new ResultPair("---@example.com", true), + new ResultPair("foo-bar@example.net", true), + new ResultPair("\"127.0.0.1\"@[127.0.0.1]", true), + new ResultPair("Abigail ", true), + new ResultPair("Abigail", true), + new ResultPair("Abigail<@a,@b,@c:abigail@example.com>", true), + new ResultPair("\"This is a phrase\"", true), + new ResultPair("\"Abigail \"", true), + new ResultPair("\"Joe & J. Harvey\" ", true), + new ResultPair("Abigail ", true), + new ResultPair("Abigail made this < abigail @ example . com >", true), + new ResultPair("Abigail(the bitch)@example.com", true), + new ResultPair("Abigail ", true), + new ResultPair("Abigail < (one) abigail (two) @(three)example . (bar) com (quz) >", true), + new ResultPair("Abigail (foo) (((baz)(nested) (comment)) ! ) < (one) abigail (two) @(three)example . (bar) com (quz) >", true), + new ResultPair("Abigail ", true), + new ResultPair("Abigail ", true), + new ResultPair("(foo) abigail@example.com", true), + new ResultPair("abigail@example.com (foo)", true), + new ResultPair("\"Abi\\\"gail\" ", true), + new ResultPair("abigail@[example.com]", true), + new ResultPair("abigail@[exa\\[ple.com]", true), + new ResultPair("abigail@[exa\\]ple.com]", true), + new ResultPair("\":sysmail\"@ Some-Group. Some-Org", true), + new ResultPair("Muhammed.(I am the greatest) Ali @(the)Vegas.WBA", true), + new ResultPair("mailbox.sub1.sub2@this-domain", true), + new ResultPair("sub-net.mailbox@sub-domain.domain", true), + new ResultPair("name:;", true), + new ResultPair("':;", true), + new ResultPair("name: ;", true), + new ResultPair("Alfred Neuman ", true), + new ResultPair("Neuman@BBN-TENEXA", true), + new ResultPair("\"George, Ted\" ", true), + new ResultPair("Wilt . (the Stilt) Chamberlain@NBA.US", true), + new ResultPair("Cruisers: Port@Portugal, Jones@SEA;", true), + new ResultPair("$@[]", true), + new ResultPair("*()@[]", true), + new ResultPair("\"quoted ( brackets\" ( a comment )@example.com", true), + new ResultPair("\"Joe & J. Harvey\"\\x0D\\x0A ", true), + new ResultPair("\"Joe &\\x0D\\x0A J. Harvey\" ", true), + new ResultPair("Gourmets: Pompous Person ,\\x0D\\x0A" + + " Childs\\@WGBH.Boston, \"Galloping Gourmet\"\\@\\x0D\\x0A" + + " ANT.Down-Under (Australian National Television),\\x0D\\x0A" + + " Cheapie\\@Discount-Liquors;", true), + new ResultPair(" Just a string", false), + new ResultPair("string", false), + new ResultPair("(comment)", false), + new ResultPair("()@example.com", false), + new ResultPair("fred(&)barny@example.com", false), + new ResultPair("fred\\ barny@example.com", false), + new ResultPair("Abigail ", false), + new ResultPair("Abigail ", false), + new ResultPair("Abigail ", false), + new ResultPair("\"Abi\"gail\" ", false), + new ResultPair("abigail@[exa]ple.com]", false), + new ResultPair("abigail@[exa[ple.com]", false), + new ResultPair("abigail@[exaple].com]", false), + new ResultPair("abigail@", false), + new ResultPair("@example.com", false), + new ResultPair("phrase: abigail@example.com abigail@example.com ;", false), + new ResultPair("invalid�char@example.com", false) + }; + + /** + * Write this test based on perl Mail::RFC822::Address + * which takes its example email address directly from RFC822 + * + * @throws ValidatorException + * + * FIXME This test fails so disable it with a leading _ for 1.1.4 release. + * The real solution is to fix the email parsing. + */ + public void _testEmailFromPerl() throws ValidatorException { + ValueBean info = new ValueBean(); + for (int index = 0; index < testEmailFromPerl.length; index++) { + info.setValue(testEmailFromPerl[index].item); + valueTest(info, testEmailFromPerl[index].valid); + } + } + + /** + * Utlity class to run a test on a value. + * + * @param info Value to run test on. + * @param passed Whether or not the test is expected to pass. + */ + private void valueTest(ValueBean info, boolean passed) throws ValidatorException { + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, info); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult result = results.getValidatorResult("value"); + + assertNotNull(ACTION + " value ValidatorResult should not be null.", result); + assertTrue("Value "+info.getValue()+" ValidatorResult should contain the '" + ACTION +"' action.", result.containsAction(ACTION)); + assertTrue("Value "+info.getValue()+"ValidatorResult for the '" + ACTION +"' action should have " + (passed ? "passed" : "failed") + ".", (passed ? result.isValid(ACTION) : !result.isValid(ACTION))); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/EntityImportTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/EntityImportTest.java new file mode 100644 index 000000000..200087176 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/EntityImportTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.net.URL; +import java.util.Locale; + + +/** + * Tests entity imports. + * + * @version $Revision$ + */ +public class EntityImportTest extends AbstractCommonTest { + + public EntityImportTest(String name) { + super(name); + } + + /** + * Tests the entity import loading the byteForm form. + */ + public void testEntityImport() throws Exception { + URL url = getClass().getResource("EntityImportTest-config.xml"); + ValidatorResources resources = new ValidatorResources(url.toExternalForm()); + assertNotNull("Form should be found", resources.getForm(Locale.getDefault(), "byteForm")); + } + + /** + * Tests loading ValidatorResources from a URL + */ + public void testParseURL() throws Exception { + URL url = getClass().getResource("EntityImportTest-config.xml"); + ValidatorResources resources = new ValidatorResources(url); + assertNotNull("Form should be found", resources.getForm(Locale.getDefault(), "byteForm")); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ExceptionTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ExceptionTest.java new file mode 100644 index 000000000..ff24b90bb --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ExceptionTest.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; + +import org.xml.sax.SAXException; + +/** + * Performs Validation Test for exception handling. + * + * @version $Revision$ + */ +public class ExceptionTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "exceptionForm"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "raiseException"; + + public ExceptionTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-exception.xml. + */ + @Override + protected void setUp() throws IOException, SAXException { + loadResources("ExceptionTest-config.xml"); + } + + /** + * Tests handling of checked exceptions - should become + * ValidatorExceptions. + */ + public void testValidatorException() { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("VALIDATOR"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, info); + + // Get results of the validation which can throw ValidatorException + try { + validator.validate(); + fail("ValidatorException should occur here!"); + } catch (ValidatorException expected) { + assertTrue("VALIDATOR-EXCEPTION".equals(expected.getMessage())); + } + } + + /** + * Tests handling of runtime exceptions. + * + * N.B. This test has been removed (renamed) as it currently + * serves no purpose. If/When exception handling + * is changed in Validator 2.0 it can be reconsidered + * then. + */ + public void XtestRuntimeException() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("RUNTIME"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, info); + + // Get results of the validation which can throw ValidatorException + try { + validator.validate(); + //fail("RuntimeException should occur here!"); + } catch (RuntimeException expected) { + fail("RuntimeExceptions should be treated as validation failures in Validator 1.x."); + // This will be true in Validator 2.0 + //assertTrue("RUNTIME-EXCEPTION".equals(expected.getMessage())); + } + } + + /** + * Tests handling of checked exceptions - should become + * ValidatorExceptions. + * + * N.B. This test has been removed (renamed) as it currently + * serves no purpose. If/When exception handling + * is changed in Validator 2.0 it can be reconsidered + * then. + */ + public void XtestCheckedException() { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("CHECKED"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, info); + + // Get results of the validation which can throw ValidatorException + + // Tests Validator 1.x exception handling + try { + validator.validate(); + } catch (ValidatorException expected) { + fail("Checked exceptions are not wrapped in ValidatorException in Validator 1.x."); + } catch (Exception e) { + assertTrue("CHECKED-EXCEPTION".equals(e.getMessage())); + } + + // This will be true in Validator 2.0 +// try { +// validator.validate(); +// fail("ValidatorException should occur here!"); +// } catch (ValidatorException expected) { +// assertTrue("CHECKED-EXCEPTION".equals(expected.getMessage())); +// } + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ExtensionTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ExtensionTest.java new file mode 100644 index 000000000..38301abba --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ExtensionTest.java @@ -0,0 +1,365 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.InputStream; + +import junit.framework.TestCase; + +/** + *

Performs tests for extension in form definitions. Performs the same tests + * RequiredNameTest does but with an equivalent validation definition with extension + * definitions (validator-extension.xml), plus an extra check on overriding rules and + * another one checking it mantains correct order when extending.

+ * + * @version $Revision$ + */ +public class ExtensionTest extends TestCase { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "nameForm"; + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY2 = "nameForm2"; + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String CHECK_MSG_KEY = "nameForm.lastname.displayname"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "required"; + + /** + * Resources used for validation tests. + */ + private ValidatorResources resources = null; + + /** + * Constructor de ExtensionTest. + * @param arg0 + */ + public ExtensionTest(String arg0) { + super(arg0); + } + + /** + * Load ValidatorResources from + * validator-extension.xml. + */ + @Override + protected void setUp() throws Exception { + // Load resources + InputStream in = null; + + try { + in = this.getClass().getResourceAsStream("ExtensionTest-config.xml"); + resources = new ValidatorResources(in); + } finally { + if (in != null) { + in.close(); + } + } + } + + @Override + protected void tearDown() { + } + + /** + * Tests the required validation failure. + */ + public void testRequired() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for first name if it is blank. + */ + public void testRequiredFirstNameBlank() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for first name. + */ + public void testRequiredFirstName() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Joe"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for last name if it is blank. + */ + public void testRequiredLastNameBlank() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setLastName(""); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for last name. + */ + public void testRequiredLastName() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setLastName("Smith"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have passed.", lastNameResult.isValid(ACTION)); + + } + + /** + * Tests the required validation for first and last name. + */ + public void testRequiredName() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Joe"); + name.setLastName("Smith"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have passed.", lastNameResult.isValid(ACTION)); + } + + + /** + * Tests if we can override a rule. We "can" override a rule if the message shown + * when the firstName required test fails and the lastName test is null. + */ + public void testOverrideRule() throws ValidatorException { + + // Create bean to run test on. + NameBean name = new NameBean(); + name.setLastName("Smith"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY2); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have '" + CHECK_MSG_KEY + " as a key.", firstNameResult.field.getArg(0).getKey().equals(CHECK_MSG_KEY)); + + assertNull("Last Name ValidatorResult should be null.", lastNameResult); + } + + + /** + * Tests if the order is mantained when extending a form. Parent form fields should + * preceed self form fields, except if we override the rules. + */ + public void testOrder() { + + Form form = resources.getForm(ValidatorResources.defaultLocale, FORM_KEY); + Form form2 = resources.getForm(ValidatorResources.defaultLocale, FORM_KEY2); + + assertNotNull(FORM_KEY + " is null.", form); + assertTrue("There should only be 2 fields in " + FORM_KEY, form.getFields().size() == 2); + + assertNotNull(FORM_KEY2 + " is null.", form2); + assertTrue("There should only be 2 fields in " + FORM_KEY2, form2.getFields().size() == 2); + + //get the first field + Field fieldFirstName = form.getFields().get(0); + //get the second field + Field fieldLastName = form.getFields().get(1); + assertTrue("firstName in " + FORM_KEY + " should be the first in the list", fieldFirstName.getKey().equals("firstName")); + assertTrue("lastName in " + FORM_KEY + " should be the first in the list", fieldLastName.getKey().equals("lastName")); + +// get the second field + fieldLastName = form2.getFields().get(0); + //get the first field + fieldFirstName = form2.getFields().get(1); + assertTrue("firstName in " + FORM_KEY2 + " should be the first in the list", fieldFirstName.getKey().equals("firstName")); + assertTrue("lastName in " + FORM_KEY2 + " should be the first in the list", fieldLastName.getKey().equals("lastName")); + + } +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/FieldTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/FieldTest.java new file mode 100644 index 000000000..06c728f20 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/FieldTest.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import junit.framework.TestCase; + +/** + * Test Field objects. + * + * @version $Revision$ + */ +public class FieldTest extends TestCase { + + + protected Field field; + + /** + * FieldTest constructor. + */ + public FieldTest() { + super(); + } + + /** + * FieldTest constructor. + * @param name + */ + public FieldTest(String name) { + super(name); + } + + /** + * Test setup + */ + @Override + public void setUp() { + field = new Field(); + } + + /** + * Test clean up + */ + @Override + public void tearDown() { + field = null; + } + + /** + * test Field with no arguments + */ + public void testEmptyArgs() { + + assertEquals("Empty Args(1) ", 0, field.getArgs("required").length); + + } + /** + * test Field with only 'default' arguments, no positions specified. + */ + public void testDefaultPositionImplied() { + + field.addArg(createArg("default-position-0")); + field.addArg(createArg("default-position-1")); + field.addArg(createArg("default-position-2")); + + assertEquals("testDefaultPositionImplied(1) ", 3, field.getArgs("required").length); + assertEquals("testDefaultPositionImplied(2) ", "default-position-0", field.getArg("required", 0).getKey()); + assertEquals("testDefaultPositionImplied(3) ", "default-position-1", field.getArg("required", 1).getKey()); + assertEquals("testDefaultPositionImplied(4) ", "default-position-2", field.getArg("required", 2).getKey()); + + } + + /** + * test Field with only 'default' arguments, positions specified. + */ + public void testDefaultUsingPositions() { + + field.addArg(createArg("default-position-1", 1)); + field.addArg(createArg("default-position-0", 0)); + field.addArg(createArg("default-position-2", 2)); + + assertEquals("testDefaultUsingPositions(1) ", 3, field.getArgs("required").length); + assertEquals("testDefaultUsingPositions(2) ", "default-position-0", field.getArg("required", 0).getKey()); + assertEquals("testDefaultUsingPositions(3) ", "default-position-1", field.getArg("required", 1).getKey()); + assertEquals("testDefaultUsingPositions(4) ", "default-position-2", field.getArg("required", 2).getKey()); + + } + + /** + * test Field with only 'default' arguments, position specified for one argument + */ + public void testDefaultOnePosition() { + + field.addArg(createArg("default-position-0")); + field.addArg(createArg("default-position-2", 2)); + field.addArg(createArg("default-position-3")); + + assertEquals("testDefaultOnePosition(1) ", 4, field.getArgs("required").length); + assertEquals("testDefaultOnePosition(2) ", "default-position-0", field.getArg("required", 0).getKey()); + assertNull("testDefaultOnePosition(3) ", field.getArg("required", 1)); + assertEquals("testDefaultOnePosition(4) ", "default-position-2", field.getArg("required", 2).getKey()); + assertEquals("testDefaultOnePosition(5) ", "default-position-3", field.getArg("required", 3).getKey()); + + } + + /** + * test Field with only 'default' arguments, some position specified. + */ + public void testDefaultSomePositions() { + + field.addArg(createArg("default-position-0")); + field.addArg(createArg("default-position-2", 2)); + field.addArg(createArg("default-position-3")); + field.addArg(createArg("default-position-1", 1)); + + assertEquals("testDefaultSomePositions(1) ", 4, field.getArgs("required").length); + assertEquals("testDefaultSomePositions(2) ", "default-position-0", field.getArg("required", 0).getKey()); + assertEquals("testDefaultSomePositions(3) ", "default-position-1", field.getArg("required", 1).getKey()); + assertEquals("testDefaultSomePositions(4) ", "default-position-2", field.getArg("required", 2).getKey()); + assertEquals("testDefaultSomePositions(5) ", "default-position-3", field.getArg("required", 3).getKey()); + + } + + /** + * test Field with a 'default' argument overriden using 'position' property + */ + public void testOverrideUsingPositionA() { + + field.addArg(createArg("default-position-0")); + field.addArg(createArg("default-position-1")); + field.addArg(createArg("default-position-2")); + field.addArg(createArg("required-position-1", "required", 1)); + + // use 'required' as name + assertEquals("testOverrideUsingPositionA(1) ", 3, field.getArgs("required").length); + assertEquals("testOverrideUsingPositionA(2) ", "required-position-1", field.getArg("required", 1).getKey()); + + // use 'mask' as name + assertEquals("testOverrideUsingPositionA(3) ", 3, field.getArgs("mask").length); + assertEquals("testOverrideUsingPositionA(4) ", "default-position-1", field.getArg("mask", 1).getKey()); + + // Get Default + assertEquals("testOverrideUsingPositionA(5) ", "default-position-1", field.getArg(1).getKey()); + + } + + /** + * test Field with a 'default' argument overriden using 'position' property + */ + public void testOverrideUsingPositionB() { + + field.addArg(createArg("required-position-3", "required", 3)); + field.addArg(createArg("required-position-1", "required", 1)); + field.addArg(createArg("default-position-0")); + field.addArg(createArg("default-position-1")); + field.addArg(createArg("default-position-2")); + + // use 'required' as name + assertEquals("testOverrideUsingPositionB(1) ", 4, field.getArgs("required").length); + assertEquals("testOverrideUsingPositionB(2) ", "default-position-0", field.getArg("required", 0).getKey()); + assertEquals("testOverrideUsingPositionB(3) ", "required-position-1", field.getArg("required", 1).getKey()); + assertEquals("testOverrideUsingPositionB(4) ", "default-position-2", field.getArg("required", 2).getKey()); + assertEquals("testOverrideUsingPositionB(5) ", "required-position-3", field.getArg("required", 3).getKey()); + + // use 'mask' as name + assertEquals("testOverrideUsingPositionB(6) ", 4, field.getArgs("mask").length); + assertEquals("testOverrideUsingPositionB(6) ", "default-position-0", field.getArg("mask", 0).getKey()); + assertEquals("testOverrideUsingPositionB(7) ", "default-position-1", field.getArg("mask", 1).getKey()); + assertEquals("testOverrideUsingPositionB(8) ", "default-position-2", field.getArg("mask", 2).getKey()); + assertNull("testOverrideUsingPositionB(9) ", field.getArg("mask", 3)); + + } + + /** + * test Field with a 'default' argument overriden without positions specified. + */ + public void testOverridePositionImplied() { + + field.addArg(createArg("default-position-0")); + field.addArg(createArg("required-position-1", "required")); + field.addArg(createArg("required-position-2", "required")); + field.addArg(createArg("mask-position-1", "mask")); + + // use 'required' as name + assertEquals("testOverridePositionImplied(1) ", 3, field.getArgs("required").length); + assertEquals("testOverridePositionImplied(2) ", "default-position-0", field.getArg("required", 0).getKey()); + assertEquals("testOverridePositionImplied(3) ", "required-position-1", field.getArg("required", 1).getKey()); + assertEquals("testOverridePositionImplied(4) ", "required-position-2", field.getArg("required", 2).getKey()); + + // use 'mask' as name + assertEquals("testOverridePositionImplied(5) ", 3, field.getArgs("mask").length); + assertEquals("testOverridePositionImplied(6) ", "default-position-0", field.getArg("mask", 0).getKey()); + assertEquals("testOverridePositionImplied(7) ", "mask-position-1", field.getArg("mask", 1).getKey()); + assertNull("testOverridePositionImplied(8) ", field.getArg("mask", 2)); + + // Get Defaults + assertEquals("testOverridePositionImplied(9) ", "default-position-0", field.getArg(0).getKey()); + assertNull("testOverridePositionImplied(10) ", field.getArg(1)); + assertNull("testOverridePositionImplied(11) ", field.getArg(2)); + + } + + /** + * test Field with a 'default' argument overriden with some positions specified + */ + public void testOverrideSomePosition() { + + field.addArg(createArg("default-position-0")); + field.addArg(createArg("default-position-1")); + field.addArg(createArg("default-position-2")); + field.addArg(createArg("required-position-1", "required", 1)); + field.addArg(createArg("required-position-2", "required")); + field.addArg(createArg("mask-position-3", "mask")); + + // use 'required' as name + assertEquals("testOverrideSomePosition(1) ", 4, field.getArgs("required").length); + assertEquals("testOverrideSomePosition(2) ", "default-position-0", field.getArg("required", 0).getKey()); + assertEquals("testOverrideSomePosition(3) ", "required-position-1", field.getArg("required", 1).getKey()); + assertEquals("testOverrideSomePosition(4) ", "required-position-2", field.getArg("required", 2).getKey()); + assertNull("testOverrideSomePosition(5) ", field.getArg("required", 3)); + + // use 'mask' as name + assertEquals("testOverrideSomePosition(6) ", 4, field.getArgs("mask").length); + assertEquals("testOverrideSomePosition(7) ", "default-position-0", field.getArg("mask", 0).getKey()); + assertEquals("testOverrideSomePosition(8) ", "default-position-1", field.getArg("mask", 1).getKey()); + assertEquals("testOverrideSomePosition(9) ", "default-position-2", field.getArg("mask", 2).getKey()); + assertEquals("testOverrideSomePosition(10) ", "mask-position-3", field.getArg("mask", 3).getKey()); + + // Get Defaults + assertEquals("testOverrideSomePosition(11) ", "default-position-0", field.getArg(0).getKey()); + assertEquals("testOverrideSomePosition(12) ", "default-position-1", field.getArg(1).getKey()); + assertEquals("testOverrideSomePosition(13) ", "default-position-2", field.getArg(2).getKey()); + assertNull("testOverrideSomePosition(14) ", field.getArg(3)); + + } + + /** + * Convenience Method - create argument (no name or position specified) + */ + private Arg createArg(String key) { + Arg arg = new Arg(); + arg.setKey(key); + return arg; + } + + /** + * Convenience Method - create argument (no name, position specified) + */ + private Arg createArg(String key, int position) { + Arg arg = createArg(key); + arg.setPosition(position); + return arg; + } + + /** + * Convenience Method - create argument (name specified, no position) + */ + private Arg createArg(String key, String name) { + Arg arg = createArg(key); + arg.setName(name); + return arg; + } + + /** + * Convenience Method - create argument (name & position specified) + */ + private Arg createArg(String key, String name, int position) { + Arg arg = createArg(key, name); + arg.setPosition(position); + return arg; + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/FloatTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/FloatTest.java new file mode 100644 index 000000000..8b57a27f7 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/FloatTest.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.validator; + + +/** + * Performs Validation Test for float validations. + * + * @version $Revision$ + */ +public class FloatTest extends AbstractNumberTest { + + public FloatTest(String name) { + super(name); + ACTION = "float"; + FORM_KEY = "floatForm"; + } + + /** + * Tests the float validation. + */ + public void testFloat() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("0"); + + valueTest(info, true); + } + + /** + * Tests the float validation. + */ + public void testFloatMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Float.valueOf(Float.MIN_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the float validation. + */ + public void testFloatMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Float.valueOf(Float.MAX_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the float validation failure. + */ + public void testFloatFailure() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + valueTest(info, false); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericTypeValidatorImpl.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericTypeValidatorImpl.java new file mode 100644 index 000000000..fd5c272b1 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericTypeValidatorImpl.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.validator; + +import java.util.*; + +import org.apache.commons.validator.util.ValidatorUtils; + +/** + * Contains validation methods for different unit tests. + * + * @version $Revision$ + */ +public class GenericTypeValidatorImpl { + + /** + * Checks if the field can be successfully converted to a byte. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a byte true is returned. + * Otherwise false. + */ + public static Byte validateByte(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatByte(value); + } + + /** + * Checks if the field can be successfully converted to a byte. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a byte true is returned. + * Otherwise false. + */ + public static Byte validateByte(Object bean, Field field, Locale locale) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatByte(value, locale); + } + + /** + * Checks if the field can be successfully converted to a short. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a short true is returned. + * Otherwise false. + */ + public static Short validateShort(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatShort(value); + } + + /** + * Checks if the field can be successfully converted to a short. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a short true is returned. + * Otherwise false. + */ + public static Short validateShort(Object bean, Field field, Locale locale) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatShort(value, locale); + } + + /** + * Checks if the field can be successfully converted to a int. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a int true is returned. + * Otherwise false. + */ + public static Integer validateInt(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatInt(value); + } + + /** + * Checks if the field can be successfully converted to a int. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a int true is returned. + * Otherwise false. + */ + public static Integer validateInt(Object bean, Field field, Locale locale) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatInt(value, locale); + } + + /** + * Checks if the field can be successfully converted to a long. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a long true is returned. + * Otherwise false. + */ + public static Long validateLong(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatLong(value); + } + + /** + * Checks if the field can be successfully converted to a long. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a long true is returned. + * Otherwise false. + */ + public static Long validateLong(Object bean, Field field, Locale locale) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatLong(value, locale); + } + + /** + * Checks if the field can be successfully converted to a float. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a float true is returned. + * Otherwise false. + */ + public static Float validateFloat(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatFloat(value); + } + + /** + * Checks if the field can be successfully converted to a float. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a float true is returned. + * Otherwise false. + */ + public static Float validateFloat(Object bean, Field field, Locale locale) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatFloat(value, locale); + } + + /** + * Checks if the field can be successfully converted to a double. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a double true is returned. + * Otherwise false. + */ + public static Double validateDouble(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatDouble(value); + } + + /** + * Checks if the field can be successfully converted to a double. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a double true is returned. + * Otherwise false. + */ + public static Double validateDouble(Object bean, Field field, Locale locale) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatDouble(value, locale); + } + + /** + * Checks if the field can be successfully converted to a date. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a date true is returned. + * Otherwise false. + */ + public static Date validateDate(Object bean, Field field, Locale locale) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatDate(value, locale); + } + + /** + * Checks if the field can be successfully converted to a date. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a date true is returned. + * Otherwise false. + */ + public static Date validateDate(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + String datePattern = field.getVarValue("datePattern"); + String datePatternStrict = field.getVarValue("datePatternStrict"); + + Date result = null; + if (datePattern != null && datePattern.length() > 0) { + result = GenericTypeValidator.formatDate(value, datePattern, false); + } else if (datePatternStrict != null && datePatternStrict.length() > 0) { + result = GenericTypeValidator.formatDate(value, datePatternStrict, true); + } + + return result; + } +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericTypeValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericTypeValidatorTest.java new file mode 100644 index 000000000..6014a5523 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericTypeValidatorTest.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; +import java.util.Date; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import org.xml.sax.SAXException; + +/** + * Performs Validation Test for type validations. + * + * @version $Revision$ + */ +public class GenericTypeValidatorTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "typeForm"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "byte"; + + public GenericTypeValidatorTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-type.xml. + */ + @Override +protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("GenericTypeValidatorTest-config.xml"); + } + + @Override +protected void tearDown() { + } + + /** + * Tests the byte validation. + */ + public void testType() throws ValidatorException { + // Create bean to run test on. + TypeBean info = new TypeBean(); + info.setByte("12"); + info.setShort("129"); + info.setInteger("-144"); + info.setLong("88000"); + info.setFloat("12.1555f"); + info.setDouble("129.1551511111d"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, info); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + Map hResultValues = results.getResultValueMap(); + + assertTrue("Expecting byte result to be an instance of Byte.", (hResultValues.get("byte") instanceof Byte)); + assertTrue("Expecting short result to be an instance of Short.", (hResultValues.get("short") instanceof Short)); + assertTrue("Expecting integer result to be an instance of Integer.", (hResultValues.get("integer") instanceof Integer)); + assertTrue("Expecting long result to be an instance of Long.", (hResultValues.get("long") instanceof Long)); + assertTrue("Expecting float result to be an instance of Float.", (hResultValues.get("float") instanceof Float)); + assertTrue("Expecting double result to be an instance of Double.", (hResultValues.get("double") instanceof Double)); + + for (Iterator i = hResultValues.keySet().iterator(); i.hasNext(); ) { + String key = i.next(); + Object value = hResultValues.get(key); + + assertNotNull("value ValidatorResults.getResultValueMap() should not be null.", value); + } + + //ValidatorResult result = results.getValidatorResult("value"); + + //assertNotNull(ACTION + " value ValidatorResult should not be null.", result); + //assertTrue(ACTION + " value ValidatorResult should contain the '" + ACTION +"' action.", result.containsAction(ACTION)); + //assertTrue(ACTION + " value ValidatorResult for the '" + ACTION +"' action should have " + (passed ? "passed" : "failed") + ".", (passed ? result.isValid(ACTION) : !result.isValid(ACTION))); + + } + + /** + * Tests the us locale + */ + public void testUSLocale() throws ValidatorException { + // Create bean to run test on. + TypeBean info = new TypeBean(); + info.setByte("12"); + info.setShort("129"); + info.setInteger("-144"); + info.setLong("88000"); + info.setFloat("12.1555"); + info.setDouble("129.1551511111"); + info.setDate("12/21/2010"); + localeTest(info, Locale.US); + } + + /** + * Tests the fr locale. + */ + public void testFRLocale() throws ValidatorException { + // Create bean to run test on. + TypeBean info = new TypeBean(); + info.setByte("12"); + info.setShort("-129"); + info.setInteger("1443"); + info.setLong("88000"); + info.setFloat("12,1555"); + info.setDouble("129,1551511111"); + info.setDate("21/12/2010"); + Map map = localeTest(info, Locale.FRENCH); + assertTrue("float value not correct", ((Float)map.get("float")).intValue() == 12); + assertTrue("double value not correct", ((Double)map.get("double")).intValue() == 129); + } + + /** + * Tests the locale. + */ + private Map localeTest(TypeBean info, Locale locale) throws ValidatorException { + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, "typeLocaleForm"); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, info); + validator.setParameter("java.util.Locale", locale); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + Map hResultValues = results.getResultValueMap(); + + assertTrue("Expecting byte result to be an instance of Byte for locale: "+locale, (hResultValues.get("byte") instanceof Byte)); + assertTrue("Expecting short result to be an instance of Short for locale: "+locale, (hResultValues.get("short") instanceof Short)); + assertTrue("Expecting integer result to be an instance of Integer for locale: "+locale, (hResultValues.get("integer") instanceof Integer)); + assertTrue("Expecting long result to be an instance of Long for locale: "+locale, (hResultValues.get("long") instanceof Long)); + assertTrue("Expecting float result to be an instance of Float for locale: "+locale, (hResultValues.get("float") instanceof Float)); + assertTrue("Expecting double result to be an instance of Double for locale: "+locale, (hResultValues.get("double") instanceof Double)); + assertTrue("Expecting date result to be an instance of Date for locale: "+locale, (hResultValues.get("date") instanceof Date)); + + for (Iterator i = hResultValues.keySet().iterator(); i.hasNext(); ) { + String key = i.next(); + Object value = hResultValues.get(key); + + assertNotNull("value ValidatorResults.getResultValueMap() should not be null for locale: "+locale, value); + } + return hResultValues; + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericValidatorImpl.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericValidatorImpl.java new file mode 100644 index 000000000..9eb7b8635 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericValidatorImpl.java @@ -0,0 +1,275 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import org.apache.commons.validator.util.ValidatorUtils; + +/** + * Contains validation methods for different unit tests. + * + * @version $Revision$ + */ +public class GenericValidatorImpl { + + /** + * Throws a runtime exception if the value of the argument is "RUNTIME", + * an exception if the value of the argument is "CHECKED", and a + * ValidatorException otherwise. + * + * @throws RuntimeException with "RUNTIME-EXCEPTION as message" + * if value is "RUNTIME" + * @throws Exception with "CHECKED-EXCEPTION" as message + * if value is "CHECKED" + * @throws ValidatorException with "VALIDATOR-EXCEPTION" as message + * otherwise + */ + public static boolean validateRaiseException( + final Object bean, + final Field field) + throws Exception { + + final String value = + ValidatorUtils.getValueAsString(bean, field.getProperty()); + + if ("RUNTIME".equals(value)) { + throw new RuntimeException("RUNTIME-EXCEPTION"); + + } else if ("CHECKED".equals(value)) { + throw new Exception("CHECKED-EXCEPTION"); + + } else { + throw new ValidatorException("VALIDATOR-EXCEPTION"); + } + } + + /** + * Checks if the field is required. + * + * @return boolean If the field isn't null and + * has a length greater than zero, true is returned. + * Otherwise false. + */ + public static boolean validateRequired(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return !GenericValidator.isBlankOrNull(value); + } + + /** + * Checks if the field can be successfully converted to a byte. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a byte true is returned. + * Otherwise false. + */ + public static boolean validateByte(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericValidator.isByte(value); + } + + /** + * Checks if the field can be successfully converted to a short. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a short true is returned. + * Otherwise false. + */ + public static boolean validateShort(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericValidator.isShort(value); + } + + /** + * Checks if the field can be successfully converted to a int. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a int true is returned. + * Otherwise false. + */ + public static boolean validateInt(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericValidator.isInt(value); + } + + /** + * Checks if field is positive assuming it is an integer + * + * @param bean The value validation is being performed on. + * @param field Description of the field to be evaluated + * @return boolean If the integer field is greater than zero, returns + * true, otherwise returns false. + */ + public static boolean validatePositive(Object bean , Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericTypeValidator.formatInt(value).intValue() > 0; + } + + /** + * Checks if the field can be successfully converted to a long. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a long true is returned. + * Otherwise false. + */ + public static boolean validateLong(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericValidator.isLong(value); + } + + /** + * Checks if the field can be successfully converted to a float. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a float true is returned. + * Otherwise false. + */ + public static boolean validateFloat(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericValidator.isFloat(value); + } + + /** + * Checks if the field can be successfully converted to a double. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field can be successfully converted + * to a double true is returned. + * Otherwise false. + */ + public static boolean validateDouble(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericValidator.isDouble(value); + } + + /** + * Checks if the field is an e-mail address. + * + * @param bean The value validation is being performed on. + * @param field the field to use + * @return boolean If the field is an e-mail address + * true is returned. + * Otherwise false. + */ + public static boolean validateEmail(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + return GenericValidator.isEmail(value); + } + + public final static String FIELD_TEST_NULL = "NULL"; + public final static String FIELD_TEST_NOTNULL = "NOTNULL"; + public final static String FIELD_TEST_EQUAL = "EQUAL"; + + public static boolean validateRequiredIf( + Object bean, + Field field, + Validator validator) { + + Object form = validator.getParameterValue(Validator.BEAN_PARAM); + String value = null; + boolean required = false; + if (isStringOrNull(bean)) { + value = (String) bean; + } else { + value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + } + int i = 0; + String fieldJoin = "AND"; + if (!GenericValidator.isBlankOrNull(field.getVarValue("fieldJoin"))) { + fieldJoin = field.getVarValue("fieldJoin"); + } + if (fieldJoin.equalsIgnoreCase("AND")) { + required = true; + } + while (!GenericValidator.isBlankOrNull(field.getVarValue("field[" + i + "]"))) { + String dependProp = field.getVarValue("field[" + i + "]"); + String dependTest = field.getVarValue("fieldTest[" + i + "]"); + String dependTestValue = field.getVarValue("fieldValue[" + i + "]"); + String dependIndexed = field.getVarValue("fieldIndexed[" + i + "]"); + if (dependIndexed == null) { + dependIndexed = "false"; + } + String dependVal = null; + boolean this_required = false; + if (field.isIndexed() && dependIndexed.equalsIgnoreCase("true")) { + String key = field.getKey(); + if ((key.contains("[")) && (key.contains("]"))) { + String ind = key.substring(0, key.indexOf(".") + 1); + dependProp = ind + dependProp; + } + } + dependVal = ValidatorUtils.getValueAsString(form, dependProp); + if (dependTest.equals(FIELD_TEST_NULL)) { + if ((dependVal != null) && (dependVal.length() > 0)) { + this_required = false; + } else { + this_required = true; + } + } + if (dependTest.equals(FIELD_TEST_NOTNULL)) { + if ((dependVal != null) && (dependVal.length() > 0)) { + this_required = true; + } else { + this_required = false; + } + } + if (dependTest.equals(FIELD_TEST_EQUAL)) { + this_required = dependTestValue.equalsIgnoreCase(dependVal); + } + if (fieldJoin.equalsIgnoreCase("AND")) { + required = required && this_required; + } else { + required = required || this_required; + } + i++; + } + if (required) { + if ((value != null) && (value.length() > 0)) { + return true; + } else { + return false; + } + } + return true; + } + + private static boolean isStringOrNull(Object o) { + if (o == null) { + return true; // TODO this condition is not exercised by any tests currently + } + return (o instanceof String); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericValidatorTest.java new file mode 100644 index 000000000..782b935ce --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericValidatorTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import junit.framework.TestCase; + +/** + * Test the GenericValidator class. + * + * @version $Revision$ + */ +public class GenericValidatorTest extends TestCase { + + /** + * Constructor for GenericValidatorTest. + */ + public GenericValidatorTest(String name) { + super(name); + } + + public void testMinLength() { + + // Use 0 for line end length + assertTrue("Min=5 End=0", GenericValidator.minLength("12345\n\r", 5, 0)); + assertFalse("Min=6 End=0", GenericValidator.minLength("12345\n\r", 6, 0)); + assertFalse("Min=7 End=0", GenericValidator.minLength("12345\n\r", 7, 0)); + assertFalse("Min=8 End=0", GenericValidator.minLength("12345\n\r", 8, 0)); + + // Use 1 for line end length + assertTrue("Min=5 End=1", GenericValidator.minLength("12345\n\r", 5, 1)); + assertTrue("Min=6 End=1", GenericValidator.minLength("12345\n\r", 6, 1)); + assertFalse("Min=7 End=1", GenericValidator.minLength("12345\n\r", 7, 1)); + assertFalse("Min=8 End=1", GenericValidator.minLength("12345\n\r", 8, 1)); + + // Use 2 for line end length + assertTrue("Min=5 End=2", GenericValidator.minLength("12345\n\r", 5, 2)); + assertTrue("Min=6 End=2", GenericValidator.minLength("12345\n\r", 6, 2)); + assertTrue("Min=7 End=2", GenericValidator.minLength("12345\n\r", 7, 2)); + assertFalse("Min=8 End=2", GenericValidator.minLength("12345\n\r", 8, 2)); + } + + public void testMaxLength() { + + // Use 0 for line end length + assertFalse("Max=4 End=0", GenericValidator.maxLength("12345\n\r", 4, 0)); + assertTrue("Max=5 End=0", GenericValidator.maxLength("12345\n\r", 5, 0)); + assertTrue("Max=6 End=0", GenericValidator.maxLength("12345\n\r", 6, 0)); + assertTrue("Max=7 End=0", GenericValidator.maxLength("12345\n\r", 7, 0)); + + // Use 1 for line end length + assertFalse("Max=4 End=1", GenericValidator.maxLength("12345\n\r", 4, 1)); + assertFalse("Max=5 End=1", GenericValidator.maxLength("12345\n\r", 5, 1)); + assertTrue("Max=6 End=1", GenericValidator.maxLength("12345\n\r", 6, 1)); + assertTrue("Max=7 End=1", GenericValidator.maxLength("12345\n\r", 7, 1)); + + // Use 2 for line end length + assertFalse("Max=4 End=2", GenericValidator.maxLength("12345\n\r", 4, 2)); + assertFalse("Max=5 End=2", GenericValidator.maxLength("12345\n\r", 5, 2)); + assertFalse("Max=6 End=2", GenericValidator.maxLength("12345\n\r", 6, 2)); + assertTrue("Max=7 End=2", GenericValidator.maxLength("12345\n\r", 7, 2)); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ISBNValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ISBNValidatorTest.java new file mode 100644 index 000000000..514b57693 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ISBNValidatorTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import junit.framework.TestCase; + +/** + * ISBNValidator Test Case. + * + * @version $Revision$ + * @deprecated to be removed when the org.apache.commons.validator.ISBNValidator class is removed + */ +@Deprecated +public class ISBNValidatorTest extends TestCase { + + private static final String VALID_ISBN_RAW = "1930110995"; + private static final String VALID_ISBN_DASHES = "1-930110-99-5"; + private static final String VALID_ISBN_SPACES = "1 930110 99 5"; + private static final String VALID_ISBN_X = "0-201-63385-X"; + private static final String INVALID_ISBN = "068-556-98-45"; + + public ISBNValidatorTest(String name) { + super(name); + } + + public void testIsValid() throws Exception { + ISBNValidator validator = new ISBNValidator(); + assertFalse(validator.isValid(null)); + assertFalse(validator.isValid("")); + assertFalse(validator.isValid("1")); + assertFalse(validator.isValid("12345678901234")); + assertFalse(validator.isValid("dsasdsadsads")); + assertFalse(validator.isValid("535365")); + assertFalse(validator.isValid("I love sparrows!")); + assertFalse(validator.isValid("--1 930110 99 5")); + assertFalse(validator.isValid("1 930110 99 5--")); + assertFalse(validator.isValid("1 930110-99 5-")); + + assertTrue(validator.isValid(VALID_ISBN_RAW)); + assertTrue(validator.isValid(VALID_ISBN_DASHES)); + assertTrue(validator.isValid(VALID_ISBN_SPACES)); + assertTrue(validator.isValid(VALID_ISBN_X)); + assertFalse(validator.isValid(INVALID_ISBN)); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/IntegerTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/IntegerTest.java new file mode 100644 index 000000000..14806a771 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/IntegerTest.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.validator; + + + + +/** + * Performs Validation Test for int validations. + * + * @version $Revision$ + */ +public class IntegerTest extends AbstractNumberTest { + + + public IntegerTest(String name) { + super(name); + FORM_KEY = "intForm"; + ACTION = "int"; + } + + /** + * Tests the int validation. + */ + public void testInt() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("0"); + + valueTest(info, true); + } + + /** + * Tests the int validation. + */ + public void testIntMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Integer.valueOf(Integer.MIN_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the int validation. + */ + public void testIntegerMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Integer.valueOf(Integer.MAX_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the int validation failure. + */ + public void testIntFailure() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + valueTest(info, false); + } + + /** + * Tests the int validation failure. + */ + public void testIntBeyondMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Integer.MIN_VALUE + "1"); + + valueTest(info, false); + } + + /** + * Tests the int validation failure. + */ + public void testIntBeyondMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Integer.MAX_VALUE + "1"); + + valueTest(info, false); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/LocaleTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/LocaleTest.java new file mode 100644 index 000000000..95c933aec --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/LocaleTest.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; +import java.util.Locale; + +import org.xml.sax.SAXException; + +/** + * Performs Validation Test for locale validations. + * + * @version $Revision$ + */ +public class LocaleTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation rules from the xml file. + */ + protected static String FORM_KEY = "nameForm"; + + /** The key used to retrieve the validator action. */ + protected static String ACTION = "required"; + + /** + * Constructor for the LocaleTest object + * + * @param name param + */ + public LocaleTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from validator-locale.xml. + * + * @throws IOException If something goes wrong + * @throws SAXException If something goes wrong + */ + @Override + protected void setUp() + throws IOException, SAXException { + // Load resources + loadResources("LocaleTest-config.xml"); + } + + /** The teardown method for JUnit */ + @Override + protected void tearDown() { + } + + /** + * See what happens when we try to validate with a Locale, Country and + * variant. Also check if the added locale validation field is getting used. + * + * @throws ValidatorException If something goes wrong + */ + public void testLocale1() + throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + name.setLastName(""); + + valueTest(name, new Locale("en", "US", "TEST1"), false, false, false); + } + + /** + * See what happens when we try to validate with a Locale, Country and + * variant + * + * @throws ValidatorException If something goes wrong + */ + public void testLocale2() + throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + name.setLastName(""); + + valueTest(name, new Locale("en", "US", "TEST2"), true, false, true); + } + + /** + * See what happens when we try to validate with a Locale, Country and + * variant + * + * @throws ValidatorException If something goes wrong + */ + public void testLocale3() + throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + name.setLastName(""); + + valueTest(name, new Locale("en", "UK"), false, true, true); + } + + /** + * See if a locale of en_UK_TEST falls back to en_UK instead of default form + * set. Bug #16920 states that this isn't happening, even though it is + * passing this test. see #16920. + * + * @throws ValidatorException If something goes wrong + */ + public void testLocale4() + throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + name.setLastName(""); + + valueTest(name, new Locale("en", "UK", "TEST"), false, true, true); + } + + /** + * See if a locale of language=en falls back to default form set. + * + * @throws ValidatorException If something goes wrong + */ + public void testLocale5() + throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + name.setLastName(""); + + valueTest(name, new Locale("en", ""), false, false, true); + } + + /** + * Utlity class to run a test on a value. + * + * @param name param + * @param loc param + * @param firstGood param + * @param lastGood param + * @param middleGood param + * @throws ValidatorException If something goes wrong + */ + private void valueTest(Object name, Locale loc, boolean firstGood, boolean lastGood, boolean middleGood) + throws ValidatorException { + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + validator.setParameter(Validator.LOCALE_PARAM, loc); + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult resultlast = results.getValidatorResult("lastName"); + ValidatorResult resultfirst = results.getValidatorResult("firstName"); + ValidatorResult resultmiddle = results.getValidatorResult("middleName"); + + if (firstGood) { + assertNull(resultfirst); + } + else { + assertNotNull(resultfirst); + } + + if (middleGood) { + assertNull(resultmiddle); + } + else { + assertNotNull(resultmiddle); + } + + if (lastGood) { + assertNull(resultlast); + } + else { + assertNotNull(resultlast); + } + } +} + diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/LongTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/LongTest.java new file mode 100644 index 000000000..09660af54 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/LongTest.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.validator; + + +/** + * Performs Validation Test for long validations. + * + * @version $Revision$ + */ +public class LongTest extends AbstractNumberTest { + + public LongTest(String name) { + super(name); + FORM_KEY = "longForm"; + ACTION = "long"; + } + + /** + * Tests the long validation. + */ + public void testLong() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue("0"); + + valueTest(info, true); + } + + /** + * Tests the long validation. + */ + public void testLongMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Long.valueOf(Long.MIN_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the long validation. + */ + public void testLongMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Long.valueOf(Long.MAX_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the long validation failure. + */ + public void testLongFailure() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + + valueTest(info, false); + } + + /** + * Tests the long validation failure. + */ + public void testLongBeyondMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Long.MIN_VALUE + "1"); + + valueTest(info, false); + } + + /** + * Tests the long validation failure. + */ + public void testLongBeyondMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Long.MAX_VALUE + "1"); + + valueTest(info, false); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/MultipleConfigFilesTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/MultipleConfigFilesTest.java new file mode 100644 index 000000000..67bef7fcc --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/MultipleConfigFilesTest.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.xml.sax.SAXException; + +/** + * Tests that validator rules split between 2 different XML files get + * merged properly. + * + * @version $Revision$ + */ +public class MultipleConfigFilesTest extends TestCase { + + /** + * Resources used for validation tests. + */ + private ValidatorResources resources = null; + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + private static final String FORM_KEY = "nameForm"; + + /** + * The key used to retrieve the validator action. + */ + private static final String ACTION = "required"; + + /** + * Constructor for MultipleConfigFilesTest. + * @param name + */ + public MultipleConfigFilesTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from multiple xml files. + */ + @Override + protected void setUp() throws IOException, SAXException { + InputStream[] streams = + new InputStream[] { + this.getClass().getResourceAsStream( + "MultipleConfigFilesTest-1-config.xml"), + this.getClass().getResourceAsStream( + "MultipleConfigFilesTest-2-config.xml")}; + + this.resources = new ValidatorResources(streams); + + for (int i = 0; i < streams.length; i++) { + streams[i].close(); + } + } + + /** + * Check the forms and constants from different config files have + * been merged into the same FormSet. + */ + public void testMergedConfig() { + + // *********** Default Locale ******************* + + // Check the form from the first config file exists + Form form1 = resources.getForm("", "", "", "testForm1"); + assertNotNull("Form 'testForm1' not found", form1); + + // Check the form from the second config file exists + Form form2 = resources.getForm("", "", "", "testForm2"); + assertNotNull("Form 'testForm2' not found", form2); + + // Check the Constants for the form from the first config file + Field field1 = form1.getField("testProperty1"); + assertEquals("testProperty1 - const 1", "testConstValue1", field1.getVarValue("var11")); + assertEquals("testProperty1 - const 2", "testConstValue2", field1.getVarValue("var12")); + + // Check the Constants for the form from the second config file + Field field2 = form2.getField("testProperty2"); + assertEquals("testProperty2 - const 1", "testConstValue1", field2.getVarValue("var21")); + assertEquals("testProperty2 - const 2", "testConstValue2", field2.getVarValue("var22")); + + // *********** 'fr' locale ******************* + + // Check the form from the first config file exists + Form form1_fr = resources.getForm("fr", "", "", "testForm1_fr"); + assertNotNull("Form 'testForm1_fr' not found", form1_fr); + + // Check the form from the second config file exists + Form form2_fr = resources.getForm("fr", "", "", "testForm2_fr"); + assertNotNull("Form 'testForm2_fr' not found", form2_fr); + + // Check the Constants for the form from the first config file + Field field1_fr = form1_fr.getField("testProperty1_fr"); + assertEquals("testProperty1_fr - const 1", "testConstValue1_fr", field1_fr.getVarValue("var11_fr")); + assertEquals("testProperty1_fr - const 2", "testConstValue2_fr", field1_fr.getVarValue("var12_fr")); + + // Check the Constants for the form from the second config file + Field field2_fr = form2_fr.getField("testProperty2_fr"); + assertEquals("testProperty2_fr - const 1", "testConstValue1_fr", field2_fr.getVarValue("var21_fr")); + assertEquals("testProperty2_fr - const 2", "testConstValue2_fr", field2_fr.getVarValue("var22_fr")); + } + + /** + * With nothing provided, we should fail both because both are required. + */ + public void testBothBlank() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull(firstNameResult); + assertTrue(firstNameResult.containsAction(ACTION)); + assertTrue(!firstNameResult.isValid(ACTION)); + + assertNotNull(lastNameResult); + assertTrue(lastNameResult.containsAction(ACTION)); + assertTrue(!lastNameResult.isValid(ACTION)); + assertTrue(!lastNameResult.containsAction("int")); + } + + /** + * If the first name fails required, and the second test fails int, we should get two errors. + */ + public void testRequiredFirstNameBlankLastNameShort() + throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + name.setLastName("Test"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull(firstNameResult); + assertTrue(firstNameResult.containsAction(ACTION)); + assertTrue(!firstNameResult.isValid(ACTION)); + + assertNotNull(lastNameResult); + assertTrue(lastNameResult.containsAction("int")); + assertTrue(!lastNameResult.isValid("int")); + } + + /** + * If the first name is there, and the last name fails int, we should get one error. + */ + public void testRequiredLastNameShort() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Test"); + name.setLastName("Test"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull(firstNameResult); + assertTrue(firstNameResult.containsAction(ACTION)); + assertTrue(firstNameResult.isValid(ACTION)); + + assertNotNull(lastNameResult); + assertTrue(lastNameResult.containsAction("int")); + assertTrue(!lastNameResult.isValid("int")); + } + + /** + * If first name is ok and last name is ok and is an int, no errors. + */ + public void testRequiredLastNameLong() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Joe"); + name.setLastName("12345678"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull(firstNameResult); + assertTrue(firstNameResult.containsAction(ACTION)); + assertTrue(firstNameResult.isValid(ACTION)); + + assertNotNull(lastNameResult); + assertTrue(lastNameResult.containsAction("int")); + assertTrue(lastNameResult.isValid("int")); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/MultipleTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/MultipleTest.java new file mode 100644 index 000000000..b6d6446e5 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/MultipleTest.java @@ -0,0 +1,356 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; + +import org.xml.sax.SAXException; + +/** + * Performs Validation Test. + * + * @version $Revision$ + */ +public class MultipleTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "nameForm"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "required"; + + + + public MultipleTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-multipletest.xml. + */ + @Override +protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("MultipleTests-config.xml"); + } + + @Override +protected void tearDown() { + } + + /** + * With nothing provided, we should fail both because both are required. + */ + public void testBothBlank() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + assertTrue("Last Name ValidatorResults should not contain the 'int' action.", !lastNameResult.containsAction("int")); + } + + /** + * If the first name fails required, and the second test fails int, we should get two errors. + */ + public void testRequiredFirstNameBlankLastNameShort() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + name.setLastName("Test"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the 'int' action.", lastNameResult.containsAction("int")); + assertTrue("Last Name ValidatorResult for the 'int' action should have failed.", !lastNameResult.isValid("int")); + } + + /** + * If the first name is there, and the last name fails int, we should get one error. + */ + public void testRequiredLastNameShort() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Test"); + name.setLastName("Test"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the 'int' action.", lastNameResult.containsAction("int")); + assertTrue("Last Name ValidatorResult for the 'int' action should have failed.", !lastNameResult.isValid("int")); + } + + /** + * If first name is ok and last name is ok and is an int, no errors. + */ + public void testRequiredLastNameLong() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Joe"); + name.setLastName("12345678"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the 'int' action.", lastNameResult.containsAction("int")); + assertTrue("Last Name ValidatorResult for the 'int' action should have passed.", lastNameResult.isValid("int")); + } + + /** + * If middle name is not there, then the required dependent test should fail. + * No other tests should run + * + * @throws ValidatorException + */ + public void testFailingFirstDependentValidator() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult middleNameResult = results.getValidatorResult("middleName"); + + assertNotNull("Middle Name ValidatorResult should not be null.", middleNameResult); + + assertTrue("Middle Name ValidatorResult should contain the 'required' action.", middleNameResult.containsAction("required")); + assertTrue("Middle Name ValidatorResult for the 'required' action should have failed", !middleNameResult.isValid("required")); + + assertTrue("Middle Name ValidatorResult should not contain the 'int' action.", !middleNameResult.containsAction("int")); + + assertTrue("Middle Name ValidatorResult should not contain the 'positive' action.", !middleNameResult.containsAction("positive")); + } + + /** + * If middle name is there but not int, then the required dependent test + * should pass, but the int dependent test should fail. No other tests should + * run. + * + * @throws ValidatorException + */ + public void testFailingNextDependentValidator() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setMiddleName("TEST"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult middleNameResult = results.getValidatorResult("middleName"); + + assertNotNull("Middle Name ValidatorResult should not be null.", middleNameResult); + + assertTrue("Middle Name ValidatorResult should contain the 'required' action.", middleNameResult.containsAction("required")); + assertTrue("Middle Name ValidatorResult for the 'required' action should have passed", middleNameResult.isValid("required")); + + assertTrue("Middle Name ValidatorResult should contain the 'int' action.", middleNameResult.containsAction("int")); + assertTrue("Middle Name ValidatorResult for the 'int' action should have failed", !middleNameResult.isValid("int")); + + assertTrue("Middle Name ValidatorResult should not contain the 'positive' action.", !middleNameResult.containsAction("positive")); + } + + /** + * If middle name is there and a negative int, then the required and int + * dependent tests should pass, but the positive test should fail. + * + * @throws ValidatorException + */ + public void testPassingDependentsFailingMain() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setMiddleName("-2534"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult middleNameResult = results.getValidatorResult("middleName"); + + assertNotNull("Middle Name ValidatorResult should not be null.", middleNameResult); + + assertTrue("Middle Name ValidatorResult should contain the 'required' action.", middleNameResult.containsAction("required")); + assertTrue("Middle Name ValidatorResult for the 'required' action should have passed", middleNameResult.isValid("required")); + + assertTrue("Middle Name ValidatorResult should contain the 'int' action.", middleNameResult.containsAction("int")); + assertTrue("Middle Name ValidatorResult for the 'int' action should have passed", middleNameResult.isValid("int")); + + assertTrue("Middle Name ValidatorResult should contain the 'positive' action.", middleNameResult.containsAction("positive")); + assertTrue("Middle Name ValidatorResult for the 'positive' action should have failed", !middleNameResult.isValid("positive")); + } + + /** + * If middle name is there and a positve int, then the required and int + * dependent tests should pass, and the positive test should pass. + * + * @throws ValidatorException + */ + public void testPassingDependentsPassingMain() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setMiddleName("2534"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult middleNameResult = results.getValidatorResult("middleName"); + + assertNotNull("Middle Name ValidatorResult should not be null.", middleNameResult); + + assertTrue("Middle Name ValidatorResult should contain the 'required' action.", middleNameResult.containsAction("required")); + assertTrue("Middle Name ValidatorResult for the 'required' action should have passed", middleNameResult.isValid("required")); + + assertTrue("Middle Name ValidatorResult should contain the 'int' action.", middleNameResult.containsAction("int")); + assertTrue("Middle Name ValidatorResult for the 'int' action should have passed", middleNameResult.isValid("int")); + + assertTrue("Middle Name ValidatorResult should contain the 'positive' action.", middleNameResult.containsAction("positive")); + assertTrue("Middle Name ValidatorResult for the 'positive' action should have passed", middleNameResult.isValid("positive")); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/NameBean.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/NameBean.java new file mode 100644 index 000000000..544dcb190 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/NameBean.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +/** + * Value object that contains a first name and last name. + * + * @version $Revision$ + */ +public class NameBean { + + protected String firstName = null; + + protected String middleName = null; + + protected String lastName = null; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getMiddleName() { + return middleName; + } + + public void setMiddleName(String middleName) { + this.middleName = middleName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ParameterTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ParameterTest.java new file mode 100644 index 000000000..7a6cb606b --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ParameterTest.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.validator; + +import java.io.IOException; +import java.util.Locale; + +import org.xml.sax.SAXException; + +/** + * This TestCase is a confirmation of the parameter of the validator's method. + * + * @version $Revision$ + */ +public class ParameterTest extends AbstractCommonTest { + + private static final String FORM_KEY = "nameForm"; + + private String firstName; + + private String middleName; + + private String lastName; + + /** + * Constructor. + */ + public ParameterTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * ValidatorResultsTest-config.xml. + */ + @Override + protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("ParameterTest-config.xml"); + + // initialize values + firstName = "foo"; + middleName = "123"; + lastName = "456"; + + } + + @Override + protected void tearDown() { + } + + /** + * Test all validations ran and passed. + */ + public void testAllValid() { + + // Create bean to run test on. + NameBean bean = createNameBean(); + + Validator validator = new Validator(resources, FORM_KEY); + + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, bean); + validator.setParameter(Validator.LOCALE_PARAM, Locale.getDefault()); + + // Get results of the validation. + try { + validator.validate(); + } catch(Exception e) { + fail("Validator.validate() threw " + e); + } + assertParameterValue(validator, Validator.BEAN_PARAM, Object.class); + assertParameterValue(validator, Validator.FIELD_PARAM, Field.class); + assertParameterValue(validator, Validator.FORM_PARAM, Form.class); + assertParameterValue(validator, Validator.LOCALE_PARAM, Locale.class); + assertParameterValue(validator, Validator.VALIDATOR_ACTION_PARAM, + ValidatorAction.class); + assertParameterValue(validator, Validator.VALIDATOR_PARAM, + Validator.class); + assertParameterValue(validator, Validator.VALIDATOR_RESULTS_PARAM, + ValidatorResults.class); + } + + private void assertParameterValue(Validator validator, String name, + Class type) { + Object value = validator.getParameterValue(name); + assertNotNull("Expected '" + type.getName() + "' but was null", value); + assertTrue("Expected '" + type.getName() + "' but was '" + value.getClass().getName() + "'", + type.isInstance(value)); + } + + /** + * Create a NameBean. + */ + private NameBean createNameBean() { + NameBean name = new NameBean(); + name.setFirstName(firstName); + name.setMiddleName(middleName); + name.setLastName(lastName); + return name; + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ParameterValidatorImpl.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ParameterValidatorImpl.java new file mode 100644 index 000000000..e8605d3f3 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ParameterValidatorImpl.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +/** + * Contains validation methods for different unit tests. + * + * @version $Revision$ + */ +public class ParameterValidatorImpl { + + /** + * ValidatorParameter is valid. + * + */ + public static boolean validateParameter( + final java.lang.Object bean, + final org.apache.commons.validator.Form form, + final org.apache.commons.validator.Field field, + final org.apache.commons.validator.Validator validator, + final org.apache.commons.validator.ValidatorAction action, + final org.apache.commons.validator.ValidatorResults results, + final java.util.Locale locale) + throws Exception { + + return true; + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RequiredIfTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RequiredIfTest.java new file mode 100644 index 000000000..48162fb61 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RequiredIfTest.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; + +import org.xml.sax.SAXException; + +/** + * Performs Validation Test. + * + * @version $Revision$ + */ +public class RequiredIfTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "nameForm"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "requiredif"; + + public RequiredIfTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-requiredif.xml. + */ + @Override +protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("RequiredIfTest-config.xml"); + } + + @Override +protected void tearDown() { + } + + /** + * With nothing provided, we should pass since the fields only fail on + * null if the other field is non-blank. + */ + public void testRequired() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have passed.", lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for first name if it is blank. + */ + public void testRequiredFirstNameBlank() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + name.setLastName("Test"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have passed.", lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for last name. + */ + public void testRequiredFirstName() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Test"); + name.setLastName("Test"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have passed.", lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for last name if it is blank. + */ + public void testRequiredLastNameBlank() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Joe"); + name.setLastName(""); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for last name. + */ + public void testRequiredLastName() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Joe"); + name.setLastName("Smith"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("Last Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have passed.", lastNameResult.isValid(ACTION)); + + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RequiredNameTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RequiredNameTest.java new file mode 100644 index 000000000..94637b8d9 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RequiredNameTest.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; + +import org.xml.sax.SAXException; + + +/** + * Performs Validation Test. + * + * @version $Revision$ + */ +public class RequiredNameTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "nameForm"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "required"; + + public RequiredNameTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-name-required.xml. + */ + @Override +protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("RequiredNameTest-config.xml"); + } + + @Override +protected void tearDown() { + } + + /** + * Tests the required validation failure. + */ + public void testRequired() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + // throws ValidatorException, + // but we aren't catching for testing + // since no validation methods we use + // throw this + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for first name if it is blank. + */ + public void testRequiredFirstNameBlank() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName(""); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for first name. + */ + public void testRequiredFirstName() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Joe"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for last name if it is blank. + */ + public void testRequiredLastNameBlank() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setLastName(""); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have failed.", !lastNameResult.isValid(ACTION)); + } + + /** + * Tests the required validation for last name. + */ + public void testRequiredLastName() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setLastName("Smith"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have failed.", !firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have passed.", lastNameResult.isValid(ACTION)); + + } + + /** + * Tests the required validation for first and last name. + */ + public void testRequiredName() throws ValidatorException { + // Create bean to run test on. + NameBean name = new NameBean(); + name.setFirstName("Joe"); + name.setLastName("Smith"); + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, name); + + // Get results of the validation. + ValidatorResults results = null; + + results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult firstNameResult = results.getValidatorResult("firstName"); + ValidatorResult lastNameResult = results.getValidatorResult("lastName"); + + assertNotNull("First Name ValidatorResult should not be null.", firstNameResult); + assertTrue("First Name ValidatorResult should contain the '" + ACTION +"' action.", firstNameResult.containsAction(ACTION)); + assertTrue("First Name ValidatorResult for the '" + ACTION +"' action should have passed.", firstNameResult.isValid(ACTION)); + + assertNotNull("First Name ValidatorResult should not be null.", lastNameResult); + assertTrue("Last Name ValidatorResult should contain the '" + ACTION +"' action.", lastNameResult.containsAction(ACTION)); + assertTrue("Last Name ValidatorResult for the '" + ACTION +"' action should have passed.", lastNameResult.isValid(ACTION)); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ResultPair.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ResultPair.java new file mode 100644 index 000000000..37f63c5dc --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ResultPair.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.commons.validator; + +/** + * Groups tests and expected results. + * + * @version $Revision$ + */ + public class ResultPair { + public final String item; + public final boolean valid; + + public ResultPair(String item, boolean valid) { + this.item = item; + this.valid = valid; //Whether the individual part of url is valid. + } + } diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RetrieveFormTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RetrieveFormTest.java new file mode 100644 index 000000000..4b72857b9 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RetrieveFormTest.java @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import junit.framework.TestCase; +import org.xml.sax.SAXException; + +/** + * Tests retrieving forms using different Locales. + * + * @version $Revision$ + */ +public class RetrieveFormTest extends TestCase { + + /** + * Resources used for validation tests. + */ + private ValidatorResources resources = null; + + /** + * Prefix for the forms. + */ + private static final String FORM_PREFIX = "testForm_"; + + /** + * Prefix for the forms. + */ + private static final Locale CANADA_FRENCH_XXX = new Locale("fr", "CA", "XXX"); + + /** + * Constructor for FormTest. + * @param name + */ + public RetrieveFormTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from multiple xml files. + */ + @Override + protected void setUp() throws IOException, SAXException { + InputStream[] streams = + new InputStream[] { + this.getClass().getResourceAsStream( + "RetrieveFormTest-config.xml")}; + + this.resources = new ValidatorResources(streams); + + for (int i = 0; i < streams.length; i++) { + streams[i].close(); + } + } + + /** + * Test a form defined only in the "default" formset. + */ + public void testDefaultForm() { + + String formKey = FORM_PREFIX + "default"; + + // *** US locale *** + checkForm(Locale.US, formKey, "default"); + + // *** French locale *** + checkForm(Locale.FRENCH, formKey, "default"); + + // *** France locale *** + checkForm(Locale.FRANCE, formKey, "default"); + + // *** Candian (English) locale *** + checkForm(Locale.CANADA, formKey, "default"); + + // *** Candian French locale *** + checkForm(Locale.CANADA_FRENCH, formKey, "default"); + + // *** Candian French Variant locale *** + checkForm(CANADA_FRENCH_XXX, formKey, "default"); + + } + + /** + * Test a form defined in the "default" formset and formsets + * where just the "language" is specified. + */ + public void testLanguageForm() { + + String formKey = FORM_PREFIX + "language"; + + // *** US locale *** + checkForm(Locale.US, formKey, "default"); + + // *** French locale *** + checkForm(Locale.FRENCH, formKey, "fr"); + + // *** France locale *** + checkForm(Locale.FRANCE, formKey, "fr"); + + // *** Candian (English) locale *** + checkForm(Locale.CANADA, formKey, "default"); + + // *** Candian French locale *** + checkForm(Locale.CANADA_FRENCH, formKey, "fr"); + + // *** Candian French Variant locale *** + checkForm(CANADA_FRENCH_XXX, formKey, "fr"); + + } + + /** + * Test a form defined in the "default" formset, formsets + * where just the "language" is specified and formset where + * the language and country are specified. + */ + public void testLanguageCountryForm() { + + String formKey = FORM_PREFIX + "language_country"; + + // *** US locale *** + checkForm(Locale.US, formKey, "default"); + + // *** French locale *** + checkForm(Locale.FRENCH, formKey, "fr"); + + // *** France locale *** + checkForm(Locale.FRANCE, formKey, "fr_FR"); + + // *** Candian (English) locale *** + checkForm(Locale.CANADA, formKey, "default"); + + // *** Candian French locale *** + checkForm(Locale.CANADA_FRENCH, formKey, "fr_CA"); + + // *** Candian French Variant locale *** + checkForm(CANADA_FRENCH_XXX, formKey, "fr_CA"); + + } + + /** + * Test a form defined in all the formsets + */ + public void testLanguageCountryVariantForm() { + + String formKey = FORM_PREFIX + "language_country_variant"; + + // *** US locale *** + checkForm(Locale.US, formKey, "default"); + + // *** French locale *** + checkForm(Locale.FRENCH, formKey, "fr"); + + // *** France locale *** + checkForm(Locale.FRANCE, formKey, "fr_FR"); + + // *** Candian (English) locale *** + checkForm(Locale.CANADA, formKey, "default"); + + // *** Candian French locale *** + checkForm(Locale.CANADA_FRENCH, formKey, "fr_CA"); + + // *** Candian French Variant locale *** + checkForm(CANADA_FRENCH_XXX, formKey, "fr_CA_XXX"); + + } + + /** + * Test a form not defined + */ + public void testFormNotFound() { + + String formKey = "INVALID_NAME"; + + // *** US locale *** + checkFormNotFound(Locale.US, formKey); + + // *** French locale *** + checkFormNotFound(Locale.FRENCH, formKey); + + // *** France locale *** + checkFormNotFound(Locale.FRANCE, formKey); + + // *** Candian (English) locale *** + checkFormNotFound(Locale.CANADA, formKey); + + // *** Candian French locale *** + checkFormNotFound(Locale.CANADA_FRENCH, formKey); + + // *** Candian French Variant locale *** + checkFormNotFound(CANADA_FRENCH_XXX, formKey); + + + } + + private void checkForm(Locale locale, String formKey, String expectedVarValue) { + + // Retrieve the Form + Form testForm = resources.getForm(locale, formKey); + assertNotNull("Form '" +formKey+"' null for locale " + locale, testForm); + + // Validate the expected Form is retrieved by checking the "localeVar" + // value of the field. + Field testField = testForm.getField("testProperty"); + assertEquals("Incorrect Form '" + formKey + "' for locale '" + locale + "'", + expectedVarValue, + testField.getVarValue("localeVar")); + } + + private void checkFormNotFound(Locale locale, String formKey) { + + // Retrieve the Form + Form testForm = resources.getForm(locale, formKey); + assertNull("Form '" +formKey+"' not null for locale " + locale, testForm); + + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ShortTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ShortTest.java new file mode 100644 index 000000000..d4d10152d --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ShortTest.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.validator; + + +/** + * Performs Validation Test for short validations. + * + * @version $Revision$ + */ +public class ShortTest extends AbstractNumberTest { + + public ShortTest(String name) { + super(name); + FORM_KEY = "shortForm"; + ACTION = "short"; + } + + /** + * Tests the short validation. + */ + public void testShortMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Short.valueOf(Short.MIN_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the short validation. + */ + public void testShortMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Short.valueOf(Short.MAX_VALUE).toString()); + + valueTest(info, true); + } + + /** + * Tests the short validation failure. + */ + public void testShortBeyondMin() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Short.MIN_VALUE + "1"); + + valueTest(info, false); + } + + /** + * Tests the short validation failure. + */ + public void testShortBeyondMax() throws ValidatorException { + // Create bean to run test on. + ValueBean info = new ValueBean(); + info.setValue(Short.MAX_VALUE + "1"); + + valueTest(info, false); + } +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/TypeBean.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/TypeBean.java new file mode 100644 index 000000000..09f5c9aaf --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/TypeBean.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +/** + * Value object that contains different fields to test type conversion + * validation. + * + * @version $Revision$ + */ +public class TypeBean { + + private String sByte = null; + private String sShort = null; + private String sInteger = null; + private String sLong = null; + private String sFloat = null; + private String sDouble = null; + private String sDate = null; + private String sCreditCard = null; + + public String getByte() { + return sByte; + } + + public void setByte(String sByte) { + this.sByte = sByte; + } + + public String getShort() { + return sShort; + } + + public void setShort(String sShort) { + this.sShort = sShort; + } + + public String getInteger() { + return sInteger; + } + + public void setInteger(String sInteger) { + this.sInteger = sInteger; + } + + public String getLong() { + return sLong; + } + + public void setLong(String sLong) { + this.sLong = sLong; + } + + public String getFloat() { + return sFloat; + } + + public void setFloat(String sFloat) { + this.sFloat = sFloat; + } + + public String getDouble() { + return sDouble; + } + + public void setDouble(String sDouble) { + this.sDouble = sDouble; + } + + public String getDate() { + return sDate; + } + + public void setDate(String sDate) { + this.sDate = sDate; + } + + public String getCreditCard() { + return sCreditCard; + } + + public void setCreditCard(String sCreditCard) { + this.sCreditCard = sCreditCard; + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/UrlTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/UrlTest.java new file mode 100644 index 000000000..af8c08359 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/UrlTest.java @@ -0,0 +1,284 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import junit.framework.TestCase; + +/** + * Performs Validation Test for url validations. + * + * @version $Revision$ + * @deprecated to be removed when org.apache.commons.validator.UrlValidator is removed + */ +@Deprecated +public class UrlTest extends TestCase { + + private final boolean printStatus = false; + private final boolean printIndex = false;//print index that indicates current scheme,host,port,path, query test were using. + + public UrlTest(String testName) { + super(testName); + } + + @Override +protected void setUp() { + for (int index = 0; index < testPartsIndex.length - 1; index++) { + testPartsIndex[index] = 0; + } + } + + public void testIsValid() { + testIsValid(testUrlParts, UrlValidator.ALLOW_ALL_SCHEMES); + setUp(); + int options = + UrlValidator.ALLOW_2_SLASHES + + UrlValidator.ALLOW_ALL_SCHEMES + + UrlValidator.NO_FRAGMENTS; + + testIsValid(testUrlPartsOptions, options); + } + + public void testIsValidScheme() { + if (printStatus) { + System.out.print("\n testIsValidScheme() "); + } + String[] schemes = {"http", "gopher"}; + //UrlValidator urlVal = new UrlValidator(schemes,false,false,false); + UrlValidator urlVal = new UrlValidator(schemes, 0); + for (int sIndex = 0; sIndex < testScheme.length; sIndex++) { + ResultPair testPair = testScheme[sIndex]; + boolean result = urlVal.isValidScheme(testPair.item); + assertEquals(testPair.item, testPair.valid, result); + if (printStatus) { + if (result == testPair.valid) { + System.out.print('.'); + } else { + System.out.print('X'); + } + } + } + if (printStatus) { + System.out.println(); + } + + } + + /** + * Create set of tests by taking the testUrlXXX arrays and + * running through all possible permutations of their combinations. + * + * @param testObjects Used to create a url. + */ + public void testIsValid(Object[] testObjects, int options) { + UrlValidator urlVal = new UrlValidator(null, options); + assertTrue(urlVal.isValid("http://www.google.com")); + assertTrue(urlVal.isValid("http://www.google.com/")); + int statusPerLine = 60; + int printed = 0; + if (printIndex) { + statusPerLine = 6; + } + do { + StringBuilder testBuffer = new StringBuilder(); + boolean expected = true; + for (int testPartsIndexIndex = 0; testPartsIndexIndex < testPartsIndex.length; ++testPartsIndexIndex) { + int index = testPartsIndex[testPartsIndexIndex]; + ResultPair[] part = (ResultPair[]) testObjects[testPartsIndexIndex]; + testBuffer.append(part[index].item); + expected &= part[index].valid; + } + String url = testBuffer.toString(); + boolean result = urlVal.isValid(url); + assertEquals(url, expected, result); + if (printStatus) { + if (printIndex) { + System.out.print(testPartsIndextoString()); + } else { + if (result == expected) { + System.out.print('.'); + } else { + System.out.print('X'); + } + } + printed++; + if (printed == statusPerLine) { + System.out.println(); + printed = 0; + } + } + } while (incrementTestPartsIndex(testPartsIndex, testObjects)); + if (printStatus) { + System.out.println(); + } + } + + public void testValidator202() { + String[] schemes = {"http","https"}; + UrlValidator urlValidator = new UrlValidator(schemes, UrlValidator.NO_FRAGMENTS); + urlValidator.isValid("http://www.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.logoworks.comwww.log"); + } + + public void testValidator204() { + String[] schemes = {"http","https"}; + UrlValidator urlValidator = new UrlValidator(schemes); + assertTrue(urlValidator.isValid("http://tech.yahoo.com/rc/desktops/102;_ylt=Ao8yevQHlZ4On0O3ZJGXLEQFLZA5")); + } + + static boolean incrementTestPartsIndex(int[] testPartsIndex, Object[] testParts) { + boolean carry = true; //add 1 to lowest order part. + boolean maxIndex = true; + for (int testPartsIndexIndex = testPartsIndex.length - 1; testPartsIndexIndex >= 0; --testPartsIndexIndex) { + int index = testPartsIndex[testPartsIndexIndex]; + ResultPair[] part = (ResultPair[]) testParts[testPartsIndexIndex]; + if (carry) { + if (index < part.length - 1) { + index++; + testPartsIndex[testPartsIndexIndex] = index; + carry = false; + } else { + testPartsIndex[testPartsIndexIndex] = 0; + carry = true; + } + } + maxIndex &= (index == (part.length - 1)); + } + + + return (!maxIndex); + } + + private String testPartsIndextoString() { + StringBuilder carryMsg = new StringBuilder("{"); + for (int testPartsIndexIndex = 0; testPartsIndexIndex < testPartsIndex.length; ++testPartsIndexIndex) { + carryMsg.append(testPartsIndex[testPartsIndexIndex]); + if (testPartsIndexIndex < testPartsIndex.length - 1) { + carryMsg.append(','); + } else { + carryMsg.append('}'); + } + } + return carryMsg.toString(); + + } + + public void testValidateUrl() { + assertTrue(true); + } + + /** + * Only used to debug the unit tests. + * @param argv + */ + public static void main(String[] argv) { + + UrlTest fct = new UrlTest("url test"); + fct.setUp(); + fct.testIsValid(); + fct.testIsValidScheme(); + } + //-------------------- Test data for creating a composite URL + /** + * The data given below approximates the 4 parts of a URL + * ://? except that the port number + * is broken out of authority to increase the number of permutations. + * A complete URL is composed of a scheme+authority+port+path+query, + * all of which must be individually valid for the entire URL to be considered + * valid. + */ + ResultPair[] testUrlScheme = {new ResultPair("http://", true), + new ResultPair("ftp://", true), + new ResultPair("h3t://", true), + new ResultPair("3ht://", false), + new ResultPair("http:/", false), + new ResultPair("http:", false), + new ResultPair("http/", false), + new ResultPair("://", false), + new ResultPair("", true)}; + + ResultPair[] testUrlAuthority = {new ResultPair("www.google.com", true), + new ResultPair("go.com", true), + new ResultPair("go.au", true), + new ResultPair("0.0.0.0", true), + new ResultPair("255.255.255.255", true), + new ResultPair("256.256.256.256", false), + new ResultPair("255.com", true), + new ResultPair("1.2.3.4.5", false), + new ResultPair("1.2.3.4.", false), + new ResultPair("1.2.3", false), + new ResultPair(".1.2.3.4", false), + new ResultPair("go.a", false), + new ResultPair("go.a1a", true), + new ResultPair("go.1aa", false), + new ResultPair("aaa.", false), + new ResultPair(".aaa", false), + new ResultPair("aaa", false), + new ResultPair("", false) + }; + ResultPair[] testUrlPort = {new ResultPair(":80", true), + new ResultPair(":65535", true), + new ResultPair(":0", true), + new ResultPair("", true), + new ResultPair(":-1", false), + new ResultPair(":65636", true), + new ResultPair(":65a", false) + }; + ResultPair[] testPath = {new ResultPair("/test1", true), + new ResultPair("/t123", true), + new ResultPair("/$23", true), + new ResultPair("/..", false), + new ResultPair("/../", false), + new ResultPair("/test1/", true), + new ResultPair("", true), + new ResultPair("/test1/file", true), + new ResultPair("/..//file", false), + new ResultPair("/test1//file", false) + }; + //Test allow2slash, noFragment + ResultPair[] testUrlPathOptions = {new ResultPair("/test1", true), + new ResultPair("/t123", true), + new ResultPair("/$23", true), + new ResultPair("/..", false), + new ResultPair("/../", false), + new ResultPair("/test1/", true), + new ResultPair("/#", false), + new ResultPair("", true), + new ResultPair("/test1/file", true), + new ResultPair("/t123/file", true), + new ResultPair("/$23/file", true), + new ResultPair("/../file", false), + new ResultPair("/..//file", false), + new ResultPair("/test1//file", true), + new ResultPair("/#/file", false) + }; + + ResultPair[] testUrlQuery = {new ResultPair("?action=view", true), + new ResultPair("?action=edit&mode=up", true), + new ResultPair("", true) + }; + + Object[] testUrlParts = {testUrlScheme, testUrlAuthority, testUrlPort, testPath, testUrlQuery}; + Object[] testUrlPartsOptions = {testUrlScheme, testUrlAuthority, testUrlPort, testUrlPathOptions, testUrlQuery}; + int[] testPartsIndex = {0, 0, 0, 0, 0}; + + //---------------- Test data for individual url parts ---------------- + ResultPair[] testScheme = {new ResultPair("http", true), + new ResultPair("ftp", false), + new ResultPair("httpd", false), + new ResultPair("telnet", false)}; + + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorResourcesTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorResourcesTest.java new file mode 100644 index 000000000..3b95c1878 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorResourcesTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.InputStream; + +import junit.framework.TestCase; + +/** + * Test ValidatorResources. + * + * @version $Revision$ + */ +public class ValidatorResourcesTest extends TestCase { + + /** + * Constructor. + */ + public ValidatorResourcesTest(String name) { + super(name); + } + + /** + * Test null Input Stream for Validator Resources. + */ + public void testNullInputStream() throws Exception { + + try { + new ValidatorResources((InputStream)null); + fail("Expected IllegalArgumentException"); + } catch(IllegalArgumentException e) { + // expected result + // System.out.println("Exception: " + e); + } + + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorResultsTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorResultsTest.java new file mode 100644 index 000000000..eb452f13c --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorResultsTest.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; + +import org.xml.sax.SAXException; + +/** + * Test ValidatorResults. + * + * @version $Revision$ + */ +public class ValidatorResultsTest extends AbstractCommonTest { + + private static final String FORM_KEY = "nameForm"; + private static final String firstNameField = "firstName"; + private static final String middleNameField = "middleName"; + private static final String lastNameField = "lastName"; + + private String firstName; + private String middleName; + private String lastName; + + /** + * Constructor. + */ + public ValidatorResultsTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * ValidatorResultsTest-config.xml. + */ + @Override +protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("ValidatorResultsTest-config.xml"); + + // initialize values + firstName = "foo"; + middleName = "123"; + lastName = "456"; + + } + + @Override +protected void tearDown() { + } + + /** + * Test all validations ran and passed. + */ + public void testAllValid() throws ValidatorException { + + // Create bean to run test on. + NameBean bean = createNameBean(); + + // Validate. + ValidatorResults results = validate(bean); + + // Check results + checkValidatorResult(results, firstNameField, "required", true); + checkValidatorResult(results, middleNameField, "required", true); + checkValidatorResult(results, middleNameField, "int", true); + checkValidatorResult(results, middleNameField, "positive", true); + checkValidatorResult(results, lastNameField, "required", true); + checkValidatorResult(results, lastNameField, "int", true); + + } + + /** + * Test some validations failed and some didn't run. + */ + public void testErrors() throws ValidatorException { + + middleName = "XXX"; + lastName = null; + + // Create bean to run test on. + NameBean bean = createNameBean(); + + // Validate. + ValidatorResults results = validate(bean); + + // Check results + checkValidatorResult(results, firstNameField, "required", true); + checkValidatorResult(results, middleNameField, "required", true); + checkValidatorResult(results, middleNameField, "int", false); + checkNotRun(results, middleNameField, "positive"); + checkValidatorResult(results, lastNameField, "required", false); + checkNotRun(results, lastNameField, "int"); + + } + + /** + * Check a validator has not been run for a field and the result. + */ + private void checkNotRun(ValidatorResults results, String field, String action) { + ValidatorResult result = results.getValidatorResult(field); + assertNotNull(field + " result", result); + assertFalse(field + "[" + action + "] run", result.containsAction(action)); + // System.out.println(field + "[" + action + "] not run"); + } + + /** + * Check a validator has run for a field and the result. + */ + private void checkValidatorResult(ValidatorResults results, String field, String action, boolean expected) { + ValidatorResult result = results.getValidatorResult(field); + // System.out.println(field + "[" + action + "]=" + result.isValid(action)); + assertNotNull(field + " result", result); + assertTrue(field + "[" + action + "] not run", result.containsAction(action)); + assertEquals(field + "[" + action + "] result", expected, result.isValid(action)); + } + + /** + * Create a NameBean. + */ + private NameBean createNameBean() { + NameBean name = new NameBean(); + name.setFirstName(firstName); + name.setMiddleName(middleName); + name.setLastName(lastName); + return name; + } + + /** + * Validate results. + */ + private ValidatorResults validate(Object bean) throws ValidatorException { + + // Construct validator based on the loaded resources + // and the form key + Validator validator = new Validator(resources, FORM_KEY); + + // add the name bean to the validator as a resource + // for the validations to be performed on. + validator.setParameter(Validator.BEAN_PARAM, bean); + + // Get results of the validation. + ValidatorResults results = validator.validate(); + + return results; + + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorTest.java new file mode 100644 index 000000000..aa508c249 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorTest.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.text.DateFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import junit.framework.TestCase; + +import org.apache.commons.validator.util.ValidatorUtils; + +/** + * Performs Validation Test. + * + * @version $Revision$ + */ +public class ValidatorTest extends TestCase { + + public ValidatorTest(String name) { + super(name); + } + + /** + * Verify that one value generates an error and the other passes. The validation + * method being tested returns an object (null will be considered an error). + */ + public void testManualObject() { + // property name of the method we are validating + String property = "date"; + // name of ValidatorAction + String action = "date"; + ValidatorResources resources = setupDateResources(property, action); + + TestBean bean = new TestBean(); + bean.setDate("2/3/1999"); + + Validator validator = new Validator(resources, "testForm"); + validator.setParameter(Validator.BEAN_PARAM, bean); + + try { + ValidatorResults results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult result = results.getValidatorResult(property); + + assertNotNull("Results are null.", results); + + assertTrue("ValidatorResult does not contain '" + action + "' validator result.", result.containsAction(action)); + + assertTrue("Validation of the date formatting has failed.", result.isValid(action)); + } catch (Exception e) { + fail("An exception was thrown while calling Validator.validate()"); + } + + bean.setDate("2/30/1999"); + + try { + ValidatorResults results = validator.validate(); + + assertNotNull("Results are null.", results); + + ValidatorResult result = results.getValidatorResult(property); + + assertNotNull("Results are null.", results); + + assertTrue("ValidatorResult does not contain '" + action + "' validator result.", result.containsAction(action)); + + assertTrue("Validation of the date formatting has passed when it should have failed.", !result.isValid(action)); + } catch (Exception e) { + fail("An exception was thrown while calling Validator.validate()"); + } + + } + + public void testOnlyReturnErrors() throws ValidatorException { + // property name of the method we are validating + String property = "date"; + // name of ValidatorAction + String action = "date"; + ValidatorResources resources = setupDateResources(property, action); + + TestBean bean = new TestBean(); + bean.setDate("2/3/1999"); + + Validator validator = new Validator(resources, "testForm"); + validator.setParameter(Validator.BEAN_PARAM, bean); + + ValidatorResults results = validator.validate(); + + assertNotNull(results); + + // Field passed and should be in results + assertTrue(results.getPropertyNames().contains(property)); + + // Field passed but should not be in results + validator.setOnlyReturnErrors(true); + results = validator.validate(); + assertFalse(results.getPropertyNames().contains(property)); + } + + public void testOnlyValidateField() throws ValidatorException { + // property name of the method we are validating + String property = "date"; + // name of ValidatorAction + String action = "date"; + ValidatorResources resources = setupDateResources(property, action); + + TestBean bean = new TestBean(); + bean.setDate("2/3/1999"); + + Validator validator = new Validator(resources, "testForm", property); + validator.setParameter(Validator.BEAN_PARAM, bean); + + ValidatorResults results = validator.validate(); + + assertNotNull(results); + + // Field passed and should be in results + assertTrue(results.getPropertyNames().contains(property)); + } + + + private ValidatorResources setupDateResources(String property, String action) { + + ValidatorResources resources = new ValidatorResources(); + + ValidatorAction va = new ValidatorAction(); + va.setName(action); + va.setClassname("org.apache.commons.validator.ValidatorTest"); + va.setMethod("formatDate"); + va.setMethodParams("java.lang.Object,org.apache.commons.validator.Field"); + + FormSet fs = new FormSet(); + Form form = new Form(); + form.setName("testForm"); + Field field = new Field(); + field.setProperty(property); + field.setDepends(action); + form.addField(field); + fs.addForm(form); + + resources.addValidatorAction(va); + resources.addFormSet(fs); + resources.process(); + + return resources; + } + + /** + * Verify that one value generates an error and the other passes. The validation + * method being tested returns a boolean value. + */ + public void testManualBoolean() { + ValidatorResources resources = new ValidatorResources(); + + ValidatorAction va = new ValidatorAction(); + va.setName("capLetter"); + va.setClassname("org.apache.commons.validator.ValidatorTest"); + va.setMethod("isCapLetter"); + va.setMethodParams("java.lang.Object,org.apache.commons.validator.Field,java.util.List"); + + FormSet fs = new FormSet(); + Form form = new Form(); + form.setName("testForm"); + Field field = new Field(); + field.setProperty("letter"); + field.setDepends("capLetter"); + form.addField(field); + fs.addForm(form); + + resources.addValidatorAction(va); + resources.addFormSet(fs); + resources.process(); + + List l = new ArrayList(); + + TestBean bean = new TestBean(); + bean.setLetter("A"); + + Validator validator = new Validator(resources, "testForm"); + validator.setParameter(Validator.BEAN_PARAM, bean); + validator.setParameter("java.util.List", l); + + try { + validator.validate(); + } catch (Exception e) { + fail("An exception was thrown while calling Validator.validate()"); + } + + assertEquals("Validation of the letter 'A'.", 0, l.size()); + + l.clear(); + bean.setLetter("AA"); + + try { + validator.validate(); + } catch (Exception e) { + fail("An exception was thrown while calling Validator.validate()"); + } + + assertEquals("Validation of the letter 'AA'.", 1, l.size()); + } + + /** + * Checks if the field is one upper case letter between 'A' and 'Z'. + */ + public static boolean isCapLetter(Object bean, Field field, List l) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + + if (value != null && value.length() == 1) { + if (value.charAt(0) >= 'A' && value.charAt(0) <= 'Z') { + return true; + } else { + l.add("Error"); + return false; + } + } else { + l.add("Error"); + return false; + } + } + + /** + * Formats a String to a Date. + * The Validator will interpret a null + * as validation having failed. + */ + public static Date formatDate(Object bean, Field field) { + String value = ValidatorUtils.getValueAsString(bean, field.getProperty()); + Date date = null; + + try { + DateFormat formatter = null; + formatter = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US); + + formatter.setLenient(false); + + date = formatter.parse(value); + } catch (ParseException e) { + System.out.println("ValidatorTest.formatDate() - " + e.getMessage()); + } + + return date; + } + + public class TestBean { + private String letter = null; + private String date = null; + + public String getLetter() { + return letter; + } + + public void setLetter(String letter) { + this.letter = letter; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValueBean.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValueBean.java new file mode 100644 index 000000000..263f86bb8 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValueBean.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +/** + * Value object for storing a value to run tests on. + * + * @version $Revision$ + */ +public class ValueBean { + + protected String value = null; + + /** + * Gets the value. + */ + public String getValue() { + return value; + } + + /** + * Sets the value. + */ + public void setValue(String value) { + this.value = value; + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/VarTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/VarTest.java new file mode 100644 index 000000000..55079084a --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/VarTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator; + +import java.io.IOException; +import java.util.Locale; + +import org.xml.sax.SAXException; + +/** + * Test that the new Var attributes and the + * digester rule changes work. + * + * @version $Revision$ + */ +public class VarTest extends AbstractCommonTest { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "testForm"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "byte"; + + + + public VarTest(String name) { + super(name); + } + + /** + * Load ValidatorResources from + * validator-multipletest.xml. + */ + @Override +protected void setUp() throws IOException, SAXException { + // Load resources + loadResources("VarTest-config.xml"); + } + + @Override +protected void tearDown() { + } + + /** + * With nothing provided, we should fail both because both are required. + */ + public void testVars() { + + Form form = resources.getForm(Locale.getDefault(), FORM_KEY); + + // Get field 1 + Field field1 = form.getField("field-1"); + assertNotNull("field-1 is null.", field1); + assertEquals("field-1 property is wrong", "field-1", field1.getProperty()); + + // Get var-1-1 + Var var11 = field1.getVar("var-1-1"); + assertNotNull("var-1-1 is null.", var11); + assertEquals("var-1-1 name is wrong", "var-1-1", var11.getName()); + assertEquals("var-1-1 value is wrong", "value-1-1", var11.getValue()); + assertEquals("var-1-1 jstype is wrong", "jstype-1-1", var11.getJsType()); + assertFalse("var-1-1 resource is true", var11.isResource()); + assertNull("var-1-1 bundle is not null.", var11.getBundle()); + + // Get field 2 + Field field2 = form.getField("field-2"); + assertNotNull("field-2 is null.", field2); + assertEquals("field-2 property is wrong", "field-2", field2.getProperty()); + + // Get var-2-1 + Var var21 = field2.getVar("var-2-1"); + assertNotNull("var-2-1 is null.", var21); + assertEquals("var-2-1 name is wrong", "var-2-1", var21.getName()); + assertEquals("var-2-1 value is wrong", "value-2-1", var21.getValue()); + assertEquals("var-2-1 jstype is wrong", "jstype-2-1", var21.getJsType()); + assertTrue("var-2-1 resource is false", var21.isResource()); + assertEquals("var-2-1 bundle is wrong", "bundle-2-1", var21.getBundle()); + + // Get var-2-2 + Var var22 = field2.getVar("var-2-2"); + assertNotNull("var-2-2 is null.", var22); + assertEquals("var-2-2 name is wrong", "var-2-2", var22.getName()); + assertEquals("var-2-2 value is wrong", "value-2-2", var22.getValue()); + assertNull("var-2-2 jstype is not null", var22.getJsType()); + assertFalse("var-2-2 resource is true", var22.isResource()); + assertEquals("var-2-2 bundle is wrong", "bundle-2-2", var22.getBundle()); + + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/custom/CustomValidatorResources.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/custom/CustomValidatorResources.java new file mode 100644 index 000000000..fdd673af5 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/custom/CustomValidatorResources.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.custom; + +import java.io.InputStream; +import java.io.IOException; +import org.xml.sax.SAXException; +import org.apache.commons.validator.ValidatorResources; + +/** + * Custom ValidatorResources implementation. + * + * @version $Revision$ + */ +public class CustomValidatorResources extends ValidatorResources { + + private static final long serialVersionUID = 1272843199141974642L; + + /** + * Create a custom ValidatorResources object from an uri + * + * @param in InputStream for the validation.xml configuration file. + * @throws SAXException if the validation XML files are not valid or well formed. + * @throws IOException if an I/O error occurs processing the XML files + */ + public CustomValidatorResources(InputStream in) throws IOException, SAXException { + super(in); + } + +} \ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/AbstractCalendarValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/AbstractCalendarValidatorTest.java new file mode 100644 index 000000000..067100216 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/AbstractCalendarValidatorTest.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Date; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Base Calendar Test Case. + * + * @version $Revision$ + */ +public abstract class AbstractCalendarValidatorTest extends TestCase { + + protected AbstractCalendarValidator validator; + + protected static final TimeZone GMT = TimeZone.getTimeZone("GMT"); // 0 offset + protected static final TimeZone EST = TimeZone.getTimeZone("EST"); // - 5 hours + protected static final TimeZone EET = TimeZone.getTimeZone("EET"); // + 2 hours + protected static final TimeZone UTC = TimeZone.getTimeZone("UTC"); // + 2 hours + + protected String[] patternValid = new String[] { + "2005-01-01" + ,"2005-12-31" + ,"2004-02-29" // valid leap + ,"2005-04-30" + ,"05-12-31" + ,"2005-1-1" + ,"05-1-1"}; + protected String[] localeValid = new String[] { + "01/01/2005" + ,"12/31/2005" + ,"02/29/2004" // valid leap + ,"04/30/2005" + ,"12/31/05" + ,"1/1/2005" + ,"1/1/05"}; + protected Date[] patternExpect = new Date[] { + createDate(null, 20050101, 0) + ,createDate(null, 20051231, 0) + ,createDate(null, 20040229, 0) + ,createDate(null, 20050430, 0) + ,createDate(null, 20051231, 0) + ,createDate(null, 20050101, 0) + ,createDate(null, 20050101, 0)}; + protected String[] patternInvalid = new String[] { + "2005-00-01" // zero month + ,"2005-01-00" // zero day + ,"2005-13-03" // month invalid + ,"2005-04-31" // invalid day + ,"2005-03-32" // invalid day + ,"2005-02-29" // invalid leap + ,"200X-01-01" // invalid char + ,"2005-0X-01" // invalid char + ,"2005-01-0X" // invalid char + ,"01/01/2005" // invalid pattern + ,"2005-01" // invalid pattern + ,"2005--01" // invalid pattern + ,"2005-01-"}; // invalid pattern + protected String[] localeInvalid = new String[] { + "01/00/2005" // zero month + ,"00/01/2005" // zero day + ,"13/01/2005" // month invalid + ,"04/31/2005" // invalid day + ,"03/32/2005" // invalid day + ,"02/29/2005" // invalid leap + ,"01/01/200X" // invalid char + ,"01/0X/2005" // invalid char + ,"0X/01/2005" // invalid char + ,"01-01-2005" // invalid pattern + ,"01/2005" // invalid pattern + // -------- ,"/01/2005" ---- passes on some JDK + ,"01//2005"}; // invalid pattern + + /** + * Constructor + * @param name test name + */ + public AbstractCalendarValidatorTest(String name) { + super(name); + } + + /** + * Set Up. + * @throws Exception + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + /** + * Tear down + * @throws Exception + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + validator = null; + } + + /** + * Test Valid Dates with "pattern" validation + */ + public void testPatternValid() { + for (int i = 0; i < patternValid.length; i++) { + String text = i + " value=[" +patternValid[i]+"] failed "; + Object date = validator.parse(patternValid[i], "yy-MM-dd", null, null); + assertNotNull("validateObj() " + text + date, date); + assertTrue("isValid() " + text, validator.isValid(patternValid[i], "yy-MM-dd")); + if (date instanceof Calendar) { + date = ((Calendar)date).getTime(); + } + assertEquals("compare " + text, patternExpect[i], date); + } + } + + /** + * Test Invalid Dates with "pattern" validation + */ + public void testPatternInvalid() { + for (int i = 0; i < patternInvalid.length; i++) { + String text = i + " value=[" +patternInvalid[i]+"] passed "; + Object date = validator.parse(patternInvalid[i], "yy-MM-dd", null, null); + assertNull("validateObj() " + text + date, date); + assertFalse("isValid() " + text, validator.isValid(patternInvalid[i], "yy-MM-dd")); + } + } + + /** + * Test Valid Dates with "locale" validation + */ + public void testLocaleValid() { + for (int i = 0; i < localeValid.length; i++) { + String text = i + " value=[" +localeValid[i]+"] failed "; + Object date = validator.parse(localeValid[i], null, Locale.US, null); + assertNotNull("validateObj() " + text + date, date); + assertTrue("isValid() " + text, validator.isValid(localeValid[i], Locale.US)); + if (date instanceof Calendar) { + date = ((Calendar)date).getTime(); + } + assertEquals("compare " + text, patternExpect[i], date); + } + } + + /** + * Test Invalid Dates with "locale" validation + */ + public void testLocaleInvalid() { + for (int i = 0; i < localeInvalid.length; i++) { + String text = i + " value=[" +localeInvalid[i]+"] passed "; + Object date = validator.parse(localeInvalid[i], null, Locale.US, null); + assertNull("validateObj() " + text + date, date); + assertFalse("isValid() " + text, validator.isValid(localeInvalid[i], Locale.US)); + } + } + + /** + * Test Invalid Dates with "locale" validation + */ + public void testFormat() { + + // Create a Date or Calendar + Object test = validator.parse("2005-11-28", "yyyy-MM-dd", null, null); + assertNotNull("Test Date ", test); + assertEquals("Format pattern", "28.11.05", validator.format(test, "dd.MM.yy")); + assertEquals("Format locale", "11/28/05", validator.format(test, Locale.US)); + } + + /** + * Test validator serialization. + */ + public void testSerialization() { + // Serialize the check digit routine + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(validator); + oos.flush(); + oos.close(); + } catch (Exception e) { + fail(validator.getClass().getName() + " error during serialization: " + e); + } + + // Deserialize the test object + Object result = null; + try { + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + result = ois.readObject(); + bais.close(); + } catch (Exception e) { + fail(validator.getClass().getName() + " error during deserialization: " + e); + } + assertNotNull(result); + } + + /** + * Create a calendar instance for a specified time zone, date and time. + * + * @param zone The time zone + * @param date The date in yyyyMMdd format + * @param time the time in HH:mm:ss format + * @return the new Calendar instance. + */ + protected static Calendar createCalendar(TimeZone zone, int date, int time) { + Calendar calendar = zone == null ? Calendar.getInstance() + : Calendar.getInstance(zone); + int year = ((date / 10000) * 10000); + int mth = ((date / 100) * 100) - year; + int day = date - (year + mth); + int hour = ((time / 10000) * 10000); + int min = ((time / 100) * 100) - hour; + int sec = time - (hour + min); + calendar.set(Calendar.YEAR, (year / 10000)); + calendar.set(Calendar.MONTH, ((mth / 100) - 1)); + calendar.set(Calendar.DATE, day); + calendar.set(Calendar.HOUR_OF_DAY, (hour / 10000)); + calendar.set(Calendar.MINUTE, (min / 100)); + calendar.set(Calendar.SECOND, sec); + calendar.set(Calendar.MILLISECOND, 0); + return calendar; + } + + /** + * Create a date instance for a specified time zone, date and time. + * + * @param zone The time zone + * @param date The date in yyyyMMdd format + * @param time the time in HH:mm:ss format + * @return the new Date instance. + */ + protected static Date createDate(TimeZone zone, int date, int time) { + Calendar calendar = createCalendar(zone, date, time); + return calendar.getTime(); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/AbstractNumberValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/AbstractNumberValidatorTest.java new file mode 100644 index 000000000..1d88bcc67 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/AbstractNumberValidatorTest.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import junit.framework.TestCase; + +import java.util.Locale; +import java.text.DecimalFormat; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigDecimal; +/** + * Base Number Test Case. + * + * @version $Revision$ + */ +public abstract class AbstractNumberValidatorTest extends TestCase { + + protected AbstractNumberValidator validator; + protected AbstractNumberValidator strictValidator; + + protected Number max; + protected Number maxPlusOne; + protected Number min; + protected Number minMinusOne; + protected String[] invalid; + protected String[] valid; + protected Number[] validCompare; + + protected String[] invalidStrict; + protected String[] validStrict; + protected Number[] validStrictCompare; + + protected String testPattern; + protected Number testNumber; + protected Number testZero; + protected String testStringUS; + protected String testStringDE; + + protected String localeValue; + protected String localePattern; + protected Locale testLocale; + protected Number localeExpected; + + /** + * Constructor + * @param name test name + */ + public AbstractNumberValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + Locale.setDefault(Locale.US); + + } + + /** + * Tear down + * @throws Exception + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + validator = null; + strictValidator = null; + } + + /** + * Test Format Type + */ + public void testFormatType() { + assertEquals("Format Type A", 0, validator.getFormatType()); + assertEquals("Format Type B", AbstractNumberValidator.STANDARD_FORMAT, validator.getFormatType()); + } + + /** + * Test Min/Max values allowed + */ + public void testValidateMinMax() { + DecimalFormat fmt = new DecimalFormat("#"); + if (max != null) { + assertEquals("Test Max", max, validator.parse(fmt.format(max), "#", null)); + assertNull("Test Max + 1", validator.parse(fmt.format(maxPlusOne), "#", null)); + assertEquals("Test Min", min, validator.parse(fmt.format(min), "#", null)); + assertNull("Test min - 1", validator.parse(fmt.format(minMinusOne), "#", null)); + } + } + + /** + * Test Invalid, strict=true + */ + public void testInvalidStrict() { + for (int i = 0; i < invalidStrict.length; i++) { + String text = "idx=["+i+"] value=[" + invalidStrict[i] + "]"; + assertNull("(A) " + text, strictValidator.parse(invalidStrict[i], null, Locale.US)); + assertFalse("(B) " + text, strictValidator.isValid(invalidStrict[i], null, Locale.US)); + assertNull("(C) " + text, strictValidator.parse(invalidStrict[i], testPattern, null)); + assertFalse("(D) " + text, strictValidator.isValid(invalidStrict[i], testPattern, null)); + } + } + + /** + * Test Invalid, strict=false + */ + public void testInvalidNotStrict() { + for (int i = 0; i < invalid.length; i++) { + String text = "idx=["+i+"] value=[" + invalid[i] + "]"; + assertNull("(A) " + text, validator.parse(invalid[i], null, Locale.US)); + assertFalse("(B) " + text, validator.isValid(invalid[i], null, Locale.US)); + assertNull("(C) " + text, validator.parse(invalid[i], testPattern, null)); + assertFalse("(D) " + text, validator.isValid(invalid[i], testPattern, null)); + } + } + + /** + * Test Valid, strict=true + */ + public void testValidStrict() { + for (int i = 0; i < validStrict.length; i++) { + String text = "idx=["+i+"] value=[" + validStrictCompare[i] + "]"; + assertEquals("(A) " + text, validStrictCompare[i], strictValidator.parse(validStrict[i], null, Locale.US)); + assertTrue("(B) " + text, strictValidator.isValid(validStrict[i], null, Locale.US)); + assertEquals("(C) " + text, validStrictCompare[i], strictValidator.parse(validStrict[i], testPattern, null)); + assertTrue("(D) " + text, strictValidator.isValid(validStrict[i], testPattern, null)); + } + } + + /** + * Test Valid, strict=false + */ + public void testValidNotStrict() { + for (int i = 0; i < valid.length; i++) { + String text = "idx=["+i+"] value=[" + validCompare[i] + "]"; + assertEquals("(A) " + text, validCompare[i], validator.parse(valid[i], null, Locale.US)); + assertTrue("(B) " + text, validator.isValid(valid[i], null, Locale.US)); + assertEquals("(C) " + text, validCompare[i], validator.parse(valid[i], testPattern, null)); + assertTrue("(D) " + text, validator.isValid(valid[i], testPattern, null)); + } + } + + /** + * Test different Locale + */ + public void testValidateLocale() { + + assertEquals("US Locale, US Format", testNumber, strictValidator.parse(testStringUS, null, Locale.US)); + assertNull("US Locale, DE Format", strictValidator.parse(testStringDE, null, Locale.US)); + + // Default German Locale + assertEquals("DE Locale, DE Format", testNumber, strictValidator.parse(testStringDE, null, Locale.GERMAN)); + assertNull("DE Locale, US Format", strictValidator.parse(testStringUS, null, Locale.GERMAN)); + + // Default Locale has been set to Locale.US in setup() + assertEquals("Default Locale, US Format", testNumber, strictValidator.parse(testStringUS, null, null)); + assertNull("Default Locale, DE Format", strictValidator.parse(testStringDE, null, null)); + } + + /** + * Test format() methods + */ + public void testFormat() { + Number number = new BigDecimal("1234.5"); + assertEquals("US Locale, US Format", "1,234.5", strictValidator.format(number, Locale.US)); + assertEquals("DE Locale, DE Format", "1.234,5", strictValidator.format(number, Locale.GERMAN)); + assertEquals("Pattern #,#0.00", "12,34.50", strictValidator.format(number, "#,#0.00")); + } + + /** + * Test Range/Min/Max + */ + public void testRangeMinMax() { + Number number9 = Integer.valueOf(9); + Number number10 = Integer.valueOf(10); + Number number11 = Integer.valueOf(11); + Number number19 = Integer.valueOf(19); + Number number20 = Integer.valueOf(20); + Number number21 = Integer.valueOf(21); + + // Test isInRange() + assertFalse("isInRange() < min", strictValidator.isInRange(number9 , number10, number20)); + assertTrue("isInRange() = min", strictValidator.isInRange(number10 , number10, number20)); + assertTrue("isInRange() in range", strictValidator.isInRange(number11 , number10, number20)); + assertTrue("isInRange() = max", strictValidator.isInRange(number20 , number10, number20)); + assertFalse("isInRange() > max", strictValidator.isInRange(number21 , number10, number20)); + + // Test minValue() + assertFalse("minValue() < min", strictValidator.minValue(number9 , number10)); + assertTrue("minValue() = min", strictValidator.minValue(number10 , number10)); + assertTrue("minValue() > min", strictValidator.minValue(number11 , number10)); + + // Test minValue() + assertTrue("maxValue() < max", strictValidator.maxValue(number19 , number20)); + assertTrue("maxValue() = max", strictValidator.maxValue(number20 , number20)); + assertFalse("maxValue() > max", strictValidator.maxValue(number21 , number20)); + } + + /** + * Test validator serialization. + */ + public void testSerialization() { + // Serialize the check digit routine + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(validator); + oos.flush(); + oos.close(); + } catch (Exception e) { + fail(validator.getClass().getName() + " error during serialization: " + e); + } + + // Deserialize the test object + Object result = null; + try { + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + result = ois.readObject(); + bais.close(); + } catch (Exception e) { + fail(validator.getClass().getName() + " error during deserialization: " + e); + } + assertNotNull(result); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/BigDecimalValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/BigDecimalValidatorTest.java new file mode 100644 index 000000000..5a0244662 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/BigDecimalValidatorTest.java @@ -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. + */ +package org.apache.commons.validator.routines; + +import java.math.BigDecimal; +import java.util.Locale; + +/** + * Test Case for BigDecimalValidator. + * + * @version $Revision$ + */ +public class BigDecimalValidatorTest extends AbstractNumberValidatorTest { + + /** + * Constructor + * @param name test name + */ + public BigDecimalValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validator = new BigDecimalValidator(false); + strictValidator = new BigDecimalValidator(); + + testPattern = "#,###.###"; + + // testValidateMinMax() + max = null; + maxPlusOne = null; + min = null; + minMinusOne = null; + + // testInvalidStrict() + invalidStrict = new String[] {null, "", "X", "X12", "12X", "1X2", "1.234X"}; + + // testInvalidNotStrict() + invalid = new String[] {null, "", "X", "X12"}; + + // testValid() + testNumber = new BigDecimal("1234.5"); + Number testNumber2 = new BigDecimal(".1"); + Number testNumber3 = new BigDecimal("12345.67899"); + testZero = new BigDecimal("0"); + validStrict = new String[] {"0", "1234.5", "1,234.5", ".1", "12345.678990"}; + validStrictCompare = new Number[] {testZero, testNumber, testNumber, testNumber2, testNumber3}; + valid = new String[] {"0", "1234.5", "1,234.5", "1,234.5", "1234.5X"}; + validCompare = new Number[] {testZero, testNumber, testNumber, testNumber, testNumber}; + + testStringUS = "1,234.5"; + testStringDE = "1.234,5"; + + // Localized Pattern test + localeValue = testStringDE; + localePattern = "#.###,#"; + testLocale = Locale.GERMANY; + localeExpected = testNumber; + + } + + /** + * Test BigDecimalValidator validate Methods + */ + public void testBigDecimalValidatorMethods() { + Locale locale = Locale.GERMAN; + String pattern = "0,00,00"; + String patternVal = "1,23,45"; + String germanPatternVal = "1.23.45"; + String localeVal = "12.345"; + String defaultVal = "12,345"; + String XXXX = "XXXX"; + BigDecimal expected = new BigDecimal(12345); + assertEquals("validate(A) default", expected, BigDecimalValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, BigDecimalValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, BigDecimalValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, BigDecimalValidator.getInstance().validate(germanPatternVal, pattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", BigDecimalValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", BigDecimalValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", BigDecimalValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", BigDecimalValidator.getInstance().isValid(germanPatternVal, pattern, Locale.GERMAN)); + + assertNull("validate(B) default", BigDecimalValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", BigDecimalValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", BigDecimalValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", BigDecimalValidator.getInstance().validate(patternVal, pattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", BigDecimalValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", BigDecimalValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", BigDecimalValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", BigDecimalValidator.getInstance().isValid(patternVal, pattern, Locale.GERMAN)); + } + + /** + * Test BigDecimal Range/Min/Max + */ + public void testBigDecimalRangeMinMax() { + BigDecimalValidator validator = new BigDecimalValidator(true, AbstractNumberValidator.STANDARD_FORMAT, true); + BigDecimal number9 = new BigDecimal("9"); + BigDecimal number10 = new BigDecimal("10"); + BigDecimal number11 = new BigDecimal("11"); + BigDecimal number19 = new BigDecimal("19"); + BigDecimal number20 = new BigDecimal("20"); + BigDecimal number21 = new BigDecimal("21"); + + float min = 10; + float max = 20; + + // Test isInRange() + assertFalse("isInRange(A) < min", validator.isInRange(number9, min, max)); + assertTrue("isInRange(A) = min", validator.isInRange(number10, min, max)); + assertTrue("isInRange(A) in range", validator.isInRange(number11, min, max)); + assertTrue("isInRange(A) = max", validator.isInRange(number20, min, max)); + assertFalse("isInRange(A) > max", validator.isInRange(number21, min, max)); + + // Test minValue() + assertFalse("minValue(A) < min", validator.minValue(number9, min)); + assertTrue("minValue(A) = min", validator.minValue(number10, min)); + assertTrue("minValue(A) > min", validator.minValue(number11, min)); + + // Test minValue() + assertTrue("maxValue(A) < max", validator.maxValue(number19, max)); + assertTrue("maxValue(A) = max", validator.maxValue(number20, max)); + assertFalse("maxValue(A) > max", validator.maxValue(number21, max)); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/BigIntegerValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/BigIntegerValidatorTest.java new file mode 100644 index 000000000..2a74223b6 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/BigIntegerValidatorTest.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.validator.routines; + +import java.math.BigInteger; +import java.util.Locale; + +/** + * Test Case for BigIntegerValidator. + * + * @version $Revision$ + */ +public class BigIntegerValidatorTest extends AbstractNumberValidatorTest { + + /** + * Constructor + * @param name test name + */ + public BigIntegerValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validator = new BigIntegerValidator(false, 0); + strictValidator = new BigIntegerValidator(); + + testPattern = "#,###"; + + // testValidateMinMax() + max = null; + maxPlusOne = null; + min = null; + minMinusOne = null; + + // testInvalidStrict() + invalidStrict = new String[] {null, "", "X", "X12", "12X", "1X2", "1.2"}; + + // testInvalidNotStrict() + invalid = new String[] {null, "", "X", "X12"}; + + // testValid() + testNumber = new BigInteger("1234"); + testZero = new BigInteger("0"); + validStrict = new String[] {"0", "1234", "1,234"}; + validStrictCompare = new Number[] {testZero, testNumber, testNumber}; + valid = new String[] {"0", "1234", "1,234", "1,234.5", "1234X"}; + validCompare = new Number[] {testZero, testNumber, testNumber, testNumber, testNumber}; + + testStringUS = "1,234"; + testStringDE = "1.234"; + + // Localized Pattern test + localeValue = testStringDE; + localePattern = "#.###"; + testLocale = Locale.GERMANY; + localeExpected = testNumber; + + } + + /** + * Test BigIntegerValidator validate Methods + */ + public void testBigIntegerValidatorMethods() { + Locale locale = Locale.GERMAN; + String pattern = "0,00,00"; + String patternVal = "1,23,45"; + String germanPatternVal = "1.23.45"; + String localeVal = "12.345"; + String defaultVal = "12,345"; + String XXXX = "XXXX"; + BigInteger expected = new BigInteger("12345"); + assertEquals("validate(A) default", expected, BigIntegerValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, BigIntegerValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, BigIntegerValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, BigIntegerValidator.getInstance().validate(germanPatternVal, pattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", BigIntegerValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", BigIntegerValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", BigIntegerValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", BigIntegerValidator.getInstance().isValid(germanPatternVal, pattern, Locale.GERMAN)); + + assertNull("validate(B) default", BigIntegerValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", BigIntegerValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", BigIntegerValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", BigIntegerValidator.getInstance().validate(patternVal, pattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", BigIntegerValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", BigIntegerValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", BigIntegerValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", BigIntegerValidator.getInstance().isValid(patternVal, pattern, Locale.GERMAN)); + } + + /** + * Test BigInteger Range/Min/Max + */ + public void testBigIntegerRangeMinMax() { + BigIntegerValidator validator = (BigIntegerValidator)strictValidator; + BigInteger number9 = validator.validate("9", "#"); + BigInteger number10 = validator.validate("10", "#"); + BigInteger number11 = validator.validate("11", "#"); + BigInteger number19 = validator.validate("19", "#"); + BigInteger number20 = validator.validate("20", "#"); + BigInteger number21 = validator.validate("21", "#"); + + // Test isInRange() + assertFalse("isInRange() < min", validator.isInRange(number9, 10, 20)); + assertTrue("isInRange() = min", validator.isInRange(number10, 10, 20)); + assertTrue("isInRange() in range", validator.isInRange(number11, 10, 20)); + assertTrue("isInRange() = max", validator.isInRange(number20, 10, 20)); + assertFalse("isInRange() > max", validator.isInRange(number21, 10, 20)); + + // Test minValue() + assertFalse("minValue() < min", validator.minValue(number9, 10)); + assertTrue("minValue() = min", validator.minValue(number10, 10)); + assertTrue("minValue() > min", validator.minValue(number11, 10)); + + // Test minValue() + assertTrue("maxValue() < max", validator.maxValue(number19, 20)); + assertTrue("maxValue() = max", validator.maxValue(number20, 20)); + assertFalse("maxValue() > max", validator.maxValue(number21, 20)); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ByteValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ByteValidatorTest.java new file mode 100644 index 000000000..98c2ed080 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ByteValidatorTest.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.Locale; + +/** + * Test Case for ByteValidator. + * + * @version $Revision$ + */ +public class ByteValidatorTest extends AbstractNumberValidatorTest { + + private static final Byte BYTE_MIN_VAL = Byte.valueOf(Byte.MIN_VALUE); + private static final Byte BYTE_MAX_VAL = Byte.valueOf(Byte.MAX_VALUE); + private static final String BYTE_MAX = "127"; + private static final String BYTE_MAX_0 = "127.99999999999999999999999"; // force double rounding + private static final String BYTE_MAX_1 = "128"; + private static final String BYTE_MIN = "-128"; + private static final String BYTE_MIN_0 = "-128.99999999999999999999999"; // force double rounding"; + private static final String BYTE_MIN_1 = "-129"; + /** + * Constructor + * @param name test name + */ + public ByteValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validator = new ByteValidator(false, 0); + strictValidator = new ByteValidator(); + + testPattern = "#,###"; + + // testValidateMinMax() + max = Byte.valueOf(Byte.MAX_VALUE); + maxPlusOne = Long.valueOf(max.longValue() + 1); + min = Byte.valueOf(Byte.MIN_VALUE); + minMinusOne = Long.valueOf(min.longValue() - 1); + + // testInvalidStrict() + invalidStrict = new String[] {null, "", "X", "X12", "12X", "1X2", "1.2", BYTE_MAX_1, BYTE_MIN_1, BYTE_MAX_0, BYTE_MIN_0}; + + // testInvalidNotStrict() + invalid = new String[] {null, "", "X", "X12", BYTE_MAX_1, BYTE_MIN_1}; + + // testValid() + testNumber = Byte.valueOf((byte)123); + testZero = Byte.valueOf((byte)0); + validStrict = new String[] {"0", "123", ",123", BYTE_MAX, BYTE_MIN}; + validStrictCompare = new Number[] {testZero, testNumber, testNumber, BYTE_MAX_VAL, BYTE_MIN_VAL}; + valid = new String[] {"0", "123", ",123", ",123.5", "123X", BYTE_MAX, BYTE_MIN, BYTE_MAX_0, BYTE_MIN_0}; + validCompare = new Number[] {testZero, testNumber, testNumber, testNumber, testNumber, BYTE_MAX_VAL, BYTE_MIN_VAL, BYTE_MAX_VAL, BYTE_MIN_VAL}; + + testStringUS = ",123"; + testStringDE = ".123"; + + // Localized Pattern test + localeValue = testStringDE; + localePattern = "#.###"; + testLocale = Locale.GERMANY; + localeExpected = testNumber; + + } + + /** + * Test ByteValidator validate Methods + */ + public void testByteValidatorMethods() { + Locale locale = Locale.GERMAN; + String pattern = "0,00"; + String patternVal = "1,23"; + String germanPatternVal = "1.23"; + String localeVal = ".123"; + String defaultVal = ",123"; + String XXXX = "XXXX"; + Byte expected = Byte.valueOf((byte)123); + assertEquals("validate(A) default", expected, ByteValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, ByteValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, ByteValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, ByteValidator.getInstance().validate(germanPatternVal, pattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", ByteValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", ByteValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", ByteValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", ByteValidator.getInstance().isValid(germanPatternVal, pattern, Locale.GERMAN)); + + assertNull("validate(B) default", ByteValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", ByteValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", ByteValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", ByteValidator.getInstance().validate(patternVal, pattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", ByteValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", ByteValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", ByteValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", ByteValidator.getInstance().isValid(patternVal, pattern, Locale.GERMAN)); + } + + /** + * Test Byte Range/Min/Max + */ + public void testByteRangeMinMax() { + ByteValidator validator = (ByteValidator)strictValidator; + Byte number9 = validator.validate("9", "#"); + Byte number10 = validator.validate("10", "#"); + Byte number11 = validator.validate("11", "#"); + Byte number19 = validator.validate("19", "#"); + Byte number20 = validator.validate("20", "#"); + Byte number21 = validator.validate("21", "#"); + byte min = (byte)10; + byte max = (byte)20; + + // Test isInRange() + assertFalse("isInRange() < min", validator.isInRange(number9, min, max)); + assertTrue("isInRange() = min", validator.isInRange(number10, min, max)); + assertTrue("isInRange() in range", validator.isInRange(number11, min, max)); + assertTrue("isInRange() = max", validator.isInRange(number20, min, max)); + assertFalse("isInRange() > max", validator.isInRange(number21, min, max)); + + // Test minValue() + assertFalse("minValue() < min", validator.minValue(number9, min)); + assertTrue("minValue() = min", validator.minValue(number10, min)); + assertTrue("minValue() > min", validator.minValue(number11, min)); + + // Test minValue() + assertTrue("maxValue() < max", validator.maxValue(number19, max)); + assertTrue("maxValue() = max", validator.maxValue(number20, max)); + assertFalse("maxValue() > max", validator.maxValue(number21, max)); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CalendarValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CalendarValidatorTest.java new file mode 100644 index 000000000..5e4b2be5d --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CalendarValidatorTest.java @@ -0,0 +1,279 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.Format; +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Test Case for CalendarValidator. + * + * @version $Revision$ + */ +public class CalendarValidatorTest extends AbstractCalendarValidatorTest { + + private static final int DATE_2005_11_23 = 20051123; + private static final int TIME_12_03_45 = 120345; + + private CalendarValidator calValidator; + + /** + * Constructor + * @param name test name + */ + public CalendarValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + calValidator = new CalendarValidator(); + validator = calValidator; + } + + /** + * Test CalendarValidator validate Methods + */ + public void testCalendarValidatorMethods() { + Locale.setDefault(Locale.US); + Locale locale = Locale.GERMAN; + String pattern = "yyyy-MM-dd"; + String patternVal = "2005-12-31"; + String germanVal = "31 Dez 2005"; + String germanPattern = "dd MMM yyyy"; + String localeVal = "31.12.2005"; + String defaultVal = "12/31/05"; + String XXXX = "XXXX"; + Date expected = createCalendar(null, 20051231, 0).getTime(); + assertEquals("validate(A) default", expected, CalendarValidator.getInstance().validate(defaultVal).getTime()); + assertEquals("validate(A) locale ", expected, CalendarValidator.getInstance().validate(localeVal, locale).getTime()); + assertEquals("validate(A) pattern", expected, CalendarValidator.getInstance().validate(patternVal, pattern).getTime()); + assertEquals("validate(A) both", expected, CalendarValidator.getInstance().validate(germanVal, germanPattern, Locale.GERMAN).getTime()); + + assertTrue("isValid(A) default", CalendarValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", CalendarValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", CalendarValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", CalendarValidator.getInstance().isValid(germanVal, germanPattern, Locale.GERMAN)); + + assertNull("validate(B) default", CalendarValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", CalendarValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", CalendarValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", CalendarValidator.getInstance().validate("31 Dec 2005", germanPattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", CalendarValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", CalendarValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", CalendarValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", CalendarValidator.getInstance().isValid("31 Dec 2005", germanPattern, Locale.GERMAN)); + + // Test Time Zone + TimeZone zone = (TimeZone.getDefault().getRawOffset() == EET.getRawOffset() ? EST : EET); + Date expectedZone = createCalendar(zone, 20051231, 0).getTime(); + assertFalse("default/EET same ", expected.getTime() == expectedZone.getTime()); + + assertEquals("validate(C) default", expectedZone, CalendarValidator.getInstance().validate(defaultVal, zone).getTime()); + assertEquals("validate(C) locale ", expectedZone, CalendarValidator.getInstance().validate(localeVal, locale, zone).getTime()); + assertEquals("validate(C) pattern", expectedZone, CalendarValidator.getInstance().validate(patternVal, pattern, zone).getTime()); + assertEquals("validate(C) both", expectedZone, CalendarValidator.getInstance().validate(germanVal, germanPattern, Locale.GERMAN, zone).getTime()); + } + + /** + * Test compare date methods + */ + public void testCompare() { + int sameTime = 124522; + int testDate = 20050823; + Calendar diffHour = createCalendar(GMT, testDate, 115922); // same date, different time + Calendar diffMin = createCalendar(GMT, testDate, 124422); // same date, different time + Calendar diffSec = createCalendar(GMT, testDate, 124521); // same date, different time + + Calendar value = createCalendar(GMT, testDate, sameTime); // test value + Calendar cal20050824 = createCalendar(GMT, 20050824, sameTime); // +1 day + Calendar cal20050822 = createCalendar(GMT, 20050822, sameTime); // -1 day + + Calendar cal20050830 = createCalendar(GMT, 20050830, sameTime); // +1 week + Calendar cal20050816 = createCalendar(GMT, 20050816, sameTime); // -1 week + + Calendar cal20050901 = createCalendar(GMT, 20050901, sameTime); // +1 month + Calendar cal20050801 = createCalendar(GMT, 20050801, sameTime); // same month + Calendar cal20050731 = createCalendar(GMT, 20050731, sameTime); // -1 month + + Calendar cal20051101 = createCalendar(GMT, 20051101, sameTime); // +1 quarter (Feb Start) + Calendar cal20051001 = createCalendar(GMT, 20051001, sameTime); // +1 quarter + Calendar cal20050701 = createCalendar(GMT, 20050701, sameTime); // same quarter + Calendar cal20050630 = createCalendar(GMT, 20050630, sameTime); // -1 quarter + + Calendar cal20060101 = createCalendar(GMT, 20060101, sameTime); // +1 year + Calendar cal20050101 = createCalendar(GMT, 20050101, sameTime); // same year + Calendar cal20041231 = createCalendar(GMT, 20041231, sameTime); // -1 year + + assertEquals("hour GT", 1, calValidator.compare(value, diffHour, Calendar.HOUR_OF_DAY)); + assertEquals("hour EQ", 0, calValidator.compare(value, diffMin, Calendar.HOUR_OF_DAY)); + assertEquals("mins GT", 1, calValidator.compare(value, diffMin, Calendar.MINUTE)); + assertEquals("mins EQ", 0, calValidator.compare(value, diffSec, Calendar.MINUTE)); + assertEquals("secs GT", 1, calValidator.compare(value, diffSec, Calendar.SECOND)); + + assertEquals("date LT", -1, calValidator.compareDates(value, cal20050824)); // +1 day + assertEquals("date EQ", 0, calValidator.compareDates(value, diffHour)); // same day, diff hour + assertEquals("date(B)", 0, calValidator.compare(value, diffHour, Calendar.DAY_OF_YEAR)); // same day, diff hour + assertEquals("date GT", 1, calValidator.compareDates(value, cal20050822)); // -1 day + + assertEquals("week LT", -1, calValidator.compareWeeks(value, cal20050830)); // +1 week + assertEquals("week =1", 0, calValidator.compareWeeks(value, cal20050824)); // +1 day + assertEquals("week =2", 0, calValidator.compareWeeks(value, cal20050822)); // same week + assertEquals("week =3", 0, calValidator.compare(value, cal20050822, Calendar.WEEK_OF_MONTH)); // same week + assertEquals("week =4", 0, calValidator.compareWeeks(value, cal20050822)); // -1 day + assertEquals("week GT", 1, calValidator.compareWeeks(value, cal20050816)); // -1 week + + assertEquals("mnth LT", -1, calValidator.compareMonths(value, cal20050901)); // +1 month + assertEquals("mnth =1", 0, calValidator.compareMonths(value, cal20050830)); // +1 week + assertEquals("mnth =2", 0, calValidator.compareMonths(value, cal20050801)); // same month + assertEquals("mnth =3", 0, calValidator.compareMonths(value, cal20050816)); // -1 week + assertEquals("mnth GT", 1, calValidator.compareMonths(value, cal20050731)); // -1 month + + assertEquals("qtrA <1", -1, calValidator.compareQuarters(value, cal20051101)); // +1 quarter (Feb) + assertEquals("qtrA <2", -1, calValidator.compareQuarters(value, cal20051001)); // +1 quarter + assertEquals("qtrA =1", 0, calValidator.compareQuarters(value, cal20050901)); // +1 month + assertEquals("qtrA =2", 0, calValidator.compareQuarters(value, cal20050701)); // same quarter + assertEquals("qtrA =3", 0, calValidator.compareQuarters(value, cal20050731)); // -1 month + assertEquals("qtrA GT", 1, calValidator.compareQuarters(value, cal20050630)); // -1 quarter + + // Change quarter 1 to start in Feb + assertEquals("qtrB LT", -1, calValidator.compareQuarters(value, cal20051101, 2)); // +1 quarter (Feb) + assertEquals("qtrB =1", 0, calValidator.compareQuarters(value, cal20051001, 2)); // same quarter + assertEquals("qtrB =2", 0, calValidator.compareQuarters(value, cal20050901, 2)); // +1 month + assertEquals("qtrB =3", 1, calValidator.compareQuarters(value, cal20050701, 2)); // same quarter + assertEquals("qtrB =4", 1, calValidator.compareQuarters(value, cal20050731, 2)); // -1 month + assertEquals("qtrB GT", 1, calValidator.compareQuarters(value, cal20050630, 2)); // -1 quarter + + assertEquals("year LT", -1, calValidator.compareYears(value, cal20060101)); // +1 year + assertEquals("year EQ", 0, calValidator.compareYears(value, cal20050101)); // same year + assertEquals("year GT", 1, calValidator.compareYears(value, cal20041231)); // -1 year + + // invalid compare + try { + calValidator.compare(value, value, -1); + fail("Invalid Compare field - expected IllegalArgumentException to be thrown"); + } catch (IllegalArgumentException e) { + assertEquals("check message", "Invalid field: -1", e.getMessage()); + } + } + + /** + * Test Date/Time style Validator (there isn't an implementation for this) + */ + public void testDateTimeStyle() { + // Set the default Locale + Locale origDefault = Locale.getDefault(); + Locale.setDefault(Locale.UK); + + AbstractCalendarValidator dateTimeValidator = + new AbstractCalendarValidator(true, DateFormat.SHORT, DateFormat.SHORT) { + private static final long serialVersionUID = 1L; + + @Override + protected Object processParsedValue(Object value, Format formatter) { + return value; + } + }; + assertTrue("validate(A) default", dateTimeValidator.isValid("31/12/05 14:23")); + assertTrue("validate(A) locale ", dateTimeValidator.isValid("12/31/05 2:23 PM", Locale.US)); + + // Restore the original default + Locale.setDefault(origDefault); + } + + /** + * Test format methods + */ + @Override + public void testFormat() { + // Set the default Locale + Locale origDefault = Locale.getDefault(); + Locale.setDefault(Locale.UK); + + Calendar cal20050101 = createCalendar(GMT, 20051231, 11500); + assertNull("null", calValidator.format(null)); + assertEquals("default", "31/12/05", calValidator.format(cal20050101)); + assertEquals("locale", "12/31/05", calValidator.format(cal20050101, Locale.US)); + assertEquals("patternA", "2005-12-31 01:15", calValidator.format(cal20050101, "yyyy-MM-dd HH:mm")); + assertEquals("patternB", "2005-12-31 GMT", calValidator.format(cal20050101, "yyyy-MM-dd z")); + assertEquals("both", "31 Dez 2005", calValidator.format(cal20050101, "dd MMM yyyy", Locale.GERMAN)); + + // EST Time Zone + assertEquals("EST default", "30/12/05", calValidator.format(cal20050101, EST)); + assertEquals("EST locale", "12/30/05", calValidator.format(cal20050101, Locale.US, EST)); + assertEquals("EST patternA", "2005-12-30 20:15", calValidator.format(cal20050101, "yyyy-MM-dd HH:mm", EST)); + assertEquals("EST patternB", "2005-12-30 EST", calValidator.format(cal20050101, "yyyy-MM-dd z", EST)); + assertEquals("EST both", "30 Dez 2005", calValidator.format(cal20050101, "dd MMM yyyy", Locale.GERMAN, EST)); + + // Restore the original default + Locale.setDefault(origDefault); + } + + /** + * Test adjustToTimeZone() method + */ + public void testAdjustToTimeZone() { + + Calendar calEST = createCalendar(EST, DATE_2005_11_23, TIME_12_03_45); + Date dateEST = calEST.getTime(); + + Calendar calGMT = createCalendar(GMT, DATE_2005_11_23, TIME_12_03_45); + Date dateGMT = calGMT.getTime(); + + Calendar calCET = createCalendar(EET, DATE_2005_11_23, TIME_12_03_45); + Date dateCET = calCET.getTime(); + + // Check the dates don't match + assertFalse("Check GMT != CET", dateGMT.getTime() == dateCET.getTime()); + assertFalse("Check GMT != EST", dateGMT.getTime() == dateEST.getTime()); + assertFalse("Check CET != EST", dateCET.getTime() == dateEST.getTime()); + + // EST to GMT and back + CalendarValidator.adjustToTimeZone(calEST, GMT); + assertEquals("EST to GMT", dateGMT, calEST.getTime()); + assertFalse("Check EST = GMT", dateEST == calEST.getTime()); + CalendarValidator.adjustToTimeZone(calEST, EST); + assertEquals("back to EST", dateEST, calEST.getTime()); + assertFalse("Check EST != GMT", dateGMT == calEST.getTime()); + + // CET to GMT and back + CalendarValidator.adjustToTimeZone(calCET, GMT); + assertEquals("CET to GMT", dateGMT, calCET.getTime()); + assertFalse("Check CET = GMT", dateCET == calCET.getTime()); + CalendarValidator.adjustToTimeZone(calCET, EET); + assertEquals("back to CET", dateCET, calCET.getTime()); + assertFalse("Check CET != GMT", dateGMT == calCET.getTime()); + + // Adjust to TimeZone with Same rules + Calendar calUTC = createCalendar(UTC, DATE_2005_11_23, TIME_12_03_45); + assertTrue("SAME: UTC = GMT", UTC.hasSameRules(GMT)); + assertEquals("SAME: Check time (A)", calUTC.getTime(), calGMT.getTime()); + assertFalse("SAME: Check GMT(A)", GMT.equals(calUTC.getTimeZone())); + assertTrue("SAME: Check UTC(A)", UTC.equals(calUTC.getTimeZone())); + CalendarValidator.adjustToTimeZone(calUTC, GMT); + assertEquals("SAME: Check time (B)", calUTC.getTime(), calGMT.getTime()); + assertTrue("SAME: Check GMT(B)", GMT.equals(calUTC.getTimeZone())); + assertFalse("SAME: Check UTC(B)", UTC.equals(calUTC.getTimeZone())); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CodeValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CodeValidatorTest.java new file mode 100644 index 000000000..29d79ddcf --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CodeValidatorTest.java @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import org.apache.commons.validator.routines.checkdigit.CheckDigit; +import org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit; + +import junit.framework.TestCase; + +/** + * CodeValidatorTest.java. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class CodeValidatorTest extends TestCase { + + /** + * Construct a test with the specified name. + * @param name The name of the test + */ + public CodeValidatorTest(String name) { + super(name); + } + + /** + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + /** + * @see junit.framework.TestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Test Check Digit. + */ + public void testCheckDigit() { + CodeValidator validator = new CodeValidator((String)null, -1, -1, (CheckDigit)null); + String invalidEAN = "9781930110992"; + String validEAN = "9781930110991"; + + // Test no CheckDigit (i.e. null) + assertNull("No CheckDigit", validator.getCheckDigit()); + assertEquals("No CheckDigit invalid", invalidEAN, validator.validate(invalidEAN)); + assertEquals("No CheckDigit valid", validEAN, validator.validate(validEAN)); + assertEquals("No CheckDigit (is) invalid", true, validator.isValid(invalidEAN)); + assertEquals("No CheckDigit (is) valid", true, validator.isValid(validEAN)); + + // Use the EAN-13 check digit routine + validator = new CodeValidator((String)null, -1, EAN13CheckDigit.EAN13_CHECK_DIGIT); + + assertNotNull("EAN CheckDigit", validator.getCheckDigit()); + assertEquals("EAN CheckDigit invalid", null, validator.validate(invalidEAN)); + assertEquals("EAN CheckDigit valid", validEAN, validator.validate(validEAN)); + assertEquals("EAN CheckDigit (is) invalid", false, validator.isValid(invalidEAN)); + assertEquals("EAN CheckDigit (is) valid", true, validator.isValid(validEAN)); + assertEquals("EAN CheckDigit ex", null, validator.validate("978193011099X")); + } + + /** + * Test the minimum/maximum length + */ + public void testLength() { + CodeValidator validator = new CodeValidator((String)null, -1, -1, (CheckDigit)null); + String length_10 = "1234567890"; + String length_11 = "12345678901"; + String length_12 = "123456789012"; + String length_20 = "12345678901234567890"; + String length_21 = "123456789012345678901"; + String length_22 = "1234567890123456789012"; + + assertEquals("No min", -1, validator.getMinLength()); + assertEquals("No max", -1, validator.getMaxLength()); + + assertEquals("No Length 10", length_10, validator.validate(length_10)); + assertEquals("No Length 11", length_11, validator.validate(length_11)); + assertEquals("No Length 12", length_12, validator.validate(length_12)); + assertEquals("No Length 20", length_20, validator.validate(length_20)); + assertEquals("No Length 21", length_21, validator.validate(length_21)); + assertEquals("No Length 22", length_22, validator.validate(length_22)); + + validator = new CodeValidator((String)null, 11, -1, (CheckDigit)null); + assertEquals("Min 11 - min", 11, validator.getMinLength()); + assertEquals("Min 11 - max", -1, validator.getMaxLength()); + assertEquals("Min 11 - 10", null, validator.validate(length_10)); + assertEquals("Min 11 - 11", length_11, validator.validate(length_11)); + assertEquals("Min 11 - 12", length_12, validator.validate(length_12)); + assertEquals("Min 11 - 20", length_20, validator.validate(length_20)); + assertEquals("Min 11 - 21", length_21, validator.validate(length_21)); + assertEquals("Min 11 - 22", length_22, validator.validate(length_22)); + + validator = new CodeValidator((String)null, -1, 21, (CheckDigit)null); + assertEquals("Max 21 - min", -1, validator.getMinLength()); + assertEquals("Max 21 - max", 21, validator.getMaxLength()); + assertEquals("Max 21 - 10", length_10, validator.validate(length_10)); + assertEquals("Max 21 - 11", length_11, validator.validate(length_11)); + assertEquals("Max 21 - 12", length_12, validator.validate(length_12)); + assertEquals("Max 21 - 20", length_20, validator.validate(length_20)); + assertEquals("Max 21 - 21", length_21, validator.validate(length_21)); + assertEquals("Max 21 - 22", null, validator.validate(length_22)); + + validator = new CodeValidator((String)null, 11, 21, (CheckDigit)null); + assertEquals("Min 11 / Max 21 - min", 11, validator.getMinLength()); + assertEquals("Min 11 / Max 21 - max", 21, validator.getMaxLength()); + assertEquals("Min 11 / Max 21 - 10", null, validator.validate(length_10)); + assertEquals("Min 11 / Max 21 - 11", length_11, validator.validate(length_11)); + assertEquals("Min 11 / Max 21 - 12", length_12, validator.validate(length_12)); + assertEquals("Min 11 / Max 21 - 20", length_20, validator.validate(length_20)); + assertEquals("Min 11 / Max 21 - 21", length_21, validator.validate(length_21)); + assertEquals("Min 11 / Max 21 - 22", null, validator.validate(length_22)); + + validator = new CodeValidator((String)null, 11, 11, (CheckDigit)null); + assertEquals("Exact 11 - min", 11, validator.getMinLength()); + assertEquals("Exact 11 - max", 11, validator.getMaxLength()); + assertEquals("Exact 11 - 10", null, validator.validate(length_10)); + assertEquals("Exact 11 - 11", length_11, validator.validate(length_11)); + assertEquals("Exact 11 - 12", null, validator.validate(length_12)); + } + + /** + * Test Regular Expression. + */ + public void testRegex() { + CodeValidator validator = new CodeValidator((String)null, -1, -1, (CheckDigit)null); + + String value2 = "12"; + String value3 = "123"; + String value4 = "1234"; + String value5 = "12345"; + String invalid = "12a4"; + + // No Regular Expression + assertNull("No Regex", validator.getRegexValidator()); + assertEquals("No Regex 2", value2, validator.validate(value2)); + assertEquals("No Regex 3", value3, validator.validate(value3)); + assertEquals("No Regex 4", value4, validator.validate(value4)); + assertEquals("No Regex 5", value5, validator.validate(value5)); + assertEquals("No Regex invalid", invalid, validator.validate(invalid)); + + // Regular Expression + String regex = "^([0-9]{3,4})$"; + validator = new CodeValidator(regex, -1, -1, (CheckDigit)null); + assertNotNull("No Regex", validator.getRegexValidator()); + assertEquals("Regex 2", null, validator.validate(value2)); + assertEquals("Regex 3", value3, validator.validate(value3)); + assertEquals("Regex 4", value4, validator.validate(value4)); + assertEquals("Regex 5", null, validator.validate(value5)); + assertEquals("Regex invalid", null, validator.validate(invalid)); + + // Reformatted + regex = "^([0-9]{3})(?:[-\\s])([0-9]{3})$"; + validator = new CodeValidator(new RegexValidator(regex), 6, (CheckDigit)null); + assertEquals("Reformat 123-456", "123456", validator.validate("123-456")); + assertEquals("Reformat 123 456", "123456", validator.validate("123 456")); + assertEquals("Reformat 123456", null, validator.validate("123456")); + assertEquals("Reformat 123.456", null, validator.validate("123.456")); + + regex = "^(?:([0-9]{3})(?:[-\\s])([0-9]{3}))|([0-9]{6})$"; + validator = new CodeValidator(new RegexValidator(regex), 6, (CheckDigit)null); + assertEquals("Reformat 2 Regex", "RegexValidator{" + regex + "}", validator.getRegexValidator().toString()); + assertEquals("Reformat 2 123-456", "123456", validator.validate("123-456")); + assertEquals("Reformat 2 123 456", "123456", validator.validate("123 456")); + assertEquals("Reformat 2 123456", "123456", validator.validate("123456")); + + } + + /** + * Test Regular Expression. + */ + public void testNoInput() { + CodeValidator validator = new CodeValidator((String)null, -1, -1, (CheckDigit)null); + assertEquals("Null", null, validator.validate(null)); + assertEquals("Zero Length", null, validator.validate("")); + assertEquals("Spaces", null, validator.validate(" ")); + assertEquals("Trimmed", "A", validator.validate(" A ")); + } + + public void testValidator294_1() { + CodeValidator validator = new CodeValidator((String)null, 0, -1, (CheckDigit)null); + assertEquals("Null", null, validator.validate(null)); + validator = new CodeValidator((String)null, -1, 0, (CheckDigit)null); + assertEquals("Null", null, validator.validate(null)); + } + + public void testValidator294_2() { + CodeValidator validator = new CodeValidator((String)null, -1, 0, (CheckDigit)null); + assertEquals("Null", null, validator.validate(null)); + } + + /** + * Test Regular Expression. + */ + public void testConstructors() { + CodeValidator validator = null; + RegexValidator regex = new RegexValidator("^[0-9]*$"); + + // Constructor 1 + validator = new CodeValidator(regex, EAN13CheckDigit.EAN13_CHECK_DIGIT); + assertEquals("Constructor 1 - regex", regex, validator.getRegexValidator()); + assertEquals("Constructor 1 - min length", -1, validator.getMinLength()); + assertEquals("Constructor 1 - max length", -1, validator.getMaxLength()); + assertEquals("Constructor 1 - check digit", EAN13CheckDigit.EAN13_CHECK_DIGIT, validator.getCheckDigit()); + + // Constructor 2 + validator = new CodeValidator(regex, 13, EAN13CheckDigit.EAN13_CHECK_DIGIT); + assertEquals("Constructor 2 - regex", regex, validator.getRegexValidator()); + assertEquals("Constructor 2 - min length", 13, validator.getMinLength()); + assertEquals("Constructor 2 - max length", 13, validator.getMaxLength()); + assertEquals("Constructor 2 - check digit", EAN13CheckDigit.EAN13_CHECK_DIGIT, validator.getCheckDigit()); + + // Constructor 3 + validator = new CodeValidator(regex, 10, 20, EAN13CheckDigit.EAN13_CHECK_DIGIT); + assertEquals("Constructor 3 - regex", regex, validator.getRegexValidator()); + assertEquals("Constructor 3 - min length", 10, validator.getMinLength()); + assertEquals("Constructor 3 - max length", 20, validator.getMaxLength()); + assertEquals("Constructor 3 - check digit", EAN13CheckDigit.EAN13_CHECK_DIGIT, validator.getCheckDigit()); + + // Constructor 4 + validator = new CodeValidator("^[0-9]*$", EAN13CheckDigit.EAN13_CHECK_DIGIT); + assertEquals("Constructor 4 - regex", "RegexValidator{^[0-9]*$}", validator.getRegexValidator().toString()); + assertEquals("Constructor 4 - min length", -1, validator.getMinLength()); + assertEquals("Constructor 4 - max length", -1, validator.getMaxLength()); + assertEquals("Constructor 4 - check digit", EAN13CheckDigit.EAN13_CHECK_DIGIT, validator.getCheckDigit()); + + // Constructor 5 + validator = new CodeValidator("^[0-9]*$", 13, EAN13CheckDigit.EAN13_CHECK_DIGIT); + assertEquals("Constructor 5 - regex", "RegexValidator{^[0-9]*$}", validator.getRegexValidator().toString()); + assertEquals("Constructor 5 - min length", 13, validator.getMinLength()); + assertEquals("Constructor 5 - max length", 13, validator.getMaxLength()); + assertEquals("Constructor 5 - check digit", EAN13CheckDigit.EAN13_CHECK_DIGIT, validator.getCheckDigit()); + + // Constructor 6 + validator = new CodeValidator("^[0-9]*$", 10, 20, EAN13CheckDigit.EAN13_CHECK_DIGIT); + assertEquals("Constructor 6 - regex", "RegexValidator{^[0-9]*$}", validator.getRegexValidator().toString()); + assertEquals("Constructor 6 - min length", 10, validator.getMinLength()); + assertEquals("Constructor 6 - max length", 20, validator.getMaxLength()); + assertEquals("Constructor 6 - check digit", EAN13CheckDigit.EAN13_CHECK_DIGIT, validator.getCheckDigit()); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java new file mode 100644 index 000000000..760538930 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java @@ -0,0 +1,650 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import junit.framework.TestCase; +import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit; +import org.apache.commons.validator.routines.CreditCardValidator.CreditCardRange; + +/** + * Test the CreditCardValidator class. + * + * @version $Revision$ + */ +public class CreditCardValidatorTest extends TestCase { + + private static final String VALID_VISA = "4417123456789113"; // 16 + private static final String ERROR_VISA = "4417123456789112"; + private static final String VALID_SHORT_VISA = "4222222222222"; // 13 + private static final String ERROR_SHORT_VISA = "4222222222229"; + private static final String VALID_AMEX = "378282246310005"; // 15 + private static final String ERROR_AMEX = "378282246310001"; + private static final String VALID_MASTERCARD = "5105105105105100"; + private static final String ERROR_MASTERCARD = "5105105105105105"; + private static final String VALID_DISCOVER = "6011000990139424"; + private static final String ERROR_DISCOVER = "6011000990139421"; + private static final String VALID_DISCOVER65 = "6534567890123458"; // FIXME need verified test data for Discover with "65" prefix + private static final String ERROR_DISCOVER65 = "6534567890123450"; // FIXME need verified test data for Discover with "65" prefix + private static final String VALID_DINERS = "30569309025904"; // 14 + private static final String ERROR_DINERS = "30569309025901"; + private static final String VALID_VPAY = "4370000000000061"; // 16 + private static final String VALID_VPAY2 = "4370000000000012"; + private static final String ERROR_VPAY = "4370000000000069"; + + private static final String [] VALID_CARDS = { + VALID_VISA, + VALID_SHORT_VISA, + VALID_AMEX, + VALID_MASTERCARD, + VALID_DISCOVER, + VALID_DISCOVER65, + VALID_DINERS, + VALID_VPAY, + VALID_VPAY2, + }; + + private static final String [] ERROR_CARDS = { + ERROR_VISA, + ERROR_SHORT_VISA, + ERROR_AMEX, + ERROR_MASTERCARD, + ERROR_DISCOVER, + ERROR_DISCOVER65, + ERROR_DINERS, + ERROR_VPAY, +// ERROR_VPAY2, + "", + "12345678901", // too short (11) + "12345678901234567890", // too long (20) + "4417123456789112", // invalid check digit + }; + + /** + * Constructor for CreditCardValidatorTest. + */ + public CreditCardValidatorTest(String name) { + super(name); + } + + public void testIsValid() { + CreditCardValidator ccv = new CreditCardValidator(); + + assertNull(ccv.validate(null)); + + assertFalse(ccv.isValid(null)); + assertFalse(ccv.isValid("")); + assertFalse(ccv.isValid("123456789012")); // too short + assertFalse(ccv.isValid("12345678901234567890")); // too long + assertFalse(ccv.isValid("4417123456789112")); + assertFalse(ccv.isValid("4417q23456w89113")); + assertTrue(ccv.isValid(VALID_VISA)); + assertTrue(ccv.isValid(VALID_SHORT_VISA)); + assertTrue(ccv.isValid(VALID_AMEX)); + assertTrue(ccv.isValid(VALID_MASTERCARD)); + assertTrue(ccv.isValid(VALID_DISCOVER)); + assertTrue(ccv.isValid(VALID_DISCOVER65)); + + assertFalse(ccv.isValid(ERROR_VISA)); + assertFalse(ccv.isValid(ERROR_SHORT_VISA)); + assertFalse(ccv.isValid(ERROR_AMEX)); + assertFalse(ccv.isValid(ERROR_MASTERCARD)); + assertFalse(ccv.isValid(ERROR_DISCOVER)); + assertFalse(ccv.isValid(ERROR_DISCOVER65)); + + // disallow Visa so it should fail even with good number + ccv = new CreditCardValidator(CreditCardValidator.AMEX); + assertFalse(ccv.isValid("4417123456789113")); + } + + public void testAddAllowedCardType() { + CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.NONE); + // Turned off all cards so even valid numbers should fail + assertFalse(ccv.isValid(VALID_VISA)); + assertFalse(ccv.isValid(VALID_AMEX)); + assertFalse(ccv.isValid(VALID_MASTERCARD)); + assertFalse(ccv.isValid(VALID_DISCOVER)); + assertFalse(ccv.isValid(VALID_DINERS)); + } + + /** + * Test the CodeValidator array constructor + */ + public void testArrayConstructor() { + CreditCardValidator ccv = new CreditCardValidator(new CodeValidator[] + {CreditCardValidator.VISA_VALIDATOR, CreditCardValidator.AMEX_VALIDATOR}); + + assertTrue(ccv.isValid(VALID_VISA)); + assertTrue(ccv.isValid(VALID_SHORT_VISA)); + assertTrue(ccv.isValid(VALID_AMEX)); + assertFalse(ccv.isValid(VALID_MASTERCARD)); + assertFalse(ccv.isValid(VALID_DISCOVER)); + + assertFalse(ccv.isValid(ERROR_VISA)); + assertFalse(ccv.isValid(ERROR_SHORT_VISA)); + assertFalse(ccv.isValid(ERROR_AMEX)); + assertFalse(ccv.isValid(ERROR_MASTERCARD)); + assertFalse(ccv.isValid(ERROR_DISCOVER)); + + try { + new CreditCardValidator((CodeValidator[]) null); + fail("Expected IllegalArgumentException"); + } catch(IllegalArgumentException iae) { + // expected result + } + } + + /** + * Test the Amex Card validator + */ + public void testAmexValidator() { + + CodeValidator validator = CreditCardValidator.AMEX_VALIDATOR; + RegexValidator regex = validator.getRegexValidator(); + + // ****** Test Regular Expression ****** + // length 15 and start with a "34" or "37" + assertFalse("Length 12", regex.isValid("343456789012")); + assertFalse("Length 13", regex.isValid("3434567890123")); + assertFalse("Length 14", regex.isValid("34345678901234")); + assertTrue("Length 15", regex.isValid("343456789012345")); + assertFalse("Length 16", regex.isValid("3434567890123456")); + assertFalse("Length 17", regex.isValid("34345678901234567")); + assertFalse("Length 18", regex.isValid("343456789012345678")); + assertFalse("Prefix 33", regex.isValid("333456789012345")); + assertTrue("Prefix 34", regex.isValid("343456789012345")); + assertFalse("Prefix 35", regex.isValid("353456789012345")); + assertFalse("Prefix 36", regex.isValid("363456789012345")); + assertTrue("Prefix 37", regex.isValid("373456789012345")); + assertFalse("Prefix 38", regex.isValid("383456789012345")); + assertFalse("Prefix 41", regex.isValid("413456789012345")); + assertFalse("Invalid Char", regex.isValid("3434567x9012345")); + + // *********** Test Validator ********** + assertTrue("Valid regex", regex.isValid(ERROR_AMEX)); + assertFalse("Invalid", validator.isValid(ERROR_AMEX)); + assertNull("validate()", validator.validate(ERROR_AMEX)); + assertEquals(VALID_AMEX, validator.validate(VALID_AMEX)); + + assertTrue("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertFalse("Visa", validator.isValid(VALID_VISA)); + assertFalse("Visa Short", validator.isValid(VALID_SHORT_VISA)); + + assertTrue("Valid-A", validator.isValid("371449635398431")); + assertTrue("Valid-B", validator.isValid("340000000000009")); + assertTrue("Valid-C", validator.isValid("370000000000002")); + assertTrue("Valid-D", validator.isValid("378734493671000")); + } + + /** + * Test the Amex Card option + */ + public void testAmexOption() { + CreditCardValidator validator = new CreditCardValidator(CreditCardValidator.AMEX); + assertFalse("Invalid", validator.isValid(ERROR_AMEX)); + assertNull("validate()", validator.validate(ERROR_AMEX)); + assertEquals(VALID_AMEX, validator.validate(VALID_AMEX)); + + assertTrue("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertFalse("Visa", validator.isValid(VALID_VISA)); + assertFalse("Visa Short", validator.isValid(VALID_SHORT_VISA)); + } + + /** + * Test the Diners Card validator + */ + public void testDinersValidator() { + + CodeValidator validator = CreditCardValidator.DINERS_VALIDATOR; + RegexValidator regex = validator.getRegexValidator(); + + // ****** Test Regular Expression ****** + // length 14 and start with a "300-305" or "3095" or "36" or "38" or "39" + assertFalse("Length 12-300", regex.isValid("300456789012")); + assertFalse("Length 12-36", regex.isValid("363456789012")); + assertFalse("Length 13-300", regex.isValid("3004567890123")); + assertFalse("Length 13-36", regex.isValid("3634567890123")); + assertTrue("Length 14-300", regex.isValid("30045678901234")); + assertTrue("Length 14-36", regex.isValid("36345678901234")); + assertFalse("Length 15-300", regex.isValid("300456789012345")); + assertFalse("Length 15-36", regex.isValid("363456789012345")); + assertFalse("Length 16-300", regex.isValid("3004567890123456")); + assertFalse("Length 16-36", regex.isValid("3634567890123456")); + assertFalse("Length 17-300", regex.isValid("30045678901234567")); + assertFalse("Length 17-36", regex.isValid("36345678901234567")); + assertFalse("Length 18-300", regex.isValid("300456789012345678")); + assertFalse("Length 18-36", regex.isValid("363456789012345678")); + + assertTrue("Prefix 300", regex.isValid("30045678901234")); + assertTrue("Prefix 301", regex.isValid("30145678901234")); + assertTrue("Prefix 302", regex.isValid("30245678901234")); + assertTrue("Prefix 303", regex.isValid("30345678901234")); + assertTrue("Prefix 304", regex.isValid("30445678901234")); + assertTrue("Prefix 305", regex.isValid("30545678901234")); + assertFalse("Prefix 306", regex.isValid("30645678901234")); + assertFalse("Prefix 3094", regex.isValid("30945678901234")); + assertTrue( "Prefix 3095", regex.isValid("30955678901234")); + assertFalse("Prefix 3096", regex.isValid("30965678901234")); + assertFalse("Prefix 35", regex.isValid("35345678901234")); + assertTrue("Prefix 36", regex.isValid("36345678901234")); + assertFalse("Prefix 37", regex.isValid("37345678901234")); + assertTrue("Prefix 38", regex.isValid("38345678901234")); + assertTrue("Prefix 39", regex.isValid("39345678901234")); + + assertFalse("Invalid Char-A", regex.isValid("3004567x901234")); + assertFalse("Invalid Char-B", regex.isValid("3634567x901234")); + + // *********** Test Validator ********** + assertTrue("Valid regex", regex.isValid(ERROR_DINERS)); + assertFalse("Invalid", validator.isValid(ERROR_DINERS)); + assertNull("validate()", validator.validate(ERROR_DINERS)); + assertEquals(VALID_DINERS, validator.validate(VALID_DINERS)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertTrue("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertFalse("Visa", validator.isValid(VALID_VISA)); + assertFalse("Visa Short", validator.isValid(VALID_SHORT_VISA)); + + assertTrue("Valid-A", validator.isValid("30000000000004")); + assertTrue("Valid-B", validator.isValid("30123456789019")); + assertTrue("Valid-C", validator.isValid("36432685260294")); + + } + + /** + * Test the Diners Card option + */ + public void testDinersOption() { + CreditCardValidator validator = new CreditCardValidator(CreditCardValidator.DINERS); + assertFalse("Invalid", validator.isValid(ERROR_DINERS)); + assertNull("validate()", validator.validate(ERROR_DINERS)); + assertEquals(VALID_DINERS, validator.validate(VALID_DINERS)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertTrue("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertFalse("Visa", validator.isValid(VALID_VISA)); + assertFalse("Visa Short", validator.isValid(VALID_SHORT_VISA)); + } + + /** + * Test the Discover Card validator + */ + public void testDiscoverValidator() { + + CodeValidator validator = CreditCardValidator.DISCOVER_VALIDATOR; + RegexValidator regex = validator.getRegexValidator(); + + // ****** Test Regular Expression ****** + // length 16 and start with either "6011" or or "64[4-9]" or "65" + assertFalse("Length 12-6011", regex.isValid("601156789012")); + assertFalse("Length 12-65", regex.isValid("653456789012")); + assertFalse("Length 13-6011", regex.isValid("6011567890123")); + assertFalse("Length 13-65", regex.isValid("6534567890123")); + assertFalse("Length 14-6011", regex.isValid("60115678901234")); + assertFalse("Length 14-65", regex.isValid("65345678901234")); + assertFalse("Length 15-6011", regex.isValid("601156789012345")); + assertFalse("Length 15-65", regex.isValid("653456789012345")); + assertTrue("Length 16-6011", regex.isValid("6011567890123456")); + assertTrue("Length 16-644", regex.isValid("6444567890123456")); + assertTrue("Length 16-648", regex.isValid("6484567890123456")); + assertTrue("Length 16-65", regex.isValid("6534567890123456")); + assertFalse("Length 17-6011", regex.isValid("60115678901234567")); + assertFalse("Length 17-65", regex.isValid("65345678901234567")); + assertFalse("Length 18-6011", regex.isValid("601156789012345678")); + assertFalse("Length 18-65", regex.isValid("653456789012345678")); + + assertFalse("Prefix 640", regex.isValid("6404567890123456")); + assertFalse("Prefix 641", regex.isValid("6414567890123456")); + assertFalse("Prefix 642", regex.isValid("6424567890123456")); + assertFalse("Prefix 643", regex.isValid("6434567890123456")); + assertFalse("Prefix 6010", regex.isValid("6010567890123456")); + assertFalse("Prefix 6012", regex.isValid("6012567890123456")); + assertFalse("Invalid Char", regex.isValid("6011567x90123456")); + + // *********** Test Validator ********** + assertTrue("Valid regex", regex.isValid(ERROR_DISCOVER)); + assertTrue("Valid regex65", regex.isValid(ERROR_DISCOVER65)); + assertFalse("Invalid", validator.isValid(ERROR_DISCOVER)); + assertFalse("Invalid65", validator.isValid(ERROR_DISCOVER65)); + assertNull("validate()", validator.validate(ERROR_DISCOVER)); + assertEquals(VALID_DISCOVER, validator.validate(VALID_DISCOVER)); + assertEquals(VALID_DISCOVER65, validator.validate(VALID_DISCOVER65)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertTrue("Discover", validator.isValid(VALID_DISCOVER)); + assertTrue("Discover", validator.isValid(VALID_DISCOVER65)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertFalse("Visa", validator.isValid(VALID_VISA)); + assertFalse("Visa Short", validator.isValid(VALID_SHORT_VISA)); + + assertTrue("Valid-A", validator.isValid("6011111111111117")); + assertTrue("Valid-B", validator.isValid("6011000000000004")); + assertTrue("Valid-C", validator.isValid("6011000000000012")); + + } + + /** + * Test the Discover Card option + */ + public void testDiscoverOption() { + CreditCardValidator validator = new CreditCardValidator(CreditCardValidator.DISCOVER); + assertFalse("Invalid", validator.isValid(ERROR_DISCOVER)); + assertFalse("Invalid65", validator.isValid(ERROR_DISCOVER65)); + assertNull("validate()", validator.validate(ERROR_DISCOVER)); + assertEquals(VALID_DISCOVER, validator.validate(VALID_DISCOVER)); + assertEquals(VALID_DISCOVER65, validator.validate(VALID_DISCOVER65)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertTrue("Discover", validator.isValid(VALID_DISCOVER)); + assertTrue("Discover", validator.isValid(VALID_DISCOVER65)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertFalse("Visa", validator.isValid(VALID_VISA)); + assertFalse("Visa Short", validator.isValid(VALID_SHORT_VISA)); + } + + /** + * Test the Mastercard Card validator + */ + public void testMastercardValidator() { + + CodeValidator validator = CreditCardValidator.MASTERCARD_VALIDATOR; + RegexValidator regex = validator.getRegexValidator(); + + // ****** Test Regular Expression ****** + // length 16 and start with a "51-55" + assertFalse("Length 12", regex.isValid("513456789012")); + assertFalse("Length 13", regex.isValid("5134567890123")); + assertFalse("Length 14", regex.isValid("51345678901234")); + assertFalse("Length 15", regex.isValid("513456789012345")); + assertTrue("Length 16", regex.isValid("5134567890123456")); + assertFalse("Length 17", regex.isValid("51345678901234567")); + assertFalse("Length 18", regex.isValid("513456789012345678")); + assertFalse("Prefix 41", regex.isValid("4134567890123456")); + assertFalse("Prefix 50", regex.isValid("5034567890123456")); + assertTrue("Prefix 51", regex.isValid("5134567890123456")); + assertTrue("Prefix 52", regex.isValid("5234567890123456")); + assertTrue("Prefix 53", regex.isValid("5334567890123456")); + assertTrue("Prefix 54", regex.isValid("5434567890123456")); + assertTrue("Prefix 55", regex.isValid("5534567890123456")); + assertFalse("Prefix 56", regex.isValid("5634567890123456")); + assertFalse("Prefix 61", regex.isValid("6134567890123456")); + assertFalse("Invalid Char", regex.isValid("5134567x90123456")); + + // *********** Test Validator ********** + assertTrue("Valid regex", regex.isValid(ERROR_MASTERCARD)); + assertFalse("Invalid", validator.isValid(ERROR_MASTERCARD)); + assertNull("validate()", validator.validate(ERROR_MASTERCARD)); + assertEquals(VALID_MASTERCARD, validator.validate(VALID_MASTERCARD)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertTrue("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertFalse("Visa", validator.isValid(VALID_VISA)); + assertFalse("Visa Short", validator.isValid(VALID_SHORT_VISA)); + + assertTrue("Valid-A", validator.isValid("5500000000000004")); + assertTrue("Valid-B", validator.isValid("5424000000000015")); + assertTrue("Valid-C", validator.isValid("5301250070000191")); + assertTrue("Valid-D", validator.isValid("5123456789012346")); + assertTrue("Valid-E", validator.isValid("5555555555554444")); + + RegexValidator rev = validator.getRegexValidator(); + final String PAD = "0000000000"; + assertFalse("222099",rev.isValid("222099"+PAD)); + for(int i=222100; i <= 272099; i++) { + String j = Integer.toString(i)+PAD; + assertTrue(j, rev.isValid(j)); + } + assertFalse("272100",rev.isValid("272100"+PAD)); + } + + /** + * Test the Mastercard Card option + */ + public void testMastercardOption() { + CreditCardValidator validator = new CreditCardValidator(CreditCardValidator.MASTERCARD); + assertFalse("Invalid", validator.isValid(ERROR_MASTERCARD)); + assertNull("validate()", validator.validate(ERROR_MASTERCARD)); + assertEquals(VALID_MASTERCARD, validator.validate(VALID_MASTERCARD)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertTrue("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertFalse("Visa", validator.isValid(VALID_VISA)); + assertFalse("Visa Short", validator.isValid(VALID_SHORT_VISA)); + } + + /** + * Test the Visa Card validator + */ + public void testVisaValidator() { + + CodeValidator validator = CreditCardValidator.VISA_VALIDATOR; + RegexValidator regex = validator.getRegexValidator(); + + // ****** Test Regular Expression ****** + // length 13 or 16, must start with a "4" + assertFalse("Length 12", regex.isValid("423456789012")); + assertTrue("Length 13", regex.isValid("4234567890123")); + assertFalse("Length 14", regex.isValid("42345678901234")); + assertFalse("Length 15", regex.isValid("423456789012345")); + assertTrue("Length 16", regex.isValid("4234567890123456")); + assertFalse("Length 17", regex.isValid("42345678901234567")); + assertFalse("Length 18", regex.isValid("423456789012345678")); + assertFalse("Invalid Pref-A", regex.isValid("3234567890123")); + assertFalse("Invalid Pref-B", regex.isValid("3234567890123456")); + assertFalse("Invalid Char-A", regex.isValid("4234567x90123")); + assertFalse("Invalid Char-B", regex.isValid("4234567x90123456")); + + // *********** Test Validator ********** + assertTrue("Valid regex", regex.isValid(ERROR_VISA)); + assertTrue("Valid regex-S", regex.isValid(ERROR_SHORT_VISA)); + assertFalse("Invalid", validator.isValid(ERROR_VISA)); + assertFalse("Invalid-S", validator.isValid(ERROR_SHORT_VISA)); + assertNull("validate()", validator.validate(ERROR_VISA)); + assertEquals(VALID_VISA, validator.validate(VALID_VISA)); + assertEquals(VALID_SHORT_VISA, validator.validate(VALID_SHORT_VISA)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertTrue("Visa", validator.isValid(VALID_VISA)); + assertTrue("Visa Short", validator.isValid(VALID_SHORT_VISA)); + + assertTrue("Valid-A", validator.isValid("4111111111111111")); + assertTrue("Valid-C", validator.isValid("4543059999999982")); + assertTrue("Valid-B", validator.isValid("4462000000000003")); + assertTrue("Valid-D", validator.isValid("4508750000000009")); // Electron + assertTrue("Valid-E", validator.isValid("4012888888881881")); + } + + /** + * Test the Visa Card option + */ + public void testVisaOption() { + CreditCardValidator validator = new CreditCardValidator(CreditCardValidator.VISA); + assertFalse("Invalid", validator.isValid(ERROR_VISA)); + assertFalse("Invalid-S", validator.isValid(ERROR_SHORT_VISA)); + assertNull("validate()", validator.validate(ERROR_VISA)); + assertEquals(VALID_VISA, validator.validate(VALID_VISA)); + assertEquals(VALID_SHORT_VISA, validator.validate(VALID_SHORT_VISA)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertTrue("Visa", validator.isValid(VALID_VISA)); + assertTrue("Visa Short", validator.isValid(VALID_SHORT_VISA)); + } + + public void testVPayOption() { + CreditCardValidator validator = new CreditCardValidator(CreditCardValidator.VPAY); + assertTrue("Valid", validator.isValid(VALID_VPAY)); + assertTrue("Valid", validator.isValid(VALID_VPAY2)); + assertFalse("Invalid", validator.isValid(ERROR_VPAY)); + assertEquals(VALID_VPAY, validator.validate(VALID_VPAY)); + assertEquals(VALID_VPAY2, validator.validate(VALID_VPAY2)); + + assertFalse("Amex", validator.isValid(VALID_AMEX)); + assertFalse("Diners", validator.isValid(VALID_DINERS)); + assertFalse("Discover", validator.isValid(VALID_DISCOVER)); + assertFalse("Mastercard", validator.isValid(VALID_MASTERCARD)); + assertTrue("Visa", validator.isValid(VALID_VISA)); + assertTrue("Visa Short", validator.isValid(VALID_SHORT_VISA)); + } + + /** + * Test using separators + */ + public void testMastercardUsingSeparators() { + + String MASTERCARD_REGEX_SEP = "^(5[1-5]\\d{2})(?:[- ])?(\\d{4})(?:[- ])?(\\d{4})(?:[- ])?(\\d{4})$"; + CodeValidator validator = new CodeValidator(MASTERCARD_REGEX_SEP, LuhnCheckDigit.LUHN_CHECK_DIGIT); + RegexValidator regex = validator.getRegexValidator(); + + // ****** Test Regular Expression ****** + // length 16 and start with a "51-55" + assertEquals("Number", "5134567890123456", regex.validate("5134567890123456")); + assertEquals("Hyphen", "5134567890123456", regex.validate("5134-5678-9012-3456")); + assertEquals("Space", "5134567890123456", regex.validate("5134 5678 9012 3456")); + assertEquals("MixedA", "5134567890123456", regex.validate("5134-5678 9012-3456")); + assertEquals("MixedB", "5134567890123456", regex.validate("5134 5678-9012 3456")); + + assertFalse("Invalid Separator A", regex.isValid("5134.5678.9012.3456")); + assertFalse("Invalid Separator B", regex.isValid("5134_5678_9012_3456")); + assertFalse("Invalid Grouping A", regex.isValid("513-45678-9012-3456")); + assertFalse("Invalid Grouping B", regex.isValid("5134-567-89012-3456")); + assertFalse("Invalid Grouping C", regex.isValid("5134-5678-901-23456")); + + // *********** Test Validator ********** + assertEquals("Valid-A", "5500000000000004", validator.validate("5500-0000-0000-0004")); + assertEquals("Valid-B", "5424000000000015", validator.validate("5424 0000 0000 0015")); + assertEquals("Valid-C", "5301250070000191", validator.validate("5301-250070000191")); + assertEquals("Valid-D", "5123456789012346", validator.validate("5123456789012346")); + } + + public void testGeneric() { + CreditCardValidator ccv = CreditCardValidator.genericCreditCardValidator(); + for(String s : VALID_CARDS) { + assertTrue(s, ccv.isValid(s)); + } + for(String s : ERROR_CARDS) { + assertFalse(s, ccv.isValid(s)); + } + } + + public void testRangeGeneratorNoLuhn() { + CodeValidator cv = CreditCardValidator.createRangeValidator( + new CreditCardRange[]{ + new CreditCardRange("1",null,6,7), + new CreditCardRange("644","65", 8, 8) + }, + null); + assertTrue(cv.isValid("1990000")); + assertTrue(cv.isValid("199000")); + assertFalse(cv.isValid("000000")); + assertFalse(cv.isValid("099999")); + assertFalse(cv.isValid("200000")); + + assertFalse(cv.isValid("64399999")); + assertTrue(cv.isValid("64400000")); + assertTrue(cv.isValid("64900000")); + assertTrue(cv.isValid("65000000")); + assertTrue(cv.isValid("65999999")); + assertFalse(cv.isValid("66000000")); + } + + public void testRangeGenerator() { + CreditCardValidator ccv = new CreditCardValidator( + new CodeValidator[] { + CreditCardValidator.AMEX_VALIDATOR, + CreditCardValidator.VISA_VALIDATOR, + CreditCardValidator.MASTERCARD_VALIDATOR, + CreditCardValidator.DISCOVER_VALIDATOR, + }, + // Add missing validator + new CreditCardRange[]{ + new CreditCardRange("300", "305", 14, 14), // Diners + new CreditCardRange("3095", null, 14, 14), // Diners + new CreditCardRange("36", null, 14, 14), // Diners + new CreditCardRange("38", "39", 14, 14), // Diners + } + // we don't have any VPAY examples yet that aren't handled by VISA + ); + for(String s : VALID_CARDS) { + assertTrue(s, ccv.isValid(s)); + } + for(String s : ERROR_CARDS) { + assertFalse(s, ccv.isValid(s)); + } + } + + public void testValidLength() { + assertTrue(CreditCardValidator.validLength(14, new CreditCardRange("", "", 14, 14))); + assertFalse(CreditCardValidator.validLength(15, new CreditCardRange("", "", 14, 14))); + assertFalse(CreditCardValidator.validLength(13, new CreditCardRange("", "", 14, 14))); + + assertFalse(CreditCardValidator.validLength(14, new CreditCardRange("", "", 15, 17))); + assertTrue(CreditCardValidator.validLength(15, new CreditCardRange("", "", 15, 17))); + assertTrue(CreditCardValidator.validLength(16, new CreditCardRange("", "", 15, 17))); + assertTrue(CreditCardValidator.validLength(17, new CreditCardRange("", "", 15, 17))); + assertFalse(CreditCardValidator.validLength(18, new CreditCardRange("", "", 15, 17))); + + assertFalse(CreditCardValidator.validLength(14, new CreditCardRange("", "", new int[]{15, 17}))); + assertTrue(CreditCardValidator.validLength(15, new CreditCardRange("", "", new int[]{15, 17}))); + assertFalse(CreditCardValidator.validLength(16, new CreditCardRange("", "", new int[]{15, 17}))); + assertTrue(CreditCardValidator.validLength(17, new CreditCardRange("", "", new int[]{15, 17}))); + assertFalse(CreditCardValidator.validLength(18, new CreditCardRange("", "", new int[]{15, 17}))); + } + + public void testDisjointRange() { + CreditCardValidator ccv = new CreditCardValidator( + new CreditCardRange[]{ + new CreditCardRange("305", "4", new int[]{13, 16}), + } + ); + assertEquals(13, VALID_SHORT_VISA.length()); + assertEquals(16, VALID_VISA.length()); + assertEquals(14, VALID_DINERS.length()); + assertTrue(ccv.isValid(VALID_SHORT_VISA)); + assertTrue(ccv.isValid(VALID_VISA)); + assertFalse(ccv.isValid(ERROR_SHORT_VISA)); + assertFalse(ccv.isValid(ERROR_VISA)); + assertFalse(ccv.isValid(VALID_DINERS)); + ccv = new CreditCardValidator( + new CreditCardRange[]{ + // add 14 as a valid length + new CreditCardRange("305", "4", new int[]{13, 14, 16}), + } + ); + assertTrue(ccv.isValid(VALID_DINERS)); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CurrencyValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CurrencyValidatorTest.java new file mode 100644 index 000000000..12b9000cf --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CurrencyValidatorTest.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import junit.framework.TestCase; + +import java.util.Locale; +import java.math.BigDecimal; +import java.text.DecimalFormatSymbols; + +/** + * Test Case for CurrencyValidator. + * + * @version $Revision$ + */ +public class CurrencyValidatorTest extends TestCase { + + private static final char CURRENCY_SYMBOL = '\u00A4'; + + private String US_DOLLAR; + private String UK_POUND; + + /** + * Constructor + * @param name test name + */ + public CurrencyValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + US_DOLLAR = (new DecimalFormatSymbols(Locale.US)).getCurrencySymbol(); + UK_POUND = (new DecimalFormatSymbols(Locale.UK)).getCurrencySymbol(); + } + + /** + * Tear down + * @throws Exception + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Test Format Type + */ + public void testFormatType() { + assertEquals("Format Type A", 1, CurrencyValidator.getInstance().getFormatType()); + assertEquals("Format Type B", AbstractNumberValidator.CURRENCY_FORMAT, CurrencyValidator.getInstance().getFormatType()); + } + + /** + * Test Valid currency values + */ + public void testValid() { + // Set the default Locale + Locale origDefault = Locale.getDefault(); + Locale.setDefault(Locale.UK); + + BigDecimalValidator validator = CurrencyValidator.getInstance(); + BigDecimal expected = new BigDecimal("1234.56"); + BigDecimal negative = new BigDecimal("-1234.56"); + BigDecimal noDecimal = new BigDecimal("1234.00"); + BigDecimal oneDecimal = new BigDecimal("1234.50"); + + assertEquals("Default locale", expected, validator.validate(UK_POUND + "1,234.56")); + + assertEquals("UK locale", expected, validator.validate(UK_POUND + "1,234.56", Locale.UK)); + assertEquals("UK negative", negative, validator.validate("-" + UK_POUND + "1,234.56", Locale.UK)); + assertEquals("UK no decimal", noDecimal, validator.validate(UK_POUND + "1,234", Locale.UK)); + assertEquals("UK 1 decimal", oneDecimal, validator.validate(UK_POUND + "1,234.5", Locale.UK)); + assertEquals("UK 3 decimal", expected, validator.validate(UK_POUND + "1,234.567", Locale.UK)); + assertEquals("UK no symbol", expected, validator.validate("1,234.56", Locale.UK)); + + assertEquals("US locale", expected, validator.validate(US_DOLLAR + "1,234.56", Locale.US)); + assertEquals("US negative", negative, validator.validate("(" + US_DOLLAR + "1,234.56)", Locale.US)); + assertEquals("US no decimal", noDecimal, validator.validate(US_DOLLAR + "1,234", Locale.US)); + assertEquals("US 1 decimal", oneDecimal, validator.validate(US_DOLLAR + "1,234.5", Locale.US)); + assertEquals("US 3 decimal", expected, validator.validate(US_DOLLAR + "1,234.567", Locale.US)); + assertEquals("US no symbol", expected, validator.validate("1,234.56", Locale.US)); + + // Restore the original default + Locale.setDefault(origDefault); + } + + /** + * Test Invalid currency values + */ + public void testInvalid() { + BigDecimalValidator validator = CurrencyValidator.getInstance(); + + // Invalid Missing + assertFalse("isValid() Null Value", validator.isValid(null)); + assertFalse("isValid() Empty Value", validator.isValid("")); + assertNull("validate() Null Value", validator.validate(null)); + assertNull("validate() Empty Value", validator.validate("")); + + // Invalid UK + assertFalse("UK wrong symbol", validator.isValid(US_DOLLAR + "1,234.56", Locale.UK)); + assertFalse("UK wrong negative", validator.isValid("(" + UK_POUND + "1,234.56)", Locale.UK)); + + // Invalid US + assertFalse("US wrong symbol", validator.isValid(UK_POUND + "1,234.56", Locale.US)); + assertFalse("US wrong negative", validator.isValid("-" + US_DOLLAR + "1,234.56", Locale.US)); + } + + /** + * Test Valid integer (non-decimal) currency values + */ + public void testIntegerValid() { + // Set the default Locale + Locale origDefault = Locale.getDefault(); + Locale.setDefault(Locale.UK); + + CurrencyValidator validator = new CurrencyValidator(); + BigDecimal expected = new BigDecimal("1234.00"); + BigDecimal negative = new BigDecimal("-1234.00"); + + assertEquals("Default locale", expected, validator.validate(UK_POUND +"1,234")); + + assertEquals("UK locale", expected, validator.validate(UK_POUND + "1,234", Locale.UK)); + assertEquals("UK negative", negative, validator.validate("-" + UK_POUND + "1,234", Locale.UK)); + + assertEquals("US locale", expected, validator.validate(US_DOLLAR + "1,234", Locale.US)); + assertEquals("US negative", negative, validator.validate("(" + US_DOLLAR + "1,234)", Locale.US)); + + // Restore the original default + Locale.setDefault(origDefault); + } + + /** + * Test Invalid integer (non decimal) currency values + */ + public void testIntegerInvalid() { + CurrencyValidator validator = new CurrencyValidator(true, false); + + // Invalid UK - has decimals + assertFalse("UK positive", validator.isValid(UK_POUND + "1,234.56", Locale.UK)); + assertFalse("UK negative", validator.isValid("-" + UK_POUND + "1,234.56", Locale.UK)); + + // Invalid US - has decimals + assertFalse("US positive", validator.isValid(US_DOLLAR + "1,234.56", Locale.US)); + assertFalse("US negative", validator.isValid("(" + US_DOLLAR + "1,234.56)", Locale.US)); + } + + + /** + * Test currency values with a pattern + */ + public void testPattern() { + // Set the default Locale + Locale origDefault = Locale.getDefault(); + Locale.setDefault(Locale.UK); + + BigDecimalValidator validator = CurrencyValidator.getInstance(); + String basicPattern = CURRENCY_SYMBOL + "#,##0.000"; + String pattern = basicPattern + ";[" + basicPattern +"]"; + BigDecimal expected = new BigDecimal("1234.567"); + BigDecimal negative = new BigDecimal("-1234.567"); + + // Test Pattern + assertEquals("default", expected, validator.validate(UK_POUND + "1,234.567", pattern)); + assertEquals("negative", negative, validator.validate("[" + UK_POUND + "1,234.567]", pattern)); + assertEquals("no symbol +ve", expected, validator.validate("1,234.567", pattern)); + assertEquals("no symbol -ve", negative, validator.validate("[1,234.567]", pattern)); + + // Test Pattern & Locale + assertEquals("default", expected, validator.validate(US_DOLLAR + "1,234.567", pattern, Locale.US)); + assertEquals("negative", negative, validator.validate("[" + US_DOLLAR + "1,234.567]", pattern, Locale.US)); + assertEquals("no symbol +ve", expected, validator.validate("1,234.567", pattern, Locale.US)); + assertEquals("no symbol -ve", negative, validator.validate("[1,234.567]", pattern, Locale.US)); + + // invalid + assertFalse("invalid symbol", validator.isValid(US_DOLLAR + "1,234.567", pattern)); + assertFalse("invalid symbol", validator.isValid(UK_POUND + "1,234.567", pattern, Locale.US)); + + // Restore the original default + Locale.setDefault(origDefault); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DateValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DateValidatorTest.java new file mode 100644 index 000000000..a17f6b020 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DateValidatorTest.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Test Case for DateValidator. + * + * @version $Revision$ + */ +public class DateValidatorTest extends AbstractCalendarValidatorTest { + + private DateValidator dateValidator; + + /** + * Constructor + * @param name test name + */ + public DateValidatorTest(String name) { + super(name); + } + + /** + * Set Up. + * @throws Exception + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + dateValidator = new DateValidator(); + validator = dateValidator; + } + + /** + * Test DateValidator validate Methods + */ + public void testDateValidatorMethods() { + Locale.setDefault(Locale.US); + Locale locale = Locale.GERMAN; + String pattern = "yyyy-MM-dd"; + String patternVal = "2005-12-31"; + String germanVal = "31 Dez 2005"; + String germanPattern = "dd MMM yyyy"; + String localeVal = "31.12.2005"; + String defaultVal = "12/31/05"; + String XXXX = "XXXX"; + Date expected = createCalendar(null, 20051231, 0).getTime(); + + assertEquals("validate(A) default", expected, DateValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, DateValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, DateValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, DateValidator.getInstance().validate(germanVal, germanPattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", DateValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", DateValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", DateValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", DateValidator.getInstance().isValid(germanVal, germanPattern, Locale.GERMAN)); + + assertNull("validate(B) default", DateValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", DateValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", DateValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", DateValidator.getInstance().validate("31 Dec 2005", germanPattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", DateValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", DateValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", DateValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", DateValidator.getInstance().isValid("31 Dec 2005", germanPattern, Locale.GERMAN)); + + // Test Time Zone + TimeZone zone = (TimeZone.getDefault().getRawOffset() == EET.getRawOffset() ? EST : EET); + Date expectedZone = createCalendar(zone, 20051231, 0).getTime(); + assertFalse("default/zone same "+zone, expected.getTime() == expectedZone.getTime()); + + assertEquals("validate(C) default", expectedZone, DateValidator.getInstance().validate(defaultVal, zone)); + assertEquals("validate(C) locale ", expectedZone, DateValidator.getInstance().validate(localeVal, locale, zone)); + assertEquals("validate(C) pattern", expectedZone, DateValidator.getInstance().validate(patternVal, pattern, zone)); + assertEquals("validate(C) both", expectedZone, DateValidator.getInstance().validate(germanVal, germanPattern, Locale.GERMAN, zone)); + } + + /** + * Test compare date methods + */ + public void testCompare() { + int sameTime = 124522; + int testDate = 20050823; + Date diffHour = createDate(GMT, testDate, 115922); // same date, different time + + Date value = createDate(GMT, testDate, sameTime); // test value + Date date20050824 = createDate(GMT, 20050824, sameTime); // +1 day + Date date20050822 = createDate(GMT, 20050822, sameTime); // -1 day + + Date date20050830 = createDate(GMT, 20050830, sameTime); // +1 week + Date date20050816 = createDate(GMT, 20050816, sameTime); // -1 week + + Date date20050901 = createDate(GMT, 20050901, sameTime); // +1 month + Date date20050801 = createDate(GMT, 20050801, sameTime); // same month + Date date20050731 = createDate(GMT, 20050731, sameTime); // -1 month + + Date date20051101 = createDate(GMT, 20051101, sameTime); // +1 quarter (Feb Start) + Date date20051001 = createDate(GMT, 20051001, sameTime); // +1 quarter + Date date20050701 = createDate(GMT, 20050701, sameTime); // same quarter + Date date20050630 = createDate(GMT, 20050630, sameTime); // -1 quarter + Date date20050110 = createDate(GMT, 20050110, sameTime); // Previous Year qtr (Fen start) + + Date date20060101 = createDate(GMT, 20060101, sameTime); // +1 year + Date date20050101 = createDate(GMT, 20050101, sameTime); // same year + Date date20041231 = createDate(GMT, 20041231, sameTime); // -1 year + + assertEquals("date LT", -1, dateValidator.compareDates(value, date20050824, GMT)); // +1 day + assertEquals("date EQ", 0, dateValidator.compareDates(value, diffHour, GMT)); // same day, diff hour + assertEquals("date GT", 1, dateValidator.compareDates(value, date20050822, GMT)); // -1 day + + assertEquals("week LT", -1, dateValidator.compareWeeks(value, date20050830, GMT)); // +1 week + assertEquals("week =1", 0, dateValidator.compareWeeks(value, date20050824, GMT)); // +1 day + assertEquals("week =2", 0, dateValidator.compareWeeks(value, date20050822, GMT)); // same week + assertEquals("week =3", 0, dateValidator.compareWeeks(value, date20050822, GMT)); // -1 day + assertEquals("week GT", 1, dateValidator.compareWeeks(value, date20050816, GMT)); // -1 week + + assertEquals("mnth LT", -1, dateValidator.compareMonths(value, date20050901, GMT)); // +1 month + assertEquals("mnth =1", 0, dateValidator.compareMonths(value, date20050830, GMT)); // +1 week + assertEquals("mnth =2", 0, dateValidator.compareMonths(value, date20050801, GMT)); // same month + assertEquals("mnth =3", 0, dateValidator.compareMonths(value, date20050816, GMT)); // -1 week + assertEquals("mnth GT", 1, dateValidator.compareMonths(value, date20050731, GMT)); // -1 month + + assertEquals("qtrA <1", -1, dateValidator.compareQuarters(value, date20051101, GMT)); // +1 quarter (Feb) + assertEquals("qtrA <2", -1, dateValidator.compareQuarters(value, date20051001, GMT)); // +1 quarter + assertEquals("qtrA =1", 0, dateValidator.compareQuarters(value, date20050901, GMT)); // +1 month + assertEquals("qtrA =2", 0, dateValidator.compareQuarters(value, date20050701, GMT)); // same quarter + assertEquals("qtrA =3", 0, dateValidator.compareQuarters(value, date20050731, GMT)); // -1 month + assertEquals("qtrA GT", 1, dateValidator.compareQuarters(value, date20050630, GMT)); // -1 quarter + + // Change quarter 1 to start in Feb + assertEquals("qtrB LT", -1, dateValidator.compareQuarters(value, date20051101, GMT, 2)); // +1 quarter (Feb) + assertEquals("qtrB =1", 0, dateValidator.compareQuarters(value, date20051001, GMT, 2)); // same quarter + assertEquals("qtrB =2", 0, dateValidator.compareQuarters(value, date20050901, GMT, 2)); // +1 month + assertEquals("qtrB =3", 1, dateValidator.compareQuarters(value, date20050701, GMT, 2)); // same quarter + assertEquals("qtrB =4", 1, dateValidator.compareQuarters(value, date20050731, GMT, 2)); // -1 month + assertEquals("qtrB GT", 1, dateValidator.compareQuarters(value, date20050630, GMT, 2)); // -1 quarter + assertEquals("qtrB prev", 1, dateValidator.compareQuarters(value, date20050110, GMT, 2)); // Jan Prev year qtr + + assertEquals("year LT", -1, dateValidator.compareYears(value, date20060101, GMT)); // +1 year + assertEquals("year EQ", 0, dateValidator.compareYears(value, date20050101, GMT)); // same year + assertEquals("year GT", 1, dateValidator.compareYears(value, date20041231, GMT)); // -1 year + + // Compare using alternative TimeZone + Date sameDayTwoAm = createDate(GMT, testDate, 20000); + assertEquals("date LT", -1, dateValidator.compareDates(value, date20050824, EST)); // +1 day + assertEquals("date EQ", 0, dateValidator.compareDates(value, diffHour, EST)); // same day, diff hour + assertEquals("date EQ", 1, dateValidator.compareDates(value, sameDayTwoAm, EST)); // same day, diff hour + assertEquals("date GT", 1, dateValidator.compareDates(value, date20050822, EST)); // -1 day + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DomainValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DomainValidatorTest.java new file mode 100644 index 000000000..417de6f30 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DomainValidatorTest.java @@ -0,0 +1,721 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.HttpURLConnection; +import java.net.IDN; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.validator.routines.DomainValidator.ArrayType; + +import junit.framework.TestCase; + +/** + * Tests for the DomainValidator. + * + * @version $Revision$ + */ +public class DomainValidatorTest extends TestCase { + + private DomainValidator validator; + + @Override + public void setUp() { + validator = DomainValidator.getInstance(); + DomainValidator.clearTLDOverrides(); // N.B. this clears the inUse flag, allowing overrides + } + + public void testValidDomains() { + assertTrue("apache.org should validate", validator.isValid("apache.org")); + assertTrue("www.google.com should validate", validator.isValid("www.google.com")); + + assertTrue("test-domain.com should validate", validator.isValid("test-domain.com")); + assertTrue("test---domain.com should validate", validator.isValid("test---domain.com")); + assertTrue("test-d-o-m-ain.com should validate", validator.isValid("test-d-o-m-ain.com")); + assertTrue("two-letter domain label should validate", validator.isValid("as.uk")); + + assertTrue("case-insensitive ApAchE.Org should validate", validator.isValid("ApAchE.Org")); + + assertTrue("single-character domain label should validate", validator.isValid("z.com")); + + assertTrue("i.have.an-example.domain.name should validate", validator.isValid("i.have.an-example.domain.name")); + } + + public void testInvalidDomains() { + assertFalse("bare TLD .org shouldn't validate", validator.isValid(".org")); + assertFalse("domain name with spaces shouldn't validate", validator.isValid(" apache.org ")); + assertFalse("domain name containing spaces shouldn't validate", validator.isValid("apa che.org")); + assertFalse("domain name starting with dash shouldn't validate", validator.isValid("-testdomain.name")); + assertFalse("domain name ending with dash shouldn't validate", validator.isValid("testdomain-.name")); + assertFalse("domain name starting with multiple dashes shouldn't validate", validator.isValid("---c.com")); + assertFalse("domain name ending with multiple dashes shouldn't validate", validator.isValid("c--.com")); + assertFalse("domain name with invalid TLD shouldn't validate", validator.isValid("apache.rog")); + + assertFalse("URL shouldn't validate", validator.isValid("http://www.apache.org")); + assertFalse("Empty string shouldn't validate as domain name", validator.isValid(" ")); + assertFalse("Null shouldn't validate as domain name", validator.isValid(null)); + } + + public void testTopLevelDomains() { + // infrastructure TLDs + assertTrue(".arpa should validate as iTLD", validator.isValidInfrastructureTld(".arpa")); + assertFalse(".com shouldn't validate as iTLD", validator.isValidInfrastructureTld(".com")); + + // generic TLDs + assertTrue(".name should validate as gTLD", validator.isValidGenericTld(".name")); + assertFalse(".us shouldn't validate as gTLD", validator.isValidGenericTld(".us")); + + // country code TLDs + assertTrue(".uk should validate as ccTLD", validator.isValidCountryCodeTld(".uk")); + assertFalse(".org shouldn't validate as ccTLD", validator.isValidCountryCodeTld(".org")); + + // case-insensitive + assertTrue(".COM should validate as TLD", validator.isValidTld(".COM")); + assertTrue(".BiZ should validate as TLD", validator.isValidTld(".BiZ")); + + // corner cases + assertFalse("invalid TLD shouldn't validate", validator.isValid(".nope")); // TODO this is not guaranteed invalid forever + assertFalse("empty string shouldn't validate as TLD", validator.isValid("")); + assertFalse("null shouldn't validate as TLD", validator.isValid(null)); + } + + public void testAllowLocal() { + DomainValidator noLocal = DomainValidator.getInstance(false); + DomainValidator allowLocal = DomainValidator.getInstance(true); + + // Default is false, and should use singletons + assertEquals(noLocal, validator); + + // Default won't allow local + assertFalse("localhost.localdomain should validate", noLocal.isValid("localhost.localdomain")); + assertFalse("localhost should validate", noLocal.isValid("localhost")); + + // But it may be requested + assertTrue("localhost.localdomain should validate", allowLocal.isValid("localhost.localdomain")); + assertTrue("localhost should validate", allowLocal.isValid("localhost")); + assertTrue("hostname should validate", allowLocal.isValid("hostname")); + assertTrue("machinename should validate", allowLocal.isValid("machinename")); + + // Check the localhost one with a few others + assertTrue("apache.org should validate", allowLocal.isValid("apache.org")); + assertFalse("domain name with spaces shouldn't validate", allowLocal.isValid(" apache.org ")); + } + + public void testIDN() { + assertTrue("b\u00fccher.ch in IDN should validate", validator.isValid("www.xn--bcher-kva.ch")); + } + + public void testIDNJava6OrLater() { + String version = System.getProperty("java.version"); + if (version.compareTo("1.6") < 0) { + System.out.println("Cannot run Unicode IDN tests"); + return; // Cannot run the test + } // xn--d1abbgf6aiiy.xn--p1ai http://президент.рф + assertTrue("b\u00fccher.ch should validate", validator.isValid("www.b\u00fccher.ch")); + assertTrue("xn--d1abbgf6aiiy.xn--p1ai should validate", validator.isValid("xn--d1abbgf6aiiy.xn--p1ai")); + assertTrue("президент.рф should validate", validator.isValid("президент.рф")); + assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("www.\uFFFD.ch")); + } + + // RFC2396: domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + public void testRFC2396domainlabel() { // use fixed valid TLD + assertTrue("a.ch should validate", validator.isValid("a.ch")); + assertTrue("9.ch should validate", validator.isValid("9.ch")); + assertTrue("az.ch should validate", validator.isValid("az.ch")); + assertTrue("09.ch should validate", validator.isValid("09.ch")); + assertTrue("9-1.ch should validate", validator.isValid("9-1.ch")); + assertFalse("91-.ch should not validate", validator.isValid("91-.ch")); + assertFalse("-.ch should not validate", validator.isValid("-.ch")); + } + + // RFC2396 toplabel = alpha | alpha *( alphanum | "-" ) alphanum + public void testRFC2396toplabel() { + // These tests use non-existent TLDs so currently need to use a package protected method + assertTrue("a.c (alpha) should validate", validator.isValidDomainSyntax("a.c")); + assertTrue("a.cc (alpha alpha) should validate", validator.isValidDomainSyntax("a.cc")); + assertTrue("a.c9 (alpha alphanum) should validate", validator.isValidDomainSyntax("a.c9")); + assertTrue("a.c-9 (alpha - alphanum) should validate", validator.isValidDomainSyntax("a.c-9")); + assertTrue("a.c-z (alpha - alpha) should validate", validator.isValidDomainSyntax("a.c-z")); + + assertFalse("a.9c (alphanum alpha) should fail", validator.isValidDomainSyntax("a.9c")); + assertFalse("a.c- (alpha -) should fail", validator.isValidDomainSyntax("a.c-")); + assertFalse("a.- (-) should fail", validator.isValidDomainSyntax("a.-")); + assertFalse("a.-9 (- alphanum) should fail", validator.isValidDomainSyntax("a.-9")); + } + + public void testDomainNoDots() {// rfc1123 + assertTrue("a (alpha) should validate", validator.isValidDomainSyntax("a")); + assertTrue("9 (alphanum) should validate", validator.isValidDomainSyntax("9")); + assertTrue("c-z (alpha - alpha) should validate", validator.isValidDomainSyntax("c-z")); + + assertFalse("c- (alpha -) should fail", validator.isValidDomainSyntax("c-")); + assertFalse("-c (- alpha) should fail", validator.isValidDomainSyntax("-c")); + assertFalse("- (-) should fail", validator.isValidDomainSyntax("-")); + } + + public void testValidator297() { + assertTrue("xn--d1abbgf6aiiy.xn--p1ai should validate", validator.isValid("xn--d1abbgf6aiiy.xn--p1ai")); // This uses a valid TLD + } + + // labels are a max of 63 chars and domains 253 + public void testValidator306() { + final String longString = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789A"; + assertEquals(63, longString.length()); // 26 * 2 + 11 + + assertTrue("63 chars label should validate", validator.isValidDomainSyntax(longString+".com")); + assertFalse("64 chars label should fail", validator.isValidDomainSyntax(longString+"x.com")); + + assertTrue("63 chars TLD should validate", validator.isValidDomainSyntax("test."+longString)); + assertFalse("64 chars TLD should fail", validator.isValidDomainSyntax("test.x"+longString)); + + final String longDomain = + longString + + "." + longString + + "." + longString + + "." + longString.substring(0,61) + ; + assertEquals(253, longDomain.length()); + assertTrue("253 chars domain should validate", validator.isValidDomainSyntax(longDomain)); + assertFalse("254 chars domain should fail", validator.isValidDomainSyntax(longDomain+"x")); + } + + // Check that IDN.toASCII behaves as it should (when wrapped by DomainValidator.unicodeToASCII) + // Tests show that method incorrectly trims a trailing "." character + public void testUnicodeToASCII() { + String[] asciidots = { + "", + ",", + ".", // fails IDN.toASCII, but should pass wrapped version + "a.", // ditto + "a.b", + "a..b", + "a...b", + ".a", + "..a", + }; + for(String s : asciidots) { + assertEquals(s,DomainValidator.unicodeToASCII(s)); + } + // RFC3490 3.1. 1) +// Whenever dots are used as label separators, the following +// characters MUST be recognized as dots: U+002E (full stop), U+3002 +// (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 +// (halfwidth ideographic full stop). + final String otherDots[][] = { + {"b\u3002", "b.",}, + {"b\uFF0E", "b.",}, + {"b\uFF61", "b.",}, + {"\u3002", ".",}, + {"\uFF0E", ".",}, + {"\uFF61", ".",}, + }; + for(String s[] : otherDots) { + assertEquals(s[1],DomainValidator.unicodeToASCII(s[0])); + } + } + + // Check if IDN.toASCII is broken or not + public void testIsIDNtoASCIIBroken() { + System.out.println(">>DomainValidatorTest.testIsIDNtoASCIIBroken()"); + final String input = "."; + final boolean ok = input.equals(IDN.toASCII(input)); + System.out.println("IDN.toASCII is " + (ok? "OK" : "BROKEN")); + String props[] = { + "java.version", // Java Runtime Environment version + "java.vendor", // Java Runtime Environment vendor + "java.vm.specification.version", // Java Virtual Machine specification version + "java.vm.specification.vendor", // Java Virtual Machine specification vendor + "java.vm.specification.name", // Java Virtual Machine specification name + "java.vm.version", // Java Virtual Machine implementation version + "java.vm.vendor", // Java Virtual Machine implementation vendor + "java.vm.name", // Java Virtual Machine implementation name + "java.specification.version", // Java Runtime Environment specification version + "java.specification.vendor", // Java Runtime Environment specification vendor + "java.specification.name", // Java Runtime Environment specification name + "java.class.version", // Java class format version number + }; + for(String t : props) { + System.out.println(t + "=" + System.getProperty(t)); + } + System.out.println("< ianaTlds = new HashSet(); // keep for comparison with array contents + DomainValidator dv = DomainValidator.getInstance(); + File txtFile = new File("target/tlds-alpha-by-domain.txt"); + long timestamp = download(txtFile, "https://data.iana.org/TLD/tlds-alpha-by-domain.txt", 0L); + final File htmlFile = new File("target/tlds-alpha-by-domain.html"); + // N.B. sometimes the html file may be updated a day or so after the txt file + // if the txt file contains entries not found in the html file, try again in a day or two + download(htmlFile,"https://www.iana.org/domains/root/db", timestamp); + + BufferedReader br = new BufferedReader(new FileReader(txtFile)); + String line; + final String header; + line = br.readLine(); // header + if (line.startsWith("# Version ")) { + header = line.substring(2); + } else { + br.close(); + throw new IOException("File does not have expected Version header"); + } + final boolean generateUnicodeTlds = false; // Change this to generate Unicode TLDs as well + + // Parse html page to get entries + Map htmlInfo = getHtmlInfo(htmlFile); + Map missingTLD = new TreeMap(); // stores entry and comments as String[] + Map missingCC = new TreeMap(); + while((line = br.readLine()) != null) { + if (!line.startsWith("#")) { + final String unicodeTld; // only different from asciiTld if that was punycode + final String asciiTld = line.toLowerCase(Locale.ENGLISH); + if (line.startsWith("XN--")) { + unicodeTld = IDN.toUnicode(line); + } else { + unicodeTld = asciiTld; + } + if (!dv.isValidTld(asciiTld)) { + String [] info = htmlInfo.get(asciiTld); + if (info != null) { + String type = info[0]; + String comment = info[1]; + if ("country-code".equals(type)) { // Which list to use? + missingCC.put(asciiTld, unicodeTld + " " + comment); + if (generateUnicodeTlds) { + missingCC.put(unicodeTld, asciiTld + " " + comment); + } + } else { + missingTLD.put(asciiTld, unicodeTld + " " + comment); + if (generateUnicodeTlds) { + missingTLD.put(unicodeTld, asciiTld + " " + comment); + } + } + } else { + System.err.println("Expected to find HTML info for "+ asciiTld); + } + } + ianaTlds.add(asciiTld); + // Don't merge these conditions; generateUnicodeTlds is final so needs to be separate to avoid a warning + if (generateUnicodeTlds) { + if (!unicodeTld.equals(asciiTld)) { + ianaTlds.add(unicodeTld); + } + } + } + } + br.close(); + // List html entries not in TLD text list + for(String key : (new TreeMap(htmlInfo)).keySet()) { + if (!ianaTlds.contains(key)) { + if (isNotInRootZone(key)) { + System.out.println("INFO: HTML entry not yet in root zone: "+key); + } else { + System.err.println("WARN: Expected to find text entry for html: "+key); + } + } + } + if (!missingTLD.isEmpty()) { + printMap(header, missingTLD, "TLD"); + } + if (!missingCC.isEmpty()) { + printMap(header, missingCC, "CC"); + } + // Check if internal tables contain any additional entries + isInIanaList("INFRASTRUCTURE_TLDS", ianaTlds); + isInIanaList("COUNTRY_CODE_TLDS", ianaTlds); + isInIanaList("GENERIC_TLDS", ianaTlds); + // Don't check local TLDS isInIanaList("LOCAL_TLDS", ianaTlds); + System.out.println("Finished checks"); + } + + private static void printMap(final String header, Map map, String string) { + System.out.println("Entries missing from "+ string +" List\n"); + if (header != null) { + System.out.println(" // Taken from " + header); + } + Iterator> it = map.entrySet().iterator(); + while(it.hasNext()){ + Map.Entry me = it.next(); + System.out.println(" \"" + me.getKey() + "\", // " + me.getValue()); + } + System.out.println("\nDone"); + } + + private static Map getHtmlInfo(final File f) throws IOException { + final Map info = new HashMap(); + +// .ax + final Pattern domain = Pattern.compile(".*country-code + final Pattern type = Pattern.compile("\\s+([^<]+)"); +// +// Ålands landskapsregering + final Pattern comment = Pattern.compile("\\s+([^<]+)"); + + final BufferedReader br = new BufferedReader(new FileReader(f)); + String line; + while((line=br.readLine())!=null){ + Matcher m = domain.matcher(line); + if (m.lookingAt()) { + String dom = m.group(1); + String typ = "??"; + String com = "??"; + line = br.readLine(); + while (line.matches("^\\s*$")) { // extra blank lines introduced + line = br.readLine(); + } + Matcher t = type.matcher(line); + if (t.lookingAt()) { + typ = t.group(1); + line = br.readLine(); + if (line.matches("\\s+.*")){ + line = br.readLine(); + } + line = br.readLine(); + } + // Should have comment; is it wrapped? + while(!line.matches(".*.*")){ + line += " " +br.readLine(); + } + Matcher n = comment.matcher(line); + if (n.lookingAt()) { + com = n.group(1); + } + // Don't save unused entries + if (com.contains("Not assigned") || com.contains("Retired") || typ.equals("test")) { +// System.out.println("Ignored: " + typ + " " + dom + " " +com); + } else { + info.put(dom.toLowerCase(Locale.ENGLISH), new String[]{typ, com}); +// System.out.println("Storing: " + typ + " " + dom + " " +com); + } + } else { + System.err.println("Unexpected type: " + line); + } + } + } + br.close(); + return info; + } + + /* + * Download a file if it is more recent than our cached copy. + * Unfortunately the server does not seem to honour If-Modified-Since for the + * Html page, so we check if it is newer than the txt file and skip download if so + */ + private static long download(File f, String tldurl, long timestamp) throws IOException { + final int HOUR = 60*60*1000; // an hour in ms + final long modTime; + // For testing purposes, don't download files more than once an hour + if (f.canRead()) { + modTime = f.lastModified(); + if (modTime > System.currentTimeMillis()-HOUR) { + System.out.println("Skipping download - found recent " + f); + return modTime; + } + } else { + modTime = 0; + } + HttpURLConnection hc = (HttpURLConnection) new URL(tldurl).openConnection(); + if (modTime > 0) { + SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");//Sun, 06 Nov 1994 08:49:37 GMT + String since = sdf.format(new Date(modTime)); + hc.addRequestProperty("If-Modified-Since", since); + System.out.println("Found " + f + " with date " + since); + } + if (hc.getResponseCode() == 304) { + System.out.println("Already have most recent " + tldurl); + } else { + System.out.println("Downloading " + tldurl); + byte buff[] = new byte[1024]; + InputStream is = hc.getInputStream(); + + FileOutputStream fos = new FileOutputStream(f); + int len; + while((len=is.read(buff)) != -1) { + fos.write(buff, 0, len); + } + fos.close(); + is.close(); + System.out.println("Done"); + } + return f.lastModified(); + } + + /** + * Check whether the domain is in the root zone currently. + * Reads the URL http://www.iana.org/domains/root/db/*domain*.html + * (using a local disk cache) + * and checks for the string "This domain is not present in the root zone at this time." + * @param domain the domain to check + * @return true if the string is found + */ + private static boolean isNotInRootZone(String domain) { + String tldurl = "http://www.iana.org/domains/root/db/" + domain + ".html"; + File rootCheck = new File("target","tld_" + domain + ".html"); + BufferedReader in = null; + try { + download(rootCheck, tldurl, 0L); + in = new BufferedReader(new FileReader(rootCheck)); + String inputLine; + while ((inputLine = in.readLine()) != null) { + if (inputLine.contains("This domain is not present in the root zone at this time.")) { + return true; + } + } + in.close(); + } catch (IOException e) { + } finally { + closeQuietly(in); + } + return false; + } + + private static void closeQuietly(Closeable in) { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } + } + + // isInIanaList and isSorted are split into two methods. + // If/when access to the arrays is possible without reflection, the intermediate + // methods can be dropped + private static boolean isInIanaList(String arrayName, Set ianaTlds) throws Exception { + Field f = DomainValidator.class.getDeclaredField(arrayName); + final boolean isPrivate = Modifier.isPrivate(f.getModifiers()); + if (isPrivate) { + f.setAccessible(true); + } + String[] array = (String[]) f.get(null); + try { + return isInIanaList(arrayName, array, ianaTlds); + } finally { + if (isPrivate) { + f.setAccessible(false); + } + } + } + + private static boolean isInIanaList(String name, String [] array, Set ianaTlds) { + for(int i = 0; i < array.length; i++) { + if (!ianaTlds.contains(array[i])) { + System.out.println(name + " contains unexpected value: " + array[i]); + } + } + return true; + } + + private static boolean isSortedLowerCase(String arrayName) throws Exception { + Field f = DomainValidator.class.getDeclaredField(arrayName); + final boolean isPrivate = Modifier.isPrivate(f.getModifiers()); + if (isPrivate) { + f.setAccessible(true); + } + String[] array = (String[]) f.get(null); + try { + return isSortedLowerCase(arrayName, array); + } finally { + if (isPrivate) { + f.setAccessible(false); + } + } + } + + private static boolean isLowerCase(String string) { + return string.equals(string.toLowerCase(Locale.ENGLISH)); + } + + // Check if an array is strictly sorted - and lowerCase + private static boolean isSortedLowerCase(String name, String [] array) { + boolean sorted = true; + boolean strictlySorted = true; + final int length = array.length; + boolean lowerCase = isLowerCase(array[length-1]); // Check the last entry + for(int i = 0; i < length-1; i++) { // compare all but last entry with next + final String entry = array[i]; + final String nextEntry = array[i+1]; + final int cmp = entry.compareTo(nextEntry); + if (cmp > 0) { // out of order + System.out.println("Out of order entry: " + entry + " < " + nextEntry + " in " + name); + sorted = false; + } else if (cmp == 0) { + strictlySorted = false; + System.out.println("Duplicated entry: " + entry + " in " + name); + } + if (!isLowerCase(entry)) { + System.out.println("Non lowerCase entry: " + entry + " in " + name); + lowerCase = false; + } + } + return sorted && strictlySorted && lowerCase; + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DoubleValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DoubleValidatorTest.java new file mode 100644 index 000000000..f002bc922 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DoubleValidatorTest.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.Locale; + +/** + * Test Case for DoubleValidator. + * + * @version $Revision$ + */ +public class DoubleValidatorTest extends AbstractNumberValidatorTest { + + /** + * Constructor + * @param name test name + */ + public DoubleValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validator = new DoubleValidator(false, 0); + strictValidator = new DoubleValidator(); + + testPattern = "#,###.#"; + + // testValidateMinMax() + max = null; + maxPlusOne = null; + min = null; + minMinusOne = null; + + // testInvalidStrict() + invalidStrict = new String[] {null, "", "X", "X12", "12X", "1X2"}; + + // testInvalidNotStrict() + invalid = new String[] {null, "", "X", "X12"}; + + // testValid() + testNumber = Double.valueOf(1234.5); + testZero = Double.valueOf(0); + validStrict = new String[] {"0", "1234.5", "1,234.5"}; + validStrictCompare = new Number[] {testZero, testNumber, testNumber}; + valid = new String[] {"0", "1234.5", "1,234.5", "1,234.5", "1234.5X"}; + validCompare = new Number[] {testZero, testNumber, testNumber, testNumber, testNumber}; + + testStringUS = "1,234.5"; + testStringDE = "1.234,5"; + + // Localized Pattern test + localeValue = testStringDE; + localePattern = "#.###,#"; + testLocale = Locale.GERMANY; + localeExpected = testNumber; + + } + + /** + * Test DoubleValidator validate Methods + */ + public void testDoubleValidatorMethods() { + Locale locale = Locale.GERMAN; + String pattern = "0,00,00"; + String patternVal = "1,23,45"; + String germanPatternVal = "1.23.45"; + String localeVal = "12.345"; + String defaultVal = "12,345"; + String XXXX = "XXXX"; + Double expected = Double.valueOf(12345); + assertEquals("validate(A) default", expected, DoubleValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, DoubleValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, DoubleValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, DoubleValidator.getInstance().validate(germanPatternVal, pattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", DoubleValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", DoubleValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", DoubleValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", DoubleValidator.getInstance().isValid(germanPatternVal, pattern, Locale.GERMAN)); + + assertNull("validate(B) default", DoubleValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", DoubleValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", DoubleValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", DoubleValidator.getInstance().validate(patternVal, pattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", DoubleValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", DoubleValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", DoubleValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", DoubleValidator.getInstance().isValid(patternVal, pattern, Locale.GERMAN)); + } + + /** + * Test Double Range/Min/Max + */ + public void testDoubleRangeMinMax() { + DoubleValidator validator = (DoubleValidator)strictValidator; + Double number9 = validator.validate("9", "#"); + Double number10 = validator.validate("10", "#"); + Double number11 = validator.validate("11", "#"); + Double number19 = validator.validate("19", "#"); + Double number20 = validator.validate("20", "#"); + Double number21 = validator.validate("21", "#"); + + // Test isInRange() + assertFalse("isInRange() < min", validator.isInRange(number9, 10, 20)); + assertTrue("isInRange() = min", validator.isInRange(number10, 10, 20)); + assertTrue("isInRange() in range", validator.isInRange(number11, 10, 20)); + assertTrue("isInRange() = max", validator.isInRange(number20, 10, 20)); + assertFalse("isInRange() > max", validator.isInRange(number21, 10, 20)); + + // Test minValue() + assertFalse("minValue() < min", validator.minValue(number9, 10)); + assertTrue("minValue() = min", validator.minValue(number10, 10)); + assertTrue("minValue() > min", validator.minValue(number11, 10)); + + // Test minValue() + assertTrue("maxValue() < max", validator.maxValue(number19, 20)); + assertTrue("maxValue() = max", validator.maxValue(number20, 20)); + assertFalse("maxValue() > max", validator.maxValue(number21, 20)); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/EmailValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/EmailValidatorTest.java new file mode 100644 index 000000000..0df920253 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/EmailValidatorTest.java @@ -0,0 +1,545 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import junit.framework.TestCase; + +import org.apache.commons.validator.ResultPair; + +/** + * Performs Validation Test for e-mail validations. + * + * + * @version $Revision$ + */ +public class EmailValidatorTest extends TestCase { + + /** + * The key used to retrieve the set of validation + * rules from the xml file. + */ + protected static String FORM_KEY = "emailForm"; + + /** + * The key used to retrieve the validator action. + */ + protected static String ACTION = "email"; + + private EmailValidator validator; + + public EmailValidatorTest(String name) { + super(name); + } + + @Override +protected void setUp() { + validator = EmailValidator.getInstance(); + } + + @Override +protected void tearDown() { + } + + /** + * Tests the e-mail validation. + */ + public void testEmail() { + assertTrue(validator.isValid("jsmith@apache.org")); + } + + /** + * Tests the email validation with numeric domains. + */ + public void testEmailWithNumericAddress() { + assertTrue(validator.isValid("someone@[216.109.118.76]")); + assertTrue(validator.isValid("someone@yahoo.com")); + } + + /** + * Tests the e-mail validation. + */ + public void testEmailExtension() { + assertTrue(validator.isValid("jsmith@apache.org")); + + assertTrue(validator.isValid("jsmith@apache.com")); + + assertTrue(validator.isValid("jsmith@apache.net")); + + assertTrue(validator.isValid("jsmith@apache.info")); + + assertFalse(validator.isValid("jsmith@apache.")); + + assertFalse(validator.isValid("jsmith@apache.c")); + + assertTrue(validator.isValid("someone@yahoo.museum")); + + assertFalse(validator.isValid("someone@yahoo.mu-seum")); + } + + /** + *

Tests the e-mail validation with a dash in + * the address.

+ */ + public void testEmailWithDash() { + assertTrue(validator.isValid("andy.noble@data-workshop.com")); + + assertFalse(validator.isValid("andy-noble@data-workshop.-com")); + + assertFalse(validator.isValid("andy-noble@data-workshop.c-om")); + + assertFalse(validator.isValid("andy-noble@data-workshop.co-m")); + } + + /** + * Tests the e-mail validation with a dot at the end of + * the address. + */ + public void testEmailWithDotEnd() { + assertFalse(validator.isValid("andy.noble@data-workshop.com.")); + } + + /** + * Tests the e-mail validation with an RCS-noncompliant character in + * the address. + */ + public void testEmailWithBogusCharacter() { + + assertFalse(validator.isValid("andy.noble@\u008fdata-workshop.com")); + + // The ' character is valid in an email username. + assertTrue(validator.isValid("andy.o'reilly@data-workshop.com")); + + // But not in the domain name. + assertFalse(validator.isValid("andy@o'reilly.data-workshop.com")); + + // The + character is valid in an email username. + assertTrue(validator.isValid("foo+bar@i.am.not.in.us.example.com")); + + // But not in the domain name + assertFalse(validator.isValid("foo+bar@example+3.com")); + + // Domains with only special characters aren't allowed (VALIDATOR-286) + assertFalse(validator.isValid("test@%*.com")); + assertFalse(validator.isValid("test@^&#.com")); + + } + + public void testVALIDATOR_315() { + assertFalse(validator.isValid("me@at&t.net")); + assertTrue(validator.isValid("me@att.net")); // Make sure TLD is not the cause of the failure + } + + public void testVALIDATOR_278() { + assertFalse(validator.isValid("someone@-test.com"));// hostname starts with dash/hyphen + assertFalse(validator.isValid("someone@test-.com"));// hostname ends with dash/hyphen + } + + public void testValidator235() { + String version = System.getProperty("java.version"); + if (version.compareTo("1.6") < 0) { + System.out.println("Cannot run Unicode IDN tests"); + return; // Cannot run the test + } + assertTrue("xn--d1abbgf6aiiy.xn--p1ai should validate", validator.isValid("someone@xn--d1abbgf6aiiy.xn--p1ai")); + assertTrue("президент.рф should validate", validator.isValid("someone@президент.рф")); + assertTrue("www.b\u00fccher.ch should validate", validator.isValid("someone@www.b\u00fccher.ch")); + assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("someone@www.\uFFFD.ch")); + assertTrue("www.b\u00fccher.ch should validate", validator.isValid("someone@www.b\u00fccher.ch")); + assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("someone@www.\uFFFD.ch")); + } + + /** + * Tests the email validation with commas. + */ + public void testEmailWithCommas() { + assertFalse(validator.isValid("joeblow@apa,che.org")); + + assertFalse(validator.isValid("joeblow@apache.o,rg")); + + assertFalse(validator.isValid("joeblow@apache,org")); + + } + + /** + * Tests the email validation with spaces. + */ + public void testEmailWithSpaces() { + assertFalse(validator.isValid("joeblow @apache.org")); + + assertFalse(validator.isValid("joeblow@ apache.org")); + + assertFalse(validator.isValid(" joeblow@apache.org")); + + assertFalse(validator.isValid("joeblow@apache.org ")); + + assertFalse(validator.isValid("joe blow@apache.org ")); + + assertFalse(validator.isValid("joeblow@apa che.org ")); + + assertTrue(validator.isValid("\"joeblow \"@apache.org")); + + assertTrue(validator.isValid("\" joeblow\"@apache.org")); + + assertTrue(validator.isValid("\" joe blow \"@apache.org")); + + } + + /** + * Tests the email validation with ascii control characters. + * (i.e. Ascii chars 0 - 31 and 127) + */ + public void testEmailWithControlChars() { + for (char c = 0; c < 32; c++) { + assertFalse("Test control char " + ((int)c), validator.isValid("foo" + c + "bar@domain.com")); + } + assertFalse("Test control char 127", validator.isValid("foo" + ((char)127) + "bar@domain.com")); + } + + /** + * Test that @localhost and @localhost.localdomain + * addresses are declared as valid when requested. + */ + public void testEmailLocalhost() { + // Check the default is not to allow + EmailValidator noLocal = EmailValidator.getInstance(false); + EmailValidator allowLocal = EmailValidator.getInstance(true); + assertEquals(validator, noLocal); + + // Depends on the validator + assertTrue( + "@localhost.localdomain should be accepted but wasn't", + allowLocal.isValid("joe@localhost.localdomain") + ); + assertTrue( + "@localhost should be accepted but wasn't", + allowLocal.isValid("joe@localhost") + ); + + assertFalse( + "@localhost.localdomain should be accepted but wasn't", + noLocal.isValid("joe@localhost.localdomain") + ); + assertFalse( + "@localhost should be accepted but wasn't", + noLocal.isValid("joe@localhost") + ); + } + + /** + * VALIDATOR-296 - A / or a ! is valid in the user part, + * but not in the domain part + */ + public void testEmailWithSlashes() { + assertTrue( + "/ and ! valid in username", + validator.isValid("joe!/blow@apache.org") + ); + assertFalse( + "/ not valid in domain", + validator.isValid("joe@ap/ache.org") + ); + assertFalse( + "! not valid in domain", + validator.isValid("joe@apac!he.org") + ); + } + + /** + * Write this test according to parts of RFC, as opposed to the type of character + * that is being tested. + */ + public void testEmailUserName() { + + assertTrue(validator.isValid("joe1blow@apache.org")); + + assertTrue(validator.isValid("joe$blow@apache.org")); + + assertTrue(validator.isValid("joe-@apache.org")); + + assertTrue(validator.isValid("joe_@apache.org")); + + assertTrue(validator.isValid("joe+@apache.org")); // + is valid unquoted + + assertTrue(validator.isValid("joe!@apache.org")); // ! is valid unquoted + + assertTrue(validator.isValid("joe*@apache.org")); // * is valid unquoted + + assertTrue(validator.isValid("joe'@apache.org")); // ' is valid unquoted + + assertTrue(validator.isValid("joe%45@apache.org")); // % is valid unquoted + + assertTrue(validator.isValid("joe?@apache.org")); // ? is valid unquoted + + assertTrue(validator.isValid("joe&@apache.org")); // & ditto + + assertTrue(validator.isValid("joe=@apache.org")); // = ditto + + assertTrue(validator.isValid("+joe@apache.org")); // + is valid unquoted + + assertTrue(validator.isValid("!joe@apache.org")); // ! is valid unquoted + + assertTrue(validator.isValid("*joe@apache.org")); // * is valid unquoted + + assertTrue(validator.isValid("'joe@apache.org")); // ' is valid unquoted + + assertTrue(validator.isValid("%joe45@apache.org")); // % is valid unquoted + + assertTrue(validator.isValid("?joe@apache.org")); // ? is valid unquoted + + assertTrue(validator.isValid("&joe@apache.org")); // & ditto + + assertTrue(validator.isValid("=joe@apache.org")); // = ditto + + assertTrue(validator.isValid("+@apache.org")); // + is valid unquoted + + assertTrue(validator.isValid("!@apache.org")); // ! is valid unquoted + + assertTrue(validator.isValid("*@apache.org")); // * is valid unquoted + + assertTrue(validator.isValid("'@apache.org")); // ' is valid unquoted + + assertTrue(validator.isValid("%@apache.org")); // % is valid unquoted + + assertTrue(validator.isValid("?@apache.org")); // ? is valid unquoted + + assertTrue(validator.isValid("&@apache.org")); // & ditto + + assertTrue(validator.isValid("=@apache.org")); // = ditto + + + //UnQuoted Special characters are invalid + + assertFalse(validator.isValid("joe.@apache.org")); // . not allowed at end of local part + + assertFalse(validator.isValid(".joe@apache.org")); // . not allowed at start of local part + + assertFalse(validator.isValid(".@apache.org")); // . not allowed alone + + assertTrue(validator.isValid("joe.ok@apache.org")); // . allowed embedded + + assertFalse(validator.isValid("joe..ok@apache.org")); // .. not allowed embedded + + assertFalse(validator.isValid("..@apache.org")); // .. not allowed alone + + assertFalse(validator.isValid("joe(@apache.org")); + + assertFalse(validator.isValid("joe)@apache.org")); + + assertFalse(validator.isValid("joe,@apache.org")); + + assertFalse(validator.isValid("joe;@apache.org")); + + + //Quoted Special characters are valid + assertTrue(validator.isValid("\"joe.\"@apache.org")); + + assertTrue(validator.isValid("\".joe\"@apache.org")); + + assertTrue(validator.isValid("\"joe+\"@apache.org")); + + assertTrue(validator.isValid("\"joe@\"@apache.org")); + + assertTrue(validator.isValid("\"joe!\"@apache.org")); + + assertTrue(validator.isValid("\"joe*\"@apache.org")); + + assertTrue(validator.isValid("\"joe'\"@apache.org")); + + assertTrue(validator.isValid("\"joe(\"@apache.org")); + + assertTrue(validator.isValid("\"joe)\"@apache.org")); + + assertTrue(validator.isValid("\"joe,\"@apache.org")); + + assertTrue(validator.isValid("\"joe%45\"@apache.org")); + + assertTrue(validator.isValid("\"joe;\"@apache.org")); + + assertTrue(validator.isValid("\"joe?\"@apache.org")); + + assertTrue(validator.isValid("\"joe&\"@apache.org")); + + assertTrue(validator.isValid("\"joe=\"@apache.org")); + + assertTrue(validator.isValid("\"..\"@apache.org")); + + // escaped quote character valid in quoted string + assertTrue(validator.isValid("\"john\\\"doe\"@apache.org")); + + assertTrue(validator.isValid("john56789.john56789.john56789.john56789.john56789.john56789.john@example.com")); + + assertFalse(validator.isValid("john56789.john56789.john56789.john56789.john56789.john56789.john5@example.com")); + + assertTrue(validator.isValid("\\>escape\\\\special\\^characters\\<@example.com")); + + assertTrue(validator.isValid("Abc\\@def@example.com")); + + assertFalse(validator.isValid("Abc@def@example.com")); + + assertTrue(validator.isValid("space\\ monkey@example.com")); + } + + /** + * These test values derive directly from RFC 822 & + * Mail::RFC822::Address & RFC::RFC822::Address perl test.pl + * For traceability don't combine these test values with other tests. + */ + private static final ResultPair[] testEmailFromPerl = { + new ResultPair("abigail@example.com", true), + new ResultPair("abigail@example.com ", true), + new ResultPair(" abigail@example.com", true), + new ResultPair("abigail @example.com ", true), + new ResultPair("*@example.net", true), + new ResultPair("\"\\\"\"@foo.bar", true), + new ResultPair("fred&barny@example.com", true), + new ResultPair("---@example.com", true), + new ResultPair("foo-bar@example.net", true), + new ResultPair("\"127.0.0.1\"@[127.0.0.1]", true), + new ResultPair("Abigail ", true), + new ResultPair("Abigail", true), + new ResultPair("Abigail<@a,@b,@c:abigail@example.com>", true), + new ResultPair("\"This is a phrase\"", true), + new ResultPair("\"Abigail \"", true), + new ResultPair("\"Joe & J. Harvey\" ", true), + new ResultPair("Abigail ", true), + new ResultPair("Abigail made this < abigail @ example . com >", true), + new ResultPair("Abigail(the bitch)@example.com", true), + new ResultPair("Abigail ", true), + new ResultPair("Abigail < (one) abigail (two) @(three)example . (bar) com (quz) >", true), + new ResultPair("Abigail (foo) (((baz)(nested) (comment)) ! ) < (one) abigail (two) @(three)example . (bar) com (quz) >", true), + new ResultPair("Abigail ", true), + new ResultPair("Abigail ", true), + new ResultPair("(foo) abigail@example.com", true), + new ResultPair("abigail@example.com (foo)", true), + new ResultPair("\"Abi\\\"gail\" ", true), + new ResultPair("abigail@[example.com]", true), + new ResultPair("abigail@[exa\\[ple.com]", true), + new ResultPair("abigail@[exa\\]ple.com]", true), + new ResultPair("\":sysmail\"@ Some-Group. Some-Org", true), + new ResultPair("Muhammed.(I am the greatest) Ali @(the)Vegas.WBA", true), + new ResultPair("mailbox.sub1.sub2@this-domain", true), + new ResultPair("sub-net.mailbox@sub-domain.domain", true), + new ResultPair("name:;", true), + new ResultPair("':;", true), + new ResultPair("name: ;", true), + new ResultPair("Alfred Neuman ", true), + new ResultPair("Neuman@BBN-TENEXA", true), + new ResultPair("\"George, Ted\" ", true), + new ResultPair("Wilt . (the Stilt) Chamberlain@NBA.US", true), + new ResultPair("Cruisers: Port@Portugal, Jones@SEA;", true), + new ResultPair("$@[]", true), + new ResultPair("*()@[]", true), + new ResultPair("\"quoted ( brackets\" ( a comment )@example.com", true), + new ResultPair("\"Joe & J. Harvey\"\\x0D\\x0A ", true), + new ResultPair("\"Joe &\\x0D\\x0A J. Harvey\" ", true), + new ResultPair("Gourmets: Pompous Person ,\\x0D\\x0A" + + " Childs\\@WGBH.Boston, \"Galloping Gourmet\"\\@\\x0D\\x0A" + + " ANT.Down-Under (Australian National Television),\\x0D\\x0A" + + " Cheapie\\@Discount-Liquors;", true), + new ResultPair(" Just a string", false), + new ResultPair("string", false), + new ResultPair("(comment)", false), + new ResultPair("()@example.com", false), + new ResultPair("fred(&)barny@example.com", false), + new ResultPair("fred\\ barny@example.com", false), + new ResultPair("Abigail ", false), + new ResultPair("Abigail ", false), + new ResultPair("Abigail ", false), + new ResultPair("\"Abi\"gail\" ", false), + new ResultPair("abigail@[exa]ple.com]", false), + new ResultPair("abigail@[exa[ple.com]", false), + new ResultPair("abigail@[exaple].com]", false), + new ResultPair("abigail@", false), + new ResultPair("@example.com", false), + new ResultPair("phrase: abigail@example.com abigail@example.com ;", false), + new ResultPair("invalid�char@example.com", false) + }; + + /** + * Write this test based on perl Mail::RFC822::Address + * which takes its example email address directly from RFC822 + * + * FIXME This test fails so disable it with a leading _ for 1.1.4 release. + * The real solution is to fix the email parsing. + */ + public void _testEmailFromPerl() { + for (int index = 0; index < testEmailFromPerl.length; index++) { + String item = testEmailFromPerl[index].item; + if (testEmailFromPerl[index].valid) { + assertTrue("Should be OK: "+item, validator.isValid(item)); + } else { + assertFalse("Should fail: "+item, validator.isValid(item)); + } + } + } + + public void testValidator293(){ + assertTrue(validator.isValid("abc-@abc.com")); + assertTrue(validator.isValid("abc_@abc.com")); + assertTrue(validator.isValid("abc-def@abc.com")); + assertTrue(validator.isValid("abc_def@abc.com")); + assertFalse(validator.isValid("abc@abc_def.com")); + } + + public void testValidator365() { + assertFalse(validator.isValid( + "Loremipsumdolorsitametconsecteturadipiscingelit.Nullavitaeligulamattisrhoncusnuncegestasmattisleo."+ + "Donecnonsapieninmagnatristiquedictumaacturpis.Fusceorciduifacilisisutsapieneuconsequatpharetralectus."+ + "Quisqueenimestpulvinarutquamvitaeportamattisex.Nullamquismaurisplaceratconvallisjustoquisportamauris."+ + "Innullalacusconvalliseufringillautvenenatissitametdiam.Maecenasluctusligulascelerisquepulvinarfeugiat."+ + "Sedmolestienullaaliquetorciluctusidpharetranislfinibus.Suspendissemalesuadatinciduntduisitametportaarcusollicitudinnec."+ + "Donecetmassamagna.Curabitururnadiampretiumveldignissimporttitorfringillaeuneque."+ + "Duisantetelluspharetraidtinciduntinterdummolestiesitametfelis.Utquisquamsitametantesagittisdapibusacnonodio."+ + "Namrutrummolestiediamidmattis.Cumsociisnatoquepenatibusetmagnisdisparturientmontesnasceturridiculusmus."+ + "Morbiposueresedmetusacconsectetur.Etiamquisipsumvitaejustotempusmaximus.Sedultriciesplaceratvolutpat."+ + "Integerlacuslectusmaximusacornarequissagittissitametjusto."+ + "Cumsociisnatoquepenatibusetmagnisdisparturientmontesnasceturridiculusmus.Maecenasindictumpurussedrutrumex.Nullafacilisi."+ + "Integerfinibusfinibusmietpharetranislfaucibusvel.Maecenasegetdolorlacinialobortisjustovelullamcorpersem."+ + "Vivamusaliquetpurusidvariusornaresapienrisusrutrumnisitinciduntmollissemnequeidmetus."+ + "Etiamquiseleifendpurus.Nuncfelisnuncscelerisqueiddignissimnecfinibusalibero."+ + "Nuncsemperenimnequesitamethendreritpurusfacilisisac.Maurisdapibussemperfelisdignissimgravida."+ + "Aeneanultricesblanditnequealiquamfinibusodioscelerisqueac.Aliquamnecmassaeumaurisfaucibusfringilla."+ + "Etiamconsequatligulanisisitametaliquamnibhtemporquis.Nuncinterdumdignissimnullaatsodalesarcusagittiseu."+ + "Proinpharetrametusneclacuspulvinarsedvolutpatliberoornare.Sedligulanislpulvinarnonlectuseublanditfacilisisante."+ + "Sedmollisnislalacusauctorsuscipit.Inhachabitasseplateadictumst.Phasellussitametvelittemporvenenatisfeliseuegestasrisus."+ + "Aliquameteratsitametnibhcommodofinibus.Morbiefficiturodiovelpulvinariaculis."+ + "Aeneantemporipsummassaaconsecteturturpisfaucibusultrices.Praesentsodalesmaurisquisportafermentum."+ + "Etiamnisinislvenenatisvelauctorutullamcorperinjusto.Proinvelligulaerat.Phasellusvestibulumgravidamassanonfeugiat."+ + "Maecenaspharetraeuismodmetusegetefficitur.Suspendisseamet@gmail.com")); + } + + /** + * Tests the e-mail validation with a user at a TLD + * + * http://tools.ietf.org/html/rfc5321#section-2.3.5 + * (In the case of a top-level domain used by itself in an + * email address, a single string is used without any dots) + */ + public void testEmailAtTLD() { + EmailValidator val = EmailValidator.getInstance(false, true); + assertTrue(val.isValid("test@com")); + } + + public void testValidator359() { + EmailValidator val = EmailValidator.getInstance(false, true); + assertFalse(val.isValid("test@.com")); + } + + public void testValidator374() { + assertTrue(validator.isValid("abc@school.school")); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/FloatValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/FloatValidatorTest.java new file mode 100644 index 000000000..ccf2703ea --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/FloatValidatorTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.text.DecimalFormat; +import java.util.Locale; + +/** + * Test Case for FloatValidator. + * + * @version $Revision$ + */ +public class FloatValidatorTest extends AbstractNumberValidatorTest { + + /** + * Constructor + * @param name test name + */ + public FloatValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validator = new FloatValidator(false, 0); + strictValidator = new FloatValidator(); + + testPattern = "#,###.#"; + + // testValidateMinMax() + max = Float.valueOf(Float.MAX_VALUE); + maxPlusOne = Double.valueOf(max.doubleValue() * 10); + min = Float.valueOf(Float.MAX_VALUE * -1); + minMinusOne = Double.valueOf(min.doubleValue() * 10); + + // testInvalidStrict() + invalidStrict = new String[] {null, "", "X", "X12", "12X", "1X2"}; + + // testInvalidNotStrict() + invalid = new String[] {null, "", "X", "X12"}; + + // testValid() + testNumber = Float.valueOf(1234.5f); + testZero = Float.valueOf(0); + validStrict = new String[] {"0", "1234.5", "1,234.5"}; + validStrictCompare = new Number[] {testZero, testNumber, testNumber}; + valid = new String[] {"0", "1234.5", "1,234.5", "1,234.5", "1234.5X"}; + validCompare = new Number[] {testZero, testNumber, testNumber, testNumber, testNumber}; + + testStringUS = "1,234.5"; + testStringDE = "1.234,5"; + + // Localized Pattern test + localeValue = testStringDE; + localePattern = "#.###,#"; + testLocale = Locale.GERMANY; + localeExpected = testNumber; + + } + + /** + * Test FloatValidator validate Methods + */ + public void testFloatValidatorMethods() { + Locale locale = Locale.GERMAN; + String pattern = "0,00,00"; + String patternVal = "1,23,45"; + String localeVal = "12.345"; + String germanPatternVal = "1.23.45"; + String defaultVal = "12,345"; + String XXXX = "XXXX"; + Float expected = Float.valueOf(12345); + assertEquals("validate(A) default", expected, FloatValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, FloatValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, FloatValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, FloatValidator.getInstance().validate(germanPatternVal, pattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", FloatValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", FloatValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", FloatValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", FloatValidator.getInstance().isValid(germanPatternVal, pattern, Locale.GERMAN)); + + assertNull("validate(B) default", FloatValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", FloatValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", FloatValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", FloatValidator.getInstance().validate(patternVal, pattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", FloatValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", FloatValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", FloatValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", FloatValidator.getInstance().isValid(patternVal, pattern, Locale.GERMAN)); + } + + /** + * Test Float validation for values too small to handle. + * (slightly different from max/min which are the largest +ve/-ve + */ + public void testFloatSmallestValues() { + String pattern = "#.#################################################################"; + DecimalFormat fmt = new DecimalFormat(pattern); + + // Validate Smallest +ve value + Float smallestPositive = Float.valueOf(Float.MIN_VALUE); + String strSmallestPositive = fmt.format(smallestPositive); + assertEquals("Smallest +ve", smallestPositive, FloatValidator.getInstance().validate(strSmallestPositive, pattern)); + + // Validate Smallest -ve value + Float smallestNegative = Float.valueOf(Float.MIN_VALUE * -1); + String strSmallestNegative = fmt.format(smallestNegative); + assertEquals("Smallest -ve", smallestNegative, FloatValidator.getInstance().validate(strSmallestNegative, pattern)); + + // Validate Too Small +ve + Double tooSmallPositive = Double.valueOf(((double)Float.MIN_VALUE / (double)10)); + String strTooSmallPositive = fmt.format(tooSmallPositive); + assertFalse("Too small +ve", FloatValidator.getInstance().isValid(strTooSmallPositive, pattern)); + + // Validate Too Small -ve + Double tooSmallNegative = Double.valueOf(tooSmallPositive.doubleValue() * -1); + String strTooSmallNegative = fmt.format(tooSmallNegative); + assertFalse("Too small -ve", FloatValidator.getInstance().isValid(strTooSmallNegative, pattern)); + } + + /** + * Test Float Range/Min/Max + */ + public void testFloatRangeMinMax() { + FloatValidator validator = (FloatValidator)strictValidator; + Float number9 = validator.validate("9", "#"); + Float number10 = validator.validate("10", "#"); + Float number11 = validator.validate("11", "#"); + Float number19 = validator.validate("19", "#"); + Float number20 = validator.validate("20", "#"); + Float number21 = validator.validate("21", "#"); + + // Test isInRange() + assertFalse("isInRange() < min", validator.isInRange(number9, 10, 20)); + assertTrue("isInRange() = min", validator.isInRange(number10, 10, 20)); + assertTrue("isInRange() in range", validator.isInRange(number11, 10, 20)); + assertTrue("isInRange() = max", validator.isInRange(number20, 10, 20)); + assertFalse("isInRange() > max", validator.isInRange(number21, 10, 20)); + + // Test minValue() + assertFalse("minValue() < min", validator.minValue(number9, 10)); + assertTrue("minValue() = min", validator.minValue(number10, 10)); + assertTrue("minValue() > min", validator.minValue(number11, 10)); + + // Test minValue() + assertTrue("maxValue() < max", validator.maxValue(number19, 20)); + assertTrue("maxValue() = max", validator.maxValue(number20, 20)); + assertFalse("maxValue() > max", validator.maxValue(number21, 20)); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/IBANValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/IBANValidatorTest.java new file mode 100644 index 000000000..beb74502c --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/IBANValidatorTest.java @@ -0,0 +1,362 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.validator.routines.IBANValidator.Validator; +import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit; +import org.junit.Test; + +/** + * IBANValidator Test Case. + * @since 1.5.0 + */ +public class IBANValidatorTest { + + // It's not clear whether IBANs can contain lower case characters + // so we test for both where possible + // Note that the BIC near the start of the code is always upper case or digits + private final String[] validIBANFormat = new String[] { + "AD1200012030200359100100", + "AE070331234567890123456", + "AL47212110090000000235698741", + "AT611904300234573201", + "AZ21NABZ00000000137010001944", + "BA391290079401028494", + "BE68539007547034", + "BG80BNBG96611020345678", + "BH67BMAG00001299123456", + "BR1800000000141455123924100C2", + "BR1800360305000010009795493C1", + "BR9700360305000010009795493P1", + "BY13NBRB3600900000002Z00AB00", + "CH9300762011623852957", + "CR05015202001026284066", + "CY17002001280000001200527600", + "CZ6508000000192000145399", + "CZ9455000000001011038930", + "DE89370400440532013000", + "DK5000400440116243", + "DO28BAGR00000001212453611324", + "EE382200221020145685", + "EG380019000500000000263180002", + "ES9121000418450200051332", + "FI2112345600000785", + "FI5542345670000081", + "FO6264600001631634", + "FR1420041010050500013M02606", + "GB29NWBK60161331926819", + "GE29NB0000000101904917", + "GI75NWBK000000007099453", + "GL8964710001000206", + "GR1601101250000000012300695", + "GT82TRAJ01020000001210029690", + "HR1210010051863000160", + "HU42117730161111101800000000", + "IE29AIBK93115212345678", + "IL620108000000099999999", + "IQ98NBIQ850123456789012", + "IS140159260076545510730339", + "IT60X0542811101000000123456", + "JO94CBJO0010000000000131000302", + "KW81CBKU0000000000001234560101", + "KZ86125KZT5004100100", + "LB62099900000001001901229114", + "LC55HEMM000100010012001200023015", + "LI21088100002324013AA", + "LT121000011101001000", + "LU280019400644750000", + "LV80BANK0000435195001", + "MC5811222000010123456789030", + "MD24AG000225100013104168", + "ME25505000012345678951", + "MK07250120000058984", + "MR1300020001010000123456753", + "MT84MALT011000012345MTLCAST001S", + "MU17BOMM0101101030300200000MUR", + "NL91ABNA0417164300", + "NO9386011117947", + "PK36SCBL0000001123456702", + "PL61109010140000071219812874", + "PS92PALS000000000400123456702", + "PT50000201231234567890154", + "QA58DOHB00001234567890ABCDEFG", + "RO49AAAA1B31007593840000", + "RS35260005601001611379", + "SA0380000000608010167519", + "SC18SSCB11010000000000001497USD", + "SE4550000000058398257466", + "SI56191000000123438", + "SI56263300012039086", + "SK3112000000198742637541", + "SM86U0322509800000000270100", + "ST68000100010051845310112", + "SV62CENR00000000000000700025", + "SV43ACAT00000000000000123123", + "TL380080012345678910157", + "TN5910006035183598478831", + "TR330006100519786457841326", + "UA213223130000026007233566001", + "UA213996220000026007233566001", + "VA59001123000012345678", + "VG96VPVG0000012345678901", + "XK051212012345678906", + }; + + private final String[] invalidIBANFormat = new String[] { + "", // empty + " ", // empty + "A", // too short + "AB", // too short + "FR1420041010050500013m02606", // lowercase version + "MT84MALT011000012345mtlcast001s", // lowercase version + "LI21088100002324013aa", // lowercase version + "QA58DOHB00001234567890abcdefg", // lowercase version + "RO49AAAA1b31007593840000", // lowercase version + "LC62HEMM000100010012001200023015", // wrong in SWIFT + "BY00NBRB3600000000000Z00AB00", // Wrong in SWIFT v73 + "ST68000200010192194210112", // ditto + "SV62CENR0000000000000700025", // ditto + }; + + private static final IBANValidator VALIDATOR = IBANValidator.getInstance(); + + @Test + public void testValid() { + for(String f : validIBANFormat) { + assertTrue("Checksum fail: "+f, IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(f)); + assertTrue("Missing validator: "+f, VALIDATOR.hasValidator(f)); + assertTrue(f, VALIDATOR.isValid(f)); + } + } + + @Test + public void testInValid() { + for(String f : invalidIBANFormat) { + assertFalse(f, VALIDATOR.isValid(f)); + } + } + + @Test + public void testNull() { + assertFalse("isValid(null)", VALIDATOR.isValid(null)); + } + + @Test + public void testHasValidator() { + assertTrue("GB", VALIDATOR.hasValidator("GB")); + assertFalse("gb", VALIDATOR.hasValidator("gb")); + } + + @Test + public void testGetValidator() { + assertNotNull("GB", VALIDATOR.getValidator("GB")); + assertNull("gb", VALIDATOR.getValidator("gb")); + } + + @Test(expected=IllegalStateException.class) + public void testSetDefaultValidator1() { + assertNotNull(VALIDATOR.setValidator("GB", 15, "GB")); + } + + @Test(expected=IllegalStateException.class) + public void testSetDefaultValidator2() { + assertNotNull(VALIDATOR.setValidator("GB", -1, "GB")); + } + + @Test(expected=IllegalArgumentException.class) + public void testSetValidatorLC() { + IBANValidator validator = new IBANValidator(); + assertNotNull(validator.setValidator("gb", 15, "GB")); + } + + @Test(expected=IllegalArgumentException.class) + public void testSetValidatorLen7() { + IBANValidator validator = new IBANValidator(); + assertNotNull(validator.setValidator("GB", 7, "GB")); + } + + @Test(expected=IllegalArgumentException.class) + public void testSetValidatorLen35() { + IBANValidator validator = new IBANValidator(); + assertNotNull(validator.setValidator("GB", 35, "GB")); // valid params, but immutable validator + } + + @Test + public void testSetValidatorLen_1() { + IBANValidator validator = new IBANValidator(); + assertNotNull("should be present",validator.setValidator("GB", -1, "")); + assertNull("no longer present",validator.setValidator("GB", -1, "")); + } + + @Test + public void testSorted() { + IBANValidator validator = new IBANValidator(); + Validator[] vals = validator.getDefaultValidators(); + assertNotNull(vals); + for(int i=1; i < vals.length; i++) { + if (vals[i].countryCode.compareTo(vals[i-1].countryCode) <= 0) { + fail("Not sorted: "+vals[i].countryCode+ " <= " + vals[i-1].countryCode); + } + } + } + + private static void checkIBAN(File file, IBANValidator val) throws Exception { + // The IBAN Registry (TXT) file is a TAB-separated file + // Rows are the entry types, columns are the countries + CSVFormat format = CSVFormat.DEFAULT.withDelimiter('\t'); + Reader rdr = new InputStreamReader(new FileInputStream(file), "ISO_8859_1"); + CSVParser p = new CSVParser(rdr, format); + CSVRecord country = null; + CSVRecord cc = null; + CSVRecord structure = null; + CSVRecord length = null; + for (CSVRecord o : p) { + String item = o.get(0); + if ("Name of country".equals(item)) { + country = o; + } else if ("IBAN prefix country code (ISO 3166)".equals(item)) { + cc = o; + } else if ("IBAN structure".equals(item)) { + structure = o; + } else if ("IBAN length".equals(item)) { + length = o; + } + } + for (int i=1; i < country.size(); i++) { + String newLength = length.get(i).split("!")[0]; // El Salvador currently has "28!n" + String newRE = fmtRE(structure.get(i), Integer.parseInt(newLength)); + final Validator valre = val.getValidator(cc.get(i)); + if (valre == null) { + System.out.println("// Missing entry:"); + printEntry( + cc.get(i), + newLength, + newRE, + country.get(i)); + } else { + String currentLength = Integer.toString(valre.lengthOfIBAN); + String currentRE = valre.validator.toString() + .replaceAll("^.+?\\{(.+)}","$1") // Extract RE from RegexValidator{re} string + .replaceAll("\\\\d","\\\\\\\\d"); // convert \d to \\d + // The above assumes that the RegexValidator contains a single Regex + if (currentRE.equals(newRE) && currentLength.equals(newLength)) { + + } else { + System.out.println("// Expected: " + newRE + ", " + newLength + " Actual: " + currentRE + ", " + currentLength); + printEntry( + cc.get(i), + newLength, + newRE, + country.get(i)); + } + + } + } + p.close(); + } + + private static void printEntry(String ccode, String length, String ib, String country) { + String fmt = String.format("\"%s\"", ib); + System.out.printf(" new Validator(\"%s\", %s, %-40s), // %s\n", + ccode, + length, + fmt, + country); + } + + // Unfortunately Java only returns the last match of repeated patterns + // Use a manual repeat instead + private static final String IBAN_PART = "(?:(\\d+)!([acn]))"; // Assume all parts are fixed length + private static final Pattern IBAN_PAT = Pattern.compile( + "([A-Z]{2})"+IBAN_PART+IBAN_PART+IBAN_PART+IBAN_PART+"?"+IBAN_PART+"?"+IBAN_PART+"?"+IBAN_PART+"?"); + + // convert IBAN type string and length to regex + private static String formatToRE(String type, int len) { + char ctype = type.charAt(0); // assume type.length() == 1 + switch(ctype) { + case 'n': + return String.format("\\\\d{%d}",len); + case 'a': + return String.format("[A-Z]{%d}",len); + case 'c': + return String.format("[A-Z0-9]{%d}",len); + default: + throw new IllegalArgumentException("Unexpected type " + type); + } + } + + private static String fmtRE(String iban_pat, int iban_len) { + Matcher m = IBAN_PAT.matcher(iban_pat); + if (m.matches()) { + StringBuilder sb = new StringBuilder(); + String cc = m.group(1); // country code + int totalLen = cc.length(); + sb.append(cc); + int len = Integer.parseInt(m.group(2)); // length of part + String curType = m.group(3); // part type + for (int i = 4; i <= m.groupCount(); i += 2) { + if (m.group(i) == null) { // reached an optional group + break; + } + int count = Integer.parseInt(m.group(i)); + String type = m.group(i+1); + if (type.equals(curType)) { // more of the same type + len += count; + } else { + sb.append(formatToRE(curType,len)); + totalLen += len; + curType = type; + len = count; + } + } + sb.append(formatToRE(curType,len)); + totalLen += len; + if (iban_len != totalLen) { + throw new IllegalArgumentException("IBAN pattern " + iban_pat + " does not match length " + iban_len); + } + return sb.toString(); + } else { + throw new IllegalArgumentException("Unexpected IBAN pattern " + iban_pat); + } + } + + public static void main(String [] a) throws Exception { + IBANValidator validator = new IBANValidator(); + File iban_tsv = new File("target","iban-registry.tsv"); + if (iban_tsv.canRead()) { + checkIBAN(iban_tsv, validator); + } else { + System.out.println("Please load the file " + iban_tsv.getCanonicalPath() + " from https://www.swift.com/standards/data-standards/iban"); + } + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISBNValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISBNValidatorTest.java new file mode 100644 index 000000000..95637093f --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISBNValidatorTest.java @@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.regex.Pattern; +import junit.framework.TestCase; + +/** + * ISBNValidator Test Case. + * + * @version $Revision$ + */ +public class ISBNValidatorTest extends TestCase { + + private final String[] validISBN10Format = new String[] { + "1234567890", + "123456789X", + "12345-1234567-123456-X", + "12345 1234567 123456 X", + "1-2-3-4", + "1 2 3 4", + }; + + private final String[] invalidISBN10Format = new String[] { + "", // empty + " ", // empty + "1", // too short + "123456789", // too short + "12345678901", // too long + "12345678X0", // X not at end + "123456-1234567-123456-X", // Group too long + "12345-12345678-123456-X", // Publisher too long + "12345-1234567-1234567-X", // Title too long + "12345-1234567-123456-X2", // Check Digit too long + "--1 930110 99 5", // format + "1 930110 99 5--", // format + "1 930110-99 5-", // format + "1.2.3.4", // Invalid Separator + "1=2=3=4", // Invalid Separator + "1_2_3_4", // Invalid Separator + "123456789Y", // Other character at the end + "dsasdsadsa", // invalid characters + "I love sparrows!", // invalid characters + "068-556-98-45" // format + }; + + private final String[] validISBN13Format = new String[] { + "9781234567890", + "9791234567890", + "978-12345-1234567-123456-1", + "979-12345-1234567-123456-1", + "978 12345 1234567 123456 1", + "979 12345 1234567 123456 1", + "978-1-2-3-4", + "979-1-2-3-4", + "978 1 2 3 4", + "979 1 2 3 4", + }; + + private final String[] invalidISBN13Format = new String[] { + "", // empty + " ", // empty + "1", // too short + "978123456789", // too short + "97812345678901", // too long + "978-123456-1234567-123456-1", // Group too long + "978-12345-12345678-123456-1", // Publisher too long + "978-12345-1234567-1234567-1", // Title too long + "978-12345-1234567-123456-12", // Check Digit too long + "--978 1 930110 99 1", // format + "978 1 930110 99 1--", // format + "978 1 930110-99 1-", // format + "123-4-567890-12-8", // format + "978.1.2.3.4", // Invalid Separator + "978=1=2=3=4", // Invalid Separator + "978_1_2_3_4", // Invalid Separator + "978123456789X", // invalid character + "978-0-201-63385-X", // invalid character + "dsasdsadsadsa", // invalid characters + "I love sparrows!", // invalid characters + "979-1-234-567-89-6" // format + }; + + /** + * Create a test case with the specified name. + * @param name The name of the test + */ + public ISBNValidatorTest(String name) { + super(name); + } + + /** + * Test Valid ISBN-10 formats. + */ + public void testValidISBN10Format() { + Pattern pattern = Pattern.compile(ISBNValidator.ISBN10_REGEX); + for (int i = 0; i < validISBN10Format.length; i++) { + assertTrue("Pattern[" + i + "]=" + validISBN10Format[i], pattern.matcher(validISBN10Format[i]).matches()); + } + } + + /** + * Test Invalid ISBN-10 formats. + */ + public void testInvalidISBN10Format() { + ISBNValidator validator = ISBNValidator.getInstance(); + Pattern pattern = Pattern.compile(ISBNValidator.ISBN10_REGEX); + for (int i = 0; i < invalidISBN10Format.length; i++) { + assertFalse("Pattern[" + i + "]=" + invalidISBN10Format[i], pattern.matcher(invalidISBN10Format[i]).matches()); + assertFalse("isValidISBN10[" + i + "]=" + invalidISBN10Format[i], validator.isValidISBN10(invalidISBN10Format[i])); + assertNull("validateISBN10[" + i + "]=" + invalidISBN10Format[i], validator.validateISBN10(invalidISBN10Format[i])); + } + } + + /** + * Test Valid ISBN-13 formats. + */ + public void testValidISBN13Format() { + Pattern pattern = Pattern.compile(ISBNValidator.ISBN13_REGEX); + for (int i = 0; i < validISBN13Format.length; i++) { + assertTrue("Pattern[" + i + "]=" + validISBN13Format[i], pattern.matcher(validISBN13Format[i]).matches()); + } + } + + /** + * Test Invalid ISBN-13 formats. + */ + public void testInvalidISBN13Format() { + Pattern pattern = Pattern.compile(ISBNValidator.ISBN13_REGEX); + ISBNValidator validator = ISBNValidator.getInstance(); + for (int i = 0; i < invalidISBN13Format.length; i++) { + assertFalse("Pattern[" + i + "]=" + invalidISBN13Format[i], pattern.matcher(invalidISBN13Format[i]).matches()); + assertFalse("isValidISBN13[" + i + "]=" + invalidISBN13Format[i], validator.isValidISBN13(invalidISBN13Format[i])); + assertNull("validateISBN13[" + i + "]=" + invalidISBN13Format[i], validator.validateISBN13(invalidISBN13Format[i])); + } + } + + /** + * Test isValid() ISBN-10 codes + */ + public void testIsValidISBN10() { + ISBNValidator validator = ISBNValidator.getInstance(); + assertTrue("isValidISBN10-1", validator.isValidISBN10("1930110995")); + assertTrue("isValidISBN10-2", validator.isValidISBN10("1-930110-99-5")); + assertTrue("isValidISBN10-3", validator.isValidISBN10("1 930110 99 5")); + assertTrue("isValidISBN10-4", validator.isValidISBN10("020163385X")); + assertTrue("isValidISBN10-5", validator.isValidISBN10("0-201-63385-X")); + assertTrue("isValidISBN10-6", validator.isValidISBN10("0 201 63385 X")); + + assertTrue("isValid-1", validator.isValid("1930110995")); + assertTrue("isValid-2", validator.isValid("1-930110-99-5")); + assertTrue("isValid-3", validator.isValid("1 930110 99 5")); + assertTrue("isValid-4", validator.isValid("020163385X")); + assertTrue("isValid-5", validator.isValid("0-201-63385-X")); + assertTrue("isValid-6", validator.isValid("0 201 63385 X")); + } + + /** + * Test isValid() ISBN-13 codes + */ + public void testIsValidISBN13() { + ISBNValidator validator = ISBNValidator.getInstance(); + assertTrue("isValidISBN13-1", validator.isValidISBN13("9781930110991")); + assertTrue("isValidISBN13-2", validator.isValidISBN13("978-1-930110-99-1")); + assertTrue("isValidISBN13-3", validator.isValidISBN13("978 1 930110 99 1")); + assertTrue("isValidISBN13-4", validator.isValidISBN13("9780201633856")); + assertTrue("isValidISBN13-5", validator.isValidISBN13("978-0-201-63385-6")); + assertTrue("isValidISBN13-6", validator.isValidISBN13("978 0 201 63385 6")); + + assertTrue("isValid-1", validator.isValid("9781930110991")); + assertTrue("isValid-2", validator.isValid("978-1-930110-99-1")); + assertTrue("isValid-3", validator.isValid("978 1 930110 99 1")); + assertTrue("isValid-4", validator.isValid("9780201633856")); + assertTrue("isValid-5", validator.isValid("978-0-201-63385-6")); + assertTrue("isValid-6", validator.isValid("978 0 201 63385 6")); + } + + /** + * Test validate() ISBN-10 codes (don't convert) + */ + public void testValidateISBN10() { + ISBNValidator validator = ISBNValidator.getInstance(false); + assertEquals("validateISBN10-1", "1930110995", validator.validateISBN10("1930110995")); + assertEquals("validateISBN10-2", "1930110995", validator.validateISBN10("1-930110-99-5")); + assertEquals("validateISBN10-3", "1930110995", validator.validateISBN10("1 930110 99 5")); + assertEquals("validateISBN10-4", "020163385X", validator.validateISBN10("020163385X")); + assertEquals("validateISBN10-5", "020163385X", validator.validateISBN10("0-201-63385-X")); + assertEquals("validateISBN10-6", "020163385X", validator.validateISBN10("0 201 63385 X")); + + assertEquals("validate-1", "1930110995", validator.validate("1930110995")); + assertEquals("validate-2", "1930110995", validator.validate("1-930110-99-5")); + assertEquals("validate-3", "1930110995", validator.validate("1 930110 99 5")); + assertEquals("validate-4", "020163385X", validator.validate("020163385X")); + assertEquals("validate-5", "020163385X", validator.validate("0-201-63385-X")); + assertEquals("validate-6", "020163385X", validator.validate("0 201 63385 X")); + } + + /** + * Test validate() ISBN-10 codes (convert) + */ + public void testValidateISBN10Convert() { + ISBNValidator validator = ISBNValidator.getInstance(); + assertEquals("validate-1", "9781930110991", validator.validate("1930110995")); + assertEquals("validate-2", "9781930110991", validator.validate("1-930110-99-5")); + assertEquals("validate-3", "9781930110991", validator.validate("1 930110 99 5")); + assertEquals("validate-4", "9780201633856", validator.validate("020163385X")); + assertEquals("validate-5", "9780201633856", validator.validate("0-201-63385-X")); + assertEquals("validate-6", "9780201633856", validator.validate("0 201 63385 X")); + } + + /** + * Test validate() ISBN-13 codes + */ + public void testValidateISBN13() { + ISBNValidator validator = ISBNValidator.getInstance(); + assertEquals("validateISBN13-1", "9781930110991", validator.validateISBN13("9781930110991")); + assertEquals("validateISBN13-2", "9781930110991", validator.validateISBN13("978-1-930110-99-1")); + assertEquals("validateISBN13-3", "9781930110991", validator.validateISBN13("978 1 930110 99 1")); + assertEquals("validateISBN13-4", "9780201633856", validator.validateISBN13("9780201633856")); + assertEquals("validateISBN13-5", "9780201633856", validator.validateISBN13("978-0-201-63385-6")); + assertEquals("validateISBN13-6", "9780201633856", validator.validateISBN13("978 0 201 63385 6")); + + assertEquals("validate-1", "9781930110991", validator.validate("9781930110991")); + assertEquals("validate-2", "9781930110991", validator.validate("978-1-930110-99-1")); + assertEquals("validate-3", "9781930110991", validator.validate("978 1 930110 99 1")); + assertEquals("validate-4", "9780201633856", validator.validate("9780201633856")); + assertEquals("validate-5", "9780201633856", validator.validate("978-0-201-63385-6")); + assertEquals("validate-6", "9780201633856", validator.validate("978 0 201 63385 6")); + } + + /** + * Test null values + */ + public void testNull() { + ISBNValidator validator = ISBNValidator.getInstance(); + assertFalse("isValid", validator.isValid(null)); + assertFalse("isValidISBN10", validator.isValidISBN10(null)); + assertFalse("isValidISBN13", validator.isValidISBN13(null)); + assertNull("validate", validator.validate(null)); + assertNull("validateISBN10", validator.validateISBN10(null)); + assertNull("validateISBN13", validator.validateISBN13(null)); + assertNull("convertToISBN13", validator.convertToISBN13(null)); + } + + /** + * Test Invalid ISBN-10 codes + */ + public void testInvalid() { + ISBNValidator validator = ISBNValidator.getInstance(); + String baseCode = "193011099"; + assertFalse("ISBN10-0", validator.isValid(baseCode + "0")); + assertFalse("ISBN10-1", validator.isValid(baseCode + "1")); + assertFalse("ISBN10-2", validator.isValid(baseCode + "2")); + assertFalse("ISBN10-3", validator.isValid(baseCode + "3")); + assertFalse("ISBN10-4", validator.isValid(baseCode + "4")); + assertTrue("ISBN10-5", validator.isValid(baseCode + "5")); // valid check digit + assertFalse("ISBN10-6", validator.isValid(baseCode + "6")); + assertFalse("ISBN10-7", validator.isValid(baseCode + "7")); + assertFalse("ISBN10-8", validator.isValid(baseCode + "8")); + assertFalse("ISBN10-9", validator.isValid(baseCode + "9")); + assertFalse("ISBN10-X", validator.isValid(baseCode + "X")); + + baseCode = "978193011099"; + assertFalse("ISBN13-0", validator.isValid(baseCode + "0")); + assertTrue("ISBN13-1", validator.isValid(baseCode + "1")); // valid check digit + assertFalse("ISBN13-2", validator.isValid(baseCode + "2")); + assertFalse("ISBN13-3", validator.isValid(baseCode + "3")); + assertFalse("ISBN13-4", validator.isValid(baseCode + "4")); + assertFalse("ISBN13-5", validator.isValid(baseCode + "5")); + assertFalse("ISBN13-6", validator.isValid(baseCode + "6")); + assertFalse("ISBN13-7", validator.isValid(baseCode + "7")); + assertFalse("ISBN13-8", validator.isValid(baseCode + "8")); + assertFalse("ISBN13-9", validator.isValid(baseCode + "9")); + } + + /** + * Test method for {@link org.apache.commons.validator.routines.ISBNValidator#convertToISBN13(java.lang.String)}. + */ + public void testConversionErrors() { + ISBNValidator validator = ISBNValidator.getInstance(); + String input = null; + try { + input = "123456789 "; + validator.convertToISBN13(input); + fail("Expected IllegalArgumentException for '" + input + "'"); + } catch (IllegalArgumentException e) { + // expected result + } + try { + input = "12345678901"; + validator.convertToISBN13(input); + fail("Expected IllegalArgumentException for '" + input + "'"); + } catch (IllegalArgumentException e) { + // expected result + } + try { + input = ""; + validator.convertToISBN13(input); + fail("Expected IllegalArgumentException for '" + input + "'"); + } catch (IllegalArgumentException e) { + // expected result + } + try { + input = "X234567890"; + validator.convertToISBN13(input); + fail("Expected IllegalArgumentException for '" + input + "'"); + } catch (IllegalArgumentException e) { + // expected result + } + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISINValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISINValidatorTest.java new file mode 100644 index 000000000..afcecad37 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISINValidatorTest.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import junit.framework.TestCase; + +/** + * ISINValidator Test Case. + * + * @since 1.7 + */ +public class ISINValidatorTest extends TestCase { + + private static final ISINValidator VALIDATOR_TRUE = ISINValidator.getInstance(true); + + private static final ISINValidator VALIDATOR_FALSE = ISINValidator.getInstance(false); + + private final String[] validFormat = new String[] { + "US0378331005", + "BMG8571G1096", + "AU0000XVGZA3", + "GB0002634946", + "FR0004026250", + "DK0009763344", + "GB00B03MLX29", + "US7562071065", + "US56845T3059", + "LU0327357389", + "US032511BN64", + "INE112A01023", + "EZ0000000003", // Invented; for use in ISINValidator + "XS0000000009", + }; + + private final String[] invalidFormat = new String[] { + null, + "", // empty + " ", // empty + "US037833100O", // proper check digit is '5', see above + "BMG8571G109D", // proper check digit is '6', see above + "AU0000XVGZAD", // proper check digit is '3', see above + "GB000263494I", // proper check digit is '6', see above + "FR000402625C", // proper check digit is '0', see above + "DK000976334H", // proper check digit is '4', see above + "3133EHHF3", // see VALIDATOR-422 Valid check-digit, but not valid ISIN + "AU0000xvgzA3", // disallow lower case NSIN + "gb0002634946", // disallow lower case ISO code + }; + + // Invalid codes if country checking is enabled + private final String[] invalidFormatTrue = new String[] { + "AA0000000006", // Invalid country code + }; + + public ISINValidatorTest(String name) { + super(name); + } + + public void testIsValidTrue() { + for(String f : validFormat) { + assertTrue(f, VALIDATOR_TRUE.isValid(f)); + } + } + + public void testInvalidTrue() { + for(String f : invalidFormat) { + assertFalse(f, VALIDATOR_TRUE.isValid(f)); + } + for(String f : invalidFormatTrue) { + assertFalse(f, VALIDATOR_TRUE.isValid(f)); + } + } + + public void testIsValidFalse() { + for(String f : validFormat) { + assertTrue(f, VALIDATOR_FALSE.isValid(f)); + } + } + + public void testInvalidFalse() { + for(String f : invalidFormat) { + assertFalse(f, VALIDATOR_FALSE.isValid(f)); + } + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISSNValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISSNValidatorTest.java new file mode 100644 index 000000000..2f7c10735 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISSNValidatorTest.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.Random; + +import org.apache.commons.validator.routines.checkdigit.CheckDigit; +import org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit; + +import junit.framework.TestCase; + +/** + * ISSNValidator Test Case. + * + * @since 1.5.0 + */ +public class ISSNValidatorTest extends TestCase { + + private static final ISSNValidator VALIDATOR = ISSNValidator.getInstance(); + + private final String[] validFormat = new String[] { + "ISSN 0317-8471", + "1050-124X", + "ISSN 1562-6865", + "1063-7710", + "1748-7188", + "ISSN 0264-2875", + "1750-0095", + "1188-1534", + "1911-1479", + "ISSN 1911-1460", + "0001-6772", + "1365-201X", + "0264-3596", + "1144-875X", + }; + + private final String[] invalidFormat = new String[] { + "", // empty + " ", // empty + "ISBN 0317-8471", // wrong prefix + "'1050-124X", // leading garbage + "ISSN1562-6865", // missing separator + "10637710", // missing separator + "1748-7188'", // trailing garbage + "ISSN 0264-2875", // extra space + "1750 0095", // invalid separator + "1188_1534", // invalid separator + "1911-1478", // invalid checkdigit + }; + + /** + * Create a test case with the specified name. + * @param name The name of the test + */ + public ISSNValidatorTest(String name) { + super(name); + } + + /** + * Test isValid() ISSN codes + */ + public void testIsValidISSN() { + for(String f : validFormat) { + assertTrue(f, VALIDATOR.isValid(f)); + } + } + + /** + * Test null values + */ + public void testNull() { + assertFalse("isValid", VALIDATOR.isValid(null)); + } + + /** + * Test Invalid ISSN codes + */ + public void testInvalid() { + for(String f : invalidFormat) { + assertFalse(f, VALIDATOR.isValid(f)); + } + } + + public void testIsValidISSNConvertNull() { + assertNull(VALIDATOR.convertToEAN13(null, "00")); + } + + public void testIsValidISSNConvertSuffix() { + try { + assertNull(VALIDATOR.convertToEAN13(null, null)); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + + } + try { + assertNull(VALIDATOR.convertToEAN13(null, "")); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + + } + try { + assertNull(VALIDATOR.convertToEAN13(null, "0")); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + + } + try { + assertNull(VALIDATOR.convertToEAN13(null, "A")); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + + } + try { + assertNull(VALIDATOR.convertToEAN13(null, "AA")); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + + } + try { + assertNull(VALIDATOR.convertToEAN13(null, "999")); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + + } + } + + /** + * Test isValid() ISSN codes and convert them + */ + public void testIsValidISSNConvert() { + CheckDigit ean13cd = EAN13CheckDigit.EAN13_CHECK_DIGIT; + Random r = new Random(); + for(String f : validFormat) { + String suffix = String.format("%02d", r.nextInt(100)); + String ean13 = VALIDATOR.convertToEAN13(f, suffix); + assertTrue(ean13, ean13cd.isValid(ean13)); + } + // internet samples + assertEquals("9771144875007", VALIDATOR.convertToEAN13("1144-875X", "00")); + assertEquals("9770264359008", VALIDATOR.convertToEAN13("0264-3596", "00")); + assertEquals("9771234567003", VALIDATOR.convertToEAN13("1234-5679", "00")); + } + + /** + * Test Invalid EAN-13 ISSN prefix codes + * Test Input length + */ + public void testConversionErrors() { + String input = null; + try { + input = "9780072129519"; + VALIDATOR.extractFromEAN13(input); + fail("Expected IllegalArgumentException for '" + input + "'"); + } catch (IllegalArgumentException e) { + // expected result + } + try { + input = "9791090636071"; + VALIDATOR.extractFromEAN13(input); + fail("Expected IllegalArgumentException for '" + input + "'"); + } catch (IllegalArgumentException e) { + // expected result + } + try { + input = "03178471"; + VALIDATOR.extractFromEAN13(input); + fail("Expected IllegalArgumentException for '" + input + "'"); + } catch (IllegalArgumentException e) { + // expected result + } + } + + /** + * Test Invalid EAN-13 ISSN codes + */ + public void testValidCheckDigitEan13() { + assertNull(VALIDATOR.extractFromEAN13("9771234567001")); + assertNull(VALIDATOR.extractFromEAN13("9771234567002")); + assertNotNull(VALIDATOR.extractFromEAN13("9771234567003")); // valid check digit + assertNull(VALIDATOR.extractFromEAN13("9771234567004")); + assertNull(VALIDATOR.extractFromEAN13("9771234567005")); + assertNull(VALIDATOR.extractFromEAN13("9771234567006")); + assertNull(VALIDATOR.extractFromEAN13("9771234567007")); + assertNull(VALIDATOR.extractFromEAN13("9771234567008")); + assertNull(VALIDATOR.extractFromEAN13("9771234567009")); + assertNull(VALIDATOR.extractFromEAN13("9771234567000")); + } + /** + * Test valid EAN-13 ISSN codes and extract the ISSN + */ + public void testIsValidExtract() { + assertEquals("12345679", VALIDATOR.extractFromEAN13("9771234567003")); + assertEquals("00014664", VALIDATOR.extractFromEAN13("9770001466006")); + assertEquals("03178471", VALIDATOR.extractFromEAN13("9770317847001")); + assertEquals("1144875X", VALIDATOR.extractFromEAN13("9771144875007")); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/InetAddressValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/InetAddressValidatorTest.java new file mode 100644 index 000000000..34dae8fdd --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/InetAddressValidatorTest.java @@ -0,0 +1,621 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.validator.routines; + +import junit.framework.TestCase; + +/** + * Test cases for InetAddressValidator. + * + * @version $Revision$ + */ +public class InetAddressValidatorTest extends TestCase { + + private InetAddressValidator validator; + + /** + * Constructor. + * @param name + */ + public InetAddressValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() { + validator = new InetAddressValidator(); + } + + /** + * Test IPs that point to real, well-known hosts (without actually looking them up). + */ + public void testInetAddressesFromTheWild() { + assertTrue("www.apache.org IP should be valid", validator.isValid("140.211.11.130")); + assertTrue("www.l.google.com IP should be valid", validator.isValid("72.14.253.103")); + assertTrue("fsf.org IP should be valid", validator.isValid("199.232.41.5")); + assertTrue("appscs.ign.com IP should be valid", validator.isValid("216.35.123.87")); + } + + public void testVALIDATOR_335() { + assertTrue("2001:0438:FFFE:0000:0000:0000:0000:0A35 should be valid", validator.isValid("2001:0438:FFFE:0000:0000:0000:0000:0A35")); + } + + public void testVALIDATOR_419() { + String addr; + addr = "0:0:0:0:0:0:13.1.68.3"; + assertTrue(addr, validator.isValid(addr)); + addr = "0:0:0:0:0:FFFF:129.144.52.38"; + assertTrue(addr, validator.isValid(addr)); + addr = "::13.1.68.3"; + assertTrue(addr, validator.isValid(addr)); + addr = "::FFFF:129.144.52.38"; + assertTrue(addr, validator.isValid(addr)); + + addr = "::ffff:192.168.1.1:192.168.1.1"; + assertFalse(addr, validator.isValid(addr)); + addr = "::192.168.1.1:192.168.1.1"; + assertFalse(addr, validator.isValid(addr)); + } + + /** + * Test valid and invalid IPs from each address class. + */ + public void testInetAddressesByClass() { + assertTrue("class A IP should be valid", validator.isValid("24.25.231.12")); + assertFalse("illegal class A IP should be invalid", validator.isValid("2.41.32.324")); + + assertTrue("class B IP should be valid", validator.isValid("135.14.44.12")); + assertFalse("illegal class B IP should be invalid", validator.isValid("154.123.441.123")); + + assertTrue("class C IP should be valid", validator.isValid("213.25.224.32")); + assertFalse("illegal class C IP should be invalid", validator.isValid("201.543.23.11")); + + assertTrue("class D IP should be valid", validator.isValid("229.35.159.6")); + assertFalse("illegal class D IP should be invalid", validator.isValid("231.54.11.987")); + + assertTrue("class E IP should be valid", validator.isValid("248.85.24.92")); + assertFalse("illegal class E IP should be invalid", validator.isValid("250.21.323.48")); + } + + /** + * Test reserved IPs. + */ + public void testReservedInetAddresses() { + assertTrue("localhost IP should be valid", validator.isValid("127.0.0.1")); + assertTrue("broadcast IP should be valid", validator.isValid("255.255.255.255")); + } + + /** + * Test obviously broken IPs. + */ + public void testBrokenInetAddresses() { + assertFalse("IP with characters should be invalid", validator.isValid("124.14.32.abc")); +// assertFalse("IP with leading zeroes should be invalid", validator.isValid("124.14.32.01")); + assertFalse("IP with three groups should be invalid", validator.isValid("23.64.12")); + assertFalse("IP with five groups should be invalid", validator.isValid("26.34.23.77.234")); + } + + /** + * Test IPv6 addresses. + *

These tests were ported from a + * Perl script.

+ * + */ + public void testIPv6() { + // The original Perl script contained a lot of duplicate tests. + // I removed the duplicates I noticed, but there may be more. +// assertFalse("IPV6 empty string should be invalid", validator.isValidInet6Address(""));// empty string + assertTrue("IPV6 ::1 should be valid", validator.isValidInet6Address("::1"));// loopback, compressed, non-routable + assertTrue("IPV6 :: should be valid", validator.isValidInet6Address("::"));// unspecified, compressed, non-routable + assertTrue("IPV6 0:0:0:0:0:0:0:1 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0:1"));// loopback, full + assertTrue("IPV6 0:0:0:0:0:0:0:0 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0:0"));// unspecified, full + assertTrue("IPV6 2001:DB8:0:0:8:800:200C:417A should be valid", validator.isValidInet6Address("2001:DB8:0:0:8:800:200C:417A"));// unicast, full + assertTrue("IPV6 FF01:0:0:0:0:0:0:101 should be valid", validator.isValidInet6Address("FF01:0:0:0:0:0:0:101"));// multicast, full + assertTrue("IPV6 2001:DB8::8:800:200C:417A should be valid", validator.isValidInet6Address("2001:DB8::8:800:200C:417A"));// unicast, compressed + assertTrue("IPV6 FF01::101 should be valid", validator.isValidInet6Address("FF01::101"));// multicast, compressed + assertFalse("IPV6 2001:DB8:0:0:8:800:200C:417A:221 should be invalid", validator.isValidInet6Address("2001:DB8:0:0:8:800:200C:417A:221"));// unicast, full + assertFalse("IPV6 FF01::101::2 should be invalid", validator.isValidInet6Address("FF01::101::2"));// multicast, compressed + assertTrue("IPV6 fe80::217:f2ff:fe07:ed62 should be valid", validator.isValidInet6Address("fe80::217:f2ff:fe07:ed62")); + assertTrue("IPV6 2001:0000:1234:0000:0000:C1C0:ABCD:0876 should be valid", validator.isValidInet6Address("2001:0000:1234:0000:0000:C1C0:ABCD:0876")); + assertTrue("IPV6 3ffe:0b00:0000:0000:0001:0000:0000:000a should be valid", validator.isValidInet6Address("3ffe:0b00:0000:0000:0001:0000:0000:000a")); + assertTrue("IPV6 FF02:0000:0000:0000:0000:0000:0000:0001 should be valid", validator.isValidInet6Address("FF02:0000:0000:0000:0000:0000:0000:0001")); + assertTrue("IPV6 0000:0000:0000:0000:0000:0000:0000:0001 should be valid", validator.isValidInet6Address("0000:0000:0000:0000:0000:0000:0000:0001")); + assertTrue("IPV6 0000:0000:0000:0000:0000:0000:0000:0000 should be valid", validator.isValidInet6Address("0000:0000:0000:0000:0000:0000:0000:0000")); + assertFalse("IPV6 02001:0000:1234:0000:0000:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("02001:0000:1234:0000:0000:C1C0:ABCD:0876")); // extra 0 not allowed! + assertFalse("IPV6 2001:0000:1234:0000:00001:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("2001:0000:1234:0000:00001:C1C0:ABCD:0876")); // extra 0 not allowed! + assertFalse("IPV6 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0 should be invalid", validator.isValidInet6Address("2001:0000:1234:0000:0000:C1C0:ABCD:0876 0")); // junk after valid address + assertFalse("IPV6 2001:0000:1234: 0000:0000:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("2001:0000:1234: 0000:0000:C1C0:ABCD:0876")); // internal space + assertFalse("IPV6 3ffe:0b00:0000:0001:0000:0000:000a should be invalid", validator.isValidInet6Address("3ffe:0b00:0000:0001:0000:0000:000a")); // seven segments + assertFalse("IPV6 FF02:0000:0000:0000:0000:0000:0000:0000:0001 should be invalid", validator.isValidInet6Address("FF02:0000:0000:0000:0000:0000:0000:0000:0001")); // nine segments + assertFalse("IPV6 3ffe:b00::1::a should be invalid", validator.isValidInet6Address("3ffe:b00::1::a")); // double "::" + assertFalse("IPV6 ::1111:2222:3333:4444:5555:6666:: should be invalid", validator.isValidInet6Address("::1111:2222:3333:4444:5555:6666::")); // double "::" + assertTrue("IPV6 2::10 should be valid", validator.isValidInet6Address("2::10")); + assertTrue("IPV6 ff02::1 should be valid", validator.isValidInet6Address("ff02::1")); + assertTrue("IPV6 fe80:: should be valid", validator.isValidInet6Address("fe80::")); + assertTrue("IPV6 2002:: should be valid", validator.isValidInet6Address("2002::")); + assertTrue("IPV6 2001:db8:: should be valid", validator.isValidInet6Address("2001:db8::")); + assertTrue("IPV6 2001:0db8:1234:: should be valid", validator.isValidInet6Address("2001:0db8:1234::")); + assertTrue("IPV6 ::ffff:0:0 should be valid", validator.isValidInet6Address("::ffff:0:0")); + assertTrue("IPV6 1:2:3:4:5:6:7:8 should be valid", validator.isValidInet6Address("1:2:3:4:5:6:7:8")); + assertTrue("IPV6 1:2:3:4:5:6::8 should be valid", validator.isValidInet6Address("1:2:3:4:5:6::8")); + assertTrue("IPV6 1:2:3:4:5::8 should be valid", validator.isValidInet6Address("1:2:3:4:5::8")); + assertTrue("IPV6 1:2:3:4::8 should be valid", validator.isValidInet6Address("1:2:3:4::8")); + assertTrue("IPV6 1:2:3::8 should be valid", validator.isValidInet6Address("1:2:3::8")); + assertTrue("IPV6 1:2::8 should be valid", validator.isValidInet6Address("1:2::8")); + assertTrue("IPV6 1::8 should be valid", validator.isValidInet6Address("1::8")); + assertTrue("IPV6 1::2:3:4:5:6:7 should be valid", validator.isValidInet6Address("1::2:3:4:5:6:7")); + assertTrue("IPV6 1::2:3:4:5:6 should be valid", validator.isValidInet6Address("1::2:3:4:5:6")); + assertTrue("IPV6 1::2:3:4:5 should be valid", validator.isValidInet6Address("1::2:3:4:5")); + assertTrue("IPV6 1::2:3:4 should be valid", validator.isValidInet6Address("1::2:3:4")); + assertTrue("IPV6 1::2:3 should be valid", validator.isValidInet6Address("1::2:3")); + assertTrue("IPV6 ::2:3:4:5:6:7:8 should be valid", validator.isValidInet6Address("::2:3:4:5:6:7:8")); + assertTrue("IPV6 ::2:3:4:5:6:7 should be valid", validator.isValidInet6Address("::2:3:4:5:6:7")); + assertTrue("IPV6 ::2:3:4:5:6 should be valid", validator.isValidInet6Address("::2:3:4:5:6")); + assertTrue("IPV6 ::2:3:4:5 should be valid", validator.isValidInet6Address("::2:3:4:5")); + assertTrue("IPV6 ::2:3:4 should be valid", validator.isValidInet6Address("::2:3:4")); + assertTrue("IPV6 ::2:3 should be valid", validator.isValidInet6Address("::2:3")); + assertTrue("IPV6 ::8 should be valid", validator.isValidInet6Address("::8")); + assertTrue("IPV6 1:2:3:4:5:6:: should be valid", validator.isValidInet6Address("1:2:3:4:5:6::")); + assertTrue("IPV6 1:2:3:4:5:: should be valid", validator.isValidInet6Address("1:2:3:4:5::")); + assertTrue("IPV6 1:2:3:4:: should be valid", validator.isValidInet6Address("1:2:3:4::")); + assertTrue("IPV6 1:2:3:: should be valid", validator.isValidInet6Address("1:2:3::")); + assertTrue("IPV6 1:2:: should be valid", validator.isValidInet6Address("1:2::")); + assertTrue("IPV6 1:: should be valid", validator.isValidInet6Address("1::")); + assertTrue("IPV6 1:2:3:4:5::7:8 should be valid", validator.isValidInet6Address("1:2:3:4:5::7:8")); + assertFalse("IPV6 1:2:3::4:5::7:8 should be invalid", validator.isValidInet6Address("1:2:3::4:5::7:8")); // Double "::" + assertFalse("IPV6 12345::6:7:8 should be invalid", validator.isValidInet6Address("12345::6:7:8")); + assertTrue("IPV6 1:2:3:4::7:8 should be valid", validator.isValidInet6Address("1:2:3:4::7:8")); + assertTrue("IPV6 1:2:3::7:8 should be valid", validator.isValidInet6Address("1:2:3::7:8")); + assertTrue("IPV6 1:2::7:8 should be valid", validator.isValidInet6Address("1:2::7:8")); + assertTrue("IPV6 1::7:8 should be valid", validator.isValidInet6Address("1::7:8")); + // IPv4 addresses as dotted-quads + assertTrue("IPV6 1:2:3:4:5:6:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4:5:6:1.2.3.4")); + assertTrue("IPV6 1:2:3:4:5::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4:5::1.2.3.4")); + assertTrue("IPV6 1:2:3:4::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4::1.2.3.4")); + assertTrue("IPV6 1:2:3::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3::1.2.3.4")); + assertTrue("IPV6 1:2::1.2.3.4 should be valid", validator.isValidInet6Address("1:2::1.2.3.4")); + assertTrue("IPV6 1::1.2.3.4 should be valid", validator.isValidInet6Address("1::1.2.3.4")); + assertTrue("IPV6 1:2:3:4::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4::5:1.2.3.4")); + assertTrue("IPV6 1:2:3::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3::5:1.2.3.4")); + assertTrue("IPV6 1:2::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2::5:1.2.3.4")); + assertTrue("IPV6 1::5:1.2.3.4 should be valid", validator.isValidInet6Address("1::5:1.2.3.4")); + assertTrue("IPV6 1::5:11.22.33.44 should be valid", validator.isValidInet6Address("1::5:11.22.33.44")); + assertFalse("IPV6 1::5:400.2.3.4 should be invalid", validator.isValidInet6Address("1::5:400.2.3.4")); + assertFalse("IPV6 1::5:260.2.3.4 should be invalid", validator.isValidInet6Address("1::5:260.2.3.4")); + assertFalse("IPV6 1::5:256.2.3.4 should be invalid", validator.isValidInet6Address("1::5:256.2.3.4")); + assertFalse("IPV6 1::5:1.256.3.4 should be invalid", validator.isValidInet6Address("1::5:1.256.3.4")); + assertFalse("IPV6 1::5:1.2.256.4 should be invalid", validator.isValidInet6Address("1::5:1.2.256.4")); + assertFalse("IPV6 1::5:1.2.3.256 should be invalid", validator.isValidInet6Address("1::5:1.2.3.256")); + assertFalse("IPV6 1::5:300.2.3.4 should be invalid", validator.isValidInet6Address("1::5:300.2.3.4")); + assertFalse("IPV6 1::5:1.300.3.4 should be invalid", validator.isValidInet6Address("1::5:1.300.3.4")); + assertFalse("IPV6 1::5:1.2.300.4 should be invalid", validator.isValidInet6Address("1::5:1.2.300.4")); + assertFalse("IPV6 1::5:1.2.3.300 should be invalid", validator.isValidInet6Address("1::5:1.2.3.300")); + assertFalse("IPV6 1::5:900.2.3.4 should be invalid", validator.isValidInet6Address("1::5:900.2.3.4")); + assertFalse("IPV6 1::5:1.900.3.4 should be invalid", validator.isValidInet6Address("1::5:1.900.3.4")); + assertFalse("IPV6 1::5:1.2.900.4 should be invalid", validator.isValidInet6Address("1::5:1.2.900.4")); + assertFalse("IPV6 1::5:1.2.3.900 should be invalid", validator.isValidInet6Address("1::5:1.2.3.900")); + assertFalse("IPV6 1::5:300.300.300.300 should be invalid", validator.isValidInet6Address("1::5:300.300.300.300")); + assertFalse("IPV6 1::5:3000.30.30.30 should be invalid", validator.isValidInet6Address("1::5:3000.30.30.30")); + assertFalse("IPV6 1::400.2.3.4 should be invalid", validator.isValidInet6Address("1::400.2.3.4")); + assertFalse("IPV6 1::260.2.3.4 should be invalid", validator.isValidInet6Address("1::260.2.3.4")); + assertFalse("IPV6 1::256.2.3.4 should be invalid", validator.isValidInet6Address("1::256.2.3.4")); + assertFalse("IPV6 1::1.256.3.4 should be invalid", validator.isValidInet6Address("1::1.256.3.4")); + assertFalse("IPV6 1::1.2.256.4 should be invalid", validator.isValidInet6Address("1::1.2.256.4")); + assertFalse("IPV6 1::1.2.3.256 should be invalid", validator.isValidInet6Address("1::1.2.3.256")); + assertFalse("IPV6 1::300.2.3.4 should be invalid", validator.isValidInet6Address("1::300.2.3.4")); + assertFalse("IPV6 1::1.300.3.4 should be invalid", validator.isValidInet6Address("1::1.300.3.4")); + assertFalse("IPV6 1::1.2.300.4 should be invalid", validator.isValidInet6Address("1::1.2.300.4")); + assertFalse("IPV6 1::1.2.3.300 should be invalid", validator.isValidInet6Address("1::1.2.3.300")); + assertFalse("IPV6 1::900.2.3.4 should be invalid", validator.isValidInet6Address("1::900.2.3.4")); + assertFalse("IPV6 1::1.900.3.4 should be invalid", validator.isValidInet6Address("1::1.900.3.4")); + assertFalse("IPV6 1::1.2.900.4 should be invalid", validator.isValidInet6Address("1::1.2.900.4")); + assertFalse("IPV6 1::1.2.3.900 should be invalid", validator.isValidInet6Address("1::1.2.3.900")); + assertFalse("IPV6 1::300.300.300.300 should be invalid", validator.isValidInet6Address("1::300.300.300.300")); + assertFalse("IPV6 1::3000.30.30.30 should be invalid", validator.isValidInet6Address("1::3000.30.30.30")); + assertFalse("IPV6 ::400.2.3.4 should be invalid", validator.isValidInet6Address("::400.2.3.4")); + assertFalse("IPV6 ::260.2.3.4 should be invalid", validator.isValidInet6Address("::260.2.3.4")); + assertFalse("IPV6 ::256.2.3.4 should be invalid", validator.isValidInet6Address("::256.2.3.4")); + assertFalse("IPV6 ::1.256.3.4 should be invalid", validator.isValidInet6Address("::1.256.3.4")); + assertFalse("IPV6 ::1.2.256.4 should be invalid", validator.isValidInet6Address("::1.2.256.4")); + assertFalse("IPV6 ::1.2.3.256 should be invalid", validator.isValidInet6Address("::1.2.3.256")); + assertFalse("IPV6 ::300.2.3.4 should be invalid", validator.isValidInet6Address("::300.2.3.4")); + assertFalse("IPV6 ::1.300.3.4 should be invalid", validator.isValidInet6Address("::1.300.3.4")); + assertFalse("IPV6 ::1.2.300.4 should be invalid", validator.isValidInet6Address("::1.2.300.4")); + assertFalse("IPV6 ::1.2.3.300 should be invalid", validator.isValidInet6Address("::1.2.3.300")); + assertFalse("IPV6 ::900.2.3.4 should be invalid", validator.isValidInet6Address("::900.2.3.4")); + assertFalse("IPV6 ::1.900.3.4 should be invalid", validator.isValidInet6Address("::1.900.3.4")); + assertFalse("IPV6 ::1.2.900.4 should be invalid", validator.isValidInet6Address("::1.2.900.4")); + assertFalse("IPV6 ::1.2.3.900 should be invalid", validator.isValidInet6Address("::1.2.3.900")); + assertFalse("IPV6 ::300.300.300.300 should be invalid", validator.isValidInet6Address("::300.300.300.300")); + assertFalse("IPV6 ::3000.30.30.30 should be invalid", validator.isValidInet6Address("::3000.30.30.30")); + assertTrue("IPV6 fe80::217:f2ff:254.7.237.98 should be valid", validator.isValidInet6Address("fe80::217:f2ff:254.7.237.98")); + assertTrue("IPV6 ::ffff:192.168.1.26 should be valid", validator.isValidInet6Address("::ffff:192.168.1.26")); + assertFalse("IPV6 2001:1:1:1:1:1:255Z255X255Y255 should be invalid", validator.isValidInet6Address("2001:1:1:1:1:1:255Z255X255Y255")); // garbage instead of "." in IPv4 + assertFalse("IPV6 ::ffff:192x168.1.26 should be invalid", validator.isValidInet6Address("::ffff:192x168.1.26")); // ditto + assertTrue("IPV6 ::ffff:192.168.1.1 should be valid", validator.isValidInet6Address("::ffff:192.168.1.1")); + assertTrue("IPV6 0:0:0:0:0:0:13.1.68.3 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:13.1.68.3"));// IPv4-compatible IPv6 address, full, deprecated + assertTrue("IPV6 0:0:0:0:0:FFFF:129.144.52.38 should be valid", validator.isValidInet6Address("0:0:0:0:0:FFFF:129.144.52.38"));// IPv4-mapped IPv6 address, full + assertTrue("IPV6 ::13.1.68.3 should be valid", validator.isValidInet6Address("::13.1.68.3"));// IPv4-compatible IPv6 address, compressed, deprecated + assertTrue("IPV6 ::FFFF:129.144.52.38 should be valid", validator.isValidInet6Address("::FFFF:129.144.52.38"));// IPv4-mapped IPv6 address, compressed + assertTrue("IPV6 fe80:0:0:0:204:61ff:254.157.241.86 should be valid", validator.isValidInet6Address("fe80:0:0:0:204:61ff:254.157.241.86")); + assertTrue("IPV6 fe80::204:61ff:254.157.241.86 should be valid", validator.isValidInet6Address("fe80::204:61ff:254.157.241.86")); + assertTrue("IPV6 ::ffff:12.34.56.78 should be valid", validator.isValidInet6Address("::ffff:12.34.56.78")); + assertFalse("IPV6 ::ffff:2.3.4 should be invalid", validator.isValidInet6Address("::ffff:2.3.4")); + assertFalse("IPV6 ::ffff:257.1.2.3 should be invalid", validator.isValidInet6Address("::ffff:257.1.2.3")); + assertFalse("IPV6 1.2.3.4 should be invalid", validator.isValidInet6Address("1.2.3.4")); + assertFalse("IPV6 1.2.3.4:1111:2222:3333:4444::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222:3333:4444::5555")); + assertFalse("IPV6 1.2.3.4:1111:2222:3333::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222:3333::5555")); + assertFalse("IPV6 1.2.3.4:1111:2222::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222::5555")); + assertFalse("IPV6 1.2.3.4:1111::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111::5555")); + assertFalse("IPV6 1.2.3.4::5555 should be invalid", validator.isValidInet6Address("1.2.3.4::5555")); + assertFalse("IPV6 1.2.3.4:: should be invalid", validator.isValidInet6Address("1.2.3.4::")); + // Testing IPv4 addresses represented as dotted-quads + // Leading zeroes in IPv4 addresses not allowed: some systems treat the leading "0" in ".086" as the start of an octal number + // Update: The BNF in RFC-3986 explicitly defines the dec-octet (for IPv4 addresses) not to have a leading zero + assertFalse("IPV6 fe80:0000:0000:0000:0204:61ff:254.157.241.086 should be invalid", validator.isValidInet6Address("fe80:0000:0000:0000:0204:61ff:254.157.241.086")); + assertTrue("IPV6 ::ffff:192.0.2.128 should be valid", validator.isValidInet6Address("::ffff:192.0.2.128")); // but this is OK, since there's a single digit + assertFalse("IPV6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4 should be invalid", validator.isValidInet6Address("XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:00.00.00.00 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:00.00.00.00")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:000.000.000.000 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:000.000.000.000")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:256.256.256.256 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:256.256.256.256")); + assertTrue("IPV6 fe80:0000:0000:0000:0204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80:0000:0000:0000:0204:61ff:fe9d:f156")); + assertTrue("IPV6 fe80:0:0:0:204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80:0:0:0:204:61ff:fe9d:f156")); + assertTrue("IPV6 fe80::204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80::204:61ff:fe9d:f156")); + assertFalse("IPV6 : should be invalid", validator.isValidInet6Address(":")); + assertTrue("IPV6 ::ffff:c000:280 should be valid", validator.isValidInet6Address("::ffff:c000:280")); + assertFalse("IPV6 1111:2222:3333:4444::5555: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::5555:")); + assertFalse("IPV6 1111:2222:3333::5555: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:")); + assertFalse("IPV6 1111:2222::5555: should be invalid", validator.isValidInet6Address("1111:2222::5555:")); + assertFalse("IPV6 1111::5555: should be invalid", validator.isValidInet6Address("1111::5555:")); + assertFalse("IPV6 ::5555: should be invalid", validator.isValidInet6Address("::5555:")); + assertFalse("IPV6 ::: should be invalid", validator.isValidInet6Address(":::")); + assertFalse("IPV6 1111: should be invalid", validator.isValidInet6Address("1111:")); + assertFalse("IPV6 :1111:2222:3333:4444::5555 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::5555")); + assertFalse("IPV6 :1111:2222:3333::5555 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555")); + assertFalse("IPV6 :1111:2222::5555 should be invalid", validator.isValidInet6Address(":1111:2222::5555")); + assertFalse("IPV6 :1111::5555 should be invalid", validator.isValidInet6Address(":1111::5555")); + assertFalse("IPV6 :::5555 should be invalid", validator.isValidInet6Address(":::5555")); + assertTrue("IPV6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 should be valid", validator.isValidInet6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334")); + assertTrue("IPV6 2001:db8:85a3:0:0:8a2e:370:7334 should be valid", validator.isValidInet6Address("2001:db8:85a3:0:0:8a2e:370:7334")); + assertTrue("IPV6 2001:db8:85a3::8a2e:370:7334 should be valid", validator.isValidInet6Address("2001:db8:85a3::8a2e:370:7334")); + assertTrue("IPV6 2001:0db8:0000:0000:0000:0000:1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0000:0000:0000:0000:1428:57ab")); + assertTrue("IPV6 2001:0db8:0000:0000:0000::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0000:0000:0000::1428:57ab")); + assertTrue("IPV6 2001:0db8:0:0:0:0:1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0:0:0:0:1428:57ab")); + assertTrue("IPV6 2001:0db8:0:0::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0:0::1428:57ab")); + assertTrue("IPV6 2001:0db8::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8::1428:57ab")); + assertTrue("IPV6 2001:db8::1428:57ab should be valid", validator.isValidInet6Address("2001:db8::1428:57ab")); + assertTrue("IPV6 ::ffff:0c22:384e should be valid", validator.isValidInet6Address("::ffff:0c22:384e")); + assertTrue("IPV6 2001:0db8:1234:0000:0000:0000:0000:0000 should be valid", validator.isValidInet6Address("2001:0db8:1234:0000:0000:0000:0000:0000")); + assertTrue("IPV6 2001:0db8:1234:ffff:ffff:ffff:ffff:ffff should be valid", validator.isValidInet6Address("2001:0db8:1234:ffff:ffff:ffff:ffff:ffff")); + assertTrue("IPV6 2001:db8:a::123 should be valid", validator.isValidInet6Address("2001:db8:a::123")); + assertFalse("IPV6 123 should be invalid", validator.isValidInet6Address("123")); + assertFalse("IPV6 ldkfj should be invalid", validator.isValidInet6Address("ldkfj")); + assertFalse("IPV6 2001::FFD3::57ab should be invalid", validator.isValidInet6Address("2001::FFD3::57ab")); + assertFalse("IPV6 2001:db8:85a3::8a2e:37023:7334 should be invalid", validator.isValidInet6Address("2001:db8:85a3::8a2e:37023:7334")); + assertFalse("IPV6 2001:db8:85a3::8a2e:370k:7334 should be invalid", validator.isValidInet6Address("2001:db8:85a3::8a2e:370k:7334")); + assertFalse("IPV6 1:2:3:4:5:6:7:8:9 should be invalid", validator.isValidInet6Address("1:2:3:4:5:6:7:8:9")); + assertFalse("IPV6 1::2::3 should be invalid", validator.isValidInet6Address("1::2::3")); + assertFalse("IPV6 1:::3:4:5 should be invalid", validator.isValidInet6Address("1:::3:4:5")); + assertFalse("IPV6 1:2:3::4:5:6:7:8:9 should be invalid", validator.isValidInet6Address("1:2:3::4:5:6:7:8:9")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666:7777:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777::")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::")); + assertTrue("IPV6 1111:2222:3333:4444:5555:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::")); + assertTrue("IPV6 1111:2222:3333:4444:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444::")); + assertTrue("IPV6 1111:2222:3333:: should be valid", validator.isValidInet6Address("1111:2222:3333::")); + assertTrue("IPV6 1111:2222:: should be valid", validator.isValidInet6Address("1111:2222::")); + assertTrue("IPV6 1111:: should be valid", validator.isValidInet6Address("1111::")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::8888")); + assertTrue("IPV6 1111:2222:3333:4444:5555::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::8888")); + assertTrue("IPV6 1111:2222:3333:4444::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::8888")); + assertTrue("IPV6 1111:2222:3333::8888 should be valid", validator.isValidInet6Address("1111:2222:3333::8888")); + assertTrue("IPV6 1111:2222::8888 should be valid", validator.isValidInet6Address("1111:2222::8888")); + assertTrue("IPV6 1111::8888 should be valid", validator.isValidInet6Address("1111::8888")); + assertTrue("IPV6 ::8888 should be valid", validator.isValidInet6Address("::8888")); + assertTrue("IPV6 1111:2222:3333:4444:5555::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777:8888")); + assertTrue("IPV6 1111:2222:3333:4444::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::7777:8888")); + assertTrue("IPV6 1111:2222:3333::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::7777:8888")); + assertTrue("IPV6 1111:2222::7777:8888 should be valid", validator.isValidInet6Address("1111:2222::7777:8888")); + assertTrue("IPV6 1111::7777:8888 should be valid", validator.isValidInet6Address("1111::7777:8888")); + assertTrue("IPV6 ::7777:8888 should be valid", validator.isValidInet6Address("::7777:8888")); + assertTrue("IPV6 1111:2222:3333:4444::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777:8888")); + assertTrue("IPV6 1111:2222:3333::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::6666:7777:8888")); + assertTrue("IPV6 1111:2222::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::6666:7777:8888")); + assertTrue("IPV6 1111::6666:7777:8888 should be valid", validator.isValidInet6Address("1111::6666:7777:8888")); + assertTrue("IPV6 ::6666:7777:8888 should be valid", validator.isValidInet6Address("::6666:7777:8888")); + assertTrue("IPV6 1111:2222:3333::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777:8888")); + assertTrue("IPV6 1111:2222::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::5555:6666:7777:8888")); + assertTrue("IPV6 1111::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::5555:6666:7777:8888")); + assertTrue("IPV6 ::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::5555:6666:7777:8888")); + assertTrue("IPV6 1111:2222::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777:8888")); + assertTrue("IPV6 1111::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::4444:5555:6666:7777:8888")); + assertTrue("IPV6 ::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::4444:5555:6666:7777:8888")); + assertTrue("IPV6 1111::3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777:8888")); + assertTrue("IPV6 ::3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::3333:4444:5555:6666:7777:8888")); + assertTrue("IPV6 ::2222:3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 1111:2222:3333:4444:5555::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::123.123.123.123")); + assertTrue("IPV6 1111:2222:3333:4444::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::123.123.123.123")); + assertTrue("IPV6 1111:2222:3333::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::123.123.123.123")); + assertTrue("IPV6 1111:2222::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::123.123.123.123")); + assertTrue("IPV6 1111::123.123.123.123 should be valid", validator.isValidInet6Address("1111::123.123.123.123")); + assertTrue("IPV6 ::123.123.123.123 should be valid", validator.isValidInet6Address("::123.123.123.123")); + assertTrue("IPV6 1111:2222:3333:4444::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::6666:123.123.123.123")); + assertTrue("IPV6 1111:2222:3333::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::6666:123.123.123.123")); + assertTrue("IPV6 1111:2222::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::6666:123.123.123.123")); + assertTrue("IPV6 1111::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::6666:123.123.123.123")); + assertTrue("IPV6 ::6666:123.123.123.123 should be valid", validator.isValidInet6Address("::6666:123.123.123.123")); + assertTrue("IPV6 1111:2222:3333::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::5555:6666:123.123.123.123")); + assertTrue("IPV6 1111:2222::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::5555:6666:123.123.123.123")); + assertTrue("IPV6 1111::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::5555:6666:123.123.123.123")); + assertTrue("IPV6 ::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::5555:6666:123.123.123.123")); + assertTrue("IPV6 1111:2222::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 1111::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 ::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 1111::3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::3333:4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 ::2222:3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:123.123.123.123")); + // Trying combinations of "0" and "::" + // These are all syntactically correct, but are bad form + // because "0" adjacent to "::" should be combined into "::" + assertTrue("IPV6 ::0:0:0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0:0:0")); + assertTrue("IPV6 ::0:0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0:0")); + assertTrue("IPV6 ::0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0")); + assertTrue("IPV6 ::0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0")); + assertTrue("IPV6 ::0:0:0 should be valid", validator.isValidInet6Address("::0:0:0")); + assertTrue("IPV6 ::0:0 should be valid", validator.isValidInet6Address("::0:0")); + assertTrue("IPV6 ::0 should be valid", validator.isValidInet6Address("::0")); + assertTrue("IPV6 0:0:0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0::")); + assertTrue("IPV6 0:0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0:0::")); + assertTrue("IPV6 0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0::")); + assertTrue("IPV6 0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0::")); + assertTrue("IPV6 0:0:0:: should be valid", validator.isValidInet6Address("0:0:0::")); + assertTrue("IPV6 0:0:: should be valid", validator.isValidInet6Address("0:0::")); + assertTrue("IPV6 0:: should be valid", validator.isValidInet6Address("0::")); + // Invalid data + assertFalse("IPV6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX should be invalid", validator.isValidInet6Address("XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX")); + // Too many components + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:9999 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:9999")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888::")); + assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:8888:9999 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888:9999")); + // Too few components + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666")); + assertFalse("IPV6 1111:2222:3333:4444:5555 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555")); + assertFalse("IPV6 1111:2222:3333:4444 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444")); + assertFalse("IPV6 1111:2222:3333 should be invalid", validator.isValidInet6Address("1111:2222:3333")); + assertFalse("IPV6 1111:2222 should be invalid", validator.isValidInet6Address("1111:2222")); + assertFalse("IPV6 1111 should be invalid", validator.isValidInet6Address("1111")); + // Missing : + assertFalse("IPV6 11112222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("11112222:3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:22223333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:22223333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:33334444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:33334444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:44445555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:44445555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:55556666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:55556666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:66667777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:66667777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:77778888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:77778888")); + // Missing : intended for :: + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:")); + assertFalse("IPV6 1111:2222:3333:4444:5555: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:")); + assertFalse("IPV6 1111:2222:3333:4444: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:")); + assertFalse("IPV6 1111:2222:3333: should be invalid", validator.isValidInet6Address("1111:2222:3333:")); + assertFalse("IPV6 1111:2222: should be invalid", validator.isValidInet6Address("1111:2222:")); + assertFalse("IPV6 :8888 should be invalid", validator.isValidInet6Address(":8888")); + assertFalse("IPV6 :7777:8888 should be invalid", validator.isValidInet6Address(":7777:8888")); + assertFalse("IPV6 :6666:7777:8888 should be invalid", validator.isValidInet6Address(":6666:7777:8888")); + assertFalse("IPV6 :5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":5555:6666:7777:8888")); + assertFalse("IPV6 :4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":4444:5555:6666:7777:8888")); + assertFalse("IPV6 :3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":2222:3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:7777:8888")); + // ::: + assertFalse("IPV6 :::2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:::3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:::4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:::5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:::8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:::")); + // Double :: + assertFalse("IPV6 ::2222::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222::4444:5555:6666:7777:8888")); + assertFalse("IPV6 ::2222:3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333::5555:6666:7777:8888")); + assertFalse("IPV6 ::2222:3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444::6666:7777:8888")); + assertFalse("IPV6 ::2222:3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555::7777:8888")); + assertFalse("IPV6 ::2222:3333:4444:5555:7777::8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:7777::8888")); + assertFalse("IPV6 ::2222:3333:4444:5555:7777:8888:: should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:7777:8888::")); + assertFalse("IPV6 1111::3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111::3333::5555:6666:7777:8888")); + assertFalse("IPV6 1111::3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111::3333:4444::6666:7777:8888")); + assertFalse("IPV6 1111::3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555::7777:8888")); + assertFalse("IPV6 1111::3333:4444:5555:6666::8888 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666::8888")); + assertFalse("IPV6 1111::3333:4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777::")); + assertFalse("IPV6 1111:2222::4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222::4444::6666:7777:8888")); + assertFalse("IPV6 1111:2222::4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555::7777:8888")); + assertFalse("IPV6 1111:2222::4444:5555:6666::8888 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666::8888")); + assertFalse("IPV6 1111:2222::4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777::")); + assertFalse("IPV6 1111:2222:3333::5555::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555::7777:8888")); + assertFalse("IPV6 1111:2222:3333::5555:6666::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666::8888")); + assertFalse("IPV6 1111:2222:3333::5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777::")); + assertFalse("IPV6 1111:2222:3333:4444::6666::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666::8888")); + assertFalse("IPV6 1111:2222:3333:4444::6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777::")); + assertFalse("IPV6 1111:2222:3333:4444:5555::7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777::")); + // Too many components" + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::1.2.3.4")); + assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:1.2.3.4.5 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:1.2.3.4.5")); + // Too few components + assertFalse("IPV6 1111:2222:3333:4444:5555:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:1.2.3.4")); + assertFalse("IPV6 1111:2222:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:1.2.3.4")); + assertFalse("IPV6 1111:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:1.2.3.4")); + assertFalse("IPV6 1.2.3.4 should be invalid", validator.isValidInet6Address("1.2.3.4")); + // Missing : + assertFalse("IPV6 11112222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("11112222:3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:22223333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:22223333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:33334444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:33334444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:44445555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:44445555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:55556666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:55556666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:66661.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:66661.2.3.4")); + // Missing . + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255255.255.255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255255.255.255")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255.255255.255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255.255255.255")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255.255.255255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255.255.255255")); + // Missing : intended for :: + assertFalse("IPV6 :1.2.3.4 should be invalid", validator.isValidInet6Address(":1.2.3.4")); + assertFalse("IPV6 :6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":6666:1.2.3.4")); + assertFalse("IPV6 :5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":5555:6666:1.2.3.4")); + assertFalse("IPV6 :4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":2222:3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:1.2.3.4")); + // ::: + assertFalse("IPV6 :::2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:::3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:::3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:::5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::1.2.3.4")); + // Double :: + assertFalse("IPV6 ::2222::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 ::2222:3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333::5555:6666:1.2.3.4")); + assertFalse("IPV6 ::2222:3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444::6666:1.2.3.4")); + assertFalse("IPV6 ::2222:3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555::1.2.3.4")); + assertFalse("IPV6 1111::3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333::5555:6666:1.2.3.4")); + assertFalse("IPV6 1111::3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333:4444::6666:1.2.3.4")); + assertFalse("IPV6 1111::3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555::1.2.3.4")); + assertFalse("IPV6 1111:2222::4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222::4444::6666:1.2.3.4")); + assertFalse("IPV6 1111:2222::4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555::1.2.3.4")); + assertFalse("IPV6 1111:2222:3333::5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555::1.2.3.4")); + // Missing parts + assertFalse("IPV6 ::. should be invalid", validator.isValidInet6Address("::.")); + assertFalse("IPV6 ::.. should be invalid", validator.isValidInet6Address("::..")); + assertFalse("IPV6 ::... should be invalid", validator.isValidInet6Address("::...")); + assertFalse("IPV6 ::1... should be invalid", validator.isValidInet6Address("::1...")); + assertFalse("IPV6 ::1.2.. should be invalid", validator.isValidInet6Address("::1.2..")); + assertFalse("IPV6 ::1.2.3. should be invalid", validator.isValidInet6Address("::1.2.3.")); + assertFalse("IPV6 ::.2.. should be invalid", validator.isValidInet6Address("::.2..")); + assertFalse("IPV6 ::.2.3. should be invalid", validator.isValidInet6Address("::.2.3.")); + assertFalse("IPV6 ::.2.3.4 should be invalid", validator.isValidInet6Address("::.2.3.4")); + assertFalse("IPV6 ::..3. should be invalid", validator.isValidInet6Address("::..3.")); + assertFalse("IPV6 ::..3.4 should be invalid", validator.isValidInet6Address("::..3.4")); + assertFalse("IPV6 ::...4 should be invalid", validator.isValidInet6Address("::...4")); + // Extra : in front + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:7777::")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666::")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::")); + assertFalse("IPV6 :1111:2222:3333:4444:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::")); + assertFalse("IPV6 :1111:2222:3333:: should be invalid", validator.isValidInet6Address(":1111:2222:3333::")); + assertFalse("IPV6 :1111:2222:: should be invalid", validator.isValidInet6Address(":1111:2222::")); + assertFalse("IPV6 :1111:: should be invalid", validator.isValidInet6Address(":1111::")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666::8888")); + assertFalse("IPV6 :1111:2222:3333:4444:5555::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::8888")); + assertFalse("IPV6 :1111:2222:3333:4444::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::8888")); + assertFalse("IPV6 :1111:2222:3333::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::8888")); + assertFalse("IPV6 :1111:2222::8888 should be invalid", validator.isValidInet6Address(":1111:2222::8888")); + assertFalse("IPV6 :1111::8888 should be invalid", validator.isValidInet6Address(":1111::8888")); + assertFalse("IPV6 :::8888 should be invalid", validator.isValidInet6Address(":::8888")); + assertFalse("IPV6 :1111:2222:3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::7777:8888")); + assertFalse("IPV6 :1111:2222:3333:4444::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::7777:8888")); + assertFalse("IPV6 :1111:2222:3333::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::7777:8888")); + assertFalse("IPV6 :1111:2222::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::7777:8888")); + assertFalse("IPV6 :1111::7777:8888 should be invalid", validator.isValidInet6Address(":1111::7777:8888")); + assertFalse("IPV6 :::7777:8888 should be invalid", validator.isValidInet6Address(":::7777:8888")); + assertFalse("IPV6 :1111:2222:3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::6666:7777:8888")); + assertFalse("IPV6 :1111:2222:3333::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::6666:7777:8888")); + assertFalse("IPV6 :1111:2222::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::6666:7777:8888")); + assertFalse("IPV6 :1111::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::6666:7777:8888")); + assertFalse("IPV6 :::6666:7777:8888 should be invalid", validator.isValidInet6Address(":::6666:7777:8888")); + assertFalse("IPV6 :1111:2222:3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555:6666:7777:8888")); + assertFalse("IPV6 :1111:2222::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::5555:6666:7777:8888")); + assertFalse("IPV6 :1111::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::5555:6666:7777:8888")); + assertFalse("IPV6 :::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::5555:6666:7777:8888")); + assertFalse("IPV6 :1111:2222::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::4444:5555:6666:7777:8888")); + assertFalse("IPV6 :1111::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::4444:5555:6666:7777:8888")); + assertFalse("IPV6 :::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::4444:5555:6666:7777:8888")); + assertFalse("IPV6 :1111::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :::2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333:4444::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::1.2.3.4")); + assertFalse("IPV6 :1111:2222::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::1.2.3.4")); + assertFalse("IPV6 :1111::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::1.2.3.4")); + assertFalse("IPV6 :::1.2.3.4 should be invalid", validator.isValidInet6Address(":::1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::6666:1.2.3.4")); + assertFalse("IPV6 :1111::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::6666:1.2.3.4")); + assertFalse("IPV6 :::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::5555:6666:1.2.3.4")); + assertFalse("IPV6 :::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111::3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :::2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:1.2.3.4")); + // Extra : at end + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:::")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:::")); + assertFalse("IPV6 1111:2222:3333:4444:5555::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::")); + assertFalse("IPV6 1111:2222:3333:4444::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::")); + assertFalse("IPV6 1111:2222:3333::: should be invalid", validator.isValidInet6Address("1111:2222:3333:::")); + assertFalse("IPV6 1111:2222::: should be invalid", validator.isValidInet6Address("1111:2222:::")); + assertFalse("IPV6 1111::: should be invalid", validator.isValidInet6Address("1111:::")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::8888:")); + assertFalse("IPV6 1111:2222:3333:4444:5555::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::8888:")); + assertFalse("IPV6 1111:2222:3333:4444::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::8888:")); + assertFalse("IPV6 1111:2222:3333::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::8888:")); + assertFalse("IPV6 1111:2222::8888: should be invalid", validator.isValidInet6Address("1111:2222::8888:")); + assertFalse("IPV6 1111::8888: should be invalid", validator.isValidInet6Address("1111::8888:")); + assertFalse("IPV6 ::8888: should be invalid", validator.isValidInet6Address("::8888:")); + assertFalse("IPV6 1111:2222:3333:4444:5555::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777:8888:")); + assertFalse("IPV6 1111:2222:3333:4444::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::7777:8888:")); + assertFalse("IPV6 1111:2222:3333::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::7777:8888:")); + assertFalse("IPV6 1111:2222::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::7777:8888:")); + assertFalse("IPV6 1111::7777:8888: should be invalid", validator.isValidInet6Address("1111::7777:8888:")); + assertFalse("IPV6 ::7777:8888: should be invalid", validator.isValidInet6Address("::7777:8888:")); + assertFalse("IPV6 1111:2222:3333:4444::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777:8888:")); + assertFalse("IPV6 1111:2222:3333::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::6666:7777:8888:")); + assertFalse("IPV6 1111:2222::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::6666:7777:8888:")); + assertFalse("IPV6 1111::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::6666:7777:8888:")); + assertFalse("IPV6 ::6666:7777:8888: should be invalid", validator.isValidInet6Address("::6666:7777:8888:")); + assertFalse("IPV6 1111:2222:3333::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777:8888:")); + assertFalse("IPV6 1111:2222::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::5555:6666:7777:8888:")); + assertFalse("IPV6 1111::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::5555:6666:7777:8888:")); + assertFalse("IPV6 ::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::5555:6666:7777:8888:")); + assertFalse("IPV6 1111:2222::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777:8888:")); + assertFalse("IPV6 1111::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::4444:5555:6666:7777:8888:")); + assertFalse("IPV6 ::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::4444:5555:6666:7777:8888:")); + assertFalse("IPV6 1111::3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777:8888:")); + assertFalse("IPV6 ::3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::3333:4444:5555:6666:7777:8888:")); + assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888:")); + assertTrue("IPV6 0:a:b:c:d:e:f:: should be valid", validator.isValidInet6Address("0:a:b:c:d:e:f::")); + assertTrue("IPV6 ::0:a:b:c:d:e:f should be valid", validator.isValidInet6Address("::0:a:b:c:d:e:f")); // syntactically correct, but bad form (::0:... could be combined) + assertTrue("IPV6 a:b:c:d:e:f:0:: should be valid", validator.isValidInet6Address("a:b:c:d:e:f:0::")); + assertFalse("IPV6 ':10.0.0.1 should be invalid", validator.isValidInet6Address("':10.0.0.1")); + } +} + + diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/IntegerValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/IntegerValidatorTest.java new file mode 100644 index 000000000..110ac47af --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/IntegerValidatorTest.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.Locale; + +/** + * Test Case for IntegerValidator. + * + * @version $Revision$ + */ +public class IntegerValidatorTest extends AbstractNumberValidatorTest { + + private static final Integer INT_MIN_VAL = Integer.valueOf(Integer.MIN_VALUE); + private static final Integer INT_MAX_VAL = Integer.valueOf(Integer.MAX_VALUE); + private static final String INT_MAX = "2147483647"; + private static final String INT_MAX_0 = "2147483647.99999999999999999999999"; // force double rounding + private static final String INT_MAX_1 = "2147483648"; + private static final String INT_MIN = "-2147483648"; + private static final String INT_MIN_0 = "-2147483648.99999999999999999999999"; // force double rounding"; + private static final String INT_MIN_1 = "-2147483649"; + + /** + * Constructor + * @param name test name + */ + public IntegerValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validator = new IntegerValidator(false, 0); + strictValidator = new IntegerValidator(); + + testPattern = "#,###"; + + // testValidateMinMax() + max = Integer.valueOf(Integer.MAX_VALUE); + maxPlusOne = Long.valueOf(max.longValue() + 1); + min = Integer.valueOf(Integer.MIN_VALUE); + minMinusOne = Long.valueOf(min.longValue() - 1); + + // testInvalidStrict() + invalidStrict = new String[] {null, "", "X", "X12", "12X", "1X2", "1.2", INT_MAX_1, INT_MIN_1}; + + // testInvalidNotStrict() + invalid = new String[] {null, "", "X", "X12", INT_MAX_1, INT_MIN_1}; + + // testValid() + testNumber = Integer.valueOf(1234); + testZero = Integer.valueOf(0); + validStrict = new String[] {"0", "1234", "1,234", INT_MAX, INT_MIN}; + validStrictCompare = new Number[] {testZero, testNumber, testNumber, INT_MAX_VAL, INT_MIN_VAL}; + valid = new String[] {"0", "1234", "1,234", "1,234.5", "1234X", INT_MAX, INT_MIN, INT_MAX_0, INT_MIN_0}; + validCompare = new Number[] {testZero, testNumber, testNumber, testNumber, testNumber, INT_MAX_VAL, INT_MIN_VAL, INT_MAX_VAL, INT_MIN_VAL}; + + testStringUS = "1,234"; + testStringDE = "1.234"; + + // Localized Pattern test + localeValue = testStringDE; + localePattern = "#.###"; + testLocale = Locale.GERMANY; + localeExpected = testNumber; + } + + /** + * Test IntegerValidator validate Methods + */ + public void testIntegerValidatorMethods() { + Locale locale = Locale.GERMAN; + String pattern = "0,00,00"; + String patternVal = "1,23,45"; + String germanPatternVal = "1.23.45"; + String localeVal = "12.345"; + String defaultVal = "12,345"; + String XXXX = "XXXX"; + Integer expected = Integer.valueOf(12345); + assertEquals("validate(A) default", expected, IntegerValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, IntegerValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, IntegerValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, IntegerValidator.getInstance().validate(germanPatternVal, pattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", IntegerValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", IntegerValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", IntegerValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", IntegerValidator.getInstance().isValid(germanPatternVal, pattern, Locale.GERMAN)); + + assertNull("validate(B) default", IntegerValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", IntegerValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", IntegerValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", IntegerValidator.getInstance().validate(patternVal, pattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", IntegerValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", IntegerValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", IntegerValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", IntegerValidator.getInstance().isValid(patternVal, pattern, Locale.GERMAN)); + } + + /** + * Test Integer Range/Min/Max + */ + public void testIntegerRangeMinMax() { + IntegerValidator validator = (IntegerValidator)strictValidator; + Integer number9 = validator.validate("9", "#"); + Integer number10 = validator.validate("10", "#"); + Integer number11 = validator.validate("11", "#"); + Integer number19 = validator.validate("19", "#"); + Integer number20 = validator.validate("20", "#"); + Integer number21 = validator.validate("21", "#"); + + // Test isInRange() + assertFalse("isInRange() < min", validator.isInRange(number9, 10, 20)); + assertTrue("isInRange() = min", validator.isInRange(number10, 10, 20)); + assertTrue("isInRange() in range", validator.isInRange(number11, 10, 20)); + assertTrue("isInRange() = max", validator.isInRange(number20, 10, 20)); + assertFalse("isInRange() > max", validator.isInRange(number21, 10, 20)); + + // Test minValue() + assertFalse("minValue() < min", validator.minValue(number9, 10)); + assertTrue("minValue() = min", validator.minValue(number10, 10)); + assertTrue("minValue() > min", validator.minValue(number11, 10)); + + // Test minValue() + assertTrue("maxValue() < max", validator.maxValue(number19, 20)); + assertTrue("maxValue() = max", validator.maxValue(number20, 20)); + assertFalse("maxValue() > max", validator.maxValue(number21, 20)); + } + public void testMinMaxValues() { + assertTrue("2147483647 is max integer", validator.isValid("2147483647")); + assertFalse("2147483648 > max integer", validator.isValid("2147483648")); + assertTrue("-2147483648 is min integer", validator.isValid("-2147483648")); + assertFalse("-2147483649 < min integer", validator.isValid("-2147483649")); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/LongValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/LongValidatorTest.java new file mode 100644 index 000000000..3b8fe0892 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/LongValidatorTest.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.Locale; + +/** + * Test Case for LongValidator. + * + * @version $Revision$ + */ +public class LongValidatorTest extends AbstractNumberValidatorTest { + + private static final Long LONG_MIN_VAL = Long.valueOf(Long.MIN_VALUE); + private static final Long LONG_MAX_VAL = Long.valueOf(Long.MAX_VALUE); + private static final String LONG_MAX = "9223372036854775807"; + private static final String LONG_MAX_0 = "9223372036854775807.99999999999999999999999"; // force double rounding + private static final String LONG_MAX_1 = "9223372036854775808"; + private static final String LONG_MIN = "-9223372036854775808"; + private static final String LONG_MIN_0 = "-9223372036854775808.99999999999999999999999"; // force double rounding + private static final String LONG_MIN_1 = "-9223372036854775809"; + + private static final String NINES = "9999999999999999999999999999999999999"; + /** + * Constructor + * @param name test name + */ + public LongValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validator = new LongValidator(false, 0); + strictValidator = new LongValidator(); + + testPattern = "#,###"; + + // testValidateMinMax() + max = null; + maxPlusOne = null; + min = null; + minMinusOne = null; + + + // testInvalidStrict() + invalidStrict = new String[] {null, "", "X", "X12", "12X", "1X2", "1.2", LONG_MAX_1, LONG_MIN_1, NINES}; + + // testInvalidNotStrict() + invalid = new String[] {null, "", "X", "X12", "", LONG_MAX_1, LONG_MIN_1, NINES}; + + // testValid() + testNumber = Long.valueOf(1234); + testZero = Long.valueOf(0); + validStrict = new String[] {"0", "1234", "1,234", LONG_MAX, LONG_MIN}; + validStrictCompare = new Number[] {testZero, testNumber, testNumber, LONG_MAX_VAL, LONG_MIN_VAL}; + valid = new String[] {"0", "1234", "1,234", "1,234.5", "1234X", LONG_MAX, LONG_MIN, LONG_MAX_0, LONG_MIN_0}; + validCompare = new Number[] {testZero, testNumber, testNumber, testNumber, testNumber, LONG_MAX_VAL, LONG_MIN_VAL, LONG_MAX_VAL, LONG_MIN_VAL}; + + testStringUS = "1,234"; + testStringDE = "1.234"; + + // Localized Pattern test + localeValue = testStringDE; + localePattern = "#.###"; + testLocale = Locale.GERMANY; + localeExpected = testNumber; + + } + + /** + * Test LongValidator validate Methods + */ + public void testLongValidatorMethods() { + Locale locale = Locale.GERMAN; + String pattern = "0,00,00"; + String patternVal = "1,23,45"; + String germanPatternVal = "1.23.45"; + String localeVal = "12.345"; + String defaultVal = "12,345"; + String XXXX = "XXXX"; + Long expected = Long.valueOf(12345); + assertEquals("validate(A) default", expected, LongValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, LongValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, LongValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, LongValidator.getInstance().validate(germanPatternVal, pattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", LongValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", LongValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", LongValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", LongValidator.getInstance().isValid(germanPatternVal, pattern, Locale.GERMAN)); + + assertNull("validate(B) default", LongValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", LongValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", LongValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", LongValidator.getInstance().validate(patternVal, pattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", LongValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", LongValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", LongValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", LongValidator.getInstance().isValid(patternVal, pattern, Locale.GERMAN)); + } + + /** + * Test Long Range/Min/Max + */ + public void testLongRangeMinMax() { + LongValidator validator = (LongValidator)strictValidator; + Long number9 = validator.validate("9", "#"); + Long number10 = validator.validate("10", "#"); + Long number11 = validator.validate("11", "#"); + Long number19 = validator.validate("19", "#"); + Long number20 = validator.validate("20", "#"); + Long number21 = validator.validate("21", "#"); + + // Test isInRange() + assertFalse("isInRange() < min", validator.isInRange(number9, 10, 20)); + assertTrue("isInRange() = min", validator.isInRange(number10, 10, 20)); + assertTrue("isInRange() in range", validator.isInRange(number11, 10, 20)); + assertTrue("isInRange() = max", validator.isInRange(number20, 10, 20)); + assertFalse("isInRange() > max", validator.isInRange(number21, 10, 20)); + + // Test minValue() + assertFalse("minValue() < min", validator.minValue(number9, 10)); + assertTrue("minValue() = min", validator.minValue(number10, 10)); + assertTrue("minValue() > min", validator.minValue(number11, 10)); + + // Test minValue() + assertTrue("maxValue() < max", validator.maxValue(number19, 20)); + assertTrue("maxValue() = max", validator.maxValue(number20, 20)); + assertFalse("maxValue() > max", validator.maxValue(number21, 20)); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/PercentValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/PercentValidatorTest.java new file mode 100644 index 000000000..79f819772 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/PercentValidatorTest.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.validator.routines; + +import junit.framework.TestCase; + +import java.util.Locale; +import java.math.BigDecimal; +/** + * Test Case for PercentValidator. + * + * @version $Revision$ + */ +public class PercentValidatorTest extends TestCase { + + protected PercentValidator validator; + + /** + * Constructor + * @param name test name + */ + public PercentValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + validator = new PercentValidator(); + } + + /** + * Tear down + * @throws Exception + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + validator = null; + } + + /** + * Test Format Type + */ + public void testFormatType() { + assertEquals("Format Type A", 2, PercentValidator.getInstance().getFormatType()); + assertEquals("Format Type B", AbstractNumberValidator.PERCENT_FORMAT, PercentValidator.getInstance().getFormatType()); + } + + /** + * Test Valid percentage values + */ + public void testValid() { + // Set the default Locale + Locale origDefault = Locale.getDefault(); + Locale.setDefault(Locale.UK); + + BigDecimalValidator validator = PercentValidator.getInstance(); + BigDecimal expected = new BigDecimal("0.12"); + BigDecimal negative = new BigDecimal("-0.12"); + BigDecimal hundred = new BigDecimal("1.00"); + + assertEquals("Default locale", expected, validator.validate("12%")); + assertEquals("Default negtve", negative, validator.validate("-12%")); + + // Invalid UK + assertEquals("UK locale", expected, validator.validate("12%", Locale.UK)); + assertEquals("UK negative", negative, validator.validate("-12%", Locale.UK)); + assertEquals("UK No symbol", expected, validator.validate("12", Locale.UK)); + + // Invalid US - can't find a Locale with different symbols! + assertEquals("US locale", expected, validator.validate("12%", Locale.US)); + assertEquals("US negative", negative, validator.validate("-12%", Locale.US)); + assertEquals("US No symbol", expected, validator.validate("12", Locale.US)); + + assertEquals("100%", hundred, validator.validate("100%")); + + // Restore the original default + Locale.setDefault(origDefault); + } + + /** + * Test Invalid percentage values + */ + public void testInvalid() { + BigDecimalValidator validator = PercentValidator.getInstance(); + + // Invalid Missing + assertFalse("isValid() Null Value", validator.isValid(null)); + assertFalse("isValid() Empty Value", validator.isValid("")); + assertNull("validate() Null Value", validator.validate(null)); + assertNull("validate() Empty Value", validator.validate("")); + + // Invalid UK + assertFalse("UK wrong symbol", validator.isValid("12@", Locale.UK)); // ??? + assertFalse("UK wrong negative", validator.isValid("(12%)", Locale.UK)); + + // Invalid US - can't find a Locale with different symbols! + assertFalse("US wrong symbol", validator.isValid("12@", Locale.US)); // ??? + assertFalse("US wrong negative", validator.isValid("(12%)", Locale.US)); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/RegexValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/RegexValidatorTest.java new file mode 100644 index 000000000..2be23928e --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/RegexValidatorTest.java @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.regex.PatternSyntaxException; + +import junit.framework.TestCase; + +/** + * Test Case for RegexValidatorTest. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class RegexValidatorTest extends TestCase { + + private static final String REGEX = "^([abc]*)(?:\\-)([DEF]*)(?:\\-)([123]*)$"; + + private static final String COMPONENT_1 = "([abc]{3})"; + private static final String COMPONENT_2 = "([DEF]{3})"; + private static final String COMPONENT_3 = "([123]{3})"; + private static final String SEPARATOR_1 = "(?:\\-)"; + private static final String SEPARATOR_2 = "(?:\\s)"; + private static final String REGEX_1 = "^" + COMPONENT_1 + SEPARATOR_1 + COMPONENT_2 + SEPARATOR_1 + COMPONENT_3 + "$"; + private static final String REGEX_2 = "^" + COMPONENT_1 + SEPARATOR_2 + COMPONENT_2 + SEPARATOR_2 + COMPONENT_3 + "$"; + private static final String REGEX_3 = "^" + COMPONENT_1 + COMPONENT_2 + COMPONENT_3 + "$"; + private static final String[] MULTIPLE_REGEX = new String[] {REGEX_1, REGEX_2, REGEX_3}; + + /** + * Constrct a new test case. + * @param name The name of the test + */ + public RegexValidatorTest(String name) { + super(name); + } + + /** + * Set Up. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + /** + * Tear Down. + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Test instance methods with single regular expression. + */ + public void testSingle() { + RegexValidator sensitive = new RegexValidator(REGEX); + RegexValidator insensitive = new RegexValidator(REGEX, false); + + // isValid() + assertEquals("Sensitive isValid() valid", true, sensitive.isValid("ac-DE-1")); + assertEquals("Sensitive isValid() invalid", false, sensitive.isValid("AB-de-1")); + assertEquals("Insensitive isValid() valid", true, insensitive.isValid("AB-de-1")); + assertEquals("Insensitive isValid() invalid", false, insensitive.isValid("ABd-de-1")); + + // validate() + assertEquals("Sensitive validate() valid", "acDE1", sensitive.validate("ac-DE-1")); + assertEquals("Sensitive validate() invalid", null, sensitive.validate("AB-de-1")); + assertEquals("Insensitive validate() valid", "ABde1", insensitive.validate("AB-de-1")); + assertEquals("Insensitive validate() invalid", null, insensitive.validate("ABd-de-1")); + + // match() + checkArray("Sensitive match() valid", new String[] {"ac", "DE", "1"}, sensitive.match("ac-DE-1")); + checkArray("Sensitive match() invalid", null, sensitive.match("AB-de-1")); + checkArray("Insensitive match() valid", new String[] {"AB", "de", "1"}, insensitive.match("AB-de-1")); + checkArray("Insensitive match() invalid", null, insensitive.match("ABd-de-1")); + assertEquals("validate one", "ABC", (new RegexValidator("^([A-Z]*)$")).validate("ABC")); + checkArray("match one", new String[] {"ABC"}, (new RegexValidator("^([A-Z]*)$")).match("ABC")); + } + + /** + * Test with multiple regular expressions (case sensitive). + */ + public void testMultipleSensitive() { + + // ------------ Set up Sensitive Validators + RegexValidator multiple = new RegexValidator(MULTIPLE_REGEX); + RegexValidator single1 = new RegexValidator(REGEX_1); + RegexValidator single2 = new RegexValidator(REGEX_2); + RegexValidator single3 = new RegexValidator(REGEX_3); + + // ------------ Set up test values + String value = "aac FDE 321"; + String expect = "aacFDE321"; + String[] array = new String[] {"aac", "FDE", "321"}; + + // isValid() + assertEquals("Sensitive isValid() Multiple", true, multiple.isValid(value)); + assertEquals("Sensitive isValid() 1st", false, single1.isValid(value)); + assertEquals("Sensitive isValid() 2nd", true, single2.isValid(value)); + assertEquals("Sensitive isValid() 3rd", false, single3.isValid(value)); + + // validate() + assertEquals("Sensitive validate() Multiple", expect, multiple.validate(value)); + assertEquals("Sensitive validate() 1st", null, single1.validate(value)); + assertEquals("Sensitive validate() 2nd", expect, single2.validate(value)); + assertEquals("Sensitive validate() 3rd", null, single3.validate(value)); + + // match() + checkArray("Sensitive match() Multiple", array, multiple.match(value)); + checkArray("Sensitive match() 1st", null, single1.match(value)); + checkArray("Sensitive match() 2nd", array, single2.match(value)); + checkArray("Sensitive match() 3rd", null, single3.match(value)); + + // All invalid + value = "AAC*FDE*321"; + assertEquals("isValid() Invalid", false, multiple.isValid(value)); + assertEquals("validate() Invalid", null, multiple.validate(value)); + assertEquals("match() Multiple", null, multiple.match(value)); + } + + /** + * Test with multiple regular expressions (case in-sensitive). + */ + public void testMultipleInsensitive() { + + // ------------ Set up In-sensitive Validators + RegexValidator multiple = new RegexValidator(MULTIPLE_REGEX, false); + RegexValidator single1 = new RegexValidator(REGEX_1, false); + RegexValidator single2 = new RegexValidator(REGEX_2, false); + RegexValidator single3 = new RegexValidator(REGEX_3, false); + + // ------------ Set up test values + String value = "AAC FDE 321"; + String expect = "AACFDE321"; + String[] array = new String[] {"AAC", "FDE", "321"}; + + // isValid() + assertEquals("isValid() Multiple", true, multiple.isValid(value)); + assertEquals("isValid() 1st", false, single1.isValid(value)); + assertEquals("isValid() 2nd", true, single2.isValid(value)); + assertEquals("isValid() 3rd", false, single3.isValid(value)); + + // validate() + assertEquals("validate() Multiple", expect, multiple.validate(value)); + assertEquals("validate() 1st", null, single1.validate(value)); + assertEquals("validate() 2nd", expect, single2.validate(value)); + assertEquals("validate() 3rd", null, single3.validate(value)); + + // match() + checkArray("match() Multiple", array, multiple.match(value)); + checkArray("match() 1st", null, single1.match(value)); + checkArray("match() 2nd", array, single2.match(value)); + checkArray("match() 3rd", null, single3.match(value)); + + // All invalid + value = "AAC*FDE*321"; + assertEquals("isValid() Invalid", false, multiple.isValid(value)); + assertEquals("validate() Invalid", null, multiple.validate(value)); + assertEquals("match() Multiple", null, multiple.match(value)); + } + + /** + * Test Null value + */ + public void testNullValue() { + + RegexValidator validator = new RegexValidator(REGEX); + assertEquals("Instance isValid()", false, validator.isValid(null)); + assertEquals("Instance validate()", null, validator.validate(null)); + assertEquals("Instance match()", null, validator.match(null)); + } + + /** + * Test exceptions + */ + public void testMissingRegex() { + + // Single Regular Expression - null + try { + new RegexValidator((String)null); + fail("Single Null - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Single Null", "Regular expression[0] is missing", e.getMessage()); + } + + // Single Regular Expression - Zero Length + try { + new RegexValidator(""); + fail("Single Zero Length - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Single Zero Length", "Regular expression[0] is missing", e.getMessage()); + } + + // Multiple Regular Expression - Null array + try { + new RegexValidator((String[])null); + fail("Null Array - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Null Array", "Regular expressions are missing", e.getMessage()); + } + + // Multiple Regular Expression - Zero Length array + try { + new RegexValidator(new String[0]); + fail("Zero Length Array - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Zero Length Array", "Regular expressions are missing", e.getMessage()); + } + + // Multiple Regular Expression - Array has Null + String[] expressions = new String[] {"ABC", null}; + try { + new RegexValidator(expressions); + fail("Array has Null - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Array has Null", "Regular expression[1] is missing", e.getMessage()); + } + + // Multiple Regular Expression - Array has Zero Length + expressions = new String[] {"", "ABC"}; + try { + new RegexValidator(expressions); + fail("Array has Zero Length - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Array has Zero Length", "Regular expression[0] is missing", e.getMessage()); + } + } + + /** + * Test exceptions + */ + public void testExceptions() { + String invalidRegex = "^([abCD12]*$"; + try { + new RegexValidator(invalidRegex); + } catch (PatternSyntaxException e) { + // expected + } + } + + /** + * Test toString() method + */ + public void testToString() { + RegexValidator single = new RegexValidator(REGEX); + assertEquals("Single", "RegexValidator{" + REGEX + "}", single.toString()); + + RegexValidator multiple = new RegexValidator(new String[] {REGEX, REGEX}); + assertEquals("Multiple", "RegexValidator{" + REGEX + "," + REGEX + "}", multiple.toString()); + } + + /** + * Compare two arrays + * @param label Label for the test + * @param expect Expected array + * @param result Actual array + */ + private void checkArray(String label, String[] expect, String[] result) { + + // Handle nulls + if (expect == null || result == null) { + if (expect == null && result == null) { + return; // valid, both null + } else { + fail(label + " Null expect=" + expect + " result=" + result); + } + return; // not strictly necessary, but prevents possible NPE below + } + + // Check Length + if (expect.length != result.length) { + fail(label + " Length expect=" + expect.length + " result=" + result.length); + } + + // Check Values + for (int i = 0; i < expect.length; i++) { + assertEquals(label +" value[" + i + "]", expect[i], result[i]); + } + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ShortValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ShortValidatorTest.java new file mode 100644 index 000000000..937230001 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ShortValidatorTest.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import java.util.Locale; + +/** + * Test Case for ShortValidator. + * + * @version $Revision$ + */ +public class ShortValidatorTest extends AbstractNumberValidatorTest { + + /** + * Constructor + * @param name test name + */ + public ShortValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validator = new ShortValidator(false, 0); + strictValidator = new ShortValidator(); + + testPattern = "#,###"; + + // testValidateMinMax() + max = Short.valueOf(Short.MAX_VALUE); + maxPlusOne = Long.valueOf(max.longValue() + 1); + min = Short.valueOf(Short.MIN_VALUE); + minMinusOne = Long.valueOf(min.longValue() - 1); + + // testInvalidStrict() + invalidStrict = new String[] {null, "", "X", "X12", "12X", "1X2", "1.2"}; + + // testInvalidNotStrict() + invalid = new String[] {null, "", "X", "X12"}; + + // testValid() + testNumber = Short.valueOf((short)1234); + testZero = Short.valueOf((short)0); + validStrict = new String[] {"0", "1234", "1,234"}; + validStrictCompare = new Number[] {testZero, testNumber, testNumber}; + valid = new String[] {"0", "1234", "1,234", "1,234.5", "1234X"}; + validCompare = new Number[] {testZero, testNumber, testNumber, testNumber, testNumber}; + + testStringUS = "1,234"; + testStringDE = "1.234"; + + // Localized Pattern test + localeValue = testStringDE; + localePattern = "#.###"; + testLocale = Locale.GERMANY; + localeExpected = testNumber; + + } + + /** + * Test ShortValidator validate Methods + */ + public void testShortValidatorMethods() { + Locale locale = Locale.GERMAN; + String pattern = "0,00,00"; + String patternVal = "1,23,45"; + String germanPatternVal = "1.23.45"; + String localeVal = "12.345"; + String defaultVal = "12,345"; + String XXXX = "XXXX"; + Short expected = Short.valueOf((short)12345); + assertEquals("validate(A) default", expected, ShortValidator.getInstance().validate(defaultVal)); + assertEquals("validate(A) locale ", expected, ShortValidator.getInstance().validate(localeVal, locale)); + assertEquals("validate(A) pattern", expected, ShortValidator.getInstance().validate(patternVal, pattern)); + assertEquals("validate(A) both", expected, ShortValidator.getInstance().validate(germanPatternVal, pattern, Locale.GERMAN)); + + assertTrue("isValid(A) default", ShortValidator.getInstance().isValid(defaultVal)); + assertTrue("isValid(A) locale ", ShortValidator.getInstance().isValid(localeVal, locale)); + assertTrue("isValid(A) pattern", ShortValidator.getInstance().isValid(patternVal, pattern)); + assertTrue("isValid(A) both", ShortValidator.getInstance().isValid(germanPatternVal, pattern, Locale.GERMAN)); + + assertNull("validate(B) default", ShortValidator.getInstance().validate(XXXX)); + assertNull("validate(B) locale ", ShortValidator.getInstance().validate(XXXX, locale)); + assertNull("validate(B) pattern", ShortValidator.getInstance().validate(XXXX, pattern)); + assertNull("validate(B) both", ShortValidator.getInstance().validate(patternVal, pattern, Locale.GERMAN)); + + assertFalse("isValid(B) default", ShortValidator.getInstance().isValid(XXXX)); + assertFalse("isValid(B) locale ", ShortValidator.getInstance().isValid(XXXX, locale)); + assertFalse("isValid(B) pattern", ShortValidator.getInstance().isValid(XXXX, pattern)); + assertFalse("isValid(B) both", ShortValidator.getInstance().isValid(patternVal, pattern, Locale.GERMAN)); + } + + /** + * Test Short Range/Min/Max + */ + public void testShortRangeMinMax() { + ShortValidator validator = (ShortValidator)strictValidator; + Short number9 = validator.validate("9", "#"); + Short number10 = validator.validate("10", "#"); + Short number11 = validator.validate("11", "#"); + Short number19 = validator.validate("19", "#"); + Short number20 = validator.validate("20", "#"); + Short number21 = validator.validate("21", "#"); + short min = (short)10; + short max = (short)20; + + // Test isInRange() + assertFalse("isInRange() < min", validator.isInRange(number9, min, max)); + assertTrue("isInRange() = min", validator.isInRange(number10, min, max)); + assertTrue("isInRange() in range", validator.isInRange(number11, min, max)); + assertTrue("isInRange() = max", validator.isInRange(number20, min, max)); + assertFalse("isInRange() > max", validator.isInRange(number21, min, max)); + + // Test minValue() + assertFalse("minValue() < min", validator.minValue(number9, min)); + assertTrue("minValue() = min", validator.minValue(number10, min)); + assertTrue("minValue() > min", validator.minValue(number11, min)); + + // Test minValue() + assertTrue("maxValue() < max", validator.maxValue(number19, max)); + assertTrue("maxValue() = max", validator.maxValue(number20, max)); + assertFalse("maxValue() > max", validator.maxValue(number21, max)); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/TimeValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/TimeValidatorTest.java new file mode 100644 index 000000000..aabc942f2 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/TimeValidatorTest.java @@ -0,0 +1,342 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import junit.framework.TestCase; + +import java.util.Date; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Test Case for TimeValidator. + * + * @version $Revision$ + */ +public class TimeValidatorTest extends TestCase { + + protected static final TimeZone GMT = TimeZone.getTimeZone("GMT"); // 0 offset + protected static final TimeZone EST = TimeZone.getTimeZone("EST"); // - 5 hours + + protected TimeValidator validator; + + protected String[] patternValid = new String[] { + "23-59-59" + ,"00-00-00" + ,"00-00-01" + ,"0-0-0" + ,"1-12-1" + ,"10-49-18" + ,"16-23-46"}; + protected Date[] patternExpect = new Date[] { + createDate(null, 235959, 0) + ,createDate(null, 0, 0) + ,createDate(null, 1, 0) + ,createDate(null, 0, 0) + ,createDate(null, 11201, 0) + ,createDate(null, 104918, 0) + ,createDate(null, 162346, 0)}; + protected String[] localeValid = new String[] { + "23:59" + ,"00:00" + ,"00:01" + ,"0:0" + ,"1:12" + ,"10:49" + ,"16:23"}; + protected Date[] localeExpect = new Date[] { + createDate(null, 235900, 0) + ,createDate(null, 0, 0) + ,createDate(null, 100, 0) + ,createDate(null, 0, 0) + ,createDate(null, 11200, 0) + ,createDate(null, 104900, 0) + ,createDate(null, 162300, 0)}; + protected String[] patternInvalid = new String[] { + "24-00-00" // midnight + ,"24-00-01" // past midnight + ,"25-02-03" // invalid hour + ,"10-61-31" // invalid minute + ,"10-01-61" // invalid second + ,"05:02-29" // invalid sep + ,"0X-01:01" // invalid sep + ,"05-0X-01" // invalid char + ,"10-01-0X" // invalid char + ,"01:01:05" // invalid pattern + ,"10-10" // invalid pattern + ,"10--10" // invalid pattern + ,"10-10-"}; // invalid pattern + protected String[] localeInvalid = new String[] { + "24:00" // midnight + ,"24:00" // past midnight + ,"25:02" // invalid hour + ,"10:61" // invalid minute + ,"05-02" // invalid sep + ,"0X:01" // invalid sep + ,"05:0X" // invalid char + ,"01-01" // invalid pattern + ,"10:" // invalid pattern + ,"10::1" // invalid pattern + ,"10:1:"}; // invalid pattern + + private Locale origDefault; + private TimeZone defaultZone; + + /** + * Constructor + * @param name test name + */ + public TimeValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + validator = new TimeValidator(); + defaultZone = TimeZone.getDefault(); + origDefault = Locale.getDefault(); + } + + /** + * Tear down + * @throws Exception + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + validator = null; + Locale.setDefault(origDefault); + TimeZone.setDefault(defaultZone); + } + + /** + * Test Valid Dates with "pattern" validation + */ + public void testPatternValid() { + for (int i = 0; i < patternValid.length; i++) { + String text = i + " value=[" +patternValid[i]+"] failed "; + Calendar calendar = validator.validate(patternValid[i], "HH-mm-ss"); + assertNotNull("validateObj() " + text, calendar); + Date date = calendar.getTime(); + assertTrue("isValid() " + text, validator.isValid(patternValid[i], "HH-mm-ss")); + assertEquals("compare " + text, patternExpect[i], date); + } + } + + /** + * Test Invalid Dates with "pattern" validation + */ + public void testPatternInvalid() { + for (int i = 0; i < patternInvalid.length; i++) { + String text = i + " value=[" +patternInvalid[i]+"] passed "; + Object date = validator.validate(patternInvalid[i], "HH-mm-ss"); + assertNull("validate() " + text + date, date); + assertFalse("isValid() " + text, validator.isValid(patternInvalid[i], "HH-mm-ss")); + } + } + + /** + * Test Valid Dates with "locale" validation + */ + public void testLocaleValid() { + for (int i = 0; i < localeValid.length; i++) { + String text = i + " value=[" +localeValid[i]+"] failed "; + Calendar calendar = validator.validate(localeValid[i], Locale.UK); + assertNotNull("validate() " + text, calendar); + Date date = calendar.getTime(); + assertTrue("isValid() " + text, validator.isValid(localeValid[i], Locale.UK)); + assertEquals("compare " + text, localeExpect[i], date); + } + } + + /** + * Test Invalid Dates with "locale" validation + */ + public void testLocaleInvalid() { + for (int i = 0; i < localeInvalid.length; i++) { + String text = i + " value=[" +localeInvalid[i]+"] passed "; + Object date = validator.validate(localeInvalid[i], Locale.US); + assertNull("validate() " + text + date, date); + assertFalse("isValid() " + text, validator.isValid(localeInvalid[i], Locale.UK)); + } + } + + /** + * Test time zone methods. + */ + public void testTimeZone() { + // Set the default Locale & TimeZone + Locale.setDefault(Locale.UK); + TimeZone.setDefault(GMT); + + Calendar result = null; + + // Default Locale, Default TimeZone + result = validator.validate("18:01"); + assertNotNull("default result", result); + assertEquals("default zone", GMT, result.getTimeZone()); + assertEquals("default hour", 18, result.get(Calendar.HOUR_OF_DAY)); + assertEquals("default minute", 01, result.get(Calendar.MINUTE)); + result = null; + + // Default Locale, diff TimeZone + result = validator.validate("16:49", EST); + assertNotNull("zone result", result); + assertEquals("zone zone", EST, result.getTimeZone()); + assertEquals("zone hour", 16, result.get(Calendar.HOUR_OF_DAY)); + assertEquals("zone minute", 49, result.get(Calendar.MINUTE)); + result = null; + + // Pattern, diff TimeZone + result = validator.validate("14-34", "HH-mm", EST); + assertNotNull("pattern result", result); + assertEquals("pattern zone", EST, result.getTimeZone()); + assertEquals("pattern hour", 14, result.get(Calendar.HOUR_OF_DAY)); + assertEquals("pattern minute", 34, result.get(Calendar.MINUTE)); + result = null; + + // Locale, diff TimeZone + result = validator.validate("7:18 PM", Locale.US, EST); + assertNotNull("locale result", result); + assertEquals("locale zone", EST, result.getTimeZone()); + assertEquals("locale hour", 19, result.get(Calendar.HOUR_OF_DAY)); + assertEquals("locale minute", 18, result.get(Calendar.MINUTE)); + result = null; + + // Locale & Pattern, diff TimeZone + result = validator.validate("31/Dez/05 21-05", "dd/MMM/yy HH-mm", Locale.GERMAN, EST); + assertNotNull("pattern result", result); + assertEquals("pattern zone", EST, result.getTimeZone()); + assertEquals("pattern day", 2005, result.get(Calendar.YEAR)); + assertEquals("pattern day", 11, result.get(Calendar.MONTH)); // months are 0-11 + assertEquals("pattern day", 31, result.get(Calendar.DATE)); + assertEquals("pattern hour", 21, result.get(Calendar.HOUR_OF_DAY)); + assertEquals("pattern minute", 05, result.get(Calendar.MINUTE)); + result = null; + + // Locale & Pattern, default TimeZone + result = validator.validate("31/Dez/05 21-05", "dd/MMM/yy HH-mm", Locale.GERMAN); + assertNotNull("pattern result", result); + assertEquals("pattern zone", GMT, result.getTimeZone()); + assertEquals("pattern day", 2005, result.get(Calendar.YEAR)); + assertEquals("pattern day", 11, result.get(Calendar.MONTH)); // months are 0-11 + assertEquals("pattern day", 31, result.get(Calendar.DATE)); + assertEquals("pattern hour", 21, result.get(Calendar.HOUR_OF_DAY)); + assertEquals("pattern minute", 05, result.get(Calendar.MINUTE)); + result = null; + + } + + /** + * Test Invalid Dates with "locale" validation + */ + public void testFormat() { + // Set the default Locale + Locale.setDefault(Locale.UK); + + Object test = TimeValidator.getInstance().validate("16:49:23", "HH:mm:ss"); + assertNotNull("Test Date ", test); + assertEquals("Format pattern", "16-49-23", validator.format(test, "HH-mm-ss")); + assertEquals("Format locale", "4:49 PM", validator.format(test, Locale.US)); + assertEquals("Format default", "16:49", validator.format(test)); + + } + + /** + * Test compare date methods + */ + public void testCompare() { + int testTime = 154523; + int min = 100; + int hour = 10000; + + Calendar milliGreater = createTime(GMT, testTime, 500); // > milli sec + Calendar value = createTime(GMT, testTime, 400); // test value + Calendar milliLess = createTime(GMT, testTime, 300); // < milli sec + + Calendar secGreater = createTime(GMT, testTime + 1, 100); // +1 sec + Calendar secLess = createTime(GMT, testTime - 1, 100); // -1 sec + + Calendar minGreater = createTime(GMT, testTime + min, 100); // +1 min + Calendar minLess = createTime(GMT, testTime - min, 100); // -1 min + + Calendar hourGreater = createTime(GMT, testTime + hour, 100); // +1 hour + Calendar hourLess = createTime(GMT, testTime - hour, 100); // -1 hour + + assertEquals("mili LT", -1, validator.compareTime(value, milliGreater)); // > milli + assertEquals("mili EQ", 0, validator.compareTime(value, value)); // same time + assertEquals("mili GT", 1, validator.compareTime(value, milliLess)); // < milli + + assertEquals("secs LT", -1, validator.compareSeconds(value, secGreater)); // +1 sec + assertEquals("secs =1", 0, validator.compareSeconds(value, milliGreater)); // > milli + assertEquals("secs =2", 0, validator.compareSeconds(value, value)); // same time + assertEquals("secs =3", 0, validator.compareSeconds(value, milliLess)); // < milli + assertEquals("secs GT", 1, validator.compareSeconds(value, secLess)); // -1 sec + + assertEquals("mins LT", -1, validator.compareMinutes(value, minGreater)); // +1 min + assertEquals("mins =1", 0, validator.compareMinutes(value, secGreater)); // +1 sec + assertEquals("mins =2", 0, validator.compareMinutes(value, value)); // same time + assertEquals("mins =3", 0, validator.compareMinutes(value, secLess)); // -1 sec + assertEquals("mins GT", 1, validator.compareMinutes(value, minLess)); // -1 min + + assertEquals("hour LT", -1, validator.compareHours(value, hourGreater)); // +1 hour + assertEquals("hour =1", 0, validator.compareHours(value, minGreater)); // +1 min + assertEquals("hour =2", 0, validator.compareHours(value, value)); // same time + assertEquals("hour =3", 0, validator.compareHours(value, minLess)); // -1 min + assertEquals("hour GT", 1, validator.compareHours(value, hourLess)); // -1 hour + + } + + /** + * Create a calendar instance for a specified time zone, date and time. + * + * @param zone The time zone + * @param time the time in HH:mm:ss format + * @param millisecond the milliseconds + * @return the new Calendar instance. + */ + protected static Calendar createTime(TimeZone zone, int time, int millisecond) { + Calendar calendar = zone == null ? Calendar.getInstance() + : Calendar.getInstance(zone); + int hour = ((time / 10000) * 10000); + int min = ((time / 100) * 100) - hour; + int sec = time - (hour + min); + calendar.set(Calendar.YEAR, 1970); + calendar.set(Calendar.MONTH, 0); + calendar.set(Calendar.DATE, 1); + calendar.set(Calendar.HOUR_OF_DAY, (hour / 10000)); + calendar.set(Calendar.MINUTE, (min / 100)); + calendar.set(Calendar.SECOND, sec); + calendar.set(Calendar.MILLISECOND, millisecond); + return calendar; + } + + /** + * Create a date instance for a specified time zone, date and time. + * + * @param zone The time zone + * @param time the time in HH:mm:ss format + * @param millisecond the milliseconds + * @return the new Date instance. + */ + protected static Date createDate(TimeZone zone, int time, int millisecond) { + Calendar calendar = createTime(zone, time, millisecond); + return calendar.getTime(); + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java new file mode 100644 index 000000000..b34a030be --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java @@ -0,0 +1,618 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines; + +import org.apache.commons.validator.ResultPair; + +import junit.framework.TestCase; + +/** + * Performs Validation Test for url validations. + * + * @version $Revision$ + */ +public class UrlValidatorTest extends TestCase { + + private final boolean printStatus = false; + private final boolean printIndex = false;//print index that indicates current scheme,host,port,path, query test were using. + + public UrlValidatorTest(String testName) { + super(testName); + } + + @Override + protected void setUp() { + for (int index = 0; index < testPartsIndex.length - 1; index++) { + testPartsIndex[index] = 0; + } + } + + public void testIsValid() { + testIsValid(testUrlParts, UrlValidator.ALLOW_ALL_SCHEMES); + setUp(); + long options = + UrlValidator.ALLOW_2_SLASHES + + UrlValidator.ALLOW_ALL_SCHEMES + + UrlValidator.NO_FRAGMENTS; + + testIsValid(testUrlPartsOptions, options); + } + + public void testIsValidScheme() { + if (printStatus) { + System.out.print("\n testIsValidScheme() "); + } + //UrlValidator urlVal = new UrlValidator(schemes,false,false,false); + UrlValidator urlVal = new UrlValidator(schemes, 0); + for (int sIndex = 0; sIndex < testScheme.length; sIndex++) { + ResultPair testPair = testScheme[sIndex]; + boolean result = urlVal.isValidScheme(testPair.item); + assertEquals(testPair.item, testPair.valid, result); + if (printStatus) { + if (result == testPair.valid) { + System.out.print('.'); + } else { + System.out.print('X'); + } + } + } + if (printStatus) { + System.out.println(); + } + + } + + /** + * Create set of tests by taking the testUrlXXX arrays and + * running through all possible permutations of their combinations. + * + * @param testObjects Used to create a url. + */ + public void testIsValid(Object[] testObjects, long options) { + UrlValidator urlVal = new UrlValidator(null, null, options); + assertTrue(urlVal.isValid("http://www.google.com")); + assertTrue(urlVal.isValid("http://www.google.com/")); + int statusPerLine = 60; + int printed = 0; + if (printIndex) { + statusPerLine = 6; + } + do { + StringBuilder testBuffer = new StringBuilder(); + boolean expected = true; + for (int testPartsIndexIndex = 0; testPartsIndexIndex < testPartsIndex.length; ++testPartsIndexIndex) { + int index = testPartsIndex[testPartsIndexIndex]; + ResultPair[] part = (ResultPair[]) testObjects[testPartsIndexIndex]; + testBuffer.append(part[index].item); + expected &= part[index].valid; + } + String url = testBuffer.toString(); + boolean result = urlVal.isValid(url); + assertEquals(url, expected, result); + if (printStatus) { + if (printIndex) { + System.out.print(testPartsIndextoString()); + } else { + if (result == expected) { + System.out.print('.'); + } else { + System.out.print('X'); + } + } + printed++; + if (printed == statusPerLine) { + System.out.println(); + printed = 0; + } + } + } while (incrementTestPartsIndex(testPartsIndex, testObjects)); + if (printStatus) { + System.out.println(); + } + } + + public void testValidator202() { + String[] schemes = {"http","https"}; + UrlValidator urlValidator = new UrlValidator(schemes, UrlValidator.NO_FRAGMENTS); + assertTrue(urlValidator.isValid("http://l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.org")); + } + + public void testValidator204() { + String[] schemes = {"http","https"}; + UrlValidator urlValidator = new UrlValidator(schemes); + assertTrue(urlValidator.isValid("http://tech.yahoo.com/rc/desktops/102;_ylt=Ao8yevQHlZ4On0O3ZJGXLEQFLZA5")); + } + + public void testValidator218() { + UrlValidator validator = new UrlValidator(UrlValidator.ALLOW_2_SLASHES); + assertTrue("parentheses should be valid in URLs", + validator.isValid("http://somewhere.com/pathxyz/file(1).html")); + } + + public void testValidator235() { + String version = System.getProperty("java.version"); + if (version.compareTo("1.6") < 0) { + System.out.println("Cannot run Unicode IDN tests"); + return; // Cannot run the test + } + UrlValidator validator = new UrlValidator(); + assertTrue("xn--d1abbgf6aiiy.xn--p1ai should validate", validator.isValid("http://xn--d1abbgf6aiiy.xn--p1ai")); + assertTrue("президент.рф should validate", validator.isValid("http://президент.рф")); + assertTrue("www.b\u00fccher.ch should validate", validator.isValid("http://www.b\u00fccher.ch")); + assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("http://www.\uFFFD.ch")); + assertTrue("www.b\u00fccher.ch should validate", validator.isValid("ftp://www.b\u00fccher.ch")); + assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("ftp://www.\uFFFD.ch")); + } + + public void testValidator248() { + RegexValidator regex = new RegexValidator(new String[] {"localhost", ".*\\.my-testing"}); + UrlValidator validator = new UrlValidator(regex, 0); + + assertTrue("localhost URL should validate", + validator.isValid("http://localhost/test/index.html")); + assertTrue("first.my-testing should validate", + validator.isValid("http://first.my-testing/test/index.html")); + assertTrue("sup3r.my-testing should validate", + validator.isValid("http://sup3r.my-testing/test/index.html")); + + assertFalse("broke.my-test should not validate", + validator.isValid("http://broke.my-test/test/index.html")); + + assertTrue("www.apache.org should still validate", + validator.isValid("http://www.apache.org/test/index.html")); + + // Now check using options + validator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); + + assertTrue("localhost URL should validate", + validator.isValid("http://localhost/test/index.html")); + + assertTrue("machinename URL should validate", + validator.isValid("http://machinename/test/index.html")); + + assertTrue("www.apache.org should still validate", + validator.isValid("http://www.apache.org/test/index.html")); + } + + public void testValidator288() { + UrlValidator validator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); + + assertTrue("hostname should validate", + validator.isValid("http://hostname")); + + assertTrue("hostname with path should validate", + validator.isValid("http://hostname/test/index.html")); + + assertTrue("localhost URL should validate", + validator.isValid("http://localhost/test/index.html")); + + assertFalse("first.my-testing should not validate", + validator.isValid("http://first.my-testing/test/index.html")); + + assertFalse("broke.hostname should not validate", + validator.isValid("http://broke.hostname/test/index.html")); + + assertTrue("www.apache.org should still validate", + validator.isValid("http://www.apache.org/test/index.html")); + + // Turn it off, and check + validator = new UrlValidator(0); + + assertFalse("hostname should no longer validate", + validator.isValid("http://hostname")); + + assertFalse("localhost URL should no longer validate", + validator.isValid("http://localhost/test/index.html")); + + assertTrue("www.apache.org should still validate", + validator.isValid("http://www.apache.org/test/index.html")); + } + + public void testValidator276() { + // file:// isn't allowed by default + UrlValidator validator = new UrlValidator(); + + assertTrue("http://apache.org/ should be allowed by default", + validator.isValid("http://www.apache.org/test/index.html")); + + assertFalse("file:///c:/ shouldn't be allowed by default", + validator.isValid("file:///C:/some.file")); + + assertFalse("file:///c:\\ shouldn't be allowed by default", + validator.isValid("file:///C:\\some.file")); + + assertFalse("file:///etc/ shouldn't be allowed by default", + validator.isValid("file:///etc/hosts")); + + assertFalse("file://localhost/etc/ shouldn't be allowed by default", + validator.isValid("file://localhost/etc/hosts")); + + assertFalse("file://localhost/c:/ shouldn't be allowed by default", + validator.isValid("file://localhost/c:/some.file")); + + // Turn it on, and check + // Note - we need to enable local urls when working with file: + validator = new UrlValidator(new String[] {"http","file"}, UrlValidator.ALLOW_LOCAL_URLS); + + assertTrue("http://apache.org/ should be allowed by default", + validator.isValid("http://www.apache.org/test/index.html")); + + assertTrue("file:///c:/ should now be allowed", + validator.isValid("file:///C:/some.file")); + + assertTrue("file:///c:\\ should be allowed", + validator.isValid("file:///C:\\some.file")); + + assertTrue("file:///etc/ should now be allowed", + validator.isValid("file:///etc/hosts")); + + assertTrue("file://localhost/etc/ should now be allowed", + validator.isValid("file://localhost/etc/hosts")); + + assertTrue("file://localhost/c:/ should now be allowed", + validator.isValid("file://localhost/c:/some.file")); + + // These are never valid + assertFalse("file://c:/ shouldn't ever be allowed, needs file:///c:/", + validator.isValid("file://C:/some.file")); + + assertFalse("file://c:\\ shouldn't ever be allowed, needs file:///c:/", + validator.isValid("file://C:\\some.file")); + } + + public void testValidator391OK() { + String[] schemes = {"file"}; + UrlValidator urlValidator = new UrlValidator(schemes); + assertTrue(urlValidator.isValid("file:///C:/path/to/dir/")); + } + + public void testValidator391FAILS() { + String[] schemes = {"file"}; + UrlValidator urlValidator = new UrlValidator(schemes); + assertTrue(urlValidator.isValid("file:/C:/path/to/dir/")); + } + + public void testValidator309() { + UrlValidator urlValidator = new UrlValidator(); + assertTrue(urlValidator.isValid("http://sample.ondemand.com/")); + assertTrue(urlValidator.isValid("hTtP://sample.ondemand.CoM/")); + assertTrue(urlValidator.isValid("httpS://SAMPLE.ONEMAND.COM/")); + urlValidator = new UrlValidator(new String[] {"HTTP","HTTPS"}); + assertTrue(urlValidator.isValid("http://sample.ondemand.com/")); + assertTrue(urlValidator.isValid("hTtP://sample.ondemand.CoM/")); + assertTrue(urlValidator.isValid("httpS://SAMPLE.ONEMAND.COM/")); + } + + public void testValidator339(){ + UrlValidator urlValidator = new UrlValidator(); + assertTrue(urlValidator.isValid("http://www.cnn.com/WORLD/?hpt=sitenav")); // without + assertTrue(urlValidator.isValid("http://www.cnn.com./WORLD/?hpt=sitenav")); // with + assertFalse(urlValidator.isValid("http://www.cnn.com../")); // doubly dotty + assertFalse(urlValidator.isValid("http://www.cnn.invalid/")); + assertFalse(urlValidator.isValid("http://www.cnn.invalid./")); // check . does not affect invalid domains + } + + public void testValidator339IDN(){ + UrlValidator urlValidator = new UrlValidator(); + assertTrue(urlValidator.isValid("http://президент.рф/WORLD/?hpt=sitenav")); // without + assertTrue(urlValidator.isValid("http://президент.рф./WORLD/?hpt=sitenav")); // with + assertFalse(urlValidator.isValid("http://президент.рф..../")); // very dotty + assertFalse(urlValidator.isValid("http://президент.рф.../")); // triply dotty + assertFalse(urlValidator.isValid("http://президент.рф../")); // doubly dotty + } + + public void testValidator342(){ + UrlValidator urlValidator = new UrlValidator(); + assertTrue(urlValidator.isValid("http://example.rocks/")); + assertTrue(urlValidator.isValid("http://example.rocks")); + } + + public void testValidator411(){ + UrlValidator urlValidator = new UrlValidator(); + assertTrue(urlValidator.isValid("http://example.rocks:/")); + assertTrue(urlValidator.isValid("http://example.rocks:0/")); + assertTrue(urlValidator.isValid("http://example.rocks:65535/")); + assertFalse(urlValidator.isValid("http://example.rocks:65536/")); + assertFalse(urlValidator.isValid("http://example.rocks:100000/")); + } + + public void testValidator464() { + String[] schemes = {"file"}; + UrlValidator urlValidator = new UrlValidator(schemes); + String fileOK = "file:///bad ^ domain.com/label/test"; + String fileNAK = "file://bad ^ domain.com/label/test"; + assertTrue(fileOK, urlValidator.isValid(fileOK)); + assertFalse(fileNAK, urlValidator.isValid(fileNAK)); + } + + static boolean incrementTestPartsIndex(int[] testPartsIndex, Object[] testParts) { + boolean carry = true; //add 1 to lowest order part. + boolean maxIndex = true; + for (int testPartsIndexIndex = testPartsIndex.length - 1; testPartsIndexIndex >= 0; --testPartsIndexIndex) { + int index = testPartsIndex[testPartsIndexIndex]; + ResultPair[] part = (ResultPair[]) testParts[testPartsIndexIndex]; + maxIndex &= (index == (part.length - 1)); + if (carry) { + if (index < part.length - 1) { + index++; + testPartsIndex[testPartsIndexIndex] = index; + carry = false; + } else { + testPartsIndex[testPartsIndexIndex] = 0; + carry = true; + } + } + } + + + return (!maxIndex); + } + + private String testPartsIndextoString() { + StringBuilder carryMsg = new StringBuilder("{"); + for (int testPartsIndexIndex = 0; testPartsIndexIndex < testPartsIndex.length; ++testPartsIndexIndex) { + carryMsg.append(testPartsIndex[testPartsIndexIndex]); + if (testPartsIndexIndex < testPartsIndex.length - 1) { + carryMsg.append(','); + } else { + carryMsg.append('}'); + } + } + return carryMsg.toString(); + + } + + public void testValidateUrl() { + assertTrue(true); + } + + public void testValidator290() { + UrlValidator validator = new UrlValidator(); + assertTrue(validator.isValid("http://xn--h1acbxfam.idn.icann.org/")); +// assertTrue(validator.isValid("http://xn--e1afmkfd.xn--80akhbyknj4f")); + // Internationalized country code top-level domains + assertTrue(validator.isValid("http://test.xn--lgbbat1ad8j")); //Algeria + assertTrue(validator.isValid("http://test.xn--fiqs8s")); // China + assertTrue(validator.isValid("http://test.xn--fiqz9s")); // China + assertTrue(validator.isValid("http://test.xn--wgbh1c")); // Egypt + assertTrue(validator.isValid("http://test.xn--j6w193g")); // Hong Kong + assertTrue(validator.isValid("http://test.xn--h2brj9c")); // India + assertTrue(validator.isValid("http://test.xn--mgbbh1a71e")); // India + assertTrue(validator.isValid("http://test.xn--fpcrj9c3d")); // India + assertTrue(validator.isValid("http://test.xn--gecrj9c")); // India + assertTrue(validator.isValid("http://test.xn--s9brj9c")); // India + assertTrue(validator.isValid("http://test.xn--xkc2dl3a5ee0h")); // India + assertTrue(validator.isValid("http://test.xn--45brj9c")); // India + assertTrue(validator.isValid("http://test.xn--mgba3a4f16a")); // Iran + assertTrue(validator.isValid("http://test.xn--mgbayh7gpa")); // Jordan + assertTrue(validator.isValid("http://test.xn--mgbc0a9azcg")); // Morocco + assertTrue(validator.isValid("http://test.xn--ygbi2ammx")); // Palestinian Territory + assertTrue(validator.isValid("http://test.xn--wgbl6a")); // Qatar + assertTrue(validator.isValid("http://test.xn--p1ai")); // Russia + assertTrue(validator.isValid("http://test.xn--mgberp4a5d4ar")); // Saudi Arabia + assertTrue(validator.isValid("http://test.xn--90a3ac")); // Serbia + assertTrue(validator.isValid("http://test.xn--yfro4i67o")); // Singapore + assertTrue(validator.isValid("http://test.xn--clchc0ea0b2g2a9gcd")); // Singapore + assertTrue(validator.isValid("http://test.xn--3e0b707e")); // South Korea + assertTrue(validator.isValid("http://test.xn--fzc2c9e2c")); // Sri Lanka + assertTrue(validator.isValid("http://test.xn--xkc2al3hye2a")); // Sri Lanka + assertTrue(validator.isValid("http://test.xn--ogbpf8fl")); // Syria + assertTrue(validator.isValid("http://test.xn--kprw13d")); // Taiwan + assertTrue(validator.isValid("http://test.xn--kpry57d")); // Taiwan + assertTrue(validator.isValid("http://test.xn--o3cw4h")); // Thailand + assertTrue(validator.isValid("http://test.xn--pgbs0dh")); // Tunisia + assertTrue(validator.isValid("http://test.xn--mgbaam7a8h")); // United Arab Emirates + // Proposed internationalized ccTLDs +// assertTrue(validator.isValid("http://test.xn--54b7fta0cc")); // Bangladesh +// assertTrue(validator.isValid("http://test.xn--90ae")); // Bulgaria +// assertTrue(validator.isValid("http://test.xn--node")); // Georgia +// assertTrue(validator.isValid("http://test.xn--4dbrk0ce")); // Israel +// assertTrue(validator.isValid("http://test.xn--mgb9awbf")); // Oman +// assertTrue(validator.isValid("http://test.xn--j1amh")); // Ukraine +// assertTrue(validator.isValid("http://test.xn--mgb2ddes")); // Yemen + // Test TLDs +// assertTrue(validator.isValid("http://test.xn--kgbechtv")); // Arabic +// assertTrue(validator.isValid("http://test.xn--hgbk6aj7f53bba")); // Persian +// assertTrue(validator.isValid("http://test.xn--0zwm56d")); // Chinese +// assertTrue(validator.isValid("http://test.xn--g6w251d")); // Chinese +// assertTrue(validator.isValid("http://test.xn--80akhbyknj4f")); // Russian +// assertTrue(validator.isValid("http://test.xn--11b5bs3a9aj6g")); // Hindi +// assertTrue(validator.isValid("http://test.xn--jxalpdlp")); // Greek +// assertTrue(validator.isValid("http://test.xn--9t4b11yi5a")); // Korean +// assertTrue(validator.isValid("http://test.xn--deba0ad")); // Yiddish +// assertTrue(validator.isValid("http://test.xn--zckzah")); // Japanese +// assertTrue(validator.isValid("http://test.xn--hlcj6aya9esc7a")); // Tamil + } + + public void testValidator361() { + UrlValidator validator = new UrlValidator(); + assertTrue(validator.isValid("http://hello.tokyo/")); + } + + public void testValidator363(){ + UrlValidator urlValidator = new UrlValidator(); + assertTrue(urlValidator.isValid("http://www.example.org/a/b/hello..world")); + assertTrue(urlValidator.isValid("http://www.example.org/a/hello..world")); + assertTrue(urlValidator.isValid("http://www.example.org/hello.world/")); + assertTrue(urlValidator.isValid("http://www.example.org/hello..world/")); + assertTrue(urlValidator.isValid("http://www.example.org/hello.world")); + assertTrue(urlValidator.isValid("http://www.example.org/hello..world")); + assertTrue(urlValidator.isValid("http://www.example.org/..world")); + assertTrue(urlValidator.isValid("http://www.example.org/.../world")); + assertFalse(urlValidator.isValid("http://www.example.org/../world")); + assertFalse(urlValidator.isValid("http://www.example.org/..")); + assertFalse(urlValidator.isValid("http://www.example.org/../")); + assertFalse(urlValidator.isValid("http://www.example.org/./..")); + assertFalse(urlValidator.isValid("http://www.example.org/././..")); + assertTrue(urlValidator.isValid("http://www.example.org/...")); + assertTrue(urlValidator.isValid("http://www.example.org/.../")); + assertTrue(urlValidator.isValid("http://www.example.org/.../..")); + } + + public void testValidator375() { + UrlValidator validator = new UrlValidator(); + String url = "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html"; + assertTrue("IPv6 address URL should validate: " + url, validator.isValid(url)); + url = "http://[::1]:80/index.html"; + assertTrue("IPv6 address URL should validate: " + url, validator.isValid(url)); + url = "http://FEDC:BA98:7654:3210:FEDC:BA98:7654:3210:80/index.html"; + assertFalse("IPv6 address without [] should not validate: " + url, validator.isValid(url)); + } + + + public void testValidator353() { // userinfo + UrlValidator validator = new UrlValidator(); + assertTrue(validator.isValid("http://www.apache.org:80/path")); + assertTrue(validator.isValid("http://user:pass@www.apache.org:80/path")); + assertTrue(validator.isValid("http://user:@www.apache.org:80/path")); + assertTrue(validator.isValid("http://user@www.apache.org:80/path")); + assertTrue(validator.isValid("http://us%00er:-._~!$&'()*+,;=@www.apache.org:80/path")); + assertFalse(validator.isValid("http://:pass@www.apache.org:80/path")); + assertFalse(validator.isValid("http://:@www.apache.org:80/path")); + assertFalse(validator.isValid("http://user:pa:ss@www.apache.org/path")); + assertFalse(validator.isValid("http://user:pa@ss@www.apache.org/path")); + } + + public void testValidator382() { + UrlValidator validator = new UrlValidator(); + assertTrue(validator.isValid("ftp://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose")); + } + + public void testValidator380() { + UrlValidator validator = new UrlValidator(); + assertTrue(validator.isValid("http://www.apache.org:80/path")); + assertTrue(validator.isValid("http://www.apache.org:8/path")); + assertTrue(validator.isValid("http://www.apache.org:/path")); + } + + public void testValidator420() { + UrlValidator validator = new UrlValidator(); + assertFalse(validator.isValid("http://example.com/serach?address=Main Avenue")); + assertTrue(validator.isValid("http://example.com/serach?address=Main%20Avenue")); + assertTrue(validator.isValid("http://example.com/serach?address=Main+Avenue")); + } + + public void testValidator467() { + UrlValidator validator = new UrlValidator(UrlValidator.ALLOW_2_SLASHES); + assertTrue(validator.isValid("https://example.com/some_path/path/")); + assertTrue(validator.isValid("https://example.com//somepath/path/")); + assertTrue(validator.isValid("https://example.com//some_path/path/")); + } + + //-------------------- Test data for creating a composite URL + /** + * The data given below approximates the 4 parts of a URL + * ://? except that the port number + * is broken out of authority to increase the number of permutations. + * A complete URL is composed of a scheme+authority+port+path+query, + * all of which must be individually valid for the entire URL to be considered + * valid. + */ + ResultPair[] testUrlScheme = {new ResultPair("http://", true), + new ResultPair("ftp://", true), + new ResultPair("h3t://", true), + new ResultPair("3ht://", false), + new ResultPair("http:/", false), + new ResultPair("http:", false), + new ResultPair("http/", false), + new ResultPair("://", false)}; + + ResultPair[] testUrlAuthority = {new ResultPair("www.google.com", true), + new ResultPair("www.google.com.", true), + new ResultPair("go.com", true), + new ResultPair("go.au", true), + new ResultPair("0.0.0.0", true), + new ResultPair("255.255.255.255", true), + new ResultPair("256.256.256.256", false), + new ResultPair("255.com", true), + new ResultPair("1.2.3.4.5", false), + new ResultPair("1.2.3.4.", false), + new ResultPair("1.2.3", false), + new ResultPair(".1.2.3.4", false), + new ResultPair("go.a", false), + new ResultPair("go.a1a", false), + new ResultPair("go.cc", true), + new ResultPair("go.1aa", false), + new ResultPair("aaa.", false), + new ResultPair(".aaa", false), + new ResultPair("aaa", false), + new ResultPair("", false) + }; + ResultPair[] testUrlPort = {new ResultPair(":80", true), + new ResultPair(":65535", true), // max possible + new ResultPair(":65536", false), // max possible +1 + new ResultPair(":0", true), + new ResultPair("", true), + new ResultPair(":-1", false), + new ResultPair(":65636", false), + new ResultPair(":999999999999999999", false), + new ResultPair(":65a", false) + }; + ResultPair[] testPath = {new ResultPair("/test1", true), + new ResultPair("/t123", true), + new ResultPair("/$23", true), + new ResultPair("/..", false), + new ResultPair("/../", false), + new ResultPair("/test1/", true), + new ResultPair("", true), + new ResultPair("/test1/file", true), + new ResultPair("/..//file", false), + new ResultPair("/test1//file", false) + }; + //Test allow2slash, noFragment + ResultPair[] testUrlPathOptions = {new ResultPair("/test1", true), + new ResultPair("/t123", true), + new ResultPair("/$23", true), + new ResultPair("/..", false), + new ResultPair("/../", false), + new ResultPair("/test1/", true), + new ResultPair("/#", false), + new ResultPair("", true), + new ResultPair("/test1/file", true), + new ResultPair("/t123/file", true), + new ResultPair("/$23/file", true), + new ResultPair("/../file", false), + new ResultPair("/..//file", false), + new ResultPair("/test1//file", true), + new ResultPair("/#/file", false) + }; + + ResultPair[] testUrlQuery = {new ResultPair("?action=view", true), + new ResultPair("?action=edit&mode=up", true), + new ResultPair("", true) + }; + + Object[] testUrlParts = {testUrlScheme, testUrlAuthority, testUrlPort, testPath, testUrlQuery}; + Object[] testUrlPartsOptions = {testUrlScheme, testUrlAuthority, testUrlPort, testUrlPathOptions, testUrlQuery}; + int[] testPartsIndex = {0, 0, 0, 0, 0}; + + //---------------- Test data for individual url parts ---------------- + private final String[] schemes = {"http", "gopher", "g0-To+.", + "not_valid" // TODO this will need to be dropped if the ctor validates schemes + }; + + ResultPair[] testScheme = {new ResultPair("http", true), + new ResultPair("ftp", false), + new ResultPair("httpd", false), + new ResultPair("gopher", true), + new ResultPair("g0-to+.", true), + new ResultPair("not_valid", false), // underscore not allowed + new ResultPair("HtTp", true), + new ResultPair("telnet", false)}; + + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ABANumberCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ABANumberCheckDigitTest.java new file mode 100644 index 000000000..18793dd2b --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ABANumberCheckDigitTest.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.validator.routines.checkdigit; + + +/** + * ABA Number Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class ABANumberCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public ABANumberCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = ABANumberCheckDigit.ABAN_CHECK_DIGIT; + valid = new String[] { + "123456780", + "123123123", + "011000015", + "111000038", + "231381116", + "121181976" + }; + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/AbstractCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/AbstractCheckDigitTest.java new file mode 100644 index 000000000..c903d371e --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/AbstractCheckDigitTest.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.validator.routines.checkdigit; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; +import java.util.ArrayList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import junit.framework.TestCase; + +/** + * Luhn Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public abstract class AbstractCheckDigitTest extends TestCase { + + /** logging instance */ + protected Log log = LogFactory.getLog(getClass()); + + /** Check digit routine being tested */ + protected int checkDigitLth = 1; + + /** Check digit routine being tested */ + protected CheckDigit routine; + + /** + * Array of valid code values + * These must contain valid strings *including* the check digit. + * + * They are passed to: + * CheckDigit.isValid(expects string including checkdigit) + * which is expected to return true + * and + * AbstractCheckDigitTest.createInvalidCodes() which + * mangles the last character to check that the result is now invalid. + * and + * the truncated string is passed to + * CheckDigit.calculate(expects string without checkdigit) + * the result is compared with the last character + */ + protected String[] valid; + + /** + * Array of invalid code values + * + * These are currently passed to both + * CheckDigit.calculate(expects a string without checkdigit) + * which is expected to throw an exception + * However that only applies if the string is syntactically incorrect; + * and + * CheckDigit.isValid(expects a string including checkdigit) + * which is expected to return false + * + * See https://issues.apache.org/jira/browse/VALIDATOR-344 for some dicussion on this + */ + protected String[] invalid = new String[] {"12345678A"}; + + /** code value which sums to zero */ + protected String zeroSum = "0000000000"; + + /** Prefix for error messages */ + protected String missingMessage = "Code is missing"; + + /** + * Constructor + * @param name test name + */ + public AbstractCheckDigitTest(String name) { + super(name); + } + + /** + * Tear Down - clears routine and valid codes. + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + valid = null; + routine = null; + } + + /** + * Test isValid() for valid values. + */ + public void testIsValidTrue() { + if (log.isDebugEnabled()) { + log.debug("testIsValidTrue() for " + routine.getClass().getName()); + } + + // test valid values + for (int i = 0; i < valid.length; i++) { + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Valid Code=[" + valid[i] + "]"); + } + assertTrue("valid[" + i +"]: " + valid[i], routine.isValid(valid[i])); + } + } + + /** + * Test isValid() for invalid values. + */ + public void testIsValidFalse() { + if (log.isDebugEnabled()) { + log.debug("testIsValidFalse() for " + routine.getClass().getName()); + } + + // test invalid code values + for (int i = 0; i < invalid.length; i++) { + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Invalid Code=[" + invalid[i] + "]"); + } + assertFalse("invalid[" + i +"]: " + invalid[i], routine.isValid(invalid[i])); + } + + // test invalid check digit values + String[] invalidCheckDigits = createInvalidCodes(valid); + for (int i = 0; i < invalidCheckDigits.length; i++) { + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Invalid Check Digit, Code=[" + invalidCheckDigits[i] + "]"); + } + assertFalse("invalid check digit[" + i +"]: " + invalidCheckDigits[i], routine.isValid(invalidCheckDigits[i])); + } + } + + /** + * Test calculate() for valid values. + */ + public void testCalculateValid() { + if (log.isDebugEnabled()) { + log.debug("testCalculateValid() for " + routine.getClass().getName()); + } + + // test valid values + for (int i = 0; i < valid.length; i++) { + String code = removeCheckDigit(valid[i]); + String expected = checkDigit(valid[i]); + try { + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Valid Check Digit, Code=[" + code + "] expected=[" + expected + "]"); + } + assertEquals("valid[" + i +"]: " + valid[i], expected, routine.calculate(code)); + } catch (Exception e) { + fail("valid[" + i +"]=" + valid[i] + " threw " + e); + } + } + + } + + /** + * Test calculate() for invalid values. + */ + public void testCalculateInvalid() { + + if (log.isDebugEnabled()) { + log.debug("testCalculateInvalid() for " + routine.getClass().getName()); + } + + // test invalid code values + for (int i = 0; i < invalid.length; i++) { + try { + final String code = invalid[i]; + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Invalid Check Digit, Code=[" + code + "]"); + } + String expected = checkDigit(code); + String actual = routine.calculate(removeCheckDigit(code)); + // If exception not thrown, check that the digit is incorrect instead + if (expected.equals(actual)) { + fail("Expected mismatch for " + code + " expected " + expected + " actual " + actual); + } + } catch (CheckDigitException e) { + // possible failure messages: + // Invalid ISBN Length ... + // Invalid Character[ ... + // Are there any others? + assertTrue("Invalid Character[" +i +"]=" + e.getMessage(), e.getMessage().startsWith("Invalid ")); +// WAS assertTrue("Invalid Character[" +i +"]=" + e.getMessage(), e.getMessage().startsWith("Invalid Character[")); + } + } + } + + /** + * Test missing code + */ + public void testMissingCode() { + + // isValid() null + assertFalse("isValid() Null", routine.isValid(null)); + + // isValid() zero length + assertFalse("isValid() Zero Length", routine.isValid("")); + + // isValid() length 1 + // Don't use 0, because that passes for Verhoef (not sure why yet) + assertFalse("isValid() Length 1", routine.isValid("9")); + + // calculate() null + try { + routine.calculate(null); + fail("calculate() Null - expected exception"); + } catch (Exception e) { + assertEquals("calculate() Null", missingMessage, e.getMessage()); + } + + // calculate() zero length + try { + routine.calculate(""); + fail("calculate() Zero Length - expected exception"); + } catch (Exception e) { + assertEquals("calculate() Zero Length", missingMessage, e.getMessage()); + } + } + + /** + * Test zero sum + */ + public void testZeroSum() { + + assertFalse("isValid() Zero Sum", routine.isValid(zeroSum)); + + try { + routine.calculate(zeroSum); + fail("Zero Sum - expected exception"); + } catch (Exception e) { + assertEquals("isValid() Zero Sum", "Invalid code, sum is zero", e.getMessage()); + } + + } + + /** + * Test check digit serialization. + */ + public void testSerialization() { + // Serialize the check digit routine + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(routine); + oos.flush(); + oos.close(); + } catch (Exception e) { + fail(routine.getClass().getName() + " error during serialization: " + e); + } + + // Deserialize the test object + Object result = null; + try { + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + result = ois.readObject(); + bais.close(); + } catch (Exception e) { + fail(routine.getClass().getName() + " error during deserialization: " + e); + } + assertNotNull(result); + } + + private static final String POSSIBLE_CHECK_DIGITS = "0123456789 ABCDEFHIJKLMNOPQRSTUVWXYZ\tabcdefghijklmnopqrstuvwxyz!@£$%^&*()_+"; +// private static final String POSSIBLE_CHECK_DIGITS = "0123456789"; + /** + * Returns an array of codes with invalid check digits. + * + * @param codes Codes with valid check digits + * @return Codes with invalid check digits + */ + protected String[] createInvalidCodes(String[] codes) { + List list = new ArrayList(); + + // create invalid check digit values + for (String fullCode : codes) { + String code = removeCheckDigit(fullCode); + String check = checkDigit(fullCode); + for (int j = 0; j < POSSIBLE_CHECK_DIGITS.length(); j++) { + String curr = POSSIBLE_CHECK_DIGITS.substring(j, j + 1);//"" + Character.forDigit(j, 10); + if (!curr.equals(check)) { + list.add(code + curr); + } + } + } + + return list.toArray(new String[list.size()]); + } + + /** + * Returns a code with the Check Digit (i.e. last character) removed. + * + * @param code The code + * @return The code without the check digit + */ + protected String removeCheckDigit(String code) { + if (code == null || code.length() <= checkDigitLth) { + return null; + } + return code.substring(0, code.length() - checkDigitLth); + } + + /** + * Returns the check digit (i.e. last character) for a code. + * + * @param code The code + * @return The check digit + */ + protected String checkDigit(String code) { + if (code == null || code.length() <= checkDigitLth) { + return ""; + } + int start = code.length() - checkDigitLth; + return code.substring(start); + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/CUSIPCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/CUSIPCheckDigitTest.java new file mode 100644 index 000000000..b45d19e8d --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/CUSIPCheckDigitTest.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.validator.routines.checkdigit; + + +/** + * CUSIP Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class CUSIPCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Construct a new test. + * @param name test name + */ + public CUSIPCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = CUSIPCheckDigit.CUSIP_CHECK_DIGIT; + valid = new String[] {"037833100", + "931142103", + "837649128", + "392690QT3", + "594918104", + "86770G101", + "Y8295N109", + "G8572F100" + }; + invalid = new String[] {"0378#3100"}; + } + + private static String invalidCheckDigits[] = {"DUS0421CW", + "DUS0421CN", + "DUS0421CE" + }; + + public void testVALIDATOR_336_InvalidCheckDigits() { + for (int i = 0; i < invalidCheckDigits.length; i++) { + String invalidCheckDigit = invalidCheckDigits[i]; + assertFalse("Should fail: " + invalidCheckDigit, routine.isValid(invalidCheckDigit)); + } + } + + private static String validCheckDigits[] = {"DUS0421C5"}; + + public void testVALIDATOR_336_ValidCheckDigits() { + for (int i = 0; i < validCheckDigits.length; i++) { + String validCheckDigit = validCheckDigits[i]; + assertTrue("Should fail: " + validCheckDigit, routine.isValid(validCheckDigit)); + } + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/EAN13CheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/EAN13CheckDigitTest.java new file mode 100644 index 000000000..203483eff --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/EAN13CheckDigitTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + + +/** + * EAN-13 Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class EAN13CheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public EAN13CheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = EAN13CheckDigit.EAN13_CHECK_DIGIT; + valid = new String[] { + "9780072129519", + "9780764558313", + "4025515373438", + "0095673400332"}; + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/IBANCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/IBANCheckDigitTest.java new file mode 100644 index 000000000..a2253b037 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/IBANCheckDigitTest.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.validator.routines.checkdigit; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; + + +/** + * EAN-13 Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class IBANCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public IBANCheckDigitTest(String name) { + super(name); + checkDigitLth = 2; + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = IBANCheckDigit.IBAN_CHECK_DIGIT; + valid = new String[] { + "AD1200012030200359100100", // Andorra + "AE070331234567890123456", // United Arab Emirates + "AL47212110090000000235698741", // Albania + "AT611904300234573201", // Austria + "AZ21NABZ00000000137010001944", // Azerbaijan + "BA391290079401028494", // Bosnia and Herzegovina + "BE62510007547061", // Belgium + "BE68539007547034", // Belgium + "BG80BNBG96611020345678", // Bulgaria + "BH67BMAG00001299123456", // Bahrain + "BR1800000000141455123924100C2", // Brazil + "BY13NBRB3600900000002Z00AB00", // Belarus + "CH3900700115201849173", // Switzerland + "CH9300762011623852957", // Switzerland + "CR05015202001026284066", // Costa Rica + "CY17002001280000001200527600", // Cyprus + "CZ6508000000192000145399", // Czechoslovakia + "DE89370400440532013000", // Germany + "DK5000400440116243", // Denmark + "DO28BAGR00000001212453611324", // Dominican Republic + "EE382200221020145685", // Estonia + "ES8023100001180000012345", // Spain + "FI2112345600000785", // Finland + "FO6264600001631634", // Denmark (Faroes) + "FR1420041010050500013M02606", // France + "GB29NWBK60161331926819", // UK + "GI75NWBK000000007099453", // Gibraltar + "GL8964710001000206", // Denmark (Greenland) + "GR1601101250000000012300695", // Greece + "GT82TRAJ01020000001210029690", // Guatemala + "HR1210010051863000160", // Croatia + "HU42117730161111101800000000", // Hungary + "IE29AIBK93115212345678", // Ireland + "IL620108000000099999999", // Israel + "IQ98NBIQ850123456789012", // Iraq + "IS140159260076545510730339", // Iceland + "IT60X0542811101000000123456", // Italy + "JO94CBJO0010000000000131000302",// Jordan + "KW81CBKU0000000000001234560101",// Kuwait + "KZ86125KZT5004100100", // Kazakhstan + "LB62099900000001001901229114", // Lebanon + "LC55HEMM000100010012001200023015",//Saint Lucia + "LI21088100002324013AA", // Liechtenstein (Principality of) + "LT121000011101001000", // Lithuania + "LU280019400644750000", // Luxembourg + "LV80BANK0000435195001", // Latvia + "MC5811222000010123456789030", // Monaco + "MD24AG000225100013104168", // Moldova + "ME25505000012345678951", // Montenegro + "MK07250120000058984", // Macedonia, Former Yugoslav Republic of + "MR1300020001010000123456753", // Mauritania + "MT84MALT011000012345MTLCAST001S",// Malta + "MU17BOMM0101101030300200000MUR",// Mauritius + "NL39RABO0300065264", // Netherlands + "NL91ABNA0417164300", // Netherlands + "NO9386011117947", // Norway + "PK36SCBL0000001123456702", // Pakistan + "PL27114020040000300201355387", // Poland + "PL60102010260000042270201111", // Poland + "PS92PALS000000000400123456702", // Palestine, State of + "PT50000201231234567890154", // Portugal + "QA58DOHB00001234567890ABCDEFG", // Qatar + "RO49AAAA1B31007593840000", // Romania + "RS35260005601001611379", // Serbia + "SA0380000000608010167519", // Saudi Arabia + "SC18SSCB11010000000000001497USD",// Seychelles + "SE3550000000054910000003", // Sweden + "SI56191000000123438", // Slovenia + "SK3112000000198742637541", // Slovak Republic + "SM86U0322509800000000270100", // San Marino + "ST68000100010051845310112", // Sao Tome and Principe + "SV62CENR00000000000000700025", // El Salvador + "TL380080012345678910157", // Timor-Leste + "TN5910006035183598478831", // Tunisia + "TR330006100519786457841326", // Turkey + "UA213223130000026007233566001", // Ukraine + "VA59001123000012345678", // Vatican City State + "VG96VPVG0000012345678901", // Virgin Islands, British + "XK051212012345678906", // Republic of Kosovo + + // Codes AA and ZZ will never be used as ISO countries nor in IBANs + // add some dummy calculated codes to test the limits + // Current minimum length is Norway = 15 + // Current maximum length is Malta = 31 + // N.B. These codes will fail online checkers which validate the IBAN format + //234567890123456789012345678901 + "AA0200000000053", + "AA9700000000089", + "AA9800000000071", + "ZZ02ZZZZZZZZZZZZZZZZZZZZZZZZZ04", + "ZZ97ZZZZZZZZZZZZZZZZZZZZZZZZZ40", + "ZZ98ZZZZZZZZZZZZZZZZZZZZZZZZZ22", + }; + /* + * sources + * https://intranet.birmingham.ac.uk/finance/documents/public/IBAN.pdf + * http://www.paymentscouncil.org.uk/resources_and_publications/ibans_in_europe/ + */ + invalid = new String[] { + "510007+47061BE63", + "IE01AIBK93118702569045", + "AA0000000000089", + "AA9900000000053", + }; + zeroSum = null; + missingMessage = "Invalid Code length=0"; + + } + + /** + * Test zero sum + */ + @Override + public void testZeroSum() { + // ignore, don't run this test + + // example code used to create dummy IBANs +// try { +// for(int i=0; i<97;i++) { +// String check = String.format("ZZ00ZZZZZZZZZZZZZZZZZZZZZZZZZ%02d", new Object[]{Integer.valueOf(i)}); +// String chk = routine.calculate(check); +// if (chk.equals("97")||chk.equals("98")||chk.equals("02")) { +// System.out.println(check+ " "+chk); +// } +// } +// } catch (CheckDigitException e) { +// e.printStackTrace(); +// } + } + + /** + * Returns an array of codes with invalid check digits. + * + * @param codes Codes with valid check digits + * @return Codes with invalid check digits + */ + @Override + protected String[] createInvalidCodes(String[] codes) { + List list = new ArrayList(); + + // create invalid check digit values + for (int i = 0; i < codes.length; i++) { + String code = removeCheckDigit(codes[i]); + String check = checkDigit(codes[i]); + for (int j = 2; j <= 98; j++) { // check digits can be from 02-98 (00 and 01 are not possible) + String curr = j > 9 ? "" + j : "0" + j; + if (!curr.equals(check)) { + list.add(code.substring(0, 2) + curr + code.substring(4)); + } + } + } + + return list.toArray(new String[list.size()]); + } + + /** + * Returns a code with the Check Digits (i.e. characters 3&4) set to "00". + * + * @param code The code + * @return The code with the zeroed check digits + */ + @Override + protected String removeCheckDigit(String code) { + return code.substring(0, 2) + "00" + code.substring(4); + } + + /** + * Returns the check digit (i.e. last character) for a code. + * + * @param code The code + * @return The check digit + */ + @Override + protected String checkDigit(String code) { + if (code == null || code.length() <= checkDigitLth) { + return ""; + } + return code.substring(2, 4); + } + + public void testOther() throws Exception { + BufferedReader rdr = null; + try { + rdr = new BufferedReader( + new InputStreamReader( + this.getClass().getResourceAsStream("IBANtests.txt"),"ASCII")); + String line; + while((line=rdr.readLine()) != null) { + if (!line.startsWith("#") && line.length() > 0) { + if (line.startsWith("-")) { + line = line.substring(1); + Assert.assertFalse(line, routine.isValid(line.replaceAll(" ", ""))); + } else { + Assert.assertTrue(line, routine.isValid(line.replaceAll(" ", ""))); + } + } + } + } finally { + if (rdr != null) { + rdr.close(); + } + } + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISBN10CheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISBN10CheckDigitTest.java new file mode 100644 index 000000000..9f7a8b47e --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISBN10CheckDigitTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + + +/** + * ISBN-10 Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class ISBN10CheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public ISBN10CheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = ISBN10CheckDigit.ISBN10_CHECK_DIGIT; + valid = new String[] { + "1930110995", + "020163385X", + "1932394354", + "1590596277" + }; + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISBNCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISBNCheckDigitTest.java new file mode 100644 index 000000000..98d752da6 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISBNCheckDigitTest.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.validator.routines.checkdigit; + + +/** + * ISBN-10/ISBN-13 Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class ISBNCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public ISBNCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = ISBNCheckDigit.ISBN_CHECK_DIGIT; + valid = new String[] { + "9780072129519", + "9780764558313", + "1930110995", + "020163385X", + "1590596277", // ISBN-10 Ubuntu Book + "9781590596272" // ISBN-13 Ubuntu Book + }; + missingMessage = "ISBN Code is missing"; + zeroSum = "000000000000"; + } + + /** + * Set up routine & valid codes. + */ + public void testInvalidLength() { + assertFalse("isValid() Lth 9 ", routine.isValid("123456789")); + assertFalse("isValid() Lth 11", routine.isValid("12345678901")); + assertFalse("isValid() Lth 12", routine.isValid("123456789012")); + assertFalse("isValid() Lth 14", routine.isValid("12345678901234")); + + try { + routine.calculate("12345678"); + fail("calculate() Lth 8 - expected exception"); + } catch (Exception e) { + assertEquals("calculate() Lth 8", "Invalid ISBN Length = 8", e.getMessage()); + } + + try { + routine.calculate("1234567890"); + fail("calculate() Lth 10 - expected exception"); + } catch (Exception e) { + assertEquals("calculate() Lth 10", "Invalid ISBN Length = 10", e.getMessage()); + } + + try { + routine.calculate("12345678901"); + fail("calculate() Lth 11 - expected exception"); + } catch (Exception e) { + assertEquals("calculate() Lth 11", "Invalid ISBN Length = 11", e.getMessage()); + } + + try { + routine.calculate("1234567890123"); + fail("calculate() Lth 13 - expected exception"); + } catch (Exception e) { + assertEquals("calculate() Lth 13", "Invalid ISBN Length = 13", e.getMessage()); + } + } + + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISINCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISINCheckDigitTest.java new file mode 100644 index 000000000..a3e218dfb --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISINCheckDigitTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + + +/** + * ISIN Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class ISINCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public ISINCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = ISINCheckDigit.ISIN_CHECK_DIGIT; + valid = new String[] {"US0378331005", + "BMG8571G1096", + "AU0000XVGZA3", + "GB0002634946", + "FR0004026250", + "3133EHHF3", // see VALIDATOR-422 Valid check-digit, but not valid ISIN + "DK0009763344", + "dk0009763344", // TODO lowercase is currently accepted, but is this valid? + "AU0000xvgza3", // lowercase NSIN + "EZ0000000003", // Invented; for use in ISINValidatorTest + "XS0000000009", // ditto + "AA0000000006", // ditto + }; + invalid = new String[] {"0378#3100"}; + } + + private static String invalidCheckDigits[] = + {"US037833100O", // proper check digit is '5', see above + "BMG8571G109D", // proper check digit is '6', see above + "AU0000XVGZAD", // proper check digit is '3', see above + "GB000263494I", // proper check digit is '6', see above + "FR000402625C", // proper check digit is '0', see above + "DK000976334H", // proper check digit is '4', see above + }; + + public void testVALIDATOR_345() { + for (int i = 0; i < invalidCheckDigits.length; i++) { + String invalidCheckDigit = invalidCheckDigits[i]; + assertFalse("Should fail: " + invalidCheckDigit, routine.isValid(invalidCheckDigit)); + } + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISSNCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISSNCheckDigitTest.java new file mode 100644 index 000000000..ba5120850 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISSNCheckDigitTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + + +/** + * ISSN Check Digit Test. + * + * @since 1.5.0 + */ +public class ISSNCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public ISSNCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = ISSNCheckDigit.ISSN_CHECK_DIGIT; + valid = new String[] { + "03178471", + "1050124X", + "15626865", + "10637710", + "17487188", + "02642875", + "17500095", + "11881534", + "19111479", + "19111460", + "00016772", + "1365201X", + }; + invalid = new String[] { + "03178472", // wrong check + "1050-124X", // format char + " 1365201X", + "1365201X ", + " 1365201X ", + }; + missingMessage = "Code is missing"; + zeroSum = "00000000"; + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/LuhnCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/LuhnCheckDigitTest.java new file mode 100644 index 000000000..f70bb2f6c --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/LuhnCheckDigitTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + + +/** + * Luhn Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class LuhnCheckDigitTest extends AbstractCheckDigitTest { + + private static final String VALID_VISA = "4417123456789113"; + private static final String VALID_SHORT_VISA = "4222222222222"; + private static final String VALID_AMEX = "378282246310005"; + private static final String VALID_MASTERCARD = "5105105105105100"; + private static final String VALID_DISCOVER = "6011000990139424"; + private static final String VALID_DINERS = "30569309025904"; + + /** + * Constructor + * @param name test name + */ + public LuhnCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + routine = LuhnCheckDigit.LUHN_CHECK_DIGIT; + + valid = new String[] { + VALID_VISA, + VALID_SHORT_VISA, + VALID_AMEX, + VALID_MASTERCARD, + VALID_DISCOVER, + VALID_DINERS}; + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenABACheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenABACheckDigitTest.java new file mode 100644 index 000000000..5c73d8a33 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenABACheckDigitTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + + +/** + * ModulusTenCheckDigit ABA Number Check Digit Test. + * + * @version $Revision$ + */ +public class ModulusTenABACheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public ModulusTenABACheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = new ModulusTenCheckDigit(new int[] { 1, 7, 3 }, true); + valid = new String[] { + "123456780", + "123123123", + "011000015", + "111000038", + "231381116", + "121181976" + }; + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenCUSIPCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenCUSIPCheckDigitTest.java new file mode 100644 index 000000000..35fa8bedd --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenCUSIPCheckDigitTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + + +/** + * ModulusTenCheckDigit CUSIP Test. + * + * @version $Revision$ + */ +public class ModulusTenCUSIPCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Construct a new test. + * @param name test name + */ + public ModulusTenCUSIPCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = new ModulusTenCheckDigit(new int[] { 1, 2}, true, true); + valid = new String[] {"037833100", + "931142103", + "837649128", + "392690QT3", + "594918104", + "86770G101", + "Y8295N109", + "G8572F100" + }; + invalid = new String[] {"0378#3100"}; + } + + private static String invalidCheckDigits[] = {"DUS0421CW", + "DUS0421CN", + "DUS0421CE" + }; + + public void testVALIDATOR_336_InvalidCheckDigits() { + for (int i = 0; i < invalidCheckDigits.length; i++) { + String invalidCheckDigit = invalidCheckDigits[i]; + assertFalse("Should fail: " + invalidCheckDigit, routine.isValid(invalidCheckDigit)); + } + } + + private static String validCheckDigits[] = {"DUS0421C5"}; + + public void testVALIDATOR_336_ValidCheckDigits() { + for (int i = 0; i < validCheckDigits.length; i++) { + String validCheckDigit = validCheckDigits[i]; + assertTrue("Should fail: " + validCheckDigit, routine.isValid(validCheckDigit)); + } + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenEAN13CheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenEAN13CheckDigitTest.java new file mode 100644 index 000000000..ca5df7a38 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenEAN13CheckDigitTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + + +/** + * ModulusTenCheckDigit EAN-13 Test. + * + * @version $Revision$ + */ +public class ModulusTenEAN13CheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public ModulusTenEAN13CheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = new ModulusTenCheckDigit(new int[] { 1, 3 }, true); + valid = new String[] { + "9780072129519", + "9780764558313", + "4025515373438", + "0095673400332"}; + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenLuhnCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenLuhnCheckDigitTest.java new file mode 100644 index 000000000..27bb26a6d --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenLuhnCheckDigitTest.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.validator.routines.checkdigit; + + +/** + * ModulusTenCheckDigit Luhn Test. + * + * @version $Revision$ + */ +public class ModulusTenLuhnCheckDigitTest extends AbstractCheckDigitTest { + + private static final String VALID_VISA = "4417123456789113"; + private static final String VALID_SHORT_VISA = "4222222222222"; + private static final String VALID_AMEX = "378282246310005"; + private static final String VALID_MASTERCARD = "5105105105105100"; + private static final String VALID_DISCOVER = "6011000990139424"; + private static final String VALID_DINERS = "30569309025904"; + + /** + * Constructor + * @param name test name + */ + public ModulusTenLuhnCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + routine = new ModulusTenCheckDigit(new int[] {1, 2}, true, true); + + valid = new String[] { + VALID_VISA, + VALID_SHORT_VISA, + VALID_AMEX, + VALID_MASTERCARD, + VALID_DISCOVER, + VALID_DINERS}; + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenSedolCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenSedolCheckDigitTest.java new file mode 100644 index 000000000..c38540649 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenSedolCheckDigitTest.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.validator.routines.checkdigit; + + +/** + * ModulusTenCheckDigit SEDOL Test. + * + * @version $Revision$ + */ +public class ModulusTenSedolCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public ModulusTenSedolCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = new ModulusTenCheckDigit(new int[] { 1, 3, 1, 7, 3, 9, 1 }); + valid = new String[] { + "0263494", + "0870612", + "B06LQ97", + "3437575", + "B07LF55", + }; + invalid = new String[] {"123#567"}; + zeroSum = "0000000"; + } + + private static String invalidCheckDigits[] = { + "026349E", // proper check digit is '4', see above + "087061C", // proper check digit is '2', see above + "B06LQ9H", // proper check digit is '7', see above + "343757F", // proper check digit is '5', see above + "B07LF5F", // proper check digit is '5', see above + }; + + public void testVALIDATOR_346() { + for (int i = 0; i < invalidCheckDigits.length; i++) { + String invalidCheckDigit = invalidCheckDigits[i]; + assertFalse("Should fail: " + invalidCheckDigit, routine.isValid(invalidCheckDigit)); + } + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/SedolCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/SedolCheckDigitTest.java new file mode 100644 index 000000000..269473e44 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/SedolCheckDigitTest.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.validator.routines.checkdigit; + + +/** + * ISIN Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class SedolCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Constructor + * @param name test name + */ + public SedolCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = SedolCheckDigit.SEDOL_CHECK_DIGIT; + valid = new String[] { + "0263494", + "0870612", + "B06LQ97", + "3437575", + "B07LF55", + }; + invalid = new String[] {"123#567"}; + zeroSum = "0000000"; + } + + private static String invalidCheckDigits[] = { + "026349E", // proper check digit is '4', see above + "087061C", // proper check digit is '2', see above + "B06LQ9H", // proper check digit is '7', see above + "343757F", // proper check digit is '5', see above + "B07LF5F", // proper check digit is '5', see above + }; + + public void testVALIDATOR_346() { + for (int i = 0; i < invalidCheckDigits.length; i++) { + String invalidCheckDigit = invalidCheckDigits[i]; + assertFalse("Should fail: " + invalidCheckDigit, routine.isValid(invalidCheckDigit)); + } + } + +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/VerhoeffCheckDigitTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/VerhoeffCheckDigitTest.java new file mode 100644 index 000000000..c4270e5f1 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/VerhoeffCheckDigitTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.validator.routines.checkdigit; + +/** + * Verhoeff Check Digit Test. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class VerhoeffCheckDigitTest extends AbstractCheckDigitTest { + + /** + * Construct a new test. + * @param name test name + */ + public VerhoeffCheckDigitTest(String name) { + super(name); + } + + /** + * Set up routine & valid codes. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + routine = VerhoeffCheckDigit.VERHOEFF_CHECK_DIGIT; + valid = new String[] { + "15", + "1428570", + "12345678902" + }; + } + + /** + * Test zero sum + */ + @Override + public void testZeroSum() { + // ignore, don't run this test + } +} diff --git a/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/util/FlagsTest.java b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/util/FlagsTest.java new file mode 100644 index 000000000..9ae88c088 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/util/FlagsTest.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.validator.util; + +import junit.framework.TestCase; + +/** + * Test the Flags class. + * + * @version $Revision$ + */ +public class FlagsTest extends TestCase { + + /** + * Declare some flags for testing. + */ + private static final long LONG_FLAG = 1; + private static final long LONG_FLAG_2 = 2; + private static final int INT_FLAG = 4; + + /** + * Constructor for FlagsTest. + */ + public FlagsTest(String name) { + super(name); + } + + public void testHashCode() { + Flags f = new Flags(45); + assertEquals(f.hashCode(), 45); + } + + public void testGetFlags() { + Flags f = new Flags(45); + assertEquals(f.getFlags(), 45); + } + + public void testIsOnOff() { + Flags f = new Flags(); + f.turnOn(LONG_FLAG); + f.turnOn(INT_FLAG); + assertTrue(f.isOn(LONG_FLAG)); + assertTrue(!f.isOff(LONG_FLAG)); + + assertTrue(f.isOn(INT_FLAG)); + assertTrue(!f.isOff(INT_FLAG)); + + assertTrue(f.isOff(LONG_FLAG_2)); + } + + public void testTurnOnOff() { + } + + public void testTurnOff() { + } + + public void testTurnOffAll() { + Flags f = new Flags(98432); + f.turnOffAll(); + assertEquals(0, f.getFlags()); + } + + public void testClear() { + Flags f = new Flags(98432); + f.clear(); + assertEquals(0, f.getFlags()); + } + + public void testTurnOnAll() { + Flags f = new Flags(); + f.turnOnAll(); + assertEquals(~0, f.getFlags()); + } + + public void testIsOn_isFalseWhenNotAllFlagsInArgumentAreOn() { + Flags first = new Flags(1); + long firstAndSecond = 3; + + assertFalse(first.isOn(firstAndSecond)); + } + + public void testIsOn_isTrueWhenHighOrderBitIsSetAndQueried() { + Flags allOn = new Flags(~0); + long highOrderBit = 0x8000000000000000L; + + assertTrue(allOn.isOn(highOrderBit)); + } + + /** + * Test for Object clone() + */ + public void testClone() { + } + + /** + * Test for boolean equals(Object) + */ + public void testEqualsObject() { + } + + /** + * Test for String toString() + */ + public void testToString() { + Flags f = new Flags(); + String s = f.toString(); + assertEquals(64, s.length()); + + f.turnOn(INT_FLAG); + s = f.toString(); + assertEquals(64, s.length()); + + assertEquals( + "0000000000000000000000000000000000000000000000000000000000000100", + s); + } + +} diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/DateTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/DateTest-config.xml new file mode 100644 index 000000000..10fed9763 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/DateTest-config.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + +
+ + + datePattern + MM/dd/yyyy + + +
+
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EmailTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EmailTest-config.xml new file mode 100644 index 000000000..114a2d866 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EmailTest-config.xml @@ -0,0 +1,34 @@ + + + + + + + + +
+ + +
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EntityImportTest-byteform.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EntityImportTest-byteform.xml new file mode 100644 index 000000000..a243b26b4 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EntityImportTest-byteform.xml @@ -0,0 +1,19 @@ + +
+ + diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EntityImportTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EntityImportTest-config.xml new file mode 100644 index 000000000..58d334f60 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EntityImportTest-config.xml @@ -0,0 +1,33 @@ + +]> + + + + + + + &byteform; + + diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ExceptionTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ExceptionTest-config.xml new file mode 100644 index 000000000..c6189fe98 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ExceptionTest-config.xml @@ -0,0 +1,34 @@ + + + + + + + + +
+ + +
+
\ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ExtensionTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ExtensionTest-config.xml new file mode 100644 index 000000000..e57c355d6 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ExtensionTest-config.xml @@ -0,0 +1,55 @@ + + + + + + + + + +
+ + + +
+ +
+ + + + + + + + +
+ + + +
+ +
+
\ No newline at end of file diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/GenericTypeValidatorTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/GenericTypeValidatorTest-config.xml new file mode 100644 index 000000000..c621f6878 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/GenericTypeValidatorTest-config.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + +
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/LocaleTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/LocaleTest-config.xml new file mode 100644 index 000000000..aa4875849 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/LocaleTest-config.xml @@ -0,0 +1,72 @@ + + + + + + + + +
+ + + + + + +
+
+ +
+ + + + + + + + + +
+
+ +
+ + + + + + +
+
+ +
+ + + + + + +
+
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleConfigFilesTest-1-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleConfigFilesTest-1-config.xml new file mode 100644 index 000000000..36aab8188 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleConfigFilesTest-1-config.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + testConstName1 + testConstValue1 + + +
+ + + var11 + ${testConstName1} + + + var12 + ${testConstName2} + + +
+ +
+ + + + + testConstName1_fr + testConstValue1_fr + + +
+ + + var11_fr + ${testConstName1_fr} + + + var12_fr + ${testConstName2_fr} + + +
+ +
+ +
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleConfigFilesTest-2-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleConfigFilesTest-2-config.xml new file mode 100644 index 000000000..0af4948c7 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleConfigFilesTest-2-config.xml @@ -0,0 +1,78 @@ + + + + + + + + + testConstName2 + testConstValue2 + + +
+ + + + + + +
+ +
+ + + var21 + ${testConstName1} + + + var22 + ${testConstName2} + + +
+ +
+ + + + + testConstName2_fr + testConstValue2_fr + + +
+ + + var21_fr + ${testConstName1_fr} + + + var22_fr + ${testConstName2_fr} + + +
+ +
+ +
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleTests-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleTests-config.xml new file mode 100644 index 000000000..7ae62e590 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleTests-config.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + +
+ + + + + + + + + +
+
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ParameterTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ParameterTest-config.xml new file mode 100644 index 000000000..3ef0f9309 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ParameterTest-config.xml @@ -0,0 +1,41 @@ + + + + + + + + +
+ + +
+
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RequiredIfTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RequiredIfTest-config.xml new file mode 100644 index 000000000..6a89ba7f5 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RequiredIfTest-config.xml @@ -0,0 +1,55 @@ + + + + + + + + +
+ + + + field[0] + lastName + + + fieldTest[0] + NOTNULL + + + + + + field[0] + firstName + + + fieldTest[0] + NOTNULL + + +
+
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RequiredNameTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RequiredNameTest-config.xml new file mode 100644 index 000000000..cd3fd7f89 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RequiredNameTest-config.xml @@ -0,0 +1,39 @@ + + + + + + + + +
+ + + + + + +
+
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RetrieveFormTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RetrieveFormTest-config.xml new file mode 100644 index 000000000..ff931e523 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RetrieveFormTest-config.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + +
+ + localeVar + default + + +
+ +
+ + localeVar + default + + +
+ +
+ + localeVar + default + + +
+ +
+ + localeVar + default + + +
+ +
+ + + + +
+ + localeVar + fr + + +
+ +
+ + localeVar + fr + + +
+ +
+ + localeVar + fr + + +
+ +
+ + + + +
+ + localeVar + fr_FR + + +
+ +
+ + localeVar + fr_FR + + +
+ +
+ + + +
+ + localeVar + fr_CA + + +
+ +
+ + localeVar + fr_CA + + +
+
+ + + + +
+ + localeVar + fr_CA_XXX + + +
+ +
+ +
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/TestNumber-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/TestNumber-config.xml new file mode 100644 index 000000000..796286dc7 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/TestNumber-config.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ValidatorResultsTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ValidatorResultsTest-config.xml new file mode 100644 index 000000000..0d2f2fa3a --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ValidatorResultsTest-config.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + +
+ + + + + + + + + +
+
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/VarTest-config.xml b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/VarTest-config.xml new file mode 100644 index 000000000..2657704a7 --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/VarTest-config.xml @@ -0,0 +1,52 @@ + + + + + + + + + +
+ + + var-1-1 + value-1-1 + jstype-1-1 + + + + + var-2-1 + value-2-1 + jstype-2-1 + + + var-2-2 + value-2-2 + + +
+
+
diff --git a/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/routines/checkdigit/IBANtests.txt b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/routines/checkdigit/IBANtests.txt new file mode 100644 index 000000000..c4b47599a --- /dev/null +++ b/Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/routines/checkdigit/IBANtests.txt @@ -0,0 +1,113 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Derived from: +# http://www.sparkasse.at/oberoesterreich/Firmenkunden/Produkte/auslandsgeschaeft/auslaendischer-zahlungsverkehr/IBAN-laenderliste +# + +AL47 2121 1009 0000 0002 3569 8741 +AD12 0001 2030 2003 5910 0100 +BE68 5390 0754 7034 +BA39 1290 0794 0102 8494 +BG80 BNBG 9661 1020 3456 78 +DK50 0040 0440 1162 43 +FO62 6460 0001 6316 34 +GL89 6471 0001 0002 06 +DE89 3704 0044 0532 0130 00 +GB29 NWBK 6016 1331 9268 19 +EE38 2200 2210 2014 5685 +FI21 1234 5600 0007 85 +FR14 2004 1010 0505 0001 3M02 606 +GI75 NWBK 0000 0000 7099 453 +GR16 0110 1250 0000 0001 2300 695 +IE29 AIBK 9311 5212 3456 78 +IS14 0159 2600 7654 5510 7303 39 +IT60 X054 2811 1010 0000 0123 456 +HR12 1001 0051 8630 0016 0 +LV80 BANK 0000 4351 9500 1 +LI21 0881 0000 2324 013A A +LT12 1000 0111 0100 1000 +LU28 0019 4006 4475 0000 +MT84 MALT 0110 0001 2345 MTLC AST0 01S +MK07 2501 2000 0058 984 +MC58 11222 00001 0123456789030 +ME25 5050 0001 2345 6789 51 +NL91 ABNA 0417 1643 00 +NO93 8601 1117 947 +AT61 1904 3002 3457 3201 +PL61 1090 1014 0000 0712 1981 2874 +PT50 0002 0123 1234 5678 9015 4 +RO49 AAAA 1B31 0075 9384 0000 +SM86 U032 2509 8000 0000 0270 100 +SE45 5000 0000 0583 9825 7466 +CH93 0076 2011 6238 5295 7 +RS35 2600 0560 1001 6113 79 +SK31 1200 0000 1987 4263 7541 +SI56 1910 0000 0123 438 +ES91 2100 0418 4502 0005 1332 +CZ94 5500 0000 0010 1103 8930 +TR33 0006 1005 1978 6457 8413 26 +HU42 1177 3016 1111 1018 0000 0000 +CY17 0020 0128 0000 0012 0052 7600 + +AO06 0006 0000 0100 0371 3117 4 +AZ21 NABZ 0000 0000 1370 1000 1944 +BH67 BMAG 0000 1299 1234 56 +BJ11 B006 1010 0400 2711 0119 2591 +BR97 0036 0305 0000 1000 9795 493P 1 +VG96 VPVG 0000 0123 4567 8901 +BF10 3013 4020 0154 0094 5000 643 +BI43 2010 1106 7444 +CR05 1520 2001 0262 8406 6 +DO28 BAGR 0000 0001 2124 5361 1324 +GE29 NB00 0000 0101 9049 17 +IR58 0540 1051 8002 1273 1130 07 +IL62 0108 0000 0009 9999 999 + +# Does not appear to be a valid IBAN +#JO99 BJOR 9999 1234 5678 9012 3456 78 +# This example from http://www.swift.com/dsp/resources/documents/IBAN_Registry.pdf +JO94 CBJO 0010 0000 0000 0131 0003 02 + +CM21 1000 3001 0005 0000 0605 306 +CV64 0003 0000 4547 0691 1017 6 +KZ86 125K ZT50 0410 0100 +QA58 DOHB 0000 1234 5678 90AB CDEF G +CG52 3001 1000 2021 5123 4567 890 +KW81 CBKU 0000 0000 0000 1234 5601 01 +LB62 0999 0000 0001 0019 0122 9114 +MG46 0000 5030 0101 0191 4016 056 +ML03 D008 9017 0001 0021 2000 0447 +MR13 0002 0001 0100 0012 3456 753 +MU17 BOMM 0101 1010 3030 0200 000M UR +MD24 AG00 0225 1000 1310 4168 +MZ59 0001 0000 0011 8341 9415 7 +PK36 SCBL 0000 0011 2345 6702 +PS92 PALS 0000 0000 0400 1234 5670 2 +CI05 A000 6017 4100 1785 3001 1852 +SA03 8000 0000 6080 1016 7519 +PT50 0002 0000 0163 0993 1035 5 +SN12 K001 0015 2000 0256 9000 7542 +TN59 1000 6035 1835 9847 8831 +AE07 0331 2345 6789 0123 456 +FR76 3000 7000 1100 0997 0004 942 + +# VALIDATOR-378 +CH59 09000 0001 7342 7712 +# This passes the checksum test, but the length is wrong (we don't check that) +CH59 9000 0001 7342 7712 +# However this does fail the check digit test: +# (a leading minus sign means the check should fail) +-CH59 90000 0001 7342 7712 diff --git a/Java-base/directory-mavibot/Dockerfile b/Java-base/directory-mavibot/Dockerfile new file mode 100644 index 000000000..e208c4890 --- /dev/null +++ b/Java-base/directory-mavibot/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:22.04 + +RUN export DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get install -y software-properties-common \ + && add-apt-repository ppa:deadsnakes/ppa \ + && apt-get update \ + && apt-get install -y \ + build-essential \ + git \ + vim \ + jq \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/list/* + +RUN apt-get -y install sudo \ + openjdk-8-jdk \ + maven + +RUN bash -c "echo 2 | update-alternatives --config java" + +COPY src /workspace +WORKDIR /workspace + +RUN mvn install -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false + +RUN mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 + +ENV TZ=Asia/Seoul diff --git a/Java-base/directory-mavibot/src/LICENSE b/Java-base/directory-mavibot/src/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/Java-base/directory-mavibot/src/LICENSE @@ -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/Java-base/directory-mavibot/src/NOTICE b/Java-base/directory-mavibot/src/NOTICE new file mode 100644 index 000000000..848822955 --- /dev/null +++ b/Java-base/directory-mavibot/src/NOTICE @@ -0,0 +1,5 @@ +Apache Mavibot +Copyright 2012-2014 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/Java-base/directory-mavibot/src/distribution/pom.xml b/Java-base/directory-mavibot/src/distribution/pom.xml new file mode 100644 index 000000000..c11f862c7 --- /dev/null +++ b/Java-base/directory-mavibot/src/distribution/pom.xml @@ -0,0 +1,90 @@ + + + + + + 4.0.0 + + + org.apache.directory.mavibot + mavibot-parent + 1.0.0-M9-SNAPSHOT + + + distribution + pom + Apache Mavibot Distribution + + + ${project.build.directory}/docs + + + + + + maven-deploy-plugin + + true + + + + + + + + ${project.groupId} + mavibot + ${project.version} + + + + + + apache-release + + + mavibot-${project.version} + + + + maven-assembly-plugin + + + src/main/assembly/bin.xml + src/main/assembly/src.xml + + gnu + + + + + package + + single + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/distribution/src/main/assembly/bin.xml b/Java-base/directory-mavibot/src/distribution/src/main/assembly/bin.xml new file mode 100644 index 000000000..bdb9b0d72 --- /dev/null +++ b/Java-base/directory-mavibot/src/distribution/src/main/assembly/bin.xml @@ -0,0 +1,78 @@ + + + + + bin + + tar.gz + zip + + + + + + + .. + + + LICENSE + NOTICE + + + + + + ../target/site + docs + + apidocs*/** + xref*/** + + + + + + + + dist + + + ${project.groupId}:* + + + + + *:sources + + + + + + lib + + + ${project.groupId}:* + + + *:sources + + + + diff --git a/Java-base/directory-mavibot/src/distribution/src/main/assembly/src.xml b/Java-base/directory-mavibot/src/distribution/src/main/assembly/src.xml new file mode 100644 index 000000000..c0eae3794 --- /dev/null +++ b/Java-base/directory-mavibot/src/distribution/src/main/assembly/src.xml @@ -0,0 +1,80 @@ + + + + + src + + tar.gz + tar.bz2 + zip + + + + + + .. + + + README* + LICENSE + NOTICE + + + + + + ../target/site + docs + + apidocs*/** + xref*/** + + + + + + .. + + + **/* + + + .git/** + KEYS + LICENSE-bin + NOTICE + **/target + **/target/** + **/.settings + **/.settings/** + **/.classpath + **/.project + **/*.gen + **/.wtpmodules + **/surefire* + **/cobertura.ser + **/velocity.log + **/release.properties + **/pom.xml.releaseBackup + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/BTree.graphml b/Java-base/directory-mavibot/src/mavibot/img/BTree.graphml new file mode 100644 index 000000000..ed6d1bbbf --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/BTree.graphml @@ -0,0 +1,732 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTree header 1 + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + S +I +Z +E + + + + + + + + + + + + + + + + + S +I +Z +E + + + + + + + + + + + + + + + + + S +I +Z +E + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTree header 2 + + + + + + + + + + + + + + + + + BTree header 3 + + + + + + + + + + + + + + + + + RootBT1 + + + + + + + + + + + + + + + + + RootBT2 + + + + + + + + + + + + + + + + + RootBT3 + + + + + + + + + + + + + + + + + Page + + + + + + + + + + + + + + + + + ... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + info + + + + + + + + + + + + + + + + + + + + info + + + + + + + + + + + + + + + + + + info + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/BTree.png b/Java-base/directory-mavibot/src/mavibot/img/BTree.png new file mode 100644 index 000000000..783abb529 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/BTree.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/ElementHolder.graphml b/Java-base/directory-mavibot/src/mavibot/img/ElementHolder.graphml new file mode 100644 index 000000000..d33cc38fa --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/ElementHolder.graphml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + Element<T> + + + + + + + + + + + + + + + + + + + + AbstractElement<T> + + + + + + + + + + + + + + + + + SingleElement<V> -> +AbstractElement<V> + + + + + + + + + + + + + + + + + BtreeValue<V> -> +AbstractValue<BTree<V, V>> + + + + + + + + + + + + + + + + + LeafValue<V> -> +AbstractValue<Leaf<V, V>> + + + + + + + + + + + + + + + + + NodeValue<V> -> +AbstractValue<Node<V, V>> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/ElementHolder.png b/Java-base/directory-mavibot/src/mavibot/img/ElementHolder.png new file mode 100644 index 000000000..8e65d716b Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/ElementHolder.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/Leaf.graphml b/Java-base/directory-mavibot/src/mavibot/img/Leaf.graphml new file mode 100644 index 000000000..a1fc9b134 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/Leaf.graphml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + Leaf + + + + + + + + + + + V[0..N] values + + + + + + + + + + + + + + + + + <AbstractPage> + + + + + + + + + + + + + + + + + Leaf prev + + + + + + + + + + + + + + + + + Leaf next + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/Leaf.png b/Java-base/directory-mavibot/src/mavibot/img/Leaf.png new file mode 100644 index 000000000..2a2da0265 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/Leaf.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/MavibotValue.graphml b/Java-base/directory-mavibot/src/mavibot/img/MavibotValue.graphml new file mode 100644 index 000000000..d4aa00ca1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/MavibotValue.graphml @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + ElementHolder<E, K, V> + + + + + + + + + + + + + + + + + + + + CacheHolder<E, K, V> + + + + + + + + + + + + + + + + + MemoryHolder<E, K, V> + + + + + + + + + + + + + + + + + NodeCacheHolder<K, V> -> +CacheHolder<NodeValue<K, V>, K, V> + + + + + + + + + + + + + + + + + LeafCacheHolder>K, V> -> +CacheHolder<LeafValue<K, V>, K, V> + + + + + + + + + + + + + + + + + ValueCacheHolder<K, V> -> +CacheHolder<SingleValue<V>, K, V> + + + + + + + + + + + + + + + + + NodeMemoryHolder<K, V> -> +MemoryHolder<NodeValue<K, V>, K, V> + + + + + + + + + + + + + + + + + LeafMemoryHolder<K, V> -> +MemoryHolder<LeafValue<K, V>, K, V> + + + + + + + + + + + + + + + + + ValueMemoryHolder<K, V> -> +MemoryHolder<SingleValue<V>, K, V> + + + + + + + + + + + + + + + + + MultipleValueCacheHolder<K, V> -> +CacheHolder<BTreeValue<V>, K, V> + + + + + + + + + + + + + + + + + MultipleValueMemoryHolder<K, V> -> +MemoryHolder<BTreeValue<V>, K, V> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/MavibotValue.png b/Java-base/directory-mavibot/src/mavibot/img/MavibotValue.png new file mode 100644 index 000000000..52f74ec8d Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/MavibotValue.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/Node.graphml b/Java-base/directory-mavibot/src/mavibot/img/Node.graphml new file mode 100644 index 000000000..aeabe847f --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/Node.graphml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + Node + + + + + + + + + + + Page[0..N+1] children + + + + + + + + + + + + + + + + + <AbstractPage> + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/Node.png b/Java-base/directory-mavibot/src/mavibot/img/Node.png new file mode 100644 index 000000000..55da7cfa7 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/Node.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/PageHierarchy.graphml b/Java-base/directory-mavibot/src/mavibot/img/PageHierarchy.graphml new file mode 100644 index 000000000..1d2c24f2f --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/PageHierarchy.graphml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + Page + + + + + + + + + + + + + + + + + AbstractPage + + + + + + + + + + + + + + + + + Leaf + + + + + + + + + + + + + + + + + Node + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/PageHierarchy.png b/Java-base/directory-mavibot/src/mavibot/img/PageHierarchy.png new file mode 100644 index 000000000..efb18b826 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/PageHierarchy.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/PageIOLogical.graphml b/Java-base/directory-mavibot/src/mavibot/img/PageIOLogical.graphml new file mode 100644 index 000000000..d5e0dec4a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/PageIOLogical.graphml @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + S +I +Z +E + + + + + + + + + + + + + + + + + N +E +X +T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PageIOs + + + + + + + + + + + + + + + + + + Logical page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/PageIOLogical.png b/Java-base/directory-mavibot/src/mavibot/img/PageIOLogical.png new file mode 100644 index 000000000..87f9054bc Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/PageIOLogical.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/PageMergeDelete.graphml b/Java-base/directory-mavibot/src/mavibot/img/PageMergeDelete.graphml new file mode 100644 index 000000000..49f208d39 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/PageMergeDelete.graphml @@ -0,0 +1,2353 @@ + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + + D removal +The right page +has to be completed + + + + + + + + + + + + + + + + + + Initial tree + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + We merge the +two siblings + + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + + And reorganize +the pages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/PageMergeDelete.png b/Java-base/directory-mavibot/src/mavibot/img/PageMergeDelete.png new file mode 100644 index 000000000..d1e7a6c93 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/PageMergeDelete.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/PageMergeMove.graphml b/Java-base/directory-mavibot/src/mavibot/img/PageMergeMove.graphml new file mode 100644 index 000000000..688c9dd59 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/PageMergeMove.graphml @@ -0,0 +1,2686 @@ + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + + vC + + + + + + + + + + + + + + + + + + E removal +The right page +has to be completed + + + + + + + + + + + + + + + + + + Initial tree + + + + + + + + + + + + + + + + + + vC + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + + vC + + + + + + + + + + + + + + + + + + We get one value +from the left page + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + + vC + + + + + + + + + + + + + + + + + + Resulting tree + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/PageMergeMove.png b/Java-base/directory-mavibot/src/mavibot/img/PageMergeMove.png new file mode 100644 index 000000000..6f08ed739 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/PageMergeMove.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/R1-node.graphml b/Java-base/directory-mavibot/src/mavibot/img/R1-node.graphml new file mode 100644 index 000000000..7eb08e72c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/R1-node.graphml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + r1 + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/R1-node.png b/Java-base/directory-mavibot/src/mavibot/img/R1-node.png new file mode 100644 index 000000000..a3c416f41 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/R1-node.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/R2-node.graphml b/Java-base/directory-mavibot/src/mavibot/img/R2-node.graphml new file mode 100644 index 000000000..e931dc50f --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/R2-node.graphml @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + r1 + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r2 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + copy + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/R2-node.png b/Java-base/directory-mavibot/src/mavibot/img/R2-node.png new file mode 100644 index 000000000..d2f59e513 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/R2-node.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/R3-node.graphml b/Java-base/directory-mavibot/src/mavibot/img/R3-node.graphml new file mode 100644 index 000000000..67f66baed --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/R3-node.graphml @@ -0,0 +1,697 @@ + + + + + + + + + + + + + + + + + + + + + + + + r1 + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r2 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r3 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r3 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r3 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + copy + + + + + + + + + + + + + + + + + + + + + + + copy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/R3-node.png b/Java-base/directory-mavibot/src/mavibot/img/R3-node.png new file mode 100644 index 000000000..12bba835f Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/R3-node.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/R4-node-bis.png b/Java-base/directory-mavibot/src/mavibot/img/R4-node-bis.png new file mode 100644 index 000000000..35a265430 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/R4-node-bis.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/R4-node.graphml b/Java-base/directory-mavibot/src/mavibot/img/R4-node.graphml new file mode 100644 index 000000000..9f0f1a62f --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/R4-node.graphml @@ -0,0 +1,1121 @@ + + + + + + + + + + + + + + + + + + + + + + + + r1 + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r2 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r3 + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + + r3 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r3 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + + I + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vI + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + copy + + + + + + + + + + + + + + + + + + + + + + + copy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + copy + + + + + + + + + + + + copy + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/R4-node.png b/Java-base/directory-mavibot/src/mavibot/img/R4-node.png new file mode 100644 index 000000000..5791b7820 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/R4-node.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/RMHeader.graphml b/Java-base/directory-mavibot/src/mavibot/img/RMHeader.graphml new file mode 100644 index 000000000..784ac8e62 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/RMHeader.graphml @@ -0,0 +1,854 @@ + + + + + + + + + + + + + + + + + + + + + + + RecordManager Header + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NbTree + + + + + + + + + + PageSize + + + + + + + + + + FirstFreePage + + + + + + + + + + Current B-tree of B-trees offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Previous B-tree of B-trees offset + + + + + + + + + + Current Copied Pages B-tree offset + + + + + + + + + + Current Copied Pages B-tree offset + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/RMHeader.png b/Java-base/directory-mavibot/src/mavibot/img/RMHeader.png new file mode 100644 index 000000000..937969db3 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/RMHeader.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/UC1.2.1.graphml b/Java-base/directory-mavibot/src/mavibot/img/UC1.2.1.graphml new file mode 100644 index 000000000..07b43243b --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/UC1.2.1.graphml @@ -0,0 +1,1335 @@ + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + G + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + G + + + + + + + + + + + + + + + + H + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + vG + + + + + + + + + + + + + + + + + vH + + + + + + + + + + + + + + + + + F + + + + + + + + + + + + + + + + Delete(G) + + + + + + + + + + + + + + + + + UC1.2.1 : The page has N/2 elements, sibling on the left with more than N/2 elements + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + J + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + M + + + + + + + + + + + + + + + + z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + J + + + + + + + + + + + + + + + + K + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vJ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vK + + + + + + + + + + + + + + + + + L + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vF + + + + + + + + + + + + + + + + + vL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/UC1.2.1.png b/Java-base/directory-mavibot/src/mavibot/img/UC1.2.1.png new file mode 100644 index 000000000..9e3d75086 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/UC1.2.1.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/UC1.2.2.graphml b/Java-base/directory-mavibot/src/mavibot/img/UC1.2.2.graphml new file mode 100644 index 000000000..d222948e2 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/UC1.2.2.graphml @@ -0,0 +1,1778 @@ + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + F + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete(D) + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + F + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + vF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + after deletion + + + + + + + + + + + + + + + + + UC1.2 : The page has more than N/2 elements, the key is the first one + + + + + + + + + + + + + + + + + vF + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Resulting tree + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/UC1.2.2.png b/Java-base/directory-mavibot/src/mavibot/img/UC1.2.2.png new file mode 100644 index 000000000..47378ecad Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/UC1.2.2.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/UC11.graphml b/Java-base/directory-mavibot/src/mavibot/img/UC11.graphml new file mode 100644 index 000000000..895b5ed1f --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/UC11.graphml @@ -0,0 +1,1361 @@ + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + + vC + + + + + + + + + + + + + + + + + Delete(C) + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vA + + + + + + + + + + + + + + + + + r5 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r4 + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vB + + + + + + + + + + + + + + + + + vD + + + + + + + + + + + + + + + + + vE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + after deletion + + + + + + + + + + + + + + + + + UC1.1 : The page has more than N/2 elements, The key is not the first one + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Resulting tree + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/UC11.png b/Java-base/directory-mavibot/src/mavibot/img/UC11.png new file mode 100644 index 000000000..8133c0fc9 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/UC11.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/abstractPage.graphml b/Java-base/directory-mavibot/src/mavibot/img/abstractPage.graphml new file mode 100644 index 000000000..c0f709582 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/abstractPage.graphml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + AbstractPage + + + + + + + + + + + K[0..N] keys + + + + + + + + + + + + + + + + + BTree btree + + + + + + + + + + + + + + + + + long PageID + + + + + + + + + + + + + + + + + long revision + + + + + + + + + + + + + + + + + int nbElems + + + + + + + + + + + + + + + + + long PhysicalID + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/abstractPage.png b/Java-base/directory-mavibot/src/mavibot/img/abstractPage.png new file mode 100644 index 000000000..7c6668eb1 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/abstractPage.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/btreeHeader.graphml b/Java-base/directory-mavibot/src/mavibot/img/btreeHeader.graphml new file mode 100644 index 000000000..3bcdca649 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/btreeHeader.graphml @@ -0,0 +1,986 @@ + + + + + + + + + + + + + + + + + + + + + + + BTree Header + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NbElems + + + + + + + + + + Revision + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RootPageOffset + + + + + + + + + + BTreeInfo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTreePageSize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTree name + + + + + + + + + + + + + + + + + + + + + + + + + + + xyz... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KeySerializer + + + + + + + + + + + + + + + + + + + + + + + + + + + xyz... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ValueSerializer + + + + + + + + + + + + + + + + + + + + + + + + + + + xyz... + + + + + + + + + + + + + + + + + + + + + + + + + + dupsAllowed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTreeInfoOffset + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/btreeHeader.png b/Java-base/directory-mavibot/src/mavibot/img/btreeHeader.png new file mode 100644 index 000000000..2bc53ae36 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/btreeHeader.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/datastructure.graphml b/Java-base/directory-mavibot/src/mavibot/img/datastructure.graphml new file mode 100644 index 000000000..cae19df22 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/datastructure.graphml @@ -0,0 +1,2824 @@ + + + + + + + + + + + + + + + + + + + + + + + Page + + + + + + + + + + + Node + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Revision + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[0] + + + + + + + + + + ... + + + + + + + + + + + + + + + + + Leaf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Revision + + + + + + + + + + ... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[0] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[nbElems - 1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[nbElems - 1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[0] + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[0] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[nbElems - 1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[nbElems - 1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[nbElems] + + + + + + + + + + NbElems + + + + + + + + + + NbElems + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataSize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataSize + + + + + + + + + + BTree Header + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NbElems + + + + + + + + + + Revision + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RootPageOffset + + + + + + + + + + BTreeInfo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTreePageSize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTree name + + + + + + + + + + + + + + + + + + + + + + + + + + + xyz... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KeySerializer + + + + + + + + + + + + + + + + + + + + + + + + + + + xyz... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ValueSerializer + + + + + + + + + + + + + + + + + + + + + + + + + + + xyz... + + + + + + + + + + + + + + + + + + + + + + + + + + dupsAllowed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTreeInfoOffset + + + + + + + + + + RecordManager Header + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NbTree + + + + + + + + + + PageSize + + + + + + + + + + FirstFreePage + + + + + + + + + + Current B-tree of B-trees offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Previous B-tree of B-trees offset + + + + + + + + + + Current Copied Pages B-tree offset + + + + + + + + + + Current Copied Pages B-tree offset + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/datastructure.png b/Java-base/directory-mavibot/src/mavibot/img/datastructure.png new file mode 100644 index 000000000..524682968 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/datastructure.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/leaves.graphml b/Java-base/directory-mavibot/src/mavibot/img/leaves.graphml new file mode 100644 index 000000000..dbc796ae9 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/leaves.graphml @@ -0,0 +1,1173 @@ + + + + + + + + + + + + + + + + + + + + + + + Leaf with N/2 keys and values + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Leaf with N keys and values + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Root Leaf with one key and value + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Root Leaf with no key or value + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/leaves.png b/Java-base/directory-mavibot/src/mavibot/img/leaves.png new file mode 100644 index 000000000..86a72bd6e Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/leaves.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/nodeLeaf.graphml b/Java-base/directory-mavibot/src/mavibot/img/nodeLeaf.graphml new file mode 100644 index 000000000..820f3458b --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/nodeLeaf.graphml @@ -0,0 +1,2661 @@ + + + + + + + + + + + + + + + + + + + + + + + Leaf Value sub-btree + + + + + + + + + + Leaf Value array + + + + + + + + + + Page + + + + + + + + + + + Node + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Revision + + + + + + + + + + ... + + + + + + + + + + + + + + + + + Leaf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Revision + + + + + + + + + + ... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Data (if size > 0) + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[0] + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[0] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[nbElems - 1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[nbElems - 1] + + + + + + + + + + + + + + + + + + + + + + + + + + + Value[nbElems] + + + + + + + + + + NbElems < 0 + + + + + + + + + + NbElems > 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataSize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataSize + + + + + + + + + + empty root page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Revision + + + + + + + + + + NbElems = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SubBTree offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PageRef[0] offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PageRef[0] last offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NbValues > 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[0] length + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[0] (if length > 0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PageRef[1] offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PageRef[1] last offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[1] length + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[1] (if length > 0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PageRef[NbElems - 1] offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PageRef[NbElems - 1] last offset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[n-1] length + + + + + + + + + + + + + + + + + + + + + + + + + + + Key[n-1] (if length > 0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Data size + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NbValues < 0 + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/nodeLeaf.png b/Java-base/directory-mavibot/src/mavibot/img/nodeLeaf.png new file mode 100644 index 000000000..6a61b20f1 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/nodeLeaf.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/nodes.graphml b/Java-base/directory-mavibot/src/mavibot/img/nodes.graphml new file mode 100644 index 000000000..0ef2aa036 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/nodes.graphml @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + Node with N/2 keys + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Node with N keys + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Root Node with one key + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/nodes.png b/Java-base/directory-mavibot/src/mavibot/img/nodes.png new file mode 100644 index 000000000..19dbb7d98 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/nodes.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/trans1.graphml b/Java-base/directory-mavibot/src/mavibot/img/trans1.graphml new file mode 100644 index 000000000..e1e400fd3 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/trans1.graphml @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + r 7 + + + + + + + + + + + + + + + + B-tree A + + + + + + + + + + + + + + + + r 14 + + + + + + + + + + + + + + + + B-tree B + + + + + + + + + + + + + + + + r 3 + + + + + + + + + + + + + + + + B-tree C + + + + + + + + + + + + + + + + r 25 + + + + + + + + + + + + + + + + BOB + + + + + + + + + + + + + + + + RMHeader + + + + + + + + + + + + + + + + currentBOB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/trans1.png b/Java-base/directory-mavibot/src/mavibot/img/trans1.png new file mode 100644 index 000000000..e1a581d1c Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/trans1.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/trans2.graphml b/Java-base/directory-mavibot/src/mavibot/img/trans2.graphml new file mode 100644 index 000000000..0ac569e77 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/trans2.graphml @@ -0,0 +1,407 @@ + + + + + + + + + + + + + + + + + + + + + + + r 7 + + + + + + + + + + + + + + + + B-tree A + + + + + + + + + + + + + + + + r 14 + + + + + + + + + + + + + + + + B-tree B + + + + + + + + + + + + + + + + r 3 + + + + + + + + + + + + + + + + B-tree C + + + + + + + + + + + + + + + + r 25 + + + + + + + + + + + + + + + + BOB + + + + + + + + + + + + + + + + RMHeader + + + + + + + + + + + + + + + + currentBOB + + + + + + + + + + + + + + + + r 8 + + + + + + + + + + + + + + + + B-tree A + + + + + + + + + + + + + + + + r 15 + + + + + + + + + + + + + + + + B-tree B + + + + + + + + + + + + + + + + r 27 + + + + + + + + + + + + + + + + BOB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/trans2.png b/Java-base/directory-mavibot/src/mavibot/img/trans2.png new file mode 100644 index 000000000..3577130ad Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/trans2.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/transko.graphml b/Java-base/directory-mavibot/src/mavibot/img/transko.graphml new file mode 100644 index 000000000..0b94f2cf1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/transko.graphml @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + r 7 + + + + + + + + + + + + + + + + B-tree A + + + + + + + + + + + + + + + + r 14 + + + + + + + + + + + + + + + + B-tree B + + + + + + + + + + + + + + + + r 3 + + + + + + + + + + + + + + + + B-tree C + + + + + + + + + + + + + + + + r 25 + + + + + + + + + + + + + + + + BOB + + + + + + + + + + + + + + + + RMHeader + + + + + + + + + + + + + + + + currentBOB + + + + + + + + + + + + + + + + r 8 + + + + + + + + + + + + + + + + B-tree A + + + + + + + + + + + + + + + + r 15 + + + + + + + + + + + + + + + + B-tree B + + + + + + + + + + + + + + + + r 27 + + + + + + + + + + + + + + + + BOB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/transko.png b/Java-base/directory-mavibot/src/mavibot/img/transko.png new file mode 100644 index 000000000..9bae17ac8 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/transko.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/img/transok.graphml b/Java-base/directory-mavibot/src/mavibot/img/transok.graphml new file mode 100644 index 000000000..0ab932103 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/img/transok.graphml @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + + + + + + + + + r 7 + + + + + + + + + + + + + + + + B-tree A + + + + + + + + + + + + + + + + r 14 + + + + + + + + + + + + + + + + B-tree B + + + + + + + + + + + + + + + + r 3 + + + + + + + + + + + + + + + + B-tree C + + + + + + + + + + + + + + + + r 25 + + + + + + + + + + + + + + + + BOB + + + + + + + + + + + + + + + + RMHeader + + + + + + + + + + + + + + + + currentBOB + + + + + + + + + + + + + + + + r 8 + + + + + + + + + + + + + + + + B-tree A + + + + + + + + + + + + + + + + r 15 + + + + + + + + + + + + + + + + B-tree B + + + + + + + + + + + + + + + + r 27 + + + + + + + + + + + + + + + + BOB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/img/transok.png b/Java-base/directory-mavibot/src/mavibot/img/transok.png new file mode 100644 index 000000000..8ac423ff9 Binary files /dev/null and b/Java-base/directory-mavibot/src/mavibot/img/transok.png differ diff --git a/Java-base/directory-mavibot/src/mavibot/pom.xml b/Java-base/directory-mavibot/src/mavibot/pom.xml new file mode 100644 index 000000000..bcd87efc4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/pom.xml @@ -0,0 +1,108 @@ + + + + 4.0.0 + + + org.apache.directory.mavibot + mavibot-parent + 1.0.0-M9-SNAPSHOT + + + mavibot + ApacheDS MVCC BTree implementation + bundle + + A MVCC BTree Implementation + + + + junit + junit + test + + + + commons-collections + commons-collections + ${commons.collections.version} + + + + commons-io + commons-io + ${commons.io.version} + test + + + + + + org.slf4j + slf4j-api + + + + org.slf4j + slf4j-log4j12 + ${slf4j.log4j12.version} + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + META-INF/MANIFEST.MF + false + + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.mavibot + + org.apache.directory.mavibot.btree;version=${project.version};-noimport:=true, + org.apache.directory.mavibot.btree.comparator;version=${project.version};-noimport:=true, + org.apache.directory.mavibot.btree.exception;version=${project.version};-noimport:=true, + org.apache.directory.mavibot.btree.memory;version=${project.version};-noimport:=true, + org.apache.directory.mavibot.btree.persisted;version=${project.version};-noimport:=true, + org.apache.directory.mavibot.btree.serializer;version=${project.version};-noimport:=true, + org.apache.directory.mavibot.btree.util;version=${project.version};-noimport:=true + + + + + + + + + diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractBTree.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractBTree.java new file mode 100644 index 000000000..7b50c04df --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractBTree.java @@ -0,0 +1,1119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.directory.mavibot.btree.exception.BTreeCreationException; +import org.apache.directory.mavibot.btree.exception.DuplicateValueNotAllowedException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * A BTree abstract class containing the methods shared by the PersistedBTree or the InMemoryBTree + * implementations. + * + * @param The Key type + * @param The Value type + * + * @author Apache Directory Project + */ +/* No qualifier*/abstract class AbstractBTree implements BTree +{ + /** The read transaction timeout */ + protected long readTimeOut = DEFAULT_READ_TIMEOUT; + + /** The current Header for a managed BTree */ + protected BTreeHeader currentBtreeHeader; + + /** The Key serializer used for this tree.*/ + protected ElementSerializer keySerializer; + + /** The Value serializer used for this tree. */ + protected ElementSerializer valueSerializer; + + /** The list of read transactions being executed */ + protected ConcurrentLinkedQueue> readTransactions; + + /** The size of the buffer used to write data in disk */ + protected int writeBufferSize; + + /** Flag to enable duplicate key support */ + protected boolean allowDuplicates; + + /** The number of elements in a page for this B-tree */ + protected int pageSize; + + /** The BTree name */ + protected String name; + + /** The FQCN of the Key serializer */ + protected String keySerializerFQCN; + + /** The FQCN of the Value serializer */ + protected String valueSerializerFQCN; + + /** The thread responsible for the cleanup of timed out reads */ + protected Thread readTransactionsThread; + + /** The BTree type : either in-memory, disk backed or persisted */ + protected BTreeTypeEnum btreeType; + + /** The current transaction */ + protected AtomicBoolean transactionStarted = new AtomicBoolean( false ); + + /** The map of all the used BtreeHeaders */ + protected Map> btreeRevisions = new ConcurrentHashMap>(); + + /** The current revision */ + protected AtomicLong currentRevision = new AtomicLong( 0L ); + + /** The TransactionManager used for this BTree */ + protected TransactionManager transactionManager; + + /** The size of the stack to use to manage tree searches */ + private final static int MAX_STACK_DEPTH = 32; + + + /** + * Starts a Read Only transaction. If the transaction is not closed, it will be + * automatically closed after the timeout + * + * @return The created transaction + */ + protected abstract ReadTransaction beginReadTransaction(); + + + /** + * Starts a Read Only transaction. If the transaction is not closed, it will be + * automatically closed after the timeout + * + * @return The created transaction + */ + protected abstract ReadTransaction beginReadTransaction( long revision ); + + + /** + * {@inheritDoc} + */ + public TupleCursor browse() throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction(); + + if ( transaction == null ) + { + return new EmptyTupleCursor(); + } + else + { + ParentPos[] stack = ( ParentPos[] ) Array.newInstance( ParentPos.class, MAX_STACK_DEPTH ); + + TupleCursor cursor = getRootPage().browse( transaction, stack, 0 ); + + // Set the position before the first element + cursor.beforeFirst(); + + return cursor; + } + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browse( long revision ) throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction( revision ); + + if ( transaction == null ) + { + return new EmptyTupleCursor(); + } + else + { + ParentPos[] stack = ( ParentPos[] ) Array.newInstance( ParentPos.class, MAX_STACK_DEPTH ); + + // And get the cursor + TupleCursor cursor = getRootPage( transaction.getRevision() ).browse( transaction, stack, 0 ); + + return cursor; + } + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browseFrom( K key ) throws IOException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction(); + + ParentPos[] stack = ( ParentPos[] ) Array.newInstance( ParentPos.class, MAX_STACK_DEPTH ); + + try + { + TupleCursor cursor = getRootPage( transaction.getRevision() ).browse( key, transaction, stack, 0 ); + + return cursor; + } + catch ( KeyNotFoundException e ) + { + throw new IOException( e.getMessage() ); + } + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browseFrom( long revision, K key ) throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction( revision ); + + if ( transaction == null ) + { + return new EmptyTupleCursor(); + } + else + { + ParentPos[] stack = ( ParentPos[] ) Array.newInstance( ParentPos.class, MAX_STACK_DEPTH ); + + // And get the cursor + TupleCursor cursor = getRootPage( transaction.getRevision() ).browse( key, transaction, stack, 0 ); + + return cursor; + } + } + + + /** + * {@inheritDoc} + */ + public boolean contains( K key, V value ) throws IOException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction(); + + if ( transaction == null ) + { + return false; + } + else + { + try + { + return getRootPage( transaction.getRevision() ).contains( key, value ); + } + catch ( KeyNotFoundException knfe ) + { + throw new IOException( knfe.getMessage() ); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + public boolean contains( long revision, K key, V value ) throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + // Fetch the root page for this revision + ReadTransaction transaction = beginReadTransaction( revision ); + + if ( transaction == null ) + { + return false; + } + else + { + try + { + return getRootPage( transaction.getRevision() ).contains( key, value ); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + public Tuple delete( K key ) throws IOException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + if ( key == null ) + { + throw new IllegalArgumentException( "Key must not be null" ); + } + + // Take the lock if it's not already taken by another thread + transactionManager.beginTransaction(); + + try + { + Tuple deleted = delete( key, currentRevision.get() + 1 ); + + // Commit now + transactionManager.commit(); + + return deleted; + } + catch ( IOException ioe ) + { + // We have had an exception, we must rollback the transaction + transactionManager.rollback(); + + return null; + } + } + + + /** + * {@inheritDoc} + */ + public Tuple delete( K key, V value ) throws IOException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + if ( key == null ) + { + throw new IllegalArgumentException( "Key must not be null" ); + } + + if ( value == null ) + { + throw new IllegalArgumentException( "Value must not be null" ); + } + + transactionManager.beginTransaction(); + + try + { + Tuple deleted = delete( key, value, currentRevision.get() + 1 ); + + transactionManager.commit(); + + return deleted; + } + catch ( IOException ioe ) + { + transactionManager.rollback(); + + throw ioe; + } + } + + + /** + * Delete the entry which key is given as a parameter. If the entry exists, it will + * be removed from the tree, the old tuple will be returned. Otherwise, null is returned. + * + * @param key The key for the entry we try to remove + * @return A Tuple containing the removed entry, or null if it's not found. + */ + /*no qualifier*/Tuple delete( K key, long revision ) throws IOException + { + return delete( key, null, revision ); + } + + + /*no qualifier*/abstract Tuple delete( K key, V value, long revision ) throws IOException; + + + /** + * {@inheritDoc} + */ + public V insert( K key, V value ) throws IOException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + V existingValue = null; + + if ( key == null ) + { + throw new IllegalArgumentException( "Key must not be null" ); + } + + // Take the lock if it's not already taken by another thread and if we + // aren't on a sub-btree + if ( btreeType != BTreeTypeEnum.PERSISTED_SUB ) + { + transactionManager.beginTransaction(); + } + + try + { + InsertResult result = insert( key, value, -1L ); + + if ( result instanceof ExistsResult ) + { + existingValue = value; + } + else if ( result instanceof ModifyResult ) + { + existingValue = ( ( ModifyResult ) result ).getModifiedValue(); + } + + // Commit now if it's not a sub-btree + if ( btreeType != BTreeTypeEnum.PERSISTED_SUB ) + { + //FIXME when result type is ExistsResult then we should avoid writing the headers + transactionManager.commit(); + } + + return existingValue; + } + catch ( IOException ioe ) + { + // We have had an exception, we must rollback the transaction + // if it's not a sub-btree + if ( btreeType != BTreeTypeEnum.PERSISTED_SUB ) + { + transactionManager.rollback(); + } + + return null; + } + catch ( DuplicateValueNotAllowedException e ) + { + // We have had an exception, we must rollback the transaction + // if it's not a sub-btree + if ( btreeType != BTreeTypeEnum.PERSISTED_SUB ) + { + transactionManager.rollback(); + } + + throw e; + } + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */abstract InsertResult insert( K key, V value, long revision ) throws IOException; + + + /** + * Flush the latest revision to disk. We will replace the current file by the new one, as + * we flush in a temporary file. + */ + public void flush() throws IOException + { + } + + + /** + * {@inheritDoc} + */ + public V get( K key ) throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction(); + + if ( transaction == null ) + { + return null; + } + else + { + try + { + return getRootPage( transaction.getRevision() ).get( key ); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + public V get( long revision, K key ) throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction( revision ); + + if ( transaction == null ) + { + return null; + } + else + { + try + { + return getRootPage( transaction.getRevision() ).get( key ); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + public abstract Page getRootPage(); + + + /** + * {@inheritDoc} + */ + /* no qualifier */abstract void setRootPage( Page root ); + + + /** + * {@inheritDoc} + */ + public ValueCursor getValues( K key ) throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction(); + + if ( transaction == null ) + { + return new EmptyValueCursor( 0L ); + } + else + { + try + { + return getRootPage( transaction.getRevision() ).getValues( key ); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + public boolean hasKey( K key ) throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + if ( key == null ) + { + return false; + } + + ReadTransaction transaction = beginReadTransaction(); + + if ( transaction == null ) + { + return false; + } + else + { + try + { + return getRootPage( transaction.getRevision() ).hasKey( key ); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + public boolean hasKey( long revision, K key ) throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + if ( key == null ) + { + return false; + } + + ReadTransaction transaction = beginReadTransaction( revision ); + + if ( transaction == null ) + { + return false; + } + else + { + try + { + return getRootPage( transaction.getRevision() ).hasKey( key ); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + public ElementSerializer getKeySerializer() + { + return keySerializer; + } + + + /** + * {@inheritDoc} + */ + public void setKeySerializer( ElementSerializer keySerializer ) + { + this.keySerializer = keySerializer; + keySerializerFQCN = keySerializer.getClass().getName(); + } + + + /** + * {@inheritDoc} + */ + public String getKeySerializerFQCN() + { + return keySerializerFQCN; + } + + + /** + * {@inheritDoc} + */ + public ElementSerializer getValueSerializer() + { + return valueSerializer; + } + + + /** + * {@inheritDoc} + */ + public void setValueSerializer( ElementSerializer valueSerializer ) + { + this.valueSerializer = valueSerializer; + valueSerializerFQCN = valueSerializer.getClass().getName(); + } + + + /** + * {@inheritDoc} + */ + public String getValueSerializerFQCN() + { + return valueSerializerFQCN; + } + + + /** + * {@inheritDoc} + */ + public long getRevision() + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction(); + + if ( transaction == null ) + { + return -1L; + } + else + { + try + { + return transaction.getRevision(); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */void setRevision( long revision ) + { + transactionManager.getBTreeHeader( getName() ).setRevision( revision ); + } + + + /** + * Store the new revision in the map of btrees, increment the current revision + */ + protected void storeRevision( BTreeHeader btreeHeader, boolean keepRevisions ) + { + long revision = btreeHeader.getRevision(); + + if ( keepRevisions ) + { + synchronized ( btreeRevisions ) + { + btreeRevisions.put( revision, btreeHeader ); + } + } + + currentRevision.set( revision ); + currentBtreeHeader = btreeHeader; + + // And update the newBTreeHeaders map + if ( btreeHeader.getBtree().getType() != BTreeTypeEnum.PERSISTED_SUB ) + { + transactionManager.updateNewBTreeHeaders( btreeHeader ); + } + } + + + /** + * Store the new revision in the map of btrees, increment the current revision + */ + protected void storeRevision( BTreeHeader btreeHeader ) + { + long revision = btreeHeader.getRevision(); + + synchronized ( btreeRevisions ) + { + btreeRevisions.put( revision, btreeHeader ); + } + + currentRevision.set( revision ); + currentBtreeHeader = btreeHeader; + + // And update the newBTreeHeaders map + if ( btreeHeader.getBtree().getType() != BTreeTypeEnum.PERSISTED_SUB ) + { + transactionManager.updateNewBTreeHeaders( btreeHeader ); + } + } + + + /** + * {@inheritDoc} + */ + public long getReadTimeOut() + { + return readTimeOut; + } + + + /** + * {@inheritDoc} + */ + public void setReadTimeOut( long readTimeOut ) + { + this.readTimeOut = readTimeOut; + } + + + /** + * {@inheritDoc} + */ + public long getNbElems() + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a transactionLManager" ); + } + + ReadTransaction transaction = beginReadTransaction(); + + if ( transaction == null ) + { + return -1L; + } + else + { + try + { + return transaction.getBtreeHeader().getNbElems(); + } + finally + { + transaction.close(); + } + } + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */void setNbElems( long nbElems ) + { + transactionManager.getBTreeHeader( getName() ).setNbElems( nbElems ); + } + + + /** + * {@inheritDoc} + */ + public int getPageSize() + { + return pageSize; + } + + + /** + * {@inheritDoc} + */ + public void setPageSize( int pageSize ) + { + if ( pageSize <= 2 ) + { + this.pageSize = DEFAULT_PAGE_SIZE; + } + else + { + this.pageSize = getPowerOf2( pageSize ); + } + } + + + /** + * {@inheritDoc} + */ + public String getName() + { + return name; + } + + + /** + * {@inheritDoc} + */ + public void setName( String name ) + { + this.name = name; + } + + + /** + * {@inheritDoc} + */ + public Comparator getKeyComparator() + { + return keySerializer.getComparator(); + } + + + /** + * {@inheritDoc} + */ + public Comparator getValueComparator() + { + return valueSerializer.getComparator(); + } + + + /** + * {@inheritDoc} + */ + public int getWriteBufferSize() + { + return writeBufferSize; + } + + + /** + * {@inheritDoc} + */ + public void setWriteBufferSize( int writeBufferSize ) + { + this.writeBufferSize = writeBufferSize; + } + + + /** + * {@inheritDoc} + */ + public boolean isAllowDuplicates() + { + return allowDuplicates; + } + + + /** + * {@inheritDoc} + */ + public void setAllowDuplicates( boolean allowDuplicates ) + { + this.allowDuplicates = allowDuplicates; + } + + + /** + * {@inheritDoc} + */ + public BTreeTypeEnum getType() + { + return btreeType; + } + + + /** + * @param type the type to set + */ + public void setType( BTreeTypeEnum type ) + { + this.btreeType = type; + } + + + /** + * Gets the number which is a power of 2 immediately above the given positive number. + */ + private int getPowerOf2( int size ) + { + int newSize = --size; + newSize |= newSize >> 1; + newSize |= newSize >> 2; + newSize |= newSize >> 4; + newSize |= newSize >> 8; + newSize |= newSize >> 16; + newSize++; + + return newSize; + } + + + /** + * @return The current BtreeHeader + */ + protected BTreeHeader getBtreeHeader() + { + return currentBtreeHeader; + } + + + /** + * @return The current BtreeHeader + */ + protected BTreeHeader getBtreeHeader( long revision ) + { + return btreeRevisions.get( revision ); + } + + + /** + * {@inheritDoc} + */ + public KeyCursor browseKeys() throws IOException, KeyNotFoundException + { + // Check that we have a TransactionManager + if ( transactionManager == null ) + { + throw new BTreeCreationException( "We don't have a Transaction Manager" ); + } + + ReadTransaction transaction = beginReadTransaction(); + + if ( transaction == null ) + { + return new KeyCursor(); + } + else + { + ParentPos[] stack = ( ParentPos[] ) Array.newInstance( ParentPos.class, MAX_STACK_DEPTH ); + + KeyCursor cursor = getRootPage().browseKeys( transaction, stack, 0 ); + + // Set the position before the first element + cursor.beforeFirst(); + + return cursor; + } + } + + + /** + * Create a thread that is responsible of cleaning the transactions when + * they hit the timeout + */ + /*no qualifier*/void createTransactionManager() + { + Runnable readTransactionTask = new Runnable() + { + public void run() + { + try + { + ReadTransaction transaction = null; + + while ( !Thread.currentThread().isInterrupted() ) + { + long timeoutDate = System.currentTimeMillis() - readTimeOut; + long t0 = System.currentTimeMillis(); + int nbTxns = 0; + + // Loop on all the transactions from the queue + while ( ( transaction = readTransactions.peek() ) != null ) + { + nbTxns++; + + if ( transaction.isClosed() ) + { + // The transaction is already closed, remove it from the queue + readTransactions.poll(); + continue; + } + + // Check if the transaction has timed out + if ( transaction.getCreationDate() < timeoutDate ) + { + transaction.close(); + readTransactions.poll(); + + synchronized ( btreeRevisions ) + { + btreeRevisions.remove( transaction.getRevision() ); + } + + continue; + } + + // We need to stop now + break; + } + + long t1 = System.currentTimeMillis(); + + // Wait until we reach the timeout + Thread.sleep( readTimeOut ); + } + } + catch ( InterruptedException ie ) + { + //System.out.println( "Interrupted" ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + }; + + readTransactionsThread = new Thread( readTransactionTask ); + readTransactionsThread.setDaemon( true ); + readTransactionsThread.start(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractBorrowedFromSiblingResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractBorrowedFromSiblingResult.java new file mode 100644 index 000000000..c29fcf781 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractBorrowedFromSiblingResult.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.directory.mavibot.btree; + + +import java.util.List; + + +/** + * The result of a delete operation, when the child has not been merged, and when + * we have borrowed an element from the left sibling. It contains the + * reference to the modified page, and the removed element. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier*/abstract class AbstractBorrowedFromSiblingResult extends AbstractDeleteResult implements + BorrowedFromSiblingResult +{ + /** The modified sibling reference */ + private Page modifiedSibling; + + /** Tells if the sibling is the left or right one */ + protected SiblingPosition position; + + /** The two possible position for the sibling */ + protected enum SiblingPosition + { + LEFT, + RIGHT + } + + + /** + * The default constructor for RemoveResult. + * + * @param modifiedPage The modified page + * @param modifiedSibling The modified sibling + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + /* No qualifier*/AbstractBorrowedFromSiblingResult( Page modifiedPage, Page modifiedSibling, + Tuple removedElement, SiblingPosition position ) + { + super( modifiedPage, removedElement ); + this.modifiedSibling = modifiedSibling; + this.position = position; + } + + + /** + * A constructor for RemoveResult with a list of copied pages. + * + * @param copiedPages the list of copied pages + * @param modifiedPage The modified page + * @param modifiedSibling The modified sibling + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + /* No qualifier*/AbstractBorrowedFromSiblingResult( List> copiedPages, Page modifiedPage, + Page modifiedSibling, + Tuple removedElement, SiblingPosition position ) + { + super( copiedPages, modifiedPage, removedElement ); + this.modifiedSibling = modifiedSibling; + this.position = position; + } + + + /** + * {@inheritDoc} + */ + public Page getModifiedSibling() + { + return modifiedSibling; + } + + + /** + * {@inheritDoc} + */ + public boolean isFromLeft() + { + return position == SiblingPosition.LEFT; + } + + + /** + * {@inheritDoc} + */ + public boolean isFromRight() + { + return position == SiblingPosition.RIGHT; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "\n removed element : " ).append( getRemovedElement() ); + sb.append( "\n modifiedPage : " ).append( getModifiedPage() ); + sb.append( "\n modifiedSibling : " ).append( getModifiedSibling() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractDeleteResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractDeleteResult.java new file mode 100644 index 000000000..62b197466 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractDeleteResult.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.List; + + +/** + * An abstract class to gather common elements of the DeleteResult + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier*/abstract class AbstractDeleteResult extends AbstractResult implements + DeleteResult +{ + /** The modified page reference */ + private Page modifiedPage; + + /** The removed element if the key was found in the tree*/ + private Tuple removedElement; + + + /** + * The default constructor for AbstractDeleteResult. + * + * @param modifiedPage The modified page + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + /*no qualifier*/AbstractDeleteResult( Page modifiedPage, Tuple removedElement ) + { + super(); + this.modifiedPage = modifiedPage; + this.removedElement = removedElement; + } + + + /** + * The default constructor for AbstractDeleteResult. + * + * @param copiedPages the list of copied pages + * @param modifiedPage The modified page + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + /*no qualifier*/AbstractDeleteResult( List> copiedPages, Page modifiedPage, + Tuple removedElement ) + { + super( copiedPages ); + this.modifiedPage = modifiedPage; + this.removedElement = removedElement; + } + + + /** + * {@inheritDoc} + */ + public Page getModifiedPage() + { + return modifiedPage; + } + + + /** + * {@inheritDoc} + */ + public Tuple getRemovedElement() + { + return removedElement; + } + + + /** + * @param modifiedPage the modifiedPage to set + */ + /*no qualifier*/void setModifiedPage( Page modifiedPage ) + { + this.modifiedPage = modifiedPage; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractPage.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractPage.java new file mode 100644 index 000000000..3163ff1e2 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractPage.java @@ -0,0 +1,699 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; + + +/** + * A MVCC abstract Page. It stores the field and the methods shared by the Node and Leaf + * classes. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier*/abstract class AbstractPage implements Page +{ + /** Parent B+Tree. */ + protected transient BTree btree; + + /** Keys of children nodes */ + protected KeyHolder[] keys; + + /** Children pages associated with keys. */ + protected PageHolder[] children; + + /** The number of current values in the Page */ + protected int nbElems; + + /** This BPage's revision */ + protected long revision; + + /** The first {@link PageIO} storing the serialized Page on disk */ + protected long offset = -1L; + + /** The last {@link PageIO} storing the serialized Page on disk */ + protected long lastOffset = -1L; + + + /** + * Creates a default empty AbstractPage + * + * @param btree The associated BTree + */ + protected AbstractPage( BTree btree ) + { + this.btree = btree; + } + + + /** + * Internal constructor used to create Page instance used when a page is being copied or overflow + */ + @SuppressWarnings("unchecked") + // Cannot create an array of generic objects + protected AbstractPage( BTree btree, long revision, int nbElems ) + { + this.btree = btree; + this.revision = revision; + this.nbElems = nbElems; + this.keys = ( KeyHolder[] ) Array.newInstance( KeyHolder.class, nbElems ); + } + + + /** + * {@inheritDoc} + */ + public int getNbElems() + { + return nbElems; + } + + + /** + * Sets the number of element in this page + * @param nbElems The number of elements + */ + /* no qualifier */void setNbElems( int nbElems ) + { + this.nbElems = nbElems; + } + + + /** + * {@inheritDoc} + */ + public K getKey( int pos ) + { + if ( ( pos < nbElems ) && ( keys[pos] != null ) ) + { + return keys[pos].getKey(); + } + else + { + return null; + } + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean hasKey( K key ) throws IOException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + // Here, if we have found the key in the node, then we must go down into + // the right child, not the left one + return children[-pos].getValue().hasKey( key ); + } + else + { + Page page = children[pos].getValue(); + + return page.hasKey( key ); + } + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */Page getReference( int pos ) throws IOException + { + if ( pos < nbElems + 1 ) + { + if ( children[pos] != null ) + { + return children[pos].getValue(); + } + else + { + return null; + } + } + else + { + return null; + } + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browse( K key, ReadTransaction transaction, ParentPos[] stack, int depth ) + throws IOException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + pos = -pos; + } + + // We first stack the current page + stack[depth++] = new ParentPos( this, pos ); + + Page page = children[pos].getValue(); + + return page.browse( key, transaction, stack, depth ); + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean contains( K key, V value ) throws IOException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + // Here, if we have found the key in the node, then we must go down into + // the right child, not the left one + return children[-pos].getValue().contains( key, value ); + } + else + { + return children[pos].getValue().contains( key, value ); + } + } + + + /** + * {@inheritDoc} + */ + public DeleteResult delete( K key, V value, long revision ) throws IOException + { + return delete( key, value, revision, null, -1 ); + } + + + /** + * The real delete implementation. It can be used for internal deletion in the B-tree. + * + * @param key The key to delete + * @param value The value to delete + * @param revision The revision for which we want to delete a tuple + * @param parent The parent page + * @param parentPos The position of this page in the parent page + * @return The result + * @throws IOException If we had an issue while processing the deletion + */ + /* no qualifier */abstract DeleteResult delete( K key, V value, long revision, Page parent, + int parentPos ) + throws IOException; + + + /** + * {@inheritDoc} + */ + public V get( K key ) throws IOException, KeyNotFoundException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + // Here, if we have found the key in the node, then we must go down into + // the right child, not the left one + return children[-pos].getValue().get( key ); + } + else + { + return children[pos].getValue().get( key ); + } + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */Page getPage( int pos ) + { + if ( ( pos >= 0 ) && ( pos < children.length ) ) + { + if ( children[pos] != null ) + { + return children[pos].getValue(); + } + else + { + return null; + } + } + else + { + return null; + } + } + + + /** + * Inject a pageHolder into the node, at a given position + * + * @param pos The position of the added pageHolder + * @param pageHolder The pageHolder to add + */ + /* no qualifier */void setPageHolder( int pos, PageHolder pageHolder ) + { + if ( ( pos >= 0 ) && ( pos < children.length ) ) + { + children[pos] = pageHolder; + } + } + + + /** + * {@inheritDoc} + */ + @Override + public ValueCursor getValues( K key ) throws KeyNotFoundException, IOException, IllegalArgumentException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + // Here, if we have found the key in the node, then we must go down into + // the right child, not the left one + return children[-pos].getValue().getValues( key ); + } + else + { + return children[pos].getValue().getValues( key ); + } + } + + + /** + * Sets the value at a give position + * @param pos The position in the values array + * @param value the value to inject + */ + /* no qualifier */void setValue( int pos, ValueHolder value ) + { + // Implementation in the leaves + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browse( ReadTransaction transaction, ParentPos[] stack, int depth ) + throws IOException + { + stack[depth++] = new ParentPos( this, 0 ); + + Page page = children[0].getValue(); + + return page.browse( transaction, stack, depth ); + } + + + /** + * {@inheritDoc} + */ + public KeyCursor browseKeys( ReadTransaction transaction, ParentPos[] stack, int depth ) + throws IOException + { + stack[depth++] = new ParentPos( this, 0 ); + + Page page = children[0].getValue(); + + return page.browseKeys( transaction, stack, depth ); + } + + + /** + * Selects the sibling (the previous or next page with the same parent) which has + * the more element assuming it's above N/2 + * + * @param parent The parent of the current page + * @param The position of the current page reference in its parent + * @return The position of the sibling, or -1 if we have'nt found any sibling + * @throws IOException If we have an error while trying to access the page + */ + protected int selectSibling( Page parent, int parentPos ) throws IOException + { + if ( parentPos == 0 ) + { + // The current page is referenced on the left of its parent's page : + // we will not have a previous page with the same parent + return 1; + } + + if ( parentPos == parent.getNbElems() ) + { + // The current page is referenced on the right of its parent's page : + // we will not have a next page with the same parent + return parentPos - 1; + } + + Page prevPage = ( ( AbstractPage ) parent ).getPage( parentPos - 1 ); + Page nextPage = ( ( AbstractPage ) parent ).getPage( parentPos + 1 ); + + int prevPageSize = prevPage.getNbElems(); + int nextPageSize = nextPage.getNbElems(); + + if ( prevPageSize >= nextPageSize ) + { + return parentPos - 1; + } + else + { + return parentPos + 1; + } + } + + + /** + * {@inheritDoc} + */ + public K getLeftMostKey() + { + return keys[0].getKey(); + } + + + /** + * {@inheritDoc} + */ + public K getRightMostKey() + { + return keys[nbElems - 1].getKey(); + } + + + /** + * @return the offset of the first {@link PageIO} which stores the Page on disk. + */ + /* no qualifier */long getOffset() + { + return offset; + } + + + /** + * @param offset the offset to set + */ + /* no qualifier */void setOffset( long offset ) + { + this.offset = offset; + } + + + /** + * @return the offset of the last {@link PageIO} which stores the Page on disk. + */ + /* no qualifier */long getLastOffset() + { + return lastOffset; + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */void setLastOffset( long lastOffset ) + { + this.lastOffset = lastOffset; + } + + + /** + * @return the keys + */ + /* no qualifier */KeyHolder[] getKeys() + { + return keys; + } + + + /** + * Sets the key at a give position + * + * @param pos The position in the keys array + * @param key the key to inject + */ + /* no qualifier */void setKey( int pos, KeyHolder key ) + { + keys[pos] = key; + } + + + /** + * @param revision the keys to set + */ + /* no qualifier */void setKeys( KeyHolder[] keys ) + { + this.keys = keys; + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */ValueHolder getValue( int pos ) + { + // Node don't have values. Leaf.getValue() will return the value + return null; + } + + + /** + * @return the revision + */ + public long getRevision() + { + return revision; + } + + + /** + * @param revision the revision to set + */ + /* no qualifier */void setRevision( long revision ) + { + this.revision = revision; + } + + + /** + * Compares two keys + * + * @param key1 The first key + * @param key2 The second key + * @return -1 if the first key is above the second one, 1 if it's below, and 0 + * if the two keys are equal + */ + protected final int compare( K key1, K key2 ) + { + if ( key1 == key2 ) + { + return 0; + } + + if ( key1 == null ) + { + return 1; + } + + if ( key2 == null ) + { + return -1; + } + + return btree.getKeyComparator().compare( key1, key2 ); + } + + + /** + * Finds the position of the given key in the page. If we have found the key, + * we will return its position as a negative value. + *

+ * Assuming that the array is zero-indexed, the returned value will be :
+ * position = - ( position + 1) + *
+ * So for the following table of keys :
+ *

+     * +---+---+---+---+
+     * | b | d | f | h |
+     * +---+---+---+---+
+     *   0   1   2   3
+     * 
+ * looking for 'b' will return -1 (-(0+1)) and looking for 'f' will return -3 (-(2+1)).
+ * Computing the real position is just a matter to get -(position++). + *

+ * If we don't find the key in the table, we will return the position of the key + * immediately above the key we are looking for.
+ * For instance, looking for : + *

    + *
  • 'a' will return 0
  • + *
  • 'b' will return -1
  • + *
  • 'c' will return 1
  • + *
  • 'd' will return -2
  • + *
  • 'e' will return 2
  • + *
  • 'f' will return -3
  • + *
  • 'g' will return 3
  • + *
  • 'h' will return -4
  • + *
  • 'i' will return 4
  • + *
+ * + * + * @param key The key to find + * @return The position in the page. + */ + public int findPos( K key ) + { + // Deal with the special key where we have an empty page + if ( nbElems == 0 ) + { + return 0; + } + + int min = 0; + int max = nbElems - 1; + + // binary search + while ( min < max ) + { + int middle = ( min + max + 1 ) >> 1; + + int comp = compare( keys[middle].getKey(), key ); + + if ( comp < 0 ) + { + min = middle + 1; + } + else if ( comp > 0 ) + { + max = middle - 1; + } + else + { + // Special case : the key already exists, + // we can return immediately. The value will be + // negative, and as the index may be 0, we subtract 1 + return -( middle + 1 ); + } + } + + // Special case : we don't know if the key is present + int comp = compare( keys[max].getKey(), key ); + + if ( comp == 0 ) + { + return -( max + 1 ); + } + else + { + if ( comp < 0 ) + { + return max + 1; + } + else + { + return max; + } + } + } + + + /** + * {@inheritDoc} + */ + public Tuple findLeftMost() throws EndOfFileExceededException, IOException + { + return children[0].getValue().findLeftMost(); + } + + + /** + * {@inheritDoc} + */ + public Tuple findRightMost() throws EndOfFileExceededException, IOException + { + return children[nbElems].getValue().findRightMost(); + } + + + /** + * @return the btree + */ + public BTree getBtree() + { + return btree; + } + + + /** + * {@inheritDoc} + */ + public String dumpPage( String tabs ) + { + StringBuilder sb = new StringBuilder(); + + if ( nbElems > 0 ) + { + // Start with the first child + sb.append( children[0].getValue().dumpPage( tabs + " " ) ); + + for ( int i = 0; i < nbElems; i++ ) + { + sb.append( tabs ); + sb.append( "<" ); + sb.append( getKey( i ) ).append( ">\n" ); + sb.append( children[i + 1].getValue().dumpPage( tabs + " " ) ); + } + } + + return sb.toString(); + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "r" ).append( revision ); + sb.append( ", nbElems:" ).append( nbElems ); + + if ( offset > 0 ) + { + sb.append( ", offset:" ).append( offset ); + } + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractResult.java new file mode 100644 index 000000000..cd11082db --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractResult.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.directory.mavibot.btree; + + +import java.util.ArrayList; +import java.util.List; + + +/** + * An abstract class to gather common elements of the Result classes + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier*/abstract class AbstractResult implements Result> +{ + /** The list of copied page reference */ + private List> copiedPages; + + + /** + * The default constructor for AbstractResult. + * + */ + public AbstractResult() + { + copiedPages = new ArrayList>(); + } + + + /** + * Creates an instance of AbstractResult with an initialized list of copied pages. + * + * @param copiedPages The list of copied pages to store in this result + */ + public AbstractResult( List> copiedPages ) + { + this.copiedPages = copiedPages; + } + + + /** + * {@inheritDoc} + */ + public List> getCopiedPages() + { + return copiedPages; + } + + + /** + * {@inheritDoc} + */ + public void addCopiedPage( Page page ) + { + copiedPages.add( page ); + } + + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "\n copiedPage = <" ); + + boolean isFirst = true; + + for ( Page copiedPage : getCopiedPages() ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( ( ( AbstractPage ) copiedPage ).getOffset() ); + } + + sb.append( ">" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractTransactionManager.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractTransactionManager.java new file mode 100644 index 000000000..2798a91a4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractTransactionManager.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +/** + * An abstract class implementing the TransactionManager interface. + * + * @author Apache Directory Project + */ +public abstract class AbstractTransactionManager implements TransactionManager +{ +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractValueHolder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractValueHolder.java new file mode 100644 index 000000000..a22b7ba18 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractValueHolder.java @@ -0,0 +1,441 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Comparator; +import java.util.Iterator; + +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * A holder to store the Values + * + * @author Apache Directory Project + * @param The value type + */ +/* No qualifier*/abstract class AbstractValueHolder implements ValueHolder +{ + /** The BTree storing multiple value, if we have more than one value */ + protected BTree valueBtree; + + /** The array storing from 1 to N values */ + protected V[] valueArray; + + /** The Value serializer */ + protected ElementSerializer valueSerializer; + + /** The configuration for the array <-> BTree switch. Default to 1 */ + protected int valueThresholdUp = 1; + protected int valueThresholdLow = 1; + + protected int nbArrayElems; + + + /** + * {@inheritDoc} + */ + public boolean isSubBtree() + { + return valueBtree != null; + } + + + /** + * Create a clone of this instance + */ + public ValueHolder clone() throws CloneNotSupportedException + { + ValueHolder copy = ( ValueHolder ) super.clone(); + + return copy; + } + + + /** + * @return a cursor on top of the values + */ + public ValueCursor getCursor() + { + if ( valueBtree != null ) + { + return new ValueBTreeCursor( valueBtree ); + } + else + { + return new ValueArrayCursor( valueArray ); + } + } + + + /** + * Find the position of a given value in the array, or the position where we + * would insert the element (in this case, the position will be negative). + * As we use a 0-based array, the negative position for 0 is -1. + * -1 means the element can be added in position 0 + * -2 means the element can be added in position 1 + * ... + */ + private int findPos( V value ) + { + if ( valueArray.length == 0 ) + { + return -1; + } + + // Do a search using dichotomy + int pivot = valueArray.length / 2; + int low = 0; + int high = valueArray.length - 1; + Comparator comparator = valueSerializer.getComparator(); + + while ( high > low ) + { + switch ( high - low ) + { + case 1: + // We have 2 elements + int result = comparator.compare( value, valueArray[pivot] ); + + if ( result == 0 ) + { + return pivot; + } + + if ( result < 0 ) + { + if ( pivot == low ) + { + return -( low + 1 ); + } + else + { + result = comparator.compare( value, valueArray[low] ); + + if ( result == 0 ) + { + return low; + } + + if ( result < 0 ) + { + return -( low + 1 ); + } + else + { + return -( low + 2 ); + } + } + } + else + { + if ( pivot == high ) + { + return -( high + 2 ); + } + else + { + result = comparator.compare( value, valueArray[high] ); + + if ( result == 0 ) + { + return high; + } + + if ( result < 0 ) + { + return -( high + 1 ); + } + else + { + return -( high + 2 ); + } + } + } + + default: + // We have 3 elements + result = comparator.compare( value, valueArray[pivot] ); + + if ( result == 0 ) + { + return pivot; + } + + if ( result < 0 ) + { + high = pivot - 1; + } + else + { + low = pivot + 1; + } + + pivot = ( high + low ) / 2; + + continue; + } + } + + int result = comparator.compare( value, valueArray[pivot] ); + + if ( result == 0 ) + { + return pivot; + } + + if ( result < 0 ) + { + return -( pivot + 1 ); + } + else + { + return -( pivot + 2 ); + } + } + + + /** + * Check if the array of values contains a given value + */ + private boolean arrayContains( V value ) + { + if ( valueArray.length == 0 ) + { + return false; + } + + // Do a search using dichotomy + return findPos( value ) >= 0; + } + + + /** + * Check if the subBtree contains a given value + */ + protected boolean btreeContains( V value ) + { + try + { + return valueBtree.hasKey( value ); + } + catch ( IOException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } + catch ( KeyNotFoundException knfe ) + { + knfe.printStackTrace(); + return false; + } + } + + + /** + * {@inheritDoc} + */ + public boolean contains( V checkedValue ) + { + if ( valueArray == null ) + { + return btreeContains( checkedValue ); + } + else + { + return arrayContains( checkedValue ); + } + } + + + /** + * Create a new Sub-BTree to store the values. + */ + protected abstract void createSubTree(); + + + /** + * Manage a new Sub-BTree . + */ + protected abstract void manageSubTree(); + + + /** + * Add the value in an array + */ + private void addInArray( final V value ) + { + // We have to check that we have reached the threshold or not + if ( size() >= valueThresholdUp ) + { + // Ok, transform the array into a btree + createSubTree(); + + Iterator> valueIterator = new Iterator>() + { + int pos = 0; + + + @Override + public Tuple next() + { + // We can now return the found value + if ( pos == valueArray.length ) + { + // Special case : deal with the added value + pos++; + + return new Tuple( value, value ); + } + else + { + V oldValue = valueArray[pos]; + pos++; + + return new Tuple( oldValue, oldValue ); + } + } + + + @Override + public boolean hasNext() + { + // Check that we have at least one element to read + return pos < valueArray.length + 1; + } + + + @Override + public void remove() + { + } + + }; + + try + { + BulkLoader.load( valueBtree, valueIterator, valueArray.length ); + } + catch ( IOException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + manageSubTree(); + + // And make the valueArray to be null now + valueArray = null; + } + else + { + // Create the array if it's null + if ( valueArray == null ) + { + valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), 1 ); + nbArrayElems = 1; + valueArray[0] = value; + } + else + { + // check that the value is not already present in the ValueHolder + int pos = findPos( value ); + + if ( pos >= 0 ) + { + // The value exists : nothing to do + return; + } + + // Ok, we just have to insert the new element at the right position + // We transform the position to a positive value + pos = -( pos + 1 ); + // First, copy the array + V[] newValueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length + 1 ); + + System.arraycopy( valueArray, 0, newValueArray, 0, pos ); + newValueArray[pos] = value; + System.arraycopy( valueArray, pos, newValueArray, pos + 1, valueArray.length - pos ); + + // And switch the arrays + valueArray = newValueArray; + } + } + } + + + /** + * Add the value in the subBTree + */ + private void addInBtree( V value ) + { + try + { + valueBtree.insert( value, null ); + } + catch ( IOException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + /** + * {@inheritDoc} + */ + public void add( V value ) + { + if ( valueBtree == null ) + { + addInArray( value ); + } + else + { + addInBtree( value ); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public V replaceValueArray( V newValue ) + { + if ( isSubBtree() ) + { + throw new IllegalStateException( "method is not applicable for the duplicate B-Trees" ); + } + + V tmp = valueArray[0]; + + nbArrayElems = 1; + valueArray[0] = newValue; + + return tmp; + } + +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Addition.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Addition.java new file mode 100644 index 000000000..9bc8386a3 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Addition.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.directory.mavibot.btree; + + +/** + * A class used to store an Addition modification done on a BTree. + * + * @param The key type + * @param The value type + * + * @author Apache Directory Project + */ +/* No qualifier*/class Addition extends Modification +{ + /** + * Create a new Addition instance. + * + * @param key The key being added + * @param value The value being added + */ + public Addition( K key, V value ) + { + super( key, value ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTree.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTree.java new file mode 100644 index 000000000..b515dfb06 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTree.java @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.util.Comparator; + +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * A B-tree interface, to be implemented by the PersistedBTree or the InMemoryBTree + * + * @param The Key type + * @param The Value type + * + * @author Apache Directory Project + */ +public interface BTree +{ + /** Default page size (number of entries per node) */ + int DEFAULT_PAGE_SIZE = 16; + + /** Default size of the buffer used to write data on disk. Around 1Mb */ + int DEFAULT_WRITE_BUFFER_SIZE = 4096 * 250; + + /** Define a default delay for a read transaction. This is 10 seconds */ + long DEFAULT_READ_TIMEOUT = 10 * 1000L; + + /** The B-tree allows duplicate values */ + boolean ALLOW_DUPLICATES = true; + + /** The B-tree forbids duplicate values */ + boolean FORBID_DUPLICATES = false; + + + /** + * Close the B-tree, cleaning up all the data structure + */ + void close() throws IOException; + + + /** + * Set the maximum number of elements we can store in a page. This must be a + * number greater than 1, and a power of 2. The default page size is 16. + *
+ * If the provided size is below 2, we will default to DEFAULT_PAGE_SIZE.
+ * If the provided size is not a power of 2, we will select the closest power of 2 + * higher than the given number
+ * + * @param pageSize The requested page size + */ + void setPageSize( int pageSize ); + + + /** + * @return the number of elements per page + */ + int getPageSize(); + + + /** + * Insert an entry in the B-tree. + *

+ * We will replace the value if the provided key already exists in the + * B-tree. + * + * @param key Inserted key + * @param value Inserted value + * @return Existing value, if any. + * @throws IOException TODO + */ + V insert( K key, V value ) throws IOException; + + + /** + * Delete the entry which key is given as a parameter. If the entry exists, it will + * be removed from the tree, the old tuple will be returned. Otherwise, null is returned. + * + * @param key The key for the entry we try to remove + * @return A Tuple containing the removed entry, or null if it's not found. + */ + Tuple delete( K key ) throws IOException; + + + /** + * Delete the value from an entry associated with the given key. If the value + * If the value is present, it will be deleted first, later if there are no other + * values associated with this key(which can happen when duplicates are enabled), + * we will remove the key from the tree. + * + * @param key The key for the entry we try to remove + * @param value The value to delete (can be null) + * @return A Tuple containing the removed entry, or null if it's not found. + */ + Tuple delete( K key, V value ) throws IOException; + + + /** + * Find a value in the tree, given its key. If the key is not found, + * it will throw a KeyNotFoundException.
+ * Note that we can get a null value stored, or many values. + * + * @param key The key we are looking at + * @return The found value, or null if the key is not present in the tree + * @throws KeyNotFoundException If the key is not found in the B-tree + * @throws IOException TODO + */ + V get( K key ) throws IOException, KeyNotFoundException; + + + /** + * Get the rootPage associated to a given revision. + * + * @param revision The revision we are looking for + * @return The rootPage associated to this revision + * @throws IOException If we had an issue while accessing the underlying file + * @throws KeyNotFoundException If the revision does not exist for this B-tree + */ + Page getRootPage( long revision ) throws IOException, KeyNotFoundException; + + + /** + * Get the current rootPage + * + * @return The current rootPage + */ + Page getRootPage(); + + + /** + * @see Page#getValues(Object) + */ + ValueCursor getValues( K key ) throws IOException, KeyNotFoundException; + + + /** + * Find a value in the tree, given its key, at a specific revision. If the key is not found, + * it will throw a KeyNotFoundException.
+ * Note that we can get a null value stored, or many values. + * + * @param revision The revision for which we want to find a key + * @param key The key we are looking at + * @return The found value, or null if the key is not present in the tree + * @throws KeyNotFoundException If the key is not found in the B-tree + * @throws IOException If there was an issue while fetching data from the disk + */ + V get( long revision, K key ) throws IOException, KeyNotFoundException; + + + /** + * Checks if the given key exists. + * + * @param key The key we are looking at + * @return true if the key is present, false otherwise + * @throws IOException If we have an error while trying to access the page + * @throws KeyNotFoundException If the key is not found in the B-tree + */ + boolean hasKey( K key ) throws IOException, KeyNotFoundException; + + + /** + * Checks if the given key exists for a given revision. + * + * @param revision The revision for which we want to find a key + * @param key The key we are looking at + * @return true if the key is present, false otherwise + * @throws IOException If we have an error while trying to access the page + * @throws KeyNotFoundException If the key is not found in the B-tree + */ + boolean hasKey( long revision, K key ) throws IOException, KeyNotFoundException; + + + /** + * Checks if the B-tree contains the given key with the given value. + * + * @param key The key we are looking for + * @param value The value associated with the given key + * @return true if the key and value are associated with each other, false otherwise + */ + boolean contains( K key, V value ) throws IOException; + + + /** + * Checks if the B-tree contains the given key with the given value for a given revision + * + * @param revision The revision we would like to browse + * @param key The key we are looking for + * @param value The value associated with the given key + * @return true if the key and value are associated with each other, false otherwise + * @throws KeyNotFoundException If the key is not found in the B-tree + */ + boolean contains( long revision, K key, V value ) throws IOException, KeyNotFoundException; + + + /** + * Creates a cursor starting at the beginning of the tree + * + * @return A cursor on the B-tree + * @throws IOException + */ + TupleCursor browse() throws IOException, KeyNotFoundException; + + + /** + * Creates a cursor starting at the beginning of the tree, for a given revision + * + * @param revision The revision we would like to browse + * @return A cursor on the B-tree + * @throws IOException If we had an issue while fetching data from the disk + * @throws KeyNotFoundException If the key is not found in the B-tree + */ + TupleCursor browse( long revision ) throws IOException, KeyNotFoundException; + + + /** + * Creates a cursor starting on the given key + * + * @param key The key which is the starting point. If the key is not found, + * then the cursor will always return null. + * @return A cursor on the B-tree + * @throws IOException + */ + TupleCursor browseFrom( K key ) throws IOException; + + + /** + * Creates a cursor starting on the given key at the given revision + * + * @param The revision we are looking for + * @param key The key which is the starting point. If the key is not found, + * then the cursor will always return null. + * @return A cursor on the B-tree + * @throws IOException If wxe had an issue reading the B-tree from disk + * @throws KeyNotFoundException If we can't find a rootPage for this revision + */ + TupleCursor browseFrom( long revision, K key ) throws IOException, KeyNotFoundException; + + + /** + * Creates a cursor starting at the beginning of the tree + * + * @return A cursor on the B-tree keys + * @throws IOException + */ + KeyCursor browseKeys() throws IOException, KeyNotFoundException; + + + /** + * @return the key comparator + */ + Comparator getKeyComparator(); + + + /** + * @return the value comparator + */ + Comparator getValueComparator(); + + + /** + * @param keySerializer the Key serializer to set + */ + void setKeySerializer( ElementSerializer keySerializer ); + + + /** + * @param valueSerializer the Value serializer to set + */ + void setValueSerializer( ElementSerializer valueSerializer ); + + + /** + * Flush the latest revision to disk. We will replace the current file by the new one, as + * we flush in a temporary file. + */ + void flush() throws IOException; + + + /** + * @return the readTimeOut + */ + long getReadTimeOut(); + + + /** + * @param readTimeOut the readTimeOut to set + */ + void setReadTimeOut( long readTimeOut ); + + + /** + * @return the name + */ + String getName(); + + + /** + * @param name the name to set + */ + void setName( String name ); + + + /** + * @return the writeBufferSize + */ + int getWriteBufferSize(); + + + /** + * @param writeBufferSize the writeBufferSize to set + */ + void setWriteBufferSize( int writeBufferSize ); + + + /** + * @return the keySerializer + */ + ElementSerializer getKeySerializer(); + + + /** + * @return the keySerializer FQCN + */ + String getKeySerializerFQCN(); + + + /** + * @return the valueSerializer + */ + ElementSerializer getValueSerializer(); + + + /** + * @return the valueSerializer FQCN + */ + String getValueSerializerFQCN(); + + + /** + * @return The current B-tree revision + */ + long getRevision(); + + + /** + * @return The current number of elements in the B-tree + */ + long getNbElems(); + + + /** + * @return true if this B-tree allow duplicate values + */ + boolean isAllowDuplicates(); + + + /** + * @param allowDuplicates True if the B-tree will allow duplicate values + */ + void setAllowDuplicates( boolean allowDuplicates ); + + + /** + * @return the type + */ + BTreeTypeEnum getType(); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeFactory.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeFactory.java new file mode 100644 index 000000000..18f6b63bd --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeFactory.java @@ -0,0 +1,906 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.LinkedList; + +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * This class construct a B-tree from a serialized version of a B-tree. We need it + * to avoid exposing all the methods of the B-tree class.
+ * + * All its methods are static. + * + * @author Apache Directory Project + * + * @param The B-tree key type + * @param The B-tree value type + */ +public class BTreeFactory +{ + //-------------------------------------------------------------------------------------------- + // Create persisted btrees + //-------------------------------------------------------------------------------------------- + /** + * Creates a new persisted B-tree, with no initialization. + * + * @return a new B-tree instance + */ + public static BTree createPersistedBTree() + { + BTree btree = new PersistedBTree(); + + return btree; + } + + + /** + * Creates a new persisted B-tree, with no initialization. + * + * @return a new B-tree instance + */ + public static BTree createPersistedBTree( BTreeTypeEnum type ) + { + BTree btree = new PersistedBTree(); + ( ( AbstractBTree ) btree ).setType( type ); + + return btree; + } + + + /** + * Sets the btreeHeader offset for a Persisted BTree + * + * @param btree The btree to update + * @param btreeHeaderOffset The offset + */ + public static void setBtreeHeaderOffset( PersistedBTree btree, long btreeHeaderOffset ) + { + btree.setBtreeHeaderOffset( btreeHeaderOffset ); + } + + + /** + * Creates a new persisted B-tree using the BTreeConfiguration to initialize the + * B-tree + * + * @param configuration The configuration to use + * @return a new B-tree instance + */ + public static BTree createPersistedBTree( PersistedBTreeConfiguration configuration ) + { + BTree btree = new PersistedBTree( configuration ); + + return btree; + } + + + /** + * Creates a new persisted B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @return a new B-tree instance + */ + public static BTree createPersistedBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer ) + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( BTree.DEFAULT_PAGE_SIZE ); + configuration.setAllowDuplicates( BTree.FORBID_DUPLICATES ); + configuration.setCacheSize( PersistedBTree.DEFAULT_CACHE_SIZE ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new PersistedBTree( configuration ); + + return btree; + } + + + /** + * Creates a new persisted B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param allowDuplicates Tells if the B-tree allows multiple value for a given key + * @return a new B-tree instance + */ + public static BTree createPersistedBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer, boolean allowDuplicates ) + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( BTree.DEFAULT_PAGE_SIZE ); + configuration.setAllowDuplicates( allowDuplicates ); + configuration.setCacheSize( PersistedBTree.DEFAULT_CACHE_SIZE ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new PersistedBTree( configuration ); + + return btree; + } + + + /** + * Creates a new persisted B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param allowDuplicates Tells if the B-tree allows multiple value for a given key + * @param cacheSize The size to be used for this B-tree cache + * @return a new B-tree instance + */ + public static BTree createPersistedBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer, boolean allowDuplicates, int cacheSize ) + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( BTree.DEFAULT_PAGE_SIZE ); + configuration.setAllowDuplicates( allowDuplicates ); + configuration.setCacheSize( cacheSize ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new PersistedBTree( configuration ); + + return btree; + } + + + /** + * Creates a new persisted B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param pageSize Size of the page + * @return a new B-tree instance + */ + public static BTree createPersistedBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer, int pageSize ) + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( pageSize ); + configuration.setAllowDuplicates( BTree.FORBID_DUPLICATES ); + configuration.setCacheSize( PersistedBTree.DEFAULT_CACHE_SIZE ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new PersistedBTree( configuration ); + + return btree; + } + + + /** + * Creates a new persisted B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param pageSize Size of the page + * @param allowDuplicates Tells if the B-tree allows multiple value for a given key + * @return a new B-tree instance + */ + public static BTree createPersistedBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer, int pageSize, boolean allowDuplicates ) + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( pageSize ); + configuration.setAllowDuplicates( allowDuplicates ); + configuration.setCacheSize( PersistedBTree.DEFAULT_CACHE_SIZE ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new PersistedBTree( configuration ); + + return btree; + } + + + /** + * Creates a new persisted B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param pageSize Size of the page + * @param allowDuplicates Tells if the B-tree allows multiple value for a given key + * @param cacheSize The size to be used for this B-tree cache + * @return a new B-tree instance + */ + public static BTree createPersistedBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer, int pageSize, boolean allowDuplicates, int cacheSize ) + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( pageSize ); + configuration.setAllowDuplicates( allowDuplicates ); + configuration.setCacheSize( cacheSize ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new PersistedBTree( configuration ); + + return btree; + } + + + //-------------------------------------------------------------------------------------------- + // Create in-memory B-trees + //-------------------------------------------------------------------------------------------- + /** + * Creates a new in-memory B-tree, with no initialization. + * + * @return a new B-tree instance + */ + public static BTree createInMemoryBTree() + { + BTree btree = new InMemoryBTree(); + + return btree; + } + + + /** + * Creates a new in-memory B-tree using the BTreeConfiguration to initialize the + * B-tree + * + * @param configuration The configuration to use + * @return a new B-tree instance + */ + public static BTree createInMemoryBTree( InMemoryBTreeConfiguration configuration ) + { + BTree btree = new InMemoryBTree( configuration ); + + return btree; + } + + + /** + * Creates a new in-memory B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @return a new B-tree instance + */ + public static BTree createInMemoryBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer ) + { + InMemoryBTreeConfiguration configuration = new InMemoryBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( BTree.DEFAULT_PAGE_SIZE ); + configuration.setAllowDuplicates( BTree.FORBID_DUPLICATES ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new InMemoryBTree( configuration ); + + return btree; + } + + + /** + * Creates a new in-memory B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param allowDuplicates Tells if the B-tree allows multiple value for a given key + * @return a new B-tree instance + */ + public static BTree createInMemoryBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer, boolean allowDuplicates ) + { + InMemoryBTreeConfiguration configuration = new InMemoryBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( BTree.DEFAULT_PAGE_SIZE ); + configuration.setAllowDuplicates( allowDuplicates ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new InMemoryBTree( configuration ); + + return btree; + } + + + /** + * Creates a new in-memory B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param pageSize Size of the page + * @return a new B-tree instance + */ + public static BTree createInMemoryBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer, int pageSize ) + { + InMemoryBTreeConfiguration configuration = new InMemoryBTreeConfiguration(); + + configuration.setName( name ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( pageSize ); + configuration.setAllowDuplicates( BTree.FORBID_DUPLICATES ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new InMemoryBTree( configuration ); + + return btree; + } + + + /** + * Creates a new in-memory B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param filePath The name of the data directory with absolute path + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @return a new B-tree instance + */ + public static BTree createInMemoryBTree( String name, String filePath, + ElementSerializer keySerializer, + ElementSerializer valueSerializer ) + { + InMemoryBTreeConfiguration configuration = new InMemoryBTreeConfiguration(); + + configuration.setName( name ); + configuration.setFilePath( filePath ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( BTree.DEFAULT_PAGE_SIZE ); + configuration.setAllowDuplicates( BTree.FORBID_DUPLICATES ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new InMemoryBTree( configuration ); + + return btree; + } + + + /** + * Creates a new in-memory B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param filePath The name of the data directory with absolute path + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param pageSize Size of the page + * @return a new B-tree instance + */ + public static BTree createInMemoryBTree( String name, String filePath, + ElementSerializer keySerializer, ElementSerializer valueSerializer, int pageSize ) + { + InMemoryBTreeConfiguration configuration = new InMemoryBTreeConfiguration(); + + configuration.setName( name ); + configuration.setFilePath( filePath ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( pageSize ); + configuration.setAllowDuplicates( BTree.FORBID_DUPLICATES ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new InMemoryBTree( configuration ); + + return btree; + } + + + /** + * Creates a new in-memory B-tree using the parameters to initialize the + * B-tree + * + * @param name The B-tree's name + * @param filePath The name of the data directory with absolute path + * @param keySerializer Key serializer + * @param valueSerializer Value serializer + * @param pageSize Size of the page + * @param allowDuplicates Tells if the B-tree allows multiple value for a given key + * @return a new B-tree instance + */ + public static BTree createInMemoryBTree( String name, String filePath, + ElementSerializer keySerializer, + ElementSerializer valueSerializer, int pageSize, boolean allowDuplicates ) + { + InMemoryBTreeConfiguration configuration = new InMemoryBTreeConfiguration(); + + configuration.setName( name ); + configuration.setFilePath( filePath ); + configuration.setKeySerializer( keySerializer ); + configuration.setValueSerializer( valueSerializer ); + configuration.setPageSize( pageSize ); + configuration.setAllowDuplicates( allowDuplicates ); + configuration.setWriteBufferSize( BTree.DEFAULT_WRITE_BUFFER_SIZE ); + + BTree btree = new InMemoryBTree( configuration ); + + return btree; + } + + + //-------------------------------------------------------------------------------------------- + // Create Pages + //-------------------------------------------------------------------------------------------- + /** + * Create a new Leaf for the given B-tree. + * + * @param btree The B-tree which will contain this leaf + * @param revision The Leaf's revision + * @param nbElems The number or elements in this leaf + * + * @return A Leaf instance + */ + /* no qualifier*/static Page createLeaf( BTree btree, long revision, int nbElems ) + { + if ( btree.getType() != BTreeTypeEnum.IN_MEMORY ) + { + return new PersistedLeaf( btree, revision, nbElems ); + } + else + { + return new InMemoryLeaf( btree, revision, nbElems ); + } + } + + + /** + * Create a new Node for the given B-tree. + * + * @param btree The B-tree which will contain this node + * @param revision The Node's revision + * @param nbElems The number or elements in this node + * @return A Node instance + */ + /* no qualifier*/static Page createNode( BTree btree, long revision, int nbElems ) + { + if ( btree.getType() != BTreeTypeEnum.IN_MEMORY ) + { + //System.out.println( "Creating a node with nbElems : " + nbElems ); + return new PersistedNode( btree, revision, nbElems ); + } + else + { + return new InMemoryNode( btree, revision, nbElems ); + } + } + + + //-------------------------------------------------------------------------------------------- + // Update pages + //-------------------------------------------------------------------------------------------- + /** + * Set the key at a give position + * + * @param btree The B-tree to update + * @param page The page to update + * @param pos The position in the keys array + * @param key The key to inject + */ + /* no qualifier*/static void setKey( BTree btree, Page page, int pos, K key ) + { + KeyHolder keyHolder; + + if ( btree.getType() != BTreeTypeEnum.IN_MEMORY ) + { + keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), key ); + } + else + { + keyHolder = new KeyHolder( key ); + } + + ( ( AbstractPage ) page ).setKey( pos, keyHolder ); + } + + + /** + * Set the value at a give position + * + * @param btree The B-tree to update + * @param page The page to update + * @param pos The position in the values array + * @param value the value to inject + */ + /* no qualifier*/static void setValue( BTree btree, Page page, int pos, ValueHolder value ) + { + if ( btree.getType() != BTreeTypeEnum.IN_MEMORY ) + { + ( ( PersistedLeaf ) page ).setValue( pos, value ); + } + else + { + ( ( InMemoryLeaf ) page ).setValue( pos, value ); + } + } + + + /** + * Set the page at a give position + * + * @param btree The B-tree to update + * @param page The page to update + * @param pos The position in the values array + * @param child the child page to inject + */ + /* no qualifier*/static void setPage( BTree btree, Page page, int pos, Page child ) + { + if ( btree.getType() != BTreeTypeEnum.IN_MEMORY ) + { + ( ( PersistedNode ) page ).setValue( pos, new PersistedPageHolder( btree, child ) ); + } + else + { + ( ( InMemoryNode ) page ).setPageHolder( pos, new PageHolder( btree, child ) ); + } + } + + + //-------------------------------------------------------------------------------------------- + // Update B-tree + //-------------------------------------------------------------------------------------------- + /** + * Sets the KeySerializer into the B-tree + * + * @param btree The B-tree to update + * @param keySerializerFqcn the Key serializer FQCN to set + * @throws ClassNotFoundException If the key serializer class cannot be found + * @throws InstantiationException If the key serializer class cannot be instanciated + * @throws IllegalAccessException If the key serializer class cannot be accessed + * @throws NoSuchFieldException + * @throws SecurityException + * @throws IllegalArgumentException + */ + /* no qualifier*/static void setKeySerializer( BTree btree, String keySerializerFqcn ) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, IllegalArgumentException, + SecurityException, NoSuchFieldException + { + Class keySerializer = Class.forName( keySerializerFqcn ); + @SuppressWarnings("unchecked") + ElementSerializer instance = null; + try + { + instance = ( ElementSerializer ) keySerializer.getDeclaredField( "INSTANCE" ).get( null ); + } + catch ( NoSuchFieldException e ) + { + // ignore + } + + if ( instance == null ) + { + instance = ( ElementSerializer ) keySerializer.newInstance(); + } + + btree.setKeySerializer( instance ); + } + + + /** + * Sets the ValueSerializer into the B-tree + * + * @param btree The B-tree to update + * @param valueSerializerFqcn the Value serializer FQCN to set + * @throws ClassNotFoundException If the value serializer class cannot be found + * @throws InstantiationException If the value serializer class cannot be instanciated + * @throws IllegalAccessException If the value serializer class cannot be accessed + * @throws NoSuchFieldException + * @throws SecurityException + * @throws IllegalArgumentException + */ + /* no qualifier*/static void setValueSerializer( BTree btree, String valueSerializerFqcn ) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, IllegalArgumentException, + SecurityException, NoSuchFieldException + { + Class valueSerializer = Class.forName( valueSerializerFqcn ); + @SuppressWarnings("unchecked") + ElementSerializer instance = null; + try + { + instance = ( ElementSerializer ) valueSerializer.getDeclaredField( "INSTANCE" ).get( null ); + } + catch ( NoSuchFieldException e ) + { + // ignore + } + + if ( instance == null ) + { + instance = ( ElementSerializer ) valueSerializer.newInstance(); + } + + btree.setValueSerializer( instance ); + } + + + /** + * Set the new root page for this tree. Used for debug purpose only. The revision + * will always be 0; + * + * @param btree The B-tree to update + * @param root the new root page. + */ + /* no qualifier*/static void setRootPage( BTree btree, Page root ) + { + ( ( AbstractBTree ) btree ).setRootPage( root ); + } + + + /** + * Return the B-tree root page + * + * @param btree The B-tree we want to root page from + * @return The root page + */ + /* no qualifier */static Page getRootPage( BTree btree ) + { + return btree.getRootPage(); + } + + + /** + * Update the B-tree number of elements + * + * @param btree The B-tree to update + * @param nbElems the nbElems to set + */ + /* no qualifier */static void setNbElems( BTree btree, long nbElems ) + { + ( ( AbstractBTree ) btree ).setNbElems( nbElems ); + } + + + /** + * Update the B-tree revision + * + * @param btree The B-tree to update + * @param revision the revision to set + */ + /* no qualifier*/static void setRevision( BTree btree, long revision ) + { + ( ( AbstractBTree ) btree ).setRevision( revision ); + } + + + /** + * Set the B-tree name + * + * @param btree The B-tree to update + * @param name the name to set + */ + /* no qualifier */static void setName( BTree btree, String name ) + { + btree.setName( name ); + } + + + /** + * Set the maximum number of elements we can store in a page. + * + * @param btree The B-tree to update + * @param pageSize The requested page size + */ + /* no qualifier */static void setPageSize( BTree btree, int pageSize ) + { + btree.setPageSize( pageSize ); + } + + + //-------------------------------------------------------------------------------------------- + // Utility method + //-------------------------------------------------------------------------------------------- + /** + * Includes the intermediate nodes in the path up to and including the right most leaf of the tree + * + * @param btree the B-tree + * @return a LinkedList of all the nodes and the final leaf + */ + /* no qualifier*/static LinkedList> getPathToRightMostLeaf( BTree btree ) + { + LinkedList> stack = new LinkedList>(); + + ParentPos last = new ParentPos( btree.getRootPage(), btree.getRootPage().getNbElems() ); + stack.push( last ); + + if ( btree.getRootPage().isLeaf() ) + { + Page leaf = btree.getRootPage(); + ValueHolder valueHolder = ( ( AbstractPage ) leaf ).getValue( last.pos ); + last.valueCursor = valueHolder.getCursor(); + } + else + { + Page node = btree.getRootPage(); + + while ( true ) + { + Page p = ( ( AbstractPage ) node ).getPage( node.getNbElems() ); + + last = new ParentPos( p, p.getNbElems() ); + stack.push( last ); + + if ( p.isLeaf() ) + { + Page leaf = last.page; + ValueHolder valueHolder = ( ( AbstractPage ) leaf ).getValue( last.pos ); + last.valueCursor = valueHolder.getCursor(); + break; + } + } + } + + return stack; + } + + + //-------------------------------------------------------------------------------------------- + // Persisted B-tree methods + //-------------------------------------------------------------------------------------------- + /** + * Set the rootPage offset of the B-tree + * + * @param btree The B-tree to update + * @param rootPageOffset The rootPageOffset to set + */ + /* no qualifier*/static void setRootPageOffset( BTree btree, long rootPageOffset ) + { + if ( btree instanceof PersistedBTree ) + { + ( ( PersistedBTree ) btree ).getBtreeHeader().setRootPageOffset( rootPageOffset ); + } + else + { + throw new IllegalArgumentException( "The B-tree must be a PersistedBTree" ); + } + } + + + /** + * Set the RecordManager + * + * @param btree The B-tree to update + * @param recordManager The injected RecordManager + */ + /* no qualifier*/static void setRecordManager( BTree btree, RecordManager recordManager ) + { + if ( btree instanceof PersistedBTree ) + { + ( ( PersistedBTree ) btree ).setRecordManager( recordManager ); + } + else + { + throw new IllegalArgumentException( "The B-tree must be a PersistedBTree" ); + } + } + + + /** + * Set the key at a give position + * + * @param btree The B-tree to update + * @param page The page to update + * @param pos The position of this key in the page + * @param buffer The byte[] containing the serialized key + */ + /* no qualifier*/static void setKey( BTree btree, Page page, int pos, byte[] buffer ) + { + if ( btree instanceof PersistedBTree ) + { + KeyHolder keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), buffer ); + ( ( AbstractPage ) page ).setKey( pos, keyHolder ); + } + else + { + throw new IllegalArgumentException( "The B-tree must be a PersistedBTree" ); + } + } + + + /** + * Includes the intermediate nodes in the path up to and including the left most leaf of the tree + * + * @param btree The B-tree to process + * @return a LinkedList of all the nodes and the final leaf + */ + /* no qualifier*/static LinkedList> getPathToLeftMostLeaf( BTree btree ) + { + if ( btree instanceof PersistedBTree ) + { + LinkedList> stack = new LinkedList>(); + + ParentPos first = new ParentPos( btree.getRootPage(), 0 ); + stack.push( first ); + + if ( btree.getRootPage().isLeaf() ) + { + Page leaf = btree.getRootPage(); + ValueHolder valueHolder = ( ( AbstractPage ) leaf ).getValue( first.pos ); + first.valueCursor = valueHolder.getCursor(); + } + else + { + Page node = btree.getRootPage(); + + while ( true ) + { + Page page = ( ( AbstractPage ) node ).getPage( 0 ); + + first = new ParentPos( page, 0 ); + stack.push( first ); + + if ( page.isLeaf() ) + { + ValueHolder valueHolder = ( ( AbstractPage ) page ).getValue( first.pos ); + first.valueCursor = valueHolder.getCursor(); + break; + } + } + } + + return stack; + } + else + { + throw new IllegalArgumentException( "The B-tree must be a PersistedBTree" ); + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeHeader.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeHeader.java new file mode 100644 index 000000000..9a2206c5b --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeHeader.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * Store in memory the information associated with a B-tree.
+ * A B-tree Header on disk contains the following elements : + *

+ * +--------------------+-------------+
+ * | revision           | 8 bytes     |
+ * +--------------------+-------------+
+ * | nbElems            | 8 bytes     |
+ * +--------------------+-------------+
+ * | rootPageOffset     | 8 bytes     |
+ * +--------------------+-------------+
+ * | BtreeHeaderOffset  | 8 bytes     |
+ * +--------------------+-------------+
+ * 
+ * Each B-tree Header will be written starting on a new page. + * In memory, a B-tree Header store a bit more of information : + *
  • + *
      rootPage : the associated rootPage in memory + *
        nbUsers : the number of readThreads using this revision + *
          offset : the offset of this B-tre header + * + * + * @author Apache Directory Project + */ +/* No qualifier*/class BTreeHeader implements Cloneable +{ + /** The current revision */ + private long revision = 0L; + + /** The number of elements in this B-tree */ + private Long nbElems = 0L; + + /** The offset of the B-tree RootPage */ + private long rootPageOffset; + + /** The position of the B-tree header in the file */ + private long btreeHeaderOffset = RecordManager.NO_PAGE; + + // Those are data which aren't serialized : they are in memory only */ + /** A Map containing the rootPage for this tree */ + private Page rootPage; + + /** The number of users for this BtreeHeader */ + private AtomicInteger nbUsers = new AtomicInteger( 0 ); + + /** The B-tree this header is associated with */ + private BTree btree; + + + /** + * Creates a BTreeHeader instance + */ + public BTreeHeader() + { + } + + + /** + * @return the B-tree info Offset + */ + public long getBTreeInfoOffset() + { + return ( ( PersistedBTree ) btree ).getBtreeInfoOffset(); + } + + + /** + * @return the B-tree header Offset + */ + public long getBTreeHeaderOffset() + { + return btreeHeaderOffset; + } + + + /** + * Clone the BTreeHeader + * + * @return The cloned BTreeHeader + */ + public BTreeHeader clone() + { + try + { + BTreeHeader copy = ( BTreeHeader ) super.clone(); + + return copy; + } + catch ( CloneNotSupportedException cnse ) + { + return null; + } + } + + + /** + * Copy the current B-tree header and return the copy + * @return The copied B-tree header + */ + /* no qualifier */BTreeHeader copy() + { + BTreeHeader copy = clone(); + + // Clear the fields that should not be copied + copy.rootPage = null; + copy.rootPageOffset = -1L; + copy.btreeHeaderOffset = -1L; + copy.nbUsers.set( 0 ); + + return copy; + } + + + /** + * Set the B-tree header offset + * + * @param btreeOffset the B-tree header Offset to set + */ + /* no qualifier */void setBTreeHeaderOffset( long btreeHeaderOffset ) + { + this.btreeHeaderOffset = btreeHeaderOffset; + } + + + /** + * @return the rootPageOffset + */ + public long getRootPageOffset() + { + return rootPageOffset; + } + + + /** + * Set the Root Page offset + * + * @param rootPageOffset the rootPageOffset to set + */ + /* no qualifier */void setRootPageOffset( long rootPageOffset ) + { + this.rootPageOffset = rootPageOffset; + } + + + /** + * @return the revision + */ + public long getRevision() + { + return revision; + } + + + /** + * Set the new revision + * + * @param revision the revision to set + */ + /* no qualifier */void setRevision( long revision ) + { + this.revision = revision; + } + + + /** + * @return the nbElems + */ + public long getNbElems() + { + return nbElems; + } + + + /** + * @param nbElems the nbElems to set + */ + /* no qualifier */void setNbElems( long nbElems ) + { + this.nbElems = nbElems; + } + + + /** + * Increment the number of elements + */ + /* no qualifier */void incrementNbElems() + { + nbElems++; + } + + + /** + * Decrement the number of elements + */ + /* no qualifier */void decrementNbElems() + { + nbElems--; + } + + + /** + * Get the root page + * @return the rootPage + */ + /* no qualifier */Page getRootPage() + { + return rootPage; + } + + + /** + * Set the root page + * @param rootPage the rootPage to set + */ + /* no qualifier */void setRootPage( Page rootPage ) + { + this.rootPage = rootPage; + this.rootPageOffset = ( ( AbstractPage ) rootPage ).getOffset(); + } + + + /** + * Get the number of users + * + * @return the nbUsers + */ + /* no qualifier */int getNbUsers() + { + return nbUsers.get(); + } + + + /** + * Increment the number of users + */ + /* no qualifier */void incrementNbUsers() + { + nbUsers.incrementAndGet(); + } + + + /** + * Decrement the number of users + */ + /* no qualifier */void decrementNbUsers() + { + nbUsers.decrementAndGet(); + } + + + /** + * @return the B-tree + */ + /* no qualifier */BTree getBtree() + { + return btree; + } + + + /** + * Associate a B-tree with this BTreeHeader instance + * + * @param btree the B-tree to set + */ + /* no qualifier */void setBtree( BTree btree ) + { + this.btree = btree; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "B-treeHeader " ); + sb.append( ", offset[0x" ).append( Long.toHexString( btreeHeaderOffset ) ).append( "]" ); + sb.append( ", name[" ).append( btree.getName() ).append( "]" ); + sb.append( ", revision[" ).append( revision ).append( "]" ); + sb.append( ", btreeInfoOffset[0x" ) + .append( Long.toHexString( ( ( PersistedBTree ) btree ).getBtreeInfoOffset() ) ).append( "]" ); + sb.append( ", rootPageOffset[0x" ).append( Long.toHexString( rootPageOffset ) ).append( "]" ); + sb.append( ", nbElems[" ).append( nbElems ).append( "]" ); + sb.append( ", nbUsers[" ).append( nbUsers.get() ).append( "]" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeTypeEnum.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeTypeEnum.java new file mode 100644 index 000000000..0a2db9c15 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeTypeEnum.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.directory.mavibot.btree; + + +/** + * An enum to describe the B-tree type. We have three possible type : + *
            + *
          • IN_MEMORY : the B-tree will remain in memory, and won't be persisted on disk
          • + *
          • BACKED_ON_DISK : the B-tree is in memory, but will be persisted on disk
          • + *
          • PERSISTED : the B-tree is managed by a RecordManager, and some pages may + * be swapped out from memory on demand
          • + *
          • PERSISTED_SUB : The B-tree is a Persisted B-tree, but a Sub B-tree one
          • + *
          • PERSISTED_MANAGEMENT : This is a Persisted B-tree used to manage the other B-trees
          • + *
          + * + * @author Apache Directory Project + */ +public enum BTreeTypeEnum +{ + /** Pure in-memory B-tree, not persisted on disk */ + IN_MEMORY, + + /** Persisted B-tree */ + PERSISTED, + + /** Persisted sub B-tree */ + PERSISTED_SUB, + + /** Persisted Management B-tree */ + BTREE_OF_BTREES, + + /** Persisted Management B-tree */ + COPIED_PAGES_BTREE, + + /** In-memory B-tree but saved on disk */ + BACKED_ON_DISK +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromLeftResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromLeftResult.java new file mode 100644 index 000000000..3fc95832c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromLeftResult.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.List; + + +/** + * The result of a delete operation, when the child has not been merged, and when + * we have borrowed an element from the left sibling. It contains the + * reference to the modified page, and the removed element. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/class BorrowedFromLeftResult extends AbstractBorrowedFromSiblingResult +{ + /** + * The default constructor for BorrowedFromLeftResult. + * + * @param modifiedPage The modified page + * @param modifiedSibling The modified sibling + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + public BorrowedFromLeftResult( Page modifiedPage, Page modifiedSibling, + Tuple removedElement ) + { + super( modifiedPage, modifiedSibling, removedElement, AbstractBorrowedFromSiblingResult.SiblingPosition.LEFT ); + } + + + /** + * A constructor for BorrowedFromLeftResult which takes a list of copied pages. + * + * @param copiedPages the list of copied pages + * @param modifiedPage The modified page + * @param modifiedSibling The modified sibling + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + public BorrowedFromLeftResult( List> copiedPages, Page modifiedPage, + Page modifiedSibling, + Tuple removedElement ) + { + super( copiedPages, modifiedPage, modifiedSibling, removedElement, + AbstractBorrowedFromSiblingResult.SiblingPosition.LEFT ); + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Borrowed from left" ); + sb.append( super.toString() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromRightResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromRightResult.java new file mode 100644 index 000000000..770f3de54 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromRightResult.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.List; + + +/** + * The result of a delete operation, when the child has not been merged. It contains the + * reference to the modified page, and the removed element. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/class BorrowedFromRightResult extends AbstractBorrowedFromSiblingResult +{ + /** + * The default constructor for BorrowedFromRightResult. + * + * @param modifiedPage The modified page + * @param modifiedSibling The modified sibling + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + public BorrowedFromRightResult( Page modifiedPage, Page modifiedSibling, + Tuple removedElement ) + { + super( modifiedPage, modifiedSibling, removedElement, AbstractBorrowedFromSiblingResult.SiblingPosition.RIGHT ); + } + + + /** + * A constructor for BorrowedFromRightResult which takes a list of copied pages. + * + * @param copiedPages the list of copied pages + * @param modifiedPage The modified page + * @param modifiedSibling The modified sibling + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + public BorrowedFromRightResult( List> copiedPages, Page modifiedPage, + Page modifiedSibling, Tuple removedElement ) + { + super( copiedPages, modifiedPage, modifiedSibling, removedElement, + AbstractBorrowedFromSiblingResult.SiblingPosition.RIGHT ); + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Borrowed from right" ); + sb.append( super.toString() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromSiblingResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromSiblingResult.java new file mode 100644 index 000000000..3afe8d001 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromSiblingResult.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.directory.mavibot.btree; + + +/** + * The result of an delete operation, when we have borrowed some element from a sibling. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/interface BorrowedFromSiblingResult extends DeleteResult +{ + /** + * @return the modifiedSibling + */ + Page getModifiedSibling(); + + + /** + * Tells if the sibling is on the left + * + * @return True if the sibling is on the left + */ + boolean isFromLeft(); + + + /** + * Tells if the sibling is on the right + * + * @return True if the sibling is on the right + */ + boolean isFromRight(); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BulkLoader.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BulkLoader.java new file mode 100644 index 000000000..5782a56a9 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BulkLoader.java @@ -0,0 +1,1446 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.mavibot.btree; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.apache.directory.mavibot.btree.comparator.IntComparator; +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; + + +/** + * A class used to bulk load a BTree. It will allow the load of N elements in + * a given BTree without to have to inject one by one, saving a lot of time. + * The second advantage is that the btree will be dense (the leaves will be + * complete, except the last one). + * This class can also be used to compact a BTree. + * + * @author Apache Directory Project + */ +public class BulkLoader +{ + private BulkLoader() + { + }; + + static enum LevelEnum + { + LEAF, + NODE + } + + /** + * A private class used to store the temporary sorted file. It's used by + * the bulkLoader + */ + private static class SortedFile + { + /** the file that contains the values */ + private File file; + + /** The number of stored values */ + private int nbValues; + + + /** A constructor for this class */ + /*No Qualifier*/SortedFile( File file, int nbValues ) + { + this.file = file; + this.nbValues = nbValues; + } + } + + + /** + * Process the data, and creates files to store them sorted if necessary, or store them + * TODO readElements. + * + * @param btree + * @param iterator + * @param sortedFiles + * @param tuples + * @param chunkSize + * @return + * @throws IOException + */ + private static int readElements( BTree btree, Iterator> iterator, List sortedFiles, + List> tuples, int chunkSize ) throws IOException + { + int nbRead = 0; + int nbIteration = 0; + int nbElems = 0; + boolean inMemory = true; + Set keys = new HashSet(); + + while ( true ) + { + nbIteration++; + tuples.clear(); + keys.clear(); + + // Read up to chukSize elements + while ( iterator.hasNext() && ( nbRead < chunkSize ) ) + { + Tuple tuple = iterator.next(); + tuples.add( tuple ); + + if ( !keys.contains( tuple.getKey() ) ) + { + keys.add( tuple.getKey() ); + nbRead++; + } + } + + if ( nbRead < chunkSize ) + { + if ( nbIteration != 1 ) + { + // Flush the sorted data on disk and exit + inMemory = false; + + sortedFiles.add( flushToDisk( nbIteration, tuples, btree ) ); + } + + // Update the number of read elements + nbElems += nbRead; + + break; + } + else + { + if ( !iterator.hasNext() ) + { + // special case : we have exactly chunkSize elements in the incoming data + if ( nbIteration > 1 ) + { + // Flush the sorted data on disk and exit + inMemory = false; + sortedFiles.add( flushToDisk( nbIteration, tuples, btree ) ); + } + + // We have read all the data in one round trip, let's get out, no need + // to store the data on disk + + // Update the number of read elements + nbElems += nbRead; + + break; + } + + // We have read chunkSize elements, we have to sort them on disk + nbElems += nbRead; + nbRead = 0; + sortedFiles.add( flushToDisk( nbIteration, tuples, btree ) ); + } + } + + if ( !inMemory ) + { + tuples.clear(); + } + + return nbElems; + } + + + /** + * Read all the sorted files, and inject them into one single big file containing all the + * sorted and merged elements. + * @throws IOException + */ + private static Tuple>>, SortedFile> processFiles( BTree btree, + Iterator>> dataIterator ) throws IOException + { + File file = File.createTempFile( "sortedUnique", "data" ); + file.deleteOnExit(); + FileOutputStream fos = new FileOutputStream( file ); + + // Number of read elements + int nbReads = 0; + + // Flush the tuples on disk + while ( dataIterator.hasNext() ) + { + nbReads++; + + // grab a tuple + Tuple> tuple = dataIterator.next(); + + // Serialize the key + byte[] bytesKey = btree.getKeySerializer().serialize( tuple.key ); + fos.write( IntSerializer.serialize( bytesKey.length ) ); + fos.write( bytesKey ); + + // Serialize the number of values + int nbValues = tuple.getValue().size(); + fos.write( IntSerializer.serialize( nbValues ) ); + + // Serialize the values + for ( V value : tuple.getValue() ) + { + byte[] bytesValue = btree.getValueSerializer().serialize( value ); + + // Serialize the value + fos.write( IntSerializer.serialize( bytesValue.length ) ); + fos.write( bytesValue ); + } + } + + fos.flush(); + fos.close(); + + FileInputStream fis = new FileInputStream( file ); + Iterator>> uniqueIterator = createUniqueFileIterator( btree, fis ); + SortedFile sortedFile = new SortedFile( file, nbReads ); + + Tuple>>, SortedFile> result = new Tuple>>, SortedFile>( + uniqueIterator, sortedFile ); + + return result; + } + + + /** + * Bulk Load data into a persisted BTree + * + * @param btree The persisted BTree in which we want to load the data + * @param iterator The iterator over the data to bulkload + * @param chunkSize The number of elements we may store in memory at each iteration + * @throws IOException If there is a problem while processing the data + */ + public static BTree load( BTree btree, Iterator> iterator, int chunkSize ) + throws IOException + { + if ( btree == null ) + { + throw new RuntimeException( "Invalid BTree : it's null" ); + } + + if ( iterator == null ) + { + // Nothing to do... + return null; + } + + // Iterate through the elements by chunk + boolean inMemory = true; + + // The list of files we will use to store the sorted chunks + List sortedFiles = new ArrayList(); + + // An array of chukSize tuple max + List> tuples = new ArrayList>( chunkSize ); + + // Now, start to read all the tuples to sort them. We may use intermediate files + // for that purpose if we hit the threshold. + int nbElems = readElements( btree, iterator, sortedFiles, tuples, chunkSize ); + + // If the tuple list is empty, we have to process the load based on files, not in memory + if ( nbElems > 0 ) + { + inMemory = tuples.size() > 0; + } + + // Now that we have processed all the data, we can start storing them in the btree + Iterator>> dataIterator = null; + FileInputStream[] streams = null; + BTree resultBTree = null; + + if ( inMemory ) + { + // Here, we have all the data in memory, no need to merge files + // We will build a simple iterator over the data + dataIterator = createTupleIterator( btree, tuples ); + resultBTree = bulkLoad( btree, dataIterator, nbElems ); + } + else + { + // We first have to build an iterator over the files + int nbFiles = sortedFiles.size(); + streams = new FileInputStream[nbFiles]; + + for ( int i = 0; i < nbFiles; i++ ) + { + streams[i] = new FileInputStream( sortedFiles.get( i ) ); + } + + dataIterator = createIterator( btree, streams ); + + // Process the files, and construct one single file with an iterator + Tuple>>, SortedFile> result = processFiles( btree, dataIterator ); + resultBTree = bulkLoad( btree, result.key, result.value.nbValues ); + result.value.file.delete(); + } + + // Ok, we have an iterator over sorted elements, we can now load them in the + // target btree. + // Now, close the FileInputStream, and delete them if we have some + if ( !inMemory ) + { + int nbFiles = sortedFiles.size(); + + for ( int i = 0; i < nbFiles; i++ ) + { + streams[i].close(); + sortedFiles.get( i ).delete(); + } + } + + return resultBTree; + } + + + /** + * Creates a node leaf LevelInfo based on the number of elements in the lower level. We can store + * up to PageSize + 1 references to pages in a node. + */ + /* no qualifier*/static LevelInfo computeLevel( BTree btree, int nbElems, LevelEnum levelType ) + { + int pageSize = btree.getPageSize(); + int incrementNode = 0; + + if ( levelType == LevelEnum.NODE ) + { + incrementNode = 1; + } + + LevelInfo level = new LevelInfo(); + level.setType( ( levelType == LevelEnum.NODE ) ); + level.setNbElems( nbElems ); + level.setNbPages( nbElems / ( pageSize + incrementNode ) ); + level.setLevelNumber( 0 ); + level.setNbAddedElems( 0 ); + level.setCurrentPos( 0 ); + + // Create the first level page + if ( nbElems <= pageSize + incrementNode ) + { + if ( nbElems % ( pageSize + incrementNode ) != 0 ) + { + level.setNbPages( 1 ); + } + + level.setNbElemsLimit( nbElems ); + + if ( level.isNode() ) + { + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, nbElems - 1 ) ); + } + else + { + level.setCurrentPage( BTreeFactory.createLeaf( btree, 0L, nbElems ) ); + } + } + else + { + int remaining = nbElems % ( pageSize + incrementNode ); + + if ( remaining == 0 ) + { + level.setNbElemsLimit( nbElems ); + + if ( level.isNode() ) + { + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, pageSize ) ); + } + else + { + level.setCurrentPage( BTreeFactory.createLeaf( btree, 0L, pageSize ) ); + } + } + else + { + level.incNbPages(); + + if ( remaining < ( pageSize / 2 ) + incrementNode ) + { + level.setNbElemsLimit( nbElems - remaining - ( pageSize + incrementNode ) ); + + if ( level.getNbElemsLimit() > 0 ) + { + if ( level.isNode() ) + { + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, pageSize ) ); + } + else + { + level.setCurrentPage( BTreeFactory.createLeaf( btree, 0L, pageSize ) ); + } + } + else + { + if ( level.isNode() ) + { + level + .setCurrentPage( BTreeFactory.createNode( btree, 0L, ( pageSize / 2 ) + remaining - 1 ) ); + } + else + { + level.setCurrentPage( BTreeFactory.createLeaf( btree, 0L, ( pageSize / 2 ) + remaining ) ); + } + } + } + else + { + level.setNbElemsLimit( nbElems - remaining ); + + if ( level.isNode() ) + { + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, pageSize ) ); + } + else + { + level.setCurrentPage( BTreeFactory.createLeaf( btree, 0L, pageSize ) ); + } + } + } + } + + return level; + } + + + /** + * Compute the number of pages necessary to store all the elements per level. The resulting list is + * reversed ( ie the leaves are on the left, the root page on the right. + */ + /* No Qualifier */static List> computeLevels( BTree btree, int nbElems ) + { + List> levelList = new ArrayList>(); + + // Compute the leaves info + LevelInfo leafLevel = computeLevel( btree, nbElems, LevelEnum.LEAF ); + + levelList.add( leafLevel ); + int nbPages = leafLevel.getNbPages(); + int levelNumber = 1; + + while ( nbPages > 1 ) + { + // Compute the Nodes info + LevelInfo nodeLevel = computeLevel( btree, nbPages, LevelEnum.NODE ); + nodeLevel.setLevelNumber( levelNumber++ ); + levelList.add( nodeLevel ); + nbPages = nodeLevel.getNbPages(); + } + + return levelList; + } + + + /** + * Inject a tuple into a leaf + */ + private static void injectInLeaf( BTree btree, Tuple> tuple, LevelInfo leafLevel ) + { + PersistedLeaf leaf = ( PersistedLeaf ) leafLevel.getCurrentPage(); + + KeyHolder keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), tuple.getKey() ); + leaf.setKey( leafLevel.getCurrentPos(), keyHolder ); + + if ( btree.getType() != BTreeTypeEnum.PERSISTED_SUB ) + { + ValueHolder valueHolder = new PersistedValueHolder( btree, ( V[] ) tuple.getValue().toArray() ); + leaf.setValue( leafLevel.getCurrentPos(), valueHolder ); + } + + leafLevel.incCurrentPos(); + } + + + private static int computeNbElemsLeaf( BTree btree, LevelInfo levelInfo ) + { + int pageSize = btree.getPageSize(); + int remaining = levelInfo.getNbElems() - levelInfo.getNbAddedElems(); + + if ( remaining < pageSize ) + { + return remaining; + } + else if ( remaining == pageSize ) + { + return pageSize; + } + else if ( remaining > levelInfo.getNbElems() - levelInfo.getNbElemsLimit() ) + { + return pageSize; + } + else + { + return remaining - pageSize / 2; + } + } + + + /** + * Compute the number of nodes necessary to store all the elements. + */ + /* No qualifier */int computeNbElemsNode( BTree btree, LevelInfo levelInfo ) + { + int pageSize = btree.getPageSize(); + int remaining = levelInfo.getNbElems() - levelInfo.getNbAddedElems(); + + if ( remaining < pageSize + 1 ) + { + return remaining; + } + else if ( remaining == pageSize + 1 ) + { + return pageSize + 1; + } + else if ( remaining > levelInfo.getNbElems() - levelInfo.getNbElemsLimit() ) + { + return pageSize + 1; + } + else + { + return remaining - pageSize / 2; + } + } + + + /** + * Inject a page reference into the root page. + */ + private static void injectInRoot( BTree btree, Page page, PageHolder pageHolder, + LevelInfo level ) throws IOException + { + PersistedNode node = ( PersistedNode ) level.getCurrentPage(); + + if ( ( level.getCurrentPos() == 0 ) && ( node.getPage( 0 ) == null ) ) + { + node.setPageHolder( 0, pageHolder ); + level.incNbAddedElems(); + } + else + { + // Inject the pageHolder and the page leftmost key + node.setPageHolder( level.getCurrentPos() + 1, pageHolder ); + KeyHolder keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), page.getLeftMostKey() ); + node.setKey( level.getCurrentPos(), keyHolder ); + level.incCurrentPos(); + level.incNbAddedElems(); + + // Check that we haven't added the last element. If so, + // we have to write the page on disk and update the btree + if ( level.getNbAddedElems() == level.getNbElems() ) + { + PageHolder rootHolder = ( ( PersistedBTree ) btree ).getRecordManager().writePage( + btree, node, 0L ); + ( ( PersistedBTree ) btree ).setRootPage( rootHolder.getValue() ); + } + } + + return; + } + + + /** + * Inject a page reference into a Node. This method will recurse if needed. + */ + private static void injectInNode( BTree btree, Page page, List> levels, + int levelIndex ) + throws IOException + { + int pageSize = btree.getPageSize(); + LevelInfo level = levels.get( levelIndex ); + PersistedNode node = ( PersistedNode ) level.getCurrentPage(); + + // We first have to write the page on disk + PageHolder pageHolder = ( ( PersistedBTree ) btree ).getRecordManager().writePage( btree, page, 0L ); + + // First deal with a node that has less than PageSize elements at this level. + // It will become the root node. + if ( level.getNbElems() <= pageSize + 1 ) + { + injectInRoot( btree, page, pageHolder, level ); + + return; + } + + // Now, we have some parent node. We process the 3 different use case : + // o Full node before the limit + // o Node over the limit but with at least N/2 elements + // o Node over the limit but with elements spread into 2 nodes + if ( level.getNbAddedElems() < level.getNbElemsLimit() ) + { + // Ok, we haven't yet reached the incomplete pages (if any). + // Let's add the page reference into the node + // There is one special case : when we are adding the very first page + // reference into a node. In this case, we don't store the key + if ( ( level.getCurrentPos() == 0 ) && ( node.getKey( 0 ) == null ) ) + { + node.setPageHolder( 0, pageHolder ); + } + else + { + // Inject the pageHolder and the page leftmost key + node.setPageHolder( level.getCurrentPos(), pageHolder ); + KeyHolder keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), page.getLeftMostKey() ); + node.setKey( level.getCurrentPos() - 1, keyHolder ); + } + + // Now, increment this level nb of added elements + level.incCurrentPos(); + level.incNbAddedElems(); + + // Check that we haven't added the last element. If so, + // we have to write the page on disk and update the parent's node + if ( level.getNbAddedElems() == level.getNbElems() ) + { + //PageHolder rootHolder = ( ( PersistedBTree ) btree ).getRecordManager().writePage( + // btree, node, 0L ); + //( ( PersistedBTree ) btree ).setRootPage( rootHolder.getValue() ); + injectInNode( btree, node, levels, levelIndex + 1 ); + + return; + } + else + { + // Check that we haven't completed the current node, and that this is not the root node. + if ( ( level.getCurrentPos() == pageSize + 1 ) && ( level.getLevelNumber() < levels.size() - 1 ) ) + { + // yes. We have to write the node on disk, update its parent + // and create a new current node + injectInNode( btree, node, levels, levelIndex + 1 ); + + // The page is full, we have to create a new one, with a size depending on the remaining elements + if ( level.getNbAddedElems() < level.getNbElemsLimit() ) + { + // We haven't reached the limit, create a new full node + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, pageSize ) ); + } + else if ( level.getNbElems() - level.getNbAddedElems() <= pageSize ) + { + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, + level.getNbElems() - level.getNbAddedElems() - 1 ) ); + } + else + { + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, ( level.getNbElems() - 1 ) + - ( level.getNbAddedElems() + 1 ) - pageSize / 2 ) ); + } + + level.setCurrentPos( 0 ); + } + } + + return; + } + else + { + // We are dealing with the last page or the last two pages + // We can have either one single pages which can contain up to pageSize-1 elements + // or with two pages, the first one containing ( nbElems - limit ) - pageSize/2 elements + // and the second one will contain pageSize/2 elements. + if ( level.getNbElems() - level.getNbElemsLimit() > pageSize ) + { + // As the remaining elements are above a page size, they will be spread across + // two pages. We have two cases here, depending on the page we are filling + if ( level.getNbElems() - level.getNbAddedElems() <= pageSize / 2 + 1 ) + { + // As we have less than PageSize/2 elements to write, we are on the second page + if ( ( level.getCurrentPos() == 0 ) && ( node.getKey( 0 ) == null ) ) + { + node.setPageHolder( 0, pageHolder ); + } + else + { + // Inject the pageHolder and the page leftmost key + node.setPageHolder( level.getCurrentPos(), pageHolder ); + KeyHolder keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), + page.getLeftMostKey() ); + node.setKey( level.getCurrentPos() - 1, keyHolder ); + } + + // Now, increment this level nb of added elements + level.incCurrentPos(); + level.incNbAddedElems(); + + // Check if we are done with the page + if ( level.getNbAddedElems() == level.getNbElems() ) + { + // Yes, we have to update the parent + injectInNode( btree, node, levels, levelIndex + 1 ); + } + } + else + { + // This is the first page + if ( ( level.getCurrentPos() == 0 ) && ( node.getKey( 0 ) == null ) ) + { + // First element of the page + node.setPageHolder( 0, pageHolder ); + } + else + { + // Any other following elements + // Inject the pageHolder and the page leftmost key + node.setPageHolder( level.getCurrentPos(), pageHolder ); + KeyHolder keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), + page.getLeftMostKey() ); + node.setKey( level.getCurrentPos() - 1, keyHolder ); + } + + // Now, increment this level nb of added elements + level.incCurrentPos(); + level.incNbAddedElems(); + + // Check if we are done with the page + if ( level.getCurrentPos() == node.getNbElems() + 1 ) + { + // Yes, we have to update the parent + injectInNode( btree, node, levels, levelIndex + 1 ); + + // An create a new one + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, pageSize / 2 ) ); + level.setCurrentPos( 0 ); + } + } + } + else + { + // Two cases : we don't have anything else to write, or this is a single page + if ( level.getNbAddedElems() == level.getNbElems() ) + { + // We are done with the page + injectInNode( btree, node, levels, levelIndex + 1 ); + } + else + { + // We have some more elements to add in the page + // This is the first page + if ( ( level.getCurrentPos() == 0 ) && ( node.getKey( 0 ) == null ) ) + { + // First element of the page + node.setPageHolder( 0, pageHolder ); + } + else + { + // Any other following elements + // Inject the pageHolder and the page leftmost key + node.setPageHolder( level.getCurrentPos(), pageHolder ); + KeyHolder keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), + page.getLeftMostKey() ); + node.setKey( level.getCurrentPos() - 1, keyHolder ); + } + + // Now, increment this level nb of added elements + level.incCurrentPos(); + level.incNbAddedElems(); + + // Check if we are done with the page + if ( level.getCurrentPos() == node.getNbElems() + 1 ) + { + // Yes, we have to update the parent + injectInNode( btree, node, levels, levelIndex + 1 ); + + // An create a new one + level.setCurrentPage( BTreeFactory.createNode( btree, 0L, pageSize / 2 ) ); + level.setCurrentPos( 0 ); + } + } + + return; + } + } + } + + + private static BTree bulkLoadSinglePage( BTree btree, Iterator>> dataIterator, + int nbElems ) throws IOException + { + // Use the root page + Page rootPage = btree.getRootPage(); + + // Initialize the root page + ( ( AbstractPage ) rootPage ).setNbElems( nbElems ); + KeyHolder[] keys = new KeyHolder[nbElems]; + ValueHolder[] values = new ValueHolder[nbElems]; + + switch ( btree.getType() ) + { + case IN_MEMORY: + ( ( InMemoryLeaf ) rootPage ).values = values; + break; + + default: + ( ( PersistedLeaf ) rootPage ).values = values; + } + + // We first have to inject data into the page + int pos = 0; + + while ( dataIterator.hasNext() ) + { + Tuple> tuple = dataIterator.next(); + + // Store the current element in the rootPage + KeyHolder keyHolder = new PersistedKeyHolder( btree.getKeySerializer(), tuple.getKey() ); + keys[pos] = keyHolder; + + switch ( btree.getType() ) + { + case IN_MEMORY: + ValueHolder valueHolder = new InMemoryValueHolder( btree, ( V[] ) tuple.getValue() + .toArray() ); + ( ( InMemoryLeaf ) rootPage ).values[pos] = valueHolder; + break; + + default: + valueHolder = new PersistedValueHolder( btree, ( V[] ) tuple.getValue() + .toArray() ); + ( ( PersistedLeaf ) rootPage ).values[pos] = valueHolder; + } + + pos++; + } + + // Update the rootPage + ( ( AbstractPage ) rootPage ).setKeys( keys ); + + // Update the btree with the nb of added elements, and write it$ + BTreeHeader btreeHeader = ( ( AbstractBTree ) btree ).getBtreeHeader(); + btreeHeader.setNbElems( nbElems ); + + return btree; + } + + + /** + * Construct the target BTree from the sorted data. We will use the nb of elements + * to determinate the structure of the BTree, as it must be balanced + */ + private static BTree bulkLoad( BTree btree, Iterator>> dataIterator, int nbElems ) + throws IOException + { + int pageSize = btree.getPageSize(); + + // Special case : we can store all the element sin a single page + if ( nbElems <= pageSize ) + { + return bulkLoadSinglePage( btree, dataIterator, nbElems ); + } + + // Ok, we will need more than one page to store the elements, which + // means we also will need more than one level. + // First, compute the needed number of levels. + List> levels = computeLevels( btree, nbElems ); + + // Now, let's fill the levels + LevelInfo leafLevel = levels.get( 0 ); + + while ( dataIterator.hasNext() ) + { + // let's fill page up to the point all the complete pages have been filled + if ( leafLevel.getNbAddedElems() < leafLevel.getNbElemsLimit() ) + { + // grab a tuple + Tuple> tuple = dataIterator.next(); + + injectInLeaf( btree, tuple, leafLevel ); + leafLevel.incNbAddedElems(); + + // The page is completed, update the parent's node and create a new current page + if ( leafLevel.getCurrentPos() == pageSize ) + { + injectInNode( btree, leafLevel.getCurrentPage(), levels, 1 ); + + // The page is full, we have to create a new one + leafLevel.setCurrentPage( BTreeFactory + .createLeaf( btree, 0L, computeNbElemsLeaf( btree, leafLevel ) ) ); + leafLevel.setCurrentPos( 0 ); + } + } + else + { + // We have to deal with uncompleted pages now (if we have any) + if ( leafLevel.getNbAddedElems() == nbElems ) + { + // First use case : we have injected all the elements in the btree : get out + break; + } + + if ( nbElems - leafLevel.getNbElemsLimit() > pageSize ) + { + // Second use case : the number of elements after the limit does not + // fit in a page, that means we have to split it into + // two pages + + // First page will contain nbElems - leafLevel.nbElemsLimit - PageSize/2 elements + int nbToAdd = nbElems - leafLevel.getNbElemsLimit() - pageSize / 2; + + while ( nbToAdd > 0 ) + { + // grab a tuple + Tuple> tuple = dataIterator.next(); + + injectInLeaf( btree, tuple, leafLevel ); + leafLevel.incNbAddedElems(); + nbToAdd--; + } + + // Now inject the page into the node + Page currentPage = leafLevel.getCurrentPage(); + injectInNode( btree, currentPage, levels, 1 ); + + // Create a new page for the remaining elements + nbToAdd = pageSize / 2; + leafLevel.setCurrentPage( BTreeFactory.createLeaf( btree, 0L, nbToAdd ) ); + leafLevel.setCurrentPos( 0 ); + + while ( nbToAdd > 0 ) + { + // grab a tuple + Tuple> tuple = dataIterator.next(); + + injectInLeaf( btree, tuple, leafLevel ); + leafLevel.incNbAddedElems(); + nbToAdd--; + } + + // And update the parent node + Page levelCurrentPage = leafLevel.getCurrentPage(); + injectInNode( btree, levelCurrentPage, levels, 1 ); + + // We are done + break; + } + else + { + // Third use case : we can push all the elements in the last page. + // Let's do it + int nbToAdd = nbElems - leafLevel.getNbElemsLimit(); + + while ( nbToAdd > 0 ) + { + // grab a tuple + Tuple> tuple = dataIterator.next(); + + injectInLeaf( btree, tuple, leafLevel ); + leafLevel.incNbAddedElems(); + nbToAdd--; + } + + // Now inject the page into the node + injectInNode( btree, leafLevel.getCurrentPage(), levels, 1 ); + + // and we are done + break; + } + } + } + + // Update the btree with the nb of added elements, and write it$ + BTreeHeader btreeHeader = ( ( AbstractBTree ) btree ).getBtreeHeader(); + btreeHeader.setNbElems( nbElems ); + + return btree; + } + + + /** + * Flush a list of tuples to disk after having sorted them. In the process, we may have to gather the values + * for the tuples having the same keys. + * @throws IOException + */ + private static File flushToDisk( int fileNb, List> tuples, BTree btree ) + throws IOException + { + // Sort the tuples. + Tuple>[] sortedTuples = sort( btree, tuples ); + + File file = File.createTempFile( "sorted", Integer.toString( fileNb ) ); + file.deleteOnExit(); + FileOutputStream fos = new FileOutputStream( file ); + + // Flush the tuples on disk + for ( Tuple> tuple : sortedTuples ) + { + // Serialize the key + byte[] bytesKey = btree.getKeySerializer().serialize( tuple.key ); + fos.write( IntSerializer.serialize( bytesKey.length ) ); + fos.write( bytesKey ); + + // Serialize the number of values + int nbValues = tuple.getValue().size(); + fos.write( IntSerializer.serialize( nbValues ) ); + + // Serialize the values + for ( V value : tuple.getValue() ) + { + byte[] bytesValue = btree.getValueSerializer().serialize( value ); + + // Serialize the value + fos.write( IntSerializer.serialize( bytesValue.length ) ); + fos.write( bytesValue ); + } + } + + fos.flush(); + fos.close(); + + return file; + } + + + /** + * Sort a list of tuples, eliminating the duplicate keys and storing the values in a set when we + * have a duplicate key + */ + private static Tuple>[] sort( BTree btree, List> tuples ) + { + Comparator>> tupleComparator = new TupleComparator( btree.getKeyComparator(), btree + .getValueComparator() ); + + // Sort the list + Tuple[] tuplesArray = ( Tuple[] ) tuples.toArray( new Tuple[] + {} ); + + // First, eliminate the equals keys. We use a map for that + Map> mapTuples = new HashMap>(); + + for ( Tuple tuple : tuplesArray ) + { + // Is the key present in the map ? + Set foundSet = mapTuples.get( tuple.key ); + + if ( foundSet != null ) + { + // We already have had such a key, add the value to the existing key + foundSet.add( tuple.value ); + } + else + { + // No such key present in the map : create a new set to store the values, + // and add it in the map associated with the new key + Set set = new TreeSet(); + set.add( tuple.value ); + mapTuples.put( tuple.key, set ); + } + } + + // Now, sort the map, by extracting all the key/values from the map + int size = mapTuples.size(); + Tuple>[] sortedTuples = new Tuple[size]; + int pos = 0; + + // We create an array containing all the elements + for ( Map.Entry> entry : mapTuples.entrySet() ) + { + sortedTuples[pos] = new Tuple>(); + sortedTuples[pos].key = entry.getKey(); + sortedTuples[pos].value = entry.getValue(); + pos++; + } + + // And we sort the array + Arrays.sort( sortedTuples, tupleComparator ); + + return sortedTuples; + } + + + /** + * Build an iterator over an array of sorted tuples, in memory + */ + private static Iterator>> createTupleIterator( BTree btree, List> tuples ) + { + final Tuple>[] sortedTuples = sort( btree, tuples ); + + Iterator>> tupleIterator = new Iterator>>() + { + private int pos = 0; + + + @Override + public Tuple> next() + { + // Return the current tuple, if any + if ( pos < sortedTuples.length ) + { + Tuple> tuple = sortedTuples[pos]; + pos++; + + return tuple; + } + + return null; + } + + + @Override + public boolean hasNext() + { + return pos < sortedTuples.length; + } + + + @Override + public void remove() + { + } + }; + + return tupleIterator; + } + + + private static Tuple> fetchTuple( BTree btree, FileInputStream fis ) + { + try + { + if ( fis.available() == 0 ) + { + return null; + } + + Tuple> tuple = new Tuple>(); + tuple.value = new TreeSet(); + + byte[] intBytes = new byte[4]; + + // Read the key length + fis.read( intBytes ); + int keyLength = IntSerializer.deserialize( intBytes ); + + // Read the key + byte[] keyBytes = new byte[keyLength]; + fis.read( keyBytes ); + K key = btree.getKeySerializer().fromBytes( keyBytes ); + tuple.key = key; + + // get the number of values + fis.read( intBytes ); + int nbValues = IntSerializer.deserialize( intBytes ); + + // Serialize the values + for ( int i = 0; i < nbValues; i++ ) + { + // Read the value length + fis.read( intBytes ); + int valueLength = IntSerializer.deserialize( intBytes ); + + // Read the value + byte[] valueBytes = new byte[valueLength]; + fis.read( valueBytes ); + V value = btree.getValueSerializer().fromBytes( valueBytes ); + tuple.value.add( value ); + } + + return tuple; + } + catch ( IOException ioe ) + { + return null; + } + } + + + /** + * Build an iterator over an array of sorted tuples, from files on the disk + * @throws FileNotFoundException + */ + private static Iterator>> createIterator( final BTree btree, + final FileInputStream[] streams ) + throws FileNotFoundException + { + // The number of files we have to read from + final int nbFiles = streams.length; + + // We will read only one element at a time from each file + final Tuple>[] readTuples = new Tuple[nbFiles]; + final TreeMap, Set> candidates = + new TreeMap, Set>( + new TupleComparator( btree.getKeyComparator(), IntComparator.INSTANCE ) ); + + // Read the tuple from each files + for ( int i = 0; i < nbFiles; i++ ) + { + while ( true ) + { + readTuples[i] = fetchTuple( btree, streams[i] ); + + if ( readTuples[i] != null ) + { + Tuple candidate = new Tuple( readTuples[i].key, i, btree.getKeySerializer() + .getComparator() ); + + if ( !candidates.containsKey( candidate ) ) + { + candidates.put( candidate, readTuples[i].getValue() ); + break; + } + else + { + // We have to merge the pulled tuple with the existing one, and read one more tuple + Set oldValues = candidates.get( candidate ); + oldValues.addAll( readTuples[i].getValue() ); + } + } + else + { + break; + } + } + } + + Iterator>> tupleIterator = new Iterator>>() + { + @Override + public Tuple> next() + { + // Get the first candidate + Tuple tupleCandidate = candidates.firstKey(); + + // Remove it from the set + candidates.remove( tupleCandidate ); + + // Get the the next tuple from the stream we just got the tuple from + Tuple> tuple = readTuples[tupleCandidate.value]; + + // fetch the next tuple from the file we just read teh candidate from + while ( true ) + { + // fetch it from the disk and store it into its reader + readTuples[tupleCandidate.value] = fetchTuple( btree, streams[tupleCandidate.value] ); + + if ( readTuples[tupleCandidate.value] == null ) + { + // No more tuple for this file + break; + } + + if ( readTuples[tupleCandidate.value] != null ) + { + // And store it into the candidate set + Set oldValues = candidates.get( readTuples[tupleCandidate.value] ); + + if ( oldValues != null ) + { + // We already have another element with the same key, merge them + oldValues.addAll( readTuples[tupleCandidate.value].value ); + } + else + { + Tuple newTuple = new Tuple( readTuples[tupleCandidate.value].key, + tupleCandidate.value ); + candidates.put( newTuple, readTuples[tupleCandidate.value].getValue() ); + + // and exit the loop + break; + } + } + } + + // We can now return the found value + return tuple; + } + + + @Override + public boolean hasNext() + { + // Check that we have at least one element to read + return !candidates.isEmpty(); + } + + + @Override + public void remove() + { + } + + }; + + return tupleIterator; + } + + + /** + * Build an iterator over an array of sorted tuples, from files on the disk + * @throws FileNotFoundException + */ + private static Iterator>> createUniqueFileIterator( final BTree btree, + final FileInputStream stream ) + throws FileNotFoundException + { + Iterator>> tupleIterator = new Iterator>>() + { + boolean hasNext = true; + + + @Override + public Tuple> next() + { + // Get the tuple from the stream + Tuple> tuple = fetchTuple( btree, stream ); + + // We can now return the found value + return tuple; + } + + + @Override + public boolean hasNext() + { + // Check that we have at least one element to read + try + { + return stream.available() > 0; + } + catch ( IOException e ) + { + return false; + } + } + + + @Override + public void remove() + { + } + + }; + + return tupleIterator; + } + + + /** + * Compact a given persisted BTree, making it dense. All the values will be stored + * in newly created pages, each one of them containing as much elements + * as it's size. + *
          + * The RecordManager will be stopped and restarted, do not use this method + * on a running BTree. + * + * @param recordManager The associated recordManager + * @param btree The BTree to compact + */ + public static void compact( RecordManager recordManager, BTree btree ) + { + + } + + + /** + * Compact a given in-memory BTree, making it dense. All the values will be stored + * in newly created pages, each one of them containing as much elements + * as it's size. + *
          + * + * @param btree The BTree to compact + * @throws KeyNotFoundException + * @throws IOException + */ + public static BTree compact( BTree btree ) throws IOException, KeyNotFoundException + { + // First, create a new BTree which will contain all the elements + InMemoryBTreeConfiguration configuration = new InMemoryBTreeConfiguration(); + configuration.setName( btree.getName() ); + configuration.setPageSize( btree.getPageSize() ); + configuration.setKeySerializer( btree.getKeySerializer() ); + configuration.setValueSerializer( btree.getValueSerializer() ); + configuration.setAllowDuplicates( btree.isAllowDuplicates() ); + configuration.setReadTimeOut( btree.getReadTimeOut() ); + configuration.setWriteBufferSize( btree.getWriteBufferSize() ); + + File file = ( ( InMemoryBTree ) btree ).getFile(); + + if ( file != null ) + { + configuration.setFilePath( file.getPath() ); + } + + // Create a new Btree Builder + InMemoryBTreeBuilder btreeBuilder = new InMemoryBTreeBuilder( configuration ); + + // Create a cursor over the existing btree + final TupleCursor cursor = btree.browse(); + + // Create an iterator that will iterate the existing btree + Iterator tupleItr = new Iterator() + { + @Override + public Tuple next() + { + try + { + return cursor.next(); + } + catch ( EndOfFileExceededException e ) + { + return null; + } + catch ( IOException e ) + { + return null; + } + } + + + @Override + public boolean hasNext() + { + try + { + return cursor.hasNext(); + } + catch ( EndOfFileExceededException e ) + { + return false; + } + catch ( IOException e ) + { + return false; + } + } + + + @Override + public void remove() + { + } + }; + + // And finally, compact the btree + return btreeBuilder.build( tupleItr ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Cursor.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Cursor.java new file mode 100644 index 000000000..381842f82 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Cursor.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; + + +/** + * A Cursor is used to fetch elements in a BTree and is returned by the + * @see BTree#browse method. The cursor must be closed + * when the user is done with it. + *

          + * + * @param The type for the Key + * + * @author Apache Directory Project + */ +public interface Cursor +{ + /** Static value for the beforeFrst and afterLast positions */ + int BEFORE_FIRST = -1; + int AFTER_LAST = -2; + + + /** + * Tells if the cursor can return a next element + * @return true if there are some more elements + * @throws IOException + * @throws EndOfFileExceededException + */ + boolean hasNext() throws EndOfFileExceededException, IOException; + + + /** + * Tells if the cursor can return a previous element + * @return true if there are some more elements + * @throws IOException + * @throws EndOfFileExceededException + */ + boolean hasPrev() throws EndOfFileExceededException, IOException; + + + /** + * Closes the cursor, thus releases the associated transaction + */ + void close(); + + + /** + * moves the cursor to the same position that was given at the time of instantiating the cursor. + * + * For example, if the cursor was created using browse() method, then beforeFirst() will + * place the cursor before the 0th position. + * + * If the cursor was created using browseFrom(K), then calling beforeFirst() will reset the position + * to the just before the position where K is present. + */ + void beforeFirst() throws IOException; + + + /** + * Places the cursor at the end of the last position + * + * @throws IOException + */ + void afterLast() throws IOException; +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/DeleteResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/DeleteResult.java new file mode 100644 index 000000000..d1cffb8a1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/DeleteResult.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.directory.mavibot.btree; + + +/** + * The result of an delete operation. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/interface DeleteResult extends Result> +{ + /** + * @return the modifiedPage + */ + Page getModifiedPage(); + + + /** + * @return the removed element + */ + Tuple getRemovedElement(); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Deletion.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Deletion.java new file mode 100644 index 000000000..8064aa6b4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Deletion.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +/** + * A class used to store a Delete modification done on a BTree. + * + * @param The key type + * @param The value type + * + * @author Apache Directory Project + */ +/* No qualifier*/class Deletion extends Modification +{ + /** + * Create a new Deletion instance. + * + * @param key The key to be deleted + */ + public Deletion( K key ) + { + super( key, null ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/EmptyTupleCursor.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/EmptyTupleCursor.java new file mode 100644 index 000000000..eba628138 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/EmptyTupleCursor.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.util.NoSuchElementException; + + +/** + * A Cursor which is used when we have no element to return + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +public class EmptyTupleCursor extends TupleCursor +{ + /** AN empty cursor does not have a revision */ + private static final long NO_REVISION = -1L; + + /** The creation date */ + private long creationDate; + + + /** + * Creates a new instance of EmptyTupleCursor. It will never return any result + */ + public EmptyTupleCursor() + { + super(); + + creationDate = System.currentTimeMillis(); + } + + + /** + * Change the position in the current cursor to set it after the last key + */ + public void afterLast() throws IOException + { + } + + + /** + * Change the position in the current cursor before the first key + */ + public void beforeFirst() throws IOException + { + } + + + /** + * Always return false. + * + * @return Always false + */ + public boolean hasNext() + { + return false; + } + + + /** + * Always throws a NoSuchElementException. + * + * @return Nothing + * @throws NoSuchElementException There is no element in a EmptyTupleCursor + */ + public Tuple next() throws NoSuchElementException + { + throw new NoSuchElementException( "No tuple present" ); + } + + + /** + * Always throws a NoSuchElementException. + * + * @return Nothing + * @throws NoSuchElementException There is no element in a EmptyTupleCursor + */ + public Tuple nextKey() throws NoSuchElementException + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + + + /** + * Always false + * + * @return false + */ + public boolean hasNextKey() + { + return false; + } + + + /** + * Always false + * + * @return false + */ + public boolean hasPrev() + { + return false; + } + + + /** + * Always throws a NoSuchElementException. + * + * @return Nothing + * @throws NoSuchElementException There is no element in a EmptyTupleCursor + */ + public Tuple prev() throws NoSuchElementException + { + throw new NoSuchElementException( "No more tuple present" ); + } + + + /** + * Always throws a NoSuchElementException. + * + * @return Nothing + * @throws NoSuchElementException There is no element in a EmptyTupleCursor + */ + public Tuple prevKey() throws NoSuchElementException + { + throw new NoSuchElementException( "No more tuples present" ); + } + + + /** + * Always false + * + * @return false + */ + public boolean hasPrevKey() + { + return false; + } + + + /** + * {@inheritDoc} + */ + public void close() + { + } + + + /** + * Get the creation date + * + * @return The creation date for this cursor + */ + public long getCreationDate() + { + return creationDate; + } + + + /** + * Always -1L for an empty cursor + * + * @return -1L + */ + public long getRevision() + { + return NO_REVISION; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + return "EmptyTupleCursor"; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/EmptyValueCursor.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/EmptyValueCursor.java new file mode 100644 index 000000000..f38e7a475 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/EmptyValueCursor.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; + + +/** + * A Cursor which is used when we have no value to return + *

          + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +public class EmptyValueCursor implements ValueCursor +{ + private long revision; + private long creationDate; + + /** + * Creates a new instance of Cursor, starting on a page at a given position. + * + * @param transaction The transaction this operation is protected by + * @param stack The stack of parent's from root to this page + */ + public EmptyValueCursor( long revision ) + { + super(); + + this.revision = revision; + creationDate = System.currentTimeMillis(); + } + + + /** + * Change the position in the current cursor to set it after the last key + */ + public void afterLast() throws IOException + { + } + + + /** + * Change the position in the current cursor before the first key + */ + public void beforeFirst() throws IOException + { + } + + + /** + * Tells if the cursor can return a next element + * + * @return true if there are some more elements + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasNext() throws EndOfFileExceededException, IOException + { + return false; + } + + + /** + * Tells if the cursor can return a next key + * + * @return true if there are some more keys + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasNextKey() throws EndOfFileExceededException, IOException + { + return false; + } + + + /** + * Tells if the cursor can return a previous element + * + * @return true if there are some more elements + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasPrev() throws EndOfFileExceededException, IOException + { + return false; + } + + + /** + * Tells if the cursor can return a previous key + * + * @return true if there are some more keys + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasPrevKey() throws EndOfFileExceededException, IOException + { + return false; + } + + + /** + * {@inheritDoc} + */ + @Override + public V prev() throws EndOfFileExceededException, IOException + { + return null; + } + + + /** + * {@inheritDoc} + */ + @Override + public V next() throws EndOfFileExceededException, IOException + { + return null; + } + + + /** + * {@inheritDoc} + */ + public void close() + { + } + + + /** + * Get the creation date + * @return The creation date for this cursor + */ + public long getCreationDate() + { + return creationDate; + } + + + /** + * Get the current revision + * + * @return The revision this cursor is based on + */ + public long getRevision() + { + return revision; + } + + + /** + * {@inheritDoc} + */ + @Override + public int size() + { + return 0; + } + + + public String toString() + { + return "EmptyValueCursor"; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ExistsResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ExistsResult.java new file mode 100644 index 000000000..19ffe7134 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ExistsResult.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + +/** + * The result of an insert operation, returned when the given tuple + * already exists in the btree that doesn't support duplicates, or while inserting + * a in a sub-btree that is used to hold the values of a key. + * + * @author Apache Directory Project + */ +/* No qualifier*/ class ExistsResult extends AbstractResult implements InsertResult +{ + public static final ExistsResult EXISTS = new ExistsResult(); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTree.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTree.java new file mode 100644 index 000000000..e45599c21 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTree.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.directory.mavibot.btree; + + +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.directory.mavibot.btree.exception.InitializationException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.exception.MissingSerializerException; +import org.apache.directory.mavibot.btree.serializer.BufferHandler; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The B+Tree MVCC data structure. + * + * @param The type for the keys + * @param The type for the stored values + * + * @author Apache Directory Project + */ +/* No qualifier */class InMemoryBTree extends AbstractBTree implements Closeable +{ + /** The LoggerFactory used by this class */ + protected static final Logger LOG = LoggerFactory.getLogger( InMemoryBTree.class ); + + /** The default journal name */ + public static final String DEFAULT_JOURNAL = "mavibot.log"; + + /** The default data file suffix */ + public static final String DATA_SUFFIX = ".db"; + + /** The default journal file suffix */ + public static final String JOURNAL_SUFFIX = ".log"; + + /** The type to use to create the keys */ + /** The associated file. If null, this is an in-memory btree */ + private File file; + + /** A flag used to tell the BTree that the journal is activated */ + private boolean withJournal; + + /** The associated journal. If null, this is an in-memory btree */ + private File journal; + + /** The directory where the journal will be stored */ + private File envDir; + + /** The Journal channel */ + private FileChannel journalChannel = null; + + + /** + * Creates a new BTree, with no initialization. + */ + /* no qualifier */InMemoryBTree() + { + super(); + setType( BTreeTypeEnum.IN_MEMORY ); + } + + + /** + * Creates a new in-memory BTree using the BTreeConfiguration to initialize the + * BTree + * + * @param configuration The configuration to use + */ + /* no qualifier */InMemoryBTree( InMemoryBTreeConfiguration configuration ) + { + super(); + String btreeName = configuration.getName(); + + if ( btreeName == null ) + { + throw new IllegalArgumentException( "BTree name cannot be null" ); + } + + String filePath = configuration.getFilePath(); + + if ( filePath != null ) + { + envDir = new File( filePath ); + } + + // Store the configuration in the B-tree + setName( btreeName ); + setPageSize( configuration.getPageSize() ); + setKeySerializer( configuration.getKeySerializer() ); + setValueSerializer( configuration.getValueSerializer() ); + setAllowDuplicates( configuration.isAllowDuplicates() ); + setType( configuration.getType() ); + + readTimeOut = configuration.getReadTimeOut(); + writeBufferSize = configuration.getWriteBufferSize(); + + if ( keySerializer.getComparator() == null ) + { + throw new IllegalArgumentException( "Comparator should not be null" ); + } + + // Create the B-tree header + BTreeHeader newBtreeHeader = new BTreeHeader(); + + // Create the first root page, with revision 0L. It will be empty + // and increment the revision at the same time + newBtreeHeader.setBTreeHeaderOffset( 0L ); + newBtreeHeader.setRevision( 0L ); + newBtreeHeader.setNbElems( 0L ); + newBtreeHeader.setRootPage( new InMemoryLeaf( this ) ); + newBtreeHeader.setRootPageOffset( 0L ); + + btreeRevisions.put( 0L, newBtreeHeader ); + currentBtreeHeader = newBtreeHeader; + + // Now, initialize the BTree + try + { + init(); + } + catch ( IOException ioe ) + { + throw new InitializationException( ioe.getMessage() ); + } + } + + + /** + * Initialize the BTree. + * + * @throws IOException If we get some exception while initializing the BTree + */ + private void init() throws IOException + { + // if not in-memory then default to persist mode instead of managed + if ( envDir != null ) + { + if ( !envDir.exists() ) + { + boolean created = envDir.mkdirs(); + + if ( !created ) + { + throw new IllegalStateException( "Could not create the directory " + envDir + " for storing data" ); + } + } + + this.file = new File( envDir, getName() + DATA_SUFFIX ); + + this.journal = new File( envDir, file.getName() + JOURNAL_SUFFIX ); + setType( BTreeTypeEnum.BACKED_ON_DISK ); + } + + // Create the queue containing the pending read transactions + readTransactions = new ConcurrentLinkedQueue>(); + + // Create the transaction manager + transactionManager = new InMemoryTransactionManager(); + + // Check the files and create them if missing + // Create the queue containing the modifications, if it's not a in-memory btree + if ( getType() == BTreeTypeEnum.BACKED_ON_DISK ) + { + if ( file.length() > 0 ) + { + // We have some existing file, load it + load( file ); + } + + withJournal = true; + + FileOutputStream stream = new FileOutputStream( journal ); + journalChannel = stream.getChannel(); + + // If the journal is not empty, we have to read it + // and to apply all the modifications to the current file + if ( journal.length() > 0 ) + { + applyJournal(); + } + } + else + { + setType( BTreeTypeEnum.IN_MEMORY ); + + // This is a new Btree, we have to store the BtreeHeader + BTreeHeader btreeHeader = new BTreeHeader(); + btreeHeader.setRootPage( new InMemoryLeaf( this ) ); + btreeHeader.setBtree( this ); + storeRevision( btreeHeader ); + } + + // Initialize the txnManager thread + //FIXME we should NOT create a new transaction manager thread for each BTree + //createTransactionManager(); + } + + + /** + * {@inheritDoc} + */ + protected ReadTransaction beginReadTransaction() + { + BTreeHeader btreeHeader = getBtreeHeader(); + + ReadTransaction readTransaction = new ReadTransaction( btreeHeader, readTransactions ); + + readTransactions.add( readTransaction ); + + return readTransaction; + } + + + /** + * {@inheritDoc} + */ + protected ReadTransaction beginReadTransaction( long revision ) + { + BTreeHeader btreeHeader = getBtreeHeader( revision ); + + if ( btreeHeader != null ) + { + ReadTransaction readTransaction = new ReadTransaction( btreeHeader, readTransactions ); + + readTransactions.add( readTransaction ); + + return readTransaction; + } + else + { + return null; + } + } + + + /** + * Close the BTree, cleaning up all the data structure + */ + public void close() throws IOException + { + // Stop the readTransaction thread + // readTransactionsThread.interrupt(); + // readTransactions.clear(); + + if ( getType() == BTreeTypeEnum.BACKED_ON_DISK ) + { + // Flush the data + flush(); + journalChannel.close(); + } + } + + + /** + * + * Deletes the given pair if both key and value match. If the given value is null + * and there is no null value associated with the given key then the entry with the given key + * will be removed. + * + * @param key The key to be removed + * @param value The value to be removed (can be null, and when no null value exists the key will be removed irrespective of the value) + * @param revision The revision to be associated with this operation + * @return + * @throws IOException + */ + protected Tuple delete( K key, V value, long revision ) throws IOException + { + if ( revision == -1L ) + { + revision = currentRevision.get() + 1; + } + + BTreeHeader oldBtreeHeader = getBtreeHeader(); + BTreeHeader newBtreeHeader = createNewBtreeHeader( oldBtreeHeader, revision ); + newBtreeHeader.setBtree( this ); + + // If the key exists, the existing value will be replaced. We store it + // to return it to the caller. + Tuple tuple = null; + + // Try to delete the entry starting from the root page. Here, the root + // page may be either a Node or a Leaf + DeleteResult result = getRootPage().delete( key, value, revision ); + + if ( result instanceof NotPresentResult ) + { + // Key not found. + return null; + } + + // Keep the oldRootPage so that we can later access it + //Page oldRootPage = rootPage; + + if ( result instanceof RemoveResult ) + { + // The element was found, and removed + RemoveResult removeResult = ( RemoveResult ) result; + + Page modifiedPage = removeResult.getModifiedPage(); + + // This is a new root + newBtreeHeader.setRootPage( modifiedPage ); + tuple = removeResult.getRemovedElement(); + } + + if ( withJournal ) + { + // Inject the modification into the modification queue + writeToJournal( new Deletion( key ) ); + } + + // Decrease the number of elements in the current tree if the deletion is successful + if ( tuple != null ) + { + newBtreeHeader.decrementNbElems(); + } + + storeRevision( newBtreeHeader ); + + // Return the value we have found if it was modified + if ( oldBtreeHeader.getNbUsers() == 0 ) + { + btreeRevisions.remove( oldBtreeHeader.getRevision() ); + } + + return tuple; + } + + + /** + * Insert an entry in the BTree. + *

          + * We will replace the value if the provided key already exists in the + * btree. + *

          + * The revision number is the revision to use to insert the data. + * + * @param key Inserted key + * @param value Inserted value + * @param revision The revision to use + * @return an instance of the InsertResult. + */ + /* no qualifier */InsertResult insert( K key, V value, long revision ) throws IOException + { + // We have to start a new transaction, which will be committed or rollbacked + // locally. This will duplicate the current BtreeHeader during this phase. + if ( revision == -1L ) + { + revision = currentRevision.get() + 1; + } + + BTreeHeader oldBtreeHeader = getBtreeHeader(); + BTreeHeader newBtreeHeader = createNewBtreeHeader( oldBtreeHeader, revision ); + newBtreeHeader.setBtree( this ); + + // If the key exists, the existing value will be replaced. We store it + // to return it to the caller. + V modifiedValue = null; + + // Try to insert the new value in the tree at the right place, + // starting from the root page. Here, the root page may be either + // a Node or a Leaf + InsertResult result = newBtreeHeader.getRootPage().insert( key, value, revision ); + + if ( result instanceof ExistsResult ) + { + return result; + } + + if ( result instanceof ModifyResult ) + { + ModifyResult modifyResult = ( ( ModifyResult ) result ); + + Page modifiedPage = modifyResult.getModifiedPage(); + + // The root has just been modified, we haven't split it + // Get it and make it the current root page + newBtreeHeader.setRootPage( modifiedPage ); + + modifiedValue = modifyResult.getModifiedValue(); + } + else + { + // We have split the old root, create a new one containing + // only the pivotal we got back + SplitResult splitResult = ( ( SplitResult ) result ); + + K pivot = splitResult.getPivot(); + Page leftPage = splitResult.getLeftPage(); + Page rightPage = splitResult.getRightPage(); + + // Create the new rootPage + newBtreeHeader.setRootPage( new InMemoryNode( this, revision, pivot, leftPage, rightPage ) ); + } + + // Inject the modification into the modification queue + if ( withJournal ) + { + writeToJournal( new Addition( key, value ) ); + } + + // Increase the number of element in the current tree if the insertion is successful + // and does not replace an element + if ( modifiedValue == null ) + { + newBtreeHeader.incrementNbElems(); + } + + storeRevision( newBtreeHeader ); + + if ( oldBtreeHeader.getNbUsers() == 0 ) + { + long oldRevision = oldBtreeHeader.getRevision(); + + if ( oldRevision < newBtreeHeader.getRevision() ) + { + btreeRevisions.remove( oldBtreeHeader.getRevision() ); + } + } + + // Return the value we have found if it was modified + return result; + } + + + /** + * Write the data in the ByteBuffer, and eventually on disk if needed. + * + * @param channel The channel we want to write to + * @param bb The ByteBuffer we want to feed + * @param buffer The data to inject + * @throws IOException If the write failed + */ + private void writeBuffer( FileChannel channel, ByteBuffer bb, byte[] buffer ) throws IOException + { + int size = buffer.length; + int pos = 0; + + // Loop until we have written all the data + do + { + if ( bb.remaining() >= size ) + { + // No flush, as the ByteBuffer is big enough + bb.put( buffer, pos, size ); + size = 0; + } + else + { + // Flush the data on disk, reinitialize the ByteBuffer + int len = bb.remaining(); + size -= len; + bb.put( buffer, pos, len ); + pos += len; + + bb.flip(); + + channel.write( bb ); + + bb.clear(); + } + } + while ( size > 0 ); + } + + + /** + * Flush the latest revision to disk + * @param file The file into which the data will be written + */ + public void flush( File file ) throws IOException + { + File parentFile = file.getParentFile(); + File baseDirectory = null; + + if ( parentFile != null ) + { + baseDirectory = new File( file.getParentFile().getAbsolutePath() ); + } + else + { + baseDirectory = new File( "." ); + } + + // Create a temporary file in the same directory to flush the current btree + File tmpFileFD = File.createTempFile( "mavibot", null, baseDirectory ); + FileOutputStream stream = new FileOutputStream( tmpFileFD ); + FileChannel ch = stream.getChannel(); + + // Create a buffer containing 200 4Kb pages (around 1Mb) + ByteBuffer bb = ByteBuffer.allocateDirect( writeBufferSize ); + + try + { + TupleCursor cursor = browse(); + + if ( keySerializer == null ) + { + throw new MissingSerializerException( "Cannot flush the btree without a Key serializer" ); + } + + if ( valueSerializer == null ) + { + throw new MissingSerializerException( "Cannot flush the btree without a Value serializer" ); + } + + // Write the number of elements first + bb.putLong( getBtreeHeader().getNbElems() ); + + while ( cursor.hasNext() ) + { + Tuple tuple = cursor.next(); + + byte[] keyBuffer = keySerializer.serialize( tuple.getKey() ); + + writeBuffer( ch, bb, keyBuffer ); + + byte[] valueBuffer = valueSerializer.serialize( tuple.getValue() ); + + writeBuffer( ch, bb, valueBuffer ); + } + + // Write the buffer if needed + if ( bb.position() > 0 ) + { + bb.flip(); + ch.write( bb ); + } + + // Flush to the disk for real + ch.force( true ); + ch.close(); + } + catch ( KeyNotFoundException knfe ) + { + knfe.printStackTrace(); + throw new IOException( knfe.getMessage() ); + } + + // Rename the current file to save a backup + File backupFile = File.createTempFile( "mavibot", null, baseDirectory ); + file.renameTo( backupFile ); + + // Rename the temporary file to the initial file + tmpFileFD.renameTo( file ); + + // We can now delete the backup file + backupFile.delete(); + } + + + /** + * Inject all the modification from the journal into the btree + * + * @throws IOException If we had some issue while reading the journal + */ + private void applyJournal() throws IOException + { + if ( !journal.exists() ) + { + throw new IOException( "The journal does not exist" ); + } + + FileChannel channel = + new RandomAccessFile( journal, "rw" ).getChannel(); + ByteBuffer buffer = ByteBuffer.allocate( 65536 ); + + BufferHandler bufferHandler = new BufferHandler( channel, buffer ); + + // Loop on all the elements, store them in lists atm + try + { + while ( true ) + { + // Read the type + byte[] type = bufferHandler.read( 1 ); + + if ( type[0] == Modification.ADDITION ) + { + // Read the key + K key = keySerializer.deserialize( bufferHandler ); + + //keys.add( key ); + + // Read the value + V value = valueSerializer.deserialize( bufferHandler ); + + //values.add( value ); + + // Inject the data in the tree. (to be replaced by a bulk load) + insert( key, value, getBtreeHeader().getRevision() ); + } + else + { + // Read the key + K key = keySerializer.deserialize( bufferHandler ); + + // Remove the key from the tree + delete( key, getBtreeHeader().getRevision() ); + } + } + } + catch ( EOFException eofe ) + { + eofe.printStackTrace(); + // Done reading the journal. truncate it + journalChannel.truncate( 0 ); + } + } + + + /** + * Read the data from the disk into this BTree. All the existing data in the + * BTree are kept, the read data will be associated with a new revision. + * + * @param file + * @throws IOException + */ + public void load( File file ) throws IOException + { + if ( !file.exists() ) + { + throw new IOException( "The file does not exist" ); + } + + FileChannel channel = + new RandomAccessFile( file, "rw" ).getChannel(); + ByteBuffer buffer = ByteBuffer.allocate( 65536 ); + + BufferHandler bufferHandler = new BufferHandler( channel, buffer ); + + long nbElems = LongSerializer.deserialize( bufferHandler.read( 8 ) ); + + // desactivate the journal while we load the file + boolean isJournalActivated = withJournal; + + withJournal = false; + + // Loop on all the elements, store them in lists atm + for ( long i = 0; i < nbElems; i++ ) + { + // Read the key + K key = keySerializer.deserialize( bufferHandler ); + + // Read the value + V value = valueSerializer.deserialize( bufferHandler ); + + // Inject the data in the tree. (to be replaced by a bulk load) + insert( key, value, getBtreeHeader().getRevision() ); + } + + // Restore the withJournal value + withJournal = isJournalActivated; + + // Now, process the lists to create the btree + // TODO... BulkLoad + } + + + /** + * Get the rootPage associated to a give revision. + * + * @param revision The revision we are looking for + * @return The rootPage associated to this revision + * @throws IOException If we had an issue while accessing the underlying file + * @throws KeyNotFoundException If the revision does not exist for this Btree + */ + public Page getRootPage( long revision ) throws IOException, KeyNotFoundException + { + // Atm, the in-memory BTree does not support searches in many revisions + return getBtreeHeader().getRootPage(); + } + + + /** + * Get the current rootPage + * + * @return The rootPage + */ + public Page getRootPage() + { + return getBtreeHeader().getRootPage(); + } + + + /* no qualifier */void setRootPage( Page root ) + { + getBtreeHeader().setRootPage( root ); + } + + + /** + * Flush the latest revision to disk. We will replace the current file by the new one, as + * we flush in a temporary file. + */ + public void flush() throws IOException + { + if ( getType() == BTreeTypeEnum.BACKED_ON_DISK ) + { + // Then flush the file + flush( file ); + journalChannel.truncate( 0 ); + } + } + + + /** + * @return the file + */ + public File getFile() + { + return file; + } + + + /** + * Set the file path where the journal will be stored + * + * @param filePath The file path + */ + public void setFilePath( String filePath ) + { + if ( filePath != null ) + { + envDir = new File( filePath ); + } + } + + + /** + * @return the journal + */ + public File getJournal() + { + return journal; + } + + + /** + * @return true if the BTree is fully in memory + */ + public boolean isInMemory() + { + return getType() == BTreeTypeEnum.IN_MEMORY; + } + + + /** + * @return true if the BTree is persisted on disk + */ + public boolean isPersistent() + { + return getType() == BTreeTypeEnum.IN_MEMORY; + } + + + private void writeToJournal( Modification modification ) + throws IOException + { + if ( modification instanceof Addition ) + { + byte[] keyBuffer = keySerializer.serialize( modification.getKey() ); + ByteBuffer bb = ByteBuffer.allocateDirect( keyBuffer.length + 1 ); + bb.put( Modification.ADDITION ); + bb.put( keyBuffer ); + bb.flip(); + + journalChannel.write( bb ); + + byte[] valueBuffer = valueSerializer.serialize( modification.getValue() ); + bb = ByteBuffer.allocateDirect( valueBuffer.length ); + bb.put( valueBuffer ); + bb.flip(); + + journalChannel.write( bb ); + } + else if ( modification instanceof Deletion ) + { + byte[] keyBuffer = keySerializer.serialize( modification.getKey() ); + ByteBuffer bb = ByteBuffer.allocateDirect( keyBuffer.length + 1 ); + bb.put( Modification.DELETION ); + bb.put( keyBuffer ); + bb.flip(); + + journalChannel.write( bb ); + } + + // Flush to the disk for real + journalChannel.force( true ); + } + + + /** + * Create a new B-tree header to be used for update operations + * @param revision The reclaimed revision + */ + private BTreeHeader createNewBtreeHeader( BTreeHeader btreeHeader, long revision ) + { + BTreeHeader newBtreeHeader = new BTreeHeader(); + + newBtreeHeader.setBTreeHeaderOffset( btreeHeader.getBTreeHeaderOffset() ); + newBtreeHeader.setRevision( revision ); + newBtreeHeader.setNbElems( btreeHeader.getNbElems() ); + newBtreeHeader.setRootPage( btreeHeader.getRootPage() ); + + return newBtreeHeader; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + switch ( getType() ) + { + case IN_MEMORY: + sb.append( "In-memory " ); + break; + + case BACKED_ON_DISK: + sb.append( "Persistent " ); + break; + + default: + sb.append( "Wrong type... " ); + break; + } + + sb.append( "BTree" ); + sb.append( "[" ).append( getName() ).append( "]" ); + sb.append( "( pageSize:" ).append( getPageSize() ); + + if ( getBtreeHeader().getRootPage() != null ) + { + sb.append( ", nbEntries:" ).append( getBtreeHeader().getNbElems() ); + } + else + { + sb.append( ", nbEntries:" ).append( 0 ); + } + + sb.append( ", comparator:" ); + + if ( keySerializer.getComparator() == null ) + { + sb.append( "null" ); + } + else + { + sb.append( keySerializer.getComparator().getClass().getSimpleName() ); + } + + sb.append( ", DuplicatesAllowed: " ).append( isAllowDuplicates() ); + + if ( getType() == BTreeTypeEnum.BACKED_ON_DISK ) + { + try + { + sb.append( ", file : " ); + + if ( file != null ) + { + sb.append( file.getCanonicalPath() ); + } + else + { + sb.append( "Unknown" ); + } + + sb.append( ", journal : " ); + + if ( journal != null ) + { + sb.append( journal.getCanonicalPath() ); + } + else + { + sb.append( "Unkown" ); + } + } + catch ( IOException ioe ) + { + // There is little we can do here... + } + } + + sb.append( ") : \n" ); + sb.append( getRootPage().dumpPage( "" ) ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTreeBuilder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTreeBuilder.java new file mode 100644 index 000000000..73f2b9ccd --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTreeBuilder.java @@ -0,0 +1,501 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * A BTree builder that builds a tree from the bottom. + * + * @author Apache Directory Project + */ +public class InMemoryBTreeBuilder +{ + /** The Btree configuration */ + private InMemoryBTreeConfiguration btreeConfiguration; + + + /** + * Creates a new instance of InMemoryBTreeBuilder. + * + * @param name The BTree name + * @param numKeysInNode The number of keys per node + * @param keySerializer The key serializer + * @param valueSerializer The value serializer + */ + public InMemoryBTreeBuilder( String name, int numKeysInNode, ElementSerializer keySerializer, + ElementSerializer valueSerializer ) + { + btreeConfiguration = new InMemoryBTreeConfiguration(); + btreeConfiguration.setName( name ); + btreeConfiguration.setPageSize( numKeysInNode ); + btreeConfiguration.setKeySerializer( keySerializer ); + btreeConfiguration.setValueSerializer( valueSerializer ); + } + + + /** + * Creates a new instance of InMemoryBTreeBuilder. + * + * @param btreeConfiguration The Btree configuration + */ + public InMemoryBTreeBuilder( InMemoryBTreeConfiguration btreeConfiguration ) + { + this.btreeConfiguration = btreeConfiguration; + } + + + @SuppressWarnings("unchecked") + public BTree build( Iterator> sortedTupleItr ) throws IOException + { + BTree btree = BTreeFactory.createInMemoryBTree( btreeConfiguration ); + int pageSize = btree.getPageSize(); + int maxElements = ( pageSize + 1 ) * pageSize; + + // The stack used to store all the levels. No need to have more than 16 levels, + // it will allow the storage of 2^64 elements with pages containing 4 elements each. + List>[] pageStack = new ArrayList[15]; + + for ( int i = 0; i < 15; i++ ) + { + pageStack[i] = new ArrayList>( maxElements ); + } + + // The number of added elements + int nbAdded = 0; + + // The btree height + int btreeHeight = 0; + + // An array containing the number of elements necessary to fulfill a layer : + // pageSize * (pageSize + 1) + List> tuples = new ArrayList>( maxElements ); + + // A list of nodes that are going to be created + List> nodes = new ArrayList>(); + + // Now, loop on all the elements + while ( sortedTupleItr.hasNext() ) + { + nbAdded++; + + // Get the tuple to inject + Tuple tuple = sortedTupleItr.next(); + tuples.add( tuple ); + + if ( tuples.size() == maxElements ) + { + // We have enough elements to create pageSize leaves, and the upper node + InMemoryNode node = ( InMemoryNode ) addLeaves( btree, tuples, maxElements ); + int level = 0; + + if ( node != null ) + { + while ( true ) + { + pageStack[level].add( node ); + + // If the node list has enough elements to fulfill a parent node, + // then process + if ( pageStack[level].size() > btree.getPageSize() ) + { + node = createParentNode( btree, pageStack[level], btree.getPageSize() ); + pageStack[level].clear(); + level++; + } + else + { + break; + } + } + + ( ( AbstractBTree ) btree ).setRootPage( pageStack[level].get( 0 ) ); + + // Update the btree height + if ( btreeHeight < level ) + { + btreeHeight = level; + } + } + + tuples.clear(); + } + } + + if ( tuples.size() > 0 ) + { + // Let's deal with the remaining elements + Page page = addLeaves( btree, tuples, maxElements ); + + if ( page instanceof InMemoryNode ) + { + nodes.add( ( InMemoryNode ) page ); + + // If the node list has enough elements to fulfill a parent node, + // then process + if ( nodes.size() == maxElements ) + { + Page rootPage = createParentNode( btree, nodes, maxElements ); + + ( ( AbstractBTree ) btree ).setRootPage( rootPage ); + } + } + else + { + InMemoryLeaf leaf = ( InMemoryLeaf ) page; + + // Its a leaf. That means we may have to balance the btree + if ( pageStack[0].size() != 0 ) + { + // We have some leaves in level 0, which means we just have to add the new leaf + // there, after having check we don't have to borrow some elements from the last leaf + if ( leaf.getNbElems() < btree.getPageSize() / 2 ) + { + // Not enough elements in the added leaf. Borrow some from the left. + // TODO + } + else + { + // Enough elements in the added leaf (at least N/2). We just have to update + // the parent's node. + // TODO + } + } + } + } + + // Update the number of elements + ( ( AbstractBTree ) btree ).getBtreeHeader().setNbElems( nbAdded ); + + return btree; + } + + + /** + * Creates all the nodes using the provided node pages, and update the upper laye + */ + private InMemoryNode createParentNode( BTree btree, List> nodes, int maxElements ) + { + // We have enough tuples to fulfill the upper node. + // First, create the new node + InMemoryNode parentNode = ( InMemoryNode ) BTreeFactory.createNode( btree, 0, + btreeConfiguration.getPageSize() ); + + int nodePos = 0; + + // Then iterate on the tuples, creating the needed pages + for ( InMemoryNode node : nodes ) + { + if ( nodePos != 0 ) + { + K key = node.getLeftMostKey(); + BTreeFactory.setKey( btree, parentNode, nodePos - 1, key ); + } + + PageHolder pageHolder = new PageHolder( btree, node ); + parentNode.setPageHolder( nodePos, pageHolder ); + nodePos++; + } + + // And return the node + return parentNode; + } + + + /** + * Creates all the leaves using the provided tuples, and update the upper layer if needed + */ + private Page addLeaves( BTree btree, List> tuples, int maxElements ) + { + if ( tuples.size() == 0 ) + { + // No element to inject in the BTree + return null; + } + + // The insertion position in the leaf + int leafPos = 0; + + // Deal with special cases : + // First, everything will fit in a single page + if ( tuples.size() < btree.getPageSize() ) + { + // The leaf will be the rootPage + // creates a first leaf + InMemoryLeaf leaf = ( InMemoryLeaf ) BTreeFactory.createLeaf( btree, 0, + btreeConfiguration.getPageSize() ); + + // Iterate on the tuples + for ( Tuple tuple : tuples ) + { + injectTuple( btree, leaf, leafPos, tuple ); + leafPos++; + } + + return leaf; + } + + // Second, the data will fit into a 2 level tree + if ( tuples.size() < maxElements ) + { + // We will just create the necessary leaves and the upper node if needed + // We have enough tuples to fulfill the uper node. + // First, create the new node + InMemoryNode node = ( InMemoryNode ) BTreeFactory.createNode( btree, 0, + btreeConfiguration.getPageSize() ); + + // creates a first leaf + InMemoryLeaf leaf = ( InMemoryLeaf ) BTreeFactory.createLeaf( btree, 0, + btreeConfiguration.getPageSize() ); + + int nodePos = 0; + + // Then iterate on the tuples, creating the needed pages + for ( Tuple tuple : tuples ) + { + if ( leafPos == btree.getPageSize() ) + { + // The leaf is full, we need to attach it to its parent's node + // and to create a new leaf + BTreeFactory.setKey( btree, node, nodePos, tuple.getKey() ); + PageHolder pageHolder = new PageHolder( btree, leaf ); + node.setPageHolder( nodePos, pageHolder ); + nodePos++; + + // When done, we need to create a new leaf + leaf = ( InMemoryLeaf ) BTreeFactory.createLeaf( btree, 0, + btree.getPageSize() ); + + // and inject the tuple in the leaf + injectTuple( btree, leaf, 0, tuple ); + leafPos = 1; + } + else + { + // Inject the tuple in the leaf + injectTuple( btree, leaf, leafPos, tuple ); + leafPos++; + } + } + + // Last, not least, deal with the last created leaf, which has to be injected in its parent's node + if ( leafPos > 0 ) + { + PageHolder pageHolder = new PageHolder( btree, leaf ); + node.setPageHolder( nodePos, pageHolder ); + } + + return node; + } + else + { + // We have enough tuples to fulfill the upper node. + // First, create the new node + InMemoryNode node = ( InMemoryNode ) BTreeFactory.createNode( btree, 0, + btreeConfiguration.getPageSize() ); + + // creates a first leaf + InMemoryLeaf leaf = ( InMemoryLeaf ) BTreeFactory.createLeaf( btree, 0, + btreeConfiguration.getPageSize() ); + + int nodePos = 0; + + // Then iterate on the tuples, creating the needed pages + for ( Tuple tuple : tuples ) + { + if ( leafPos == btree.getPageSize() ) + { + // The leaf is full, we need to attach it to its parent's node + // and to create a new node + BTreeFactory.setKey( btree, node, nodePos, tuple.getKey() ); + PageHolder pageHolder = new PageHolder( btree, leaf ); + node.setPageHolder( nodePos, pageHolder ); + nodePos++; + + // When done, we need to create a new leaf + leaf = ( InMemoryLeaf ) BTreeFactory.createLeaf( btree, 0, + btree.getPageSize() ); + + // and inject the tuple in the leaf + injectTuple( btree, leaf, 0, tuple ); + leafPos = 1; + } + else + { + // Inject the tuple in the leaf + injectTuple( btree, leaf, leafPos, tuple ); + leafPos++; + } + } + + // Last, not least, deal with the last created leaf, which has to be injected in its parent's node + if ( leafPos > 0 ) + { + PageHolder pageHolder = new PageHolder( btree, leaf ); + node.setPageHolder( nodePos, pageHolder ); + } + + // And return the node + return node; + } + } + + + private void injectTuple( BTree btree, InMemoryLeaf leaf, int leafPos, Tuple tuple ) + { + BTreeFactory.setKey( btree, leaf, leafPos, tuple.getKey() ); + ValueHolder valueHolder = new InMemoryValueHolder( btree, tuple.getValue() ); + BTreeFactory.setValue( btree, leaf, leafPos, valueHolder ); + } + + + private int add( BTree btree, Page[] pageStack, int level, Page page, Tuple tuple ) + { + if ( page == null ) + { + // No existing page at this level, create a new one + if ( level == 0 ) + { + // creates a leaf + InMemoryLeaf leaf = ( InMemoryLeaf ) BTreeFactory.createLeaf( btree, 0, + btreeConfiguration.getPageSize() ); + + // Store the new leaf in the stack + pageStack[level] = leaf; + + // Inject the tuple in the leaf + BTreeFactory.setKey( btree, leaf, 0, tuple.getKey() ); + ValueHolder valueHolder = new InMemoryValueHolder( btree, tuple.getValue() ); + BTreeFactory.setValue( btree, leaf, 0, valueHolder ); + } + else + { + // Create a node + InMemoryNode node = ( InMemoryNode ) BTreeFactory.createNode( btree, 0, + btreeConfiguration.getPageSize() ); + + // Inject the tuple key in the node + BTreeFactory.setKey( btree, node, 0, tuple.getKey() ); + PageHolder pageHolder = new PageHolder( btree, pageStack[level - 1] ); + node.setPageHolder( 0, pageHolder ); + } + } + else + { + // Check first if the current page is full + if ( page.getNbElems() == btree.getPageSize() ) + { + // Ok, it's full. We need to create a new page and to propagate the + // added element to the upper level + //TODO + } + else + { + // We just have to inject the tuple in the current page + // be it a leaf or a node + if ( page.isLeaf() ) + { + // It's a leaf + BTreeFactory.setKey( btree, page, page.getNbElems(), tuple.getKey() ); + ValueHolder valueHolder = new InMemoryValueHolder( btree, tuple.getValue() ); + BTreeFactory.setValue( btree, page, page.getNbElems(), valueHolder ); + } + else + { + // It's a node + BTreeFactory.setKey( btree, page, page.getNbElems(), tuple.getKey() ); + PageHolder pageHolder = new PageHolder( btree, pageStack[level - 1] ); + ( ( InMemoryNode ) page ).setPageHolder( page.getNbElems(), pageHolder ); + } + } + } + + return level; + } + + + @SuppressWarnings("unchecked") + private Page attachNodes( List> children, BTree btree ) throws IOException + { + if ( children.size() == 1 ) + { + return children.get( 0 ); + } + + List> lstNodes = new ArrayList>(); + + int numChildren = btreeConfiguration.getPageSize() + 1; + + InMemoryNode node = ( InMemoryNode ) BTreeFactory.createNode( btree, 0, + btreeConfiguration.getPageSize() ); + lstNodes.add( node ); + int i = 0; + int totalNodes = 0; + + for ( Page p : children ) + { + if ( i != 0 ) + { + BTreeFactory.setKey( btree, node, i - 1, p.getLeftMostKey() ); + } + + node.setPageHolder( i, new PageHolder( btree, p ) ); + + i++; + totalNodes++; + + if ( ( totalNodes % numChildren ) == 0 ) + { + i = 0; + node = ( InMemoryNode ) BTreeFactory.createNode( btree, 0, btreeConfiguration.getPageSize() ); + lstNodes.add( node ); + } + } + + // remove null keys and values from the last node and resize + AbstractPage lastNode = ( AbstractPage ) lstNodes.get( lstNodes.size() - 1 ); + + for ( int j = 0; j < lastNode.getNbElems(); j++ ) + { + if ( lastNode.getKey( j ) == null ) + { + int n = j; + lastNode.setNbElems( n ); + KeyHolder[] keys = lastNode.getKeys(); + + lastNode.setKeys( ( KeyHolder[] ) Array.newInstance( KeyHolder.class, n ) ); + System.arraycopy( keys, 0, lastNode.getKeys(), 0, n ); + + break; + } + } + + return attachNodes( lstNodes, btree ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTreeConfiguration.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTreeConfiguration.java new file mode 100644 index 000000000..b7cdb0887 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTreeConfiguration.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.directory.mavibot.btree; + + +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * The B+Tree Configuration. This class can be used to store all the configurable + * parameters used by the BTree class + * + * @param The type for the keys + * @param The type for the stored values + * + * @author Apache Directory Project + */ +public class InMemoryBTreeConfiguration +{ + /** Number of entries in each Page. */ + private int pageSize = InMemoryBTree.DEFAULT_PAGE_SIZE; + + /** The size of the buffer used to write data in disk */ + private int writeBufferSize = InMemoryBTree.DEFAULT_WRITE_BUFFER_SIZE; + + /** The Key and Value serializer used for this tree. If none is provided, + * the BTree will deduce the serializer to use from the generic type, and + * use the default Java serialization */ + private ElementSerializer keySerializer; + private ElementSerializer valueSerializer; + + /** The BTree name */ + private String name; + + /** The path where the BTree file will be stored. Default to the local + * temporary directory. + */ + private String filePath; + + /** + * The maximum delay to wait before a revision is considered as unused. + * This delay is necessary so that a read that does not ends does not + * hold a revision in memory forever. + * The default value is 10000 (10 seconds). If the value is 0 or below, + * the delay is considered as infinite + */ + private long readTimeOut = InMemoryBTree.DEFAULT_READ_TIMEOUT; + + /** The maximal size of the journal. When this size is reached, the tree is + * flushed on disk. + * The default size is 10 Mb + */ + private long journalSize = 10 * 1024 * 1024L; + + /** + * The journal's name. Default to "mavibot.log". + */ + private String journalName = InMemoryBTree.DEFAULT_JOURNAL; + + /** + * The delay between two checkpoints. When we reach the maximum delay, + * the BTree is flushed on disk, but only if we have had some modifications. + * The default value is 60 seconds. + */ + private long checkPointDelay = 60 * 1000L; + + /** Flag to enable duplicate key support */ + private boolean allowDuplicates; + + /** the type of BTree */ + private BTreeTypeEnum type; + + + /** + * @return the pageSize + */ + public int getPageSize() + { + return pageSize; + } + + + /** + * @param pageSize the pageSize to set + */ + public void setPageSize( int pageSize ) + { + this.pageSize = pageSize; + } + + + /** + * @return the key serializer + */ + public ElementSerializer getKeySerializer() + { + return keySerializer; + } + + + /** + * @return the value serializer + */ + public ElementSerializer getValueSerializer() + { + return valueSerializer; + } + + + /** + * @param keySerializer the key serializer to set + * @param valueSerializer the value serializer to set + */ + public void setSerializers( ElementSerializer keySerializer, ElementSerializer valueSerializer ) + { + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + } + + + /** + * @param serializer the key serializer to set + */ + public void setKeySerializer( ElementSerializer keySerializer ) + { + this.keySerializer = keySerializer; + } + + + /** + * @param serializer the key serializer to set + */ + public void setValueSerializer( ElementSerializer valueSerializer ) + { + this.valueSerializer = valueSerializer; + } + + + /** + * @return the readTimeOut + */ + public long getReadTimeOut() + { + return readTimeOut; + } + + + /** + * @param readTimeOut the readTimeOut to set + */ + public void setReadTimeOut( long readTimeOut ) + { + this.readTimeOut = readTimeOut; + } + + + /** + * @return the journalSize + */ + public long getJournalSize() + { + return journalSize; + } + + + /** + * @param journalSize the journalSize to set + */ + public void setJournalSize( long journalSize ) + { + this.journalSize = journalSize; + } + + + /** + * @return the checkPointDelay + */ + public long getCheckPointDelay() + { + return checkPointDelay; + } + + + /** + * @param checkPointDelay the checkPointDelay to set + */ + public void setCheckPointDelay( long checkPointDelay ) + { + this.checkPointDelay = checkPointDelay; + } + + + /** + * @return the filePath + */ + public String getFilePath() + { + return filePath; + } + + + /** + * @param filePath the filePath to set + */ + public void setFilePath( String filePath ) + { + this.filePath = filePath; + } + + + /** + * @return the journal name + */ + public String getJournalName() + { + return journalName; + } + + + /** + * @param journalName the journal name to set + */ + public void setJournalName( String journalName ) + { + this.journalName = journalName; + } + + + /** + * @return the writeBufferSize + */ + public int getWriteBufferSize() + { + return writeBufferSize; + } + + + /** + * @param writeBufferSize the writeBufferSize to set + */ + public void setWriteBufferSize( int writeBufferSize ) + { + this.writeBufferSize = writeBufferSize; + } + + + /** + * @return the name + */ + public String getName() + { + return name; + } + + + /** + * @param name the name to set + */ + public void setName( String name ) + { + this.name = name.trim(); + } + + + /** + * @return true if duplicate key support is enabled + */ + public boolean isAllowDuplicates() + { + return allowDuplicates; + } + + + /** + * enable duplicate key support + * + * @param allowDuplicates + * @throws IllegalStateException if the btree was already initialized or when tried to turn off duplicate suport on + * an existing btree containing duplicate keys + */ + public void setAllowDuplicates( boolean allowDuplicates ) + { + this.allowDuplicates = allowDuplicates; + } + + + /** + * @return the type of BTree + */ + public BTreeTypeEnum getType() + { + return type; + } + + + /** + * Sets the type of the BTree + * + * @param type the type of the tree + */ + public void setType( BTreeTypeEnum type ) + { + this.type = type; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryLeaf.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryLeaf.java new file mode 100644 index 000000000..793c810c1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryLeaf.java @@ -0,0 +1,1029 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; + + +/** + * A MVCC Leaf. It stores the keys and values. It does not have any children. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier */class InMemoryLeaf extends AbstractPage +{ + /** Values associated with keys */ + protected ValueHolder[] values; + + + /** + * Constructor used to create a new Leaf when we read it from a file. + * + * @param btree The BTree this page belongs to. + */ + InMemoryLeaf( BTree btree ) + { + super( btree ); + } + + + /** + * Internal constructor used to create Page instance used when a page is being copied or overflow + * + * @param btree The BTree this page belongs to. + * @param revision The page revision + * @param nbElems The number of elements this page will contain + */ + @SuppressWarnings("unchecked") + InMemoryLeaf( BTree btree, long revision, int nbElems ) + { + super( btree, revision, nbElems ); + + this.values = ( InMemoryValueHolder[] ) Array.newInstance( InMemoryValueHolder.class, nbElems ); + } + + + /** + * {@inheritDoc} + */ + public InsertResult insert( K key, V value, long revision ) throws IOException + { + // Find the key into this leaf + int pos = findPos( key ); + + if ( pos < 0 ) + { + // We already have the key in the page : replace the value + // into a copy of this page, unless the page has already be copied + int index = -( pos + 1 ); + + // Replace the existing value in a copy of the current page + InsertResult result = replaceElement( revision, key, value, index ); + + return result; + } + + // The key is not present in the leaf. We have to add it in the page + if ( nbElems < btree.getPageSize() ) + { + // The current page is not full, it can contain the added element. + // We insert it into a copied page and return the result + Page modifiedPage = addElement( revision, key, value, pos ); + + InsertResult result = new ModifyResult( modifiedPage, null ); + result.addCopiedPage( this ); + + return result; + } + else + { + // The Page is already full : we split it and return the overflow element, + // after having created two pages. + InsertResult result = addAndSplit( revision, key, value, pos ); + result.addCopiedPage( this ); + + return result; + } + } + + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + /* no qualifier */DeleteResult delete( K key, V value, long revision, Page parent, int parentPos ) + throws IOException + { + // Check that the leaf is not empty + if ( nbElems == 0 ) + { + // Empty leaf + return NotPresentResult.NOT_PRESENT; + } + + // Find the key in the page + int pos = findPos( key ); + + if ( pos >= 0 ) + { + // Not found : return the not present result. + return NotPresentResult.NOT_PRESENT; + } + + // Get the removed element + Tuple removedElement = null; + + // flag to detect if a key was completely removed + boolean keyRemoved = false; + + int index = -( pos + 1 ); + + ValueHolder valueHolder = values[index]; + + if ( value == null ) + { + // we have to delete the whole value + removedElement = new Tuple( getKey( index ), valueHolder.getCursor().next() ); // the entire value was removed + keyRemoved = true; + } + else + { + if ( valueHolder.contains( value ) ) + { + // this is a case to delete entire or + if ( valueHolder.size() == 1 ) + { + // Ok, we can remove the value + removedElement = new Tuple( getKey( index ), null ); // the entire value was removed + keyRemoved = true; + } + else + { + // Update the ValueHolder + valueHolder.remove( value ); + removedElement = new Tuple( getKey( index ), value ); // the entire value was removed + } + } + else + { + // Not found + return NotPresentResult.NOT_PRESENT; + } + } + + InMemoryLeaf newLeaf = null; + + if ( keyRemoved ) + { + newLeaf = new InMemoryLeaf( btree, revision, nbElems - 1 ); + } + else + { + newLeaf = new InMemoryLeaf( btree, revision, nbElems ); + } + + // Create the result + DeleteResult defaultResult = new RemoveResult( newLeaf, removedElement ); + + // If the parent is null, then this page is the root page. + if ( parent == null ) + { + // Just remove the entry if it's present + copyAfterRemovingElement( keyRemoved, newLeaf, index ); + + // The current page is added in the copied page list + defaultResult.addCopiedPage( this ); + + return defaultResult; + } + else if ( keyRemoved ) + { + // The current page is not the root. Check if the leaf has more than N/2 + // elements + int halfSize = btree.getPageSize() / 2; + + if ( nbElems == halfSize ) + { + // We have to find a sibling now, and either borrow an entry from it + // if it has more than N/2 elements, or to merge the two pages. + // Check in both next and previous page, if they have the same parent + // and select the biggest page with the same parent to borrow an element. + int siblingPos = selectSibling( parent, parentPos ); + InMemoryLeaf sibling = ( InMemoryLeaf ) ( ( ( InMemoryNode ) parent ) + .getPage( siblingPos ) ); + + if ( sibling.getNbElems() == halfSize ) + { + // We will merge the current page with its sibling + DeleteResult result = mergeWithSibling( removedElement, revision, sibling, + ( siblingPos < parentPos ), index ); + + return result; + } + else + { + // We can borrow the element from the left sibling + if ( siblingPos < parentPos ) + { + DeleteResult result = borrowFromLeft( removedElement, revision, sibling, index ); + + return result; + } + else + { + // Borrow from the right sibling + DeleteResult result = borrowFromRight( removedElement, revision, sibling, index ); + + return result; + } + } + } + else + { + // The page has more than N/2 elements. + // We simply remove the element from the page, and if it was the leftmost, + // we return the new pivot (it will replace any instance of the removed + // key in its parents) + copyAfterRemovingElement( keyRemoved, newLeaf, index ); + + // The current page is added in the copied page list + defaultResult.addCopiedPage( this ); + + return defaultResult; + } + } + else + { + // Last, not least : we can copy the full page + // Copy the keys and the values + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), 0, nbElems ); + System.arraycopy( values, 0, newLeaf.values, 0, nbElems ); + + // The current page is added in the copied page list + defaultResult.addCopiedPage( this ); + + return defaultResult; + } + } + + + /** + * Merges the sibling with the current leaf, after having removed the element in the page. + * + * @param revision The new revision + * @param sibling The sibling we will merge with + * @param isLeft Tells if the sibling is on the left or on the right + * @param pos The position of the removed element + * @return The new created leaf containing the sibling and the old page. + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult mergeWithSibling( Tuple removedElement, long revision, InMemoryLeaf sibling, + boolean isLeft, int pos ) + throws EndOfFileExceededException, IOException + { + // Create the new page. It will contain N - 1 elements (the maximum number) + // as we merge two pages that contain N/2 elements minus the one we remove + InMemoryLeaf newLeaf = new InMemoryLeaf( btree, revision, btree.getPageSize() - 1 ); + + if ( isLeft ) + { + // The sibling is on the left + // Copy all the elements from the sibling first + System.arraycopy( sibling.getKeys(), 0, newLeaf.getKeys(), 0, sibling.nbElems ); + System.arraycopy( sibling.values, 0, newLeaf.values, 0, sibling.nbElems ); + + // Copy all the elements from the page up to the deletion position + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), sibling.nbElems, pos ); + System.arraycopy( values, 0, newLeaf.values, sibling.nbElems, pos ); + + // And copy the remaining elements after the deletion point + System.arraycopy( getKeys(), pos + 1, newLeaf.getKeys(), sibling.nbElems + pos, nbElems - pos - 1 ); + System.arraycopy( values, pos + 1, newLeaf.values, sibling.nbElems + pos, nbElems - pos - 1 ); + } + else + { + // The sibling is on the right + // Copy all the elements from the page up to the deletion position + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), 0, pos ); + System.arraycopy( values, 0, newLeaf.values, 0, pos ); + + // Then copy the remaining elements after the deletion point + System.arraycopy( getKeys(), pos + 1, newLeaf.getKeys(), pos, nbElems - pos - 1 ); + System.arraycopy( values, pos + 1, newLeaf.values, pos, nbElems - pos - 1 ); + + // And copy all the elements from the sibling + System.arraycopy( sibling.getKeys(), 0, newLeaf.getKeys(), nbElems - 1, sibling.nbElems ); + System.arraycopy( sibling.values, 0, newLeaf.values, nbElems - 1, sibling.nbElems ); + } + + // And create the result + DeleteResult result = new MergedWithSiblingResult( newLeaf, + removedElement ); + + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * Borrows an element from the left sibling, creating a new sibling with one + * less element and creating a new page where the element to remove has been + * deleted and the borrowed element added on the left. + * + * @param revision The new revision for all the pages + * @param sibling The left sibling + * @param pos The position of the element to remove + * @return The resulting pages + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult borrowFromLeft( Tuple removedElement, long revision, InMemoryLeaf sibling, + int pos ) + throws IOException + { + // The sibling is on the left, borrow the rightmost element + K siblingKey = sibling.getKey( sibling.getNbElems() - 1 ); + ValueHolder siblingValue = sibling.values[sibling.getNbElems() - 1]; + + // Create the new sibling, with one less element at the end + InMemoryLeaf newSibling = ( InMemoryLeaf ) sibling.copy( revision, sibling.getNbElems() - 1 ); + + // Create the new page and add the new element at the beginning + // First copy the current page, with the same size + InMemoryLeaf newLeaf = new InMemoryLeaf( btree, revision, nbElems ); + + // Insert the borrowed element + newLeaf.setKey( 0, new KeyHolder( siblingKey ) ); + newLeaf.values[0] = siblingValue; + + // Copy the keys and the values up to the insertion position, + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), 1, pos ); + System.arraycopy( values, 0, newLeaf.values, 1, pos ); + + // And copy the remaining elements + System.arraycopy( getKeys(), pos + 1, newLeaf.getKeys(), pos + 1, getKeys().length - pos - 1 ); + System.arraycopy( values, pos + 1, newLeaf.values, pos + 1, values.length - pos - 1 ); + + DeleteResult result = new BorrowedFromLeftResult( newLeaf, newSibling, removedElement ); + + // Add the copied pages to the list + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * Borrows an element from the right sibling, creating a new sibling with one + * less element and creating a new page where the element to remove has been + * deleted and the borrowed element added on the right. + * + * @param revision The new revision for all the pages + * @param sibling The right sibling + * @param pos The position of the element to remove + * @return The resulting pages + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult borrowFromRight( Tuple removedElement, long revision, InMemoryLeaf sibling, + int pos ) + throws IOException + { + // The sibling is on the left, borrow the rightmost element + K siblingKey = sibling.getKey( 0 ); + ValueHolder siblingHolder = sibling.values[0]; + + // Create the new sibling + InMemoryLeaf newSibling = new InMemoryLeaf( btree, revision, sibling.getNbElems() - 1 ); + + // Copy the keys and the values from 1 to N in the new sibling + System.arraycopy( sibling.getKeys(), 1, newSibling.getKeys(), 0, sibling.nbElems - 1 ); + System.arraycopy( sibling.values, 1, newSibling.values, 0, sibling.nbElems - 1 ); + + // Create the new page and add the new element at the end + // First copy the current page, with the same size + InMemoryLeaf newLeaf = new InMemoryLeaf( btree, revision, nbElems ); + + // Insert the borrowed element at the end + newLeaf.setKey( nbElems - 1, new KeyHolder( siblingKey ) ); + newLeaf.values[nbElems - 1] = siblingHolder; + + // Copy the keys and the values up to the deletion position, + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), 0, pos ); + System.arraycopy( values, 0, newLeaf.values, 0, pos ); + + // And copy the remaining elements + System.arraycopy( getKeys(), pos + 1, newLeaf.getKeys(), pos, getKeys().length - pos - 1 ); + System.arraycopy( values, pos + 1, newLeaf.values, pos, values.length - pos - 1 ); + + DeleteResult result = new BorrowedFromRightResult( newLeaf, newSibling, removedElement ); + + // Add the copied pages to the list + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * Copies the elements of the current page to a new page + * + * @param keyRemoved a flag stating if the key was removed + * @param newLeaf The new page into which the remaining keys and values will be copied + * @param pos The position into the page of the element to remove + * @throws IOException If we have an error while trying to access the page + */ + private void copyAfterRemovingElement( boolean keyRemoved, InMemoryLeaf newLeaf, int pos ) throws IOException + { + if ( keyRemoved ) + { + // Deal with the special case of a page with only one element by skipping + // the copy, as we won't have any remaining element in the page + if ( nbElems == 1 ) + { + return; + } + + // Copy the keys and the values up to the insertion position + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), 0, pos ); + System.arraycopy( values, 0, newLeaf.values, 0, pos ); + + // And copy the elements after the position + System.arraycopy( getKeys(), pos + 1, newLeaf.getKeys(), pos, getKeys().length - pos - 1 ); + System.arraycopy( values, pos + 1, newLeaf.values, pos, values.length - pos - 1 ); + } + else + // one of the many values of the same key was removed, no change in the number of keys + { + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), 0, nbElems ); + System.arraycopy( values, 0, newLeaf.values, 0, nbElems ); + } + } + + + /** + * {@inheritDoc} + */ + public V get( K key ) throws KeyNotFoundException, IOException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + InMemoryValueHolder valueHolder = ( InMemoryValueHolder ) values[-( pos + 1 )]; + + V value = valueHolder.getCursor().next(); + + return value; + } + else + { + throw KeyNotFoundException.INSTANCE; + } + } + + + /** + * {@inheritDoc} + */ + @Override + public ValueCursor getValues( K key ) throws KeyNotFoundException, IOException, IllegalArgumentException + { + if ( !btree.isAllowDuplicates() ) + { + throw new IllegalArgumentException( "Duplicates are not allowed in this tree" ); + } + + int pos = findPos( key ); + + if ( pos < 0 ) + { + InMemoryValueHolder valueHolder = ( InMemoryValueHolder ) values[-( pos + 1 )]; + + return valueHolder.getCursor(); + } + else + { + throw KeyNotFoundException.INSTANCE; + } + } + + + /** + * {@inheritDoc} + */ + public boolean hasKey( K key ) + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + return true; + } + + return false; + } + + + @Override + public boolean contains( K key, V value ) throws IOException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + ValueHolder valueHolder = values[-( pos + 1 )]; + + return valueHolder.contains( value ); + } + else + { + return false; + } + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */ValueHolder getValue( int pos ) + { + if ( pos < nbElems ) + { + return values[pos]; + } + else + { + return null; + } + } + + + /** + * Sets the value at a give position + * @param pos The position in the values array + * @param value the value to inject + */ + /* no qualifier */void setValue( int pos, ValueHolder value ) + { + values[pos] = value; + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browse( K key, ReadTransaction transaction, ParentPos[] stack, int depth ) + { + int pos = findPos( key ); + TupleCursor cursor = null; + + if ( pos < 0 ) + { + pos = -( pos + 1 ); + + // Start at the beginning of the page + ParentPos parentPos = new ParentPos( this, pos ); + + // Create the value cursor + parentPos.valueCursor = values[pos].getCursor(); + + stack[depth] = parentPos; + + cursor = new TupleCursor( transaction, stack, depth ); + } + else + { + // The key has not been found. Select the value just above, if we have one + if ( pos < nbElems ) + { + ParentPos parentPos = new ParentPos( this, pos ); + + // Create the value cursor + parentPos.valueCursor = values[pos].getCursor(); + + stack[depth] = parentPos; + + cursor = new TupleCursor( transaction, stack, depth ); + } + else if ( nbElems > 0 ) + { + // after the last element + ParentPos parentPos = new ParentPos( this, nbElems - 1 ); + + // Create the value cursor + parentPos.valueCursor = values[nbElems - 1].getCursor(); + + stack[depth] = parentPos; + + cursor = new TupleCursor( transaction, stack, depth ); + + try + { + cursor.afterLast(); + } + catch ( IOException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + else + { + // Not found, because there are no elements : return a null cursor + stack[depth] = null; + + cursor = new TupleCursor( transaction, null, 0 ); + } + } + + return cursor; + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browse( ReadTransaction transaction, ParentPos[] stack, int depth ) + { + int pos = 0; + TupleCursor cursor = null; + + if ( nbElems == 0 ) + { + // The tree is empty, it's the root, we have nothing to return + stack[depth] = new ParentPos( null, -1 ); + + return new TupleCursor( transaction, stack, depth ); + } + else + { + // Start at the beginning of the page + ParentPos parentPos = new ParentPos( this, pos ); + + // Create the value cursor + parentPos.valueCursor = values[0].getCursor(); + + stack[depth] = parentPos; + + cursor = new TupleCursor( transaction, stack, depth ); + } + + return cursor; + } + + + /** + * {@inheritDoc} + */ + public KeyCursor browseKeys( ReadTransaction transaction, ParentPos[] stack, int depth ) + { + int pos = 0; + KeyCursor cursor = null; + + if ( nbElems == 0 ) + { + // The tree is empty, it's the root, we have nothing to return + stack[depth] = new ParentPos( null, -1 ); + + return new KeyCursor( transaction, stack, depth ); + } + else + { + // Start at the beginning of the page + ParentPos parentPos = new ParentPos( this, pos ); + + stack[depth] = parentPos; + + cursor = new KeyCursor( transaction, stack, depth ); + } + + return cursor; + } + + + /** + * Copy the current page and all of the keys, values and children, if it's not a leaf. + * + * @param revision The new revision + * @param nbElems The number of elements to copy + * @return The copied page + */ + private Page copy( long revision, int nbElems ) + { + InMemoryLeaf newLeaf = new InMemoryLeaf( btree, revision, nbElems ); + + // Copy the keys and the values + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), 0, nbElems ); + System.arraycopy( values, 0, newLeaf.values, 0, nbElems ); + + return newLeaf; + } + + + /** + * Copy the current page if needed, and replace the value at the position we have found the key. + * + * @param revision The new page revision + * @param key The new key + * @param value the new value + * @param pos The position of the key in the page + * @return The copied page + * @throws IOException If we have an error while trying to access the page + */ + private InsertResult replaceElement( long revision, K key, V value, int pos ) + throws IOException + { + InMemoryLeaf newLeaf = this; + + // Get the previous value from the leaf (it's a copy) + ValueHolder valueHolder = values[pos]; + + boolean valueExists = valueHolder.contains( value ); + + if ( this.revision != revision ) + { + // The page hasn't been modified yet, we need to copy it first + newLeaf = ( InMemoryLeaf ) copy( revision, nbElems ); + } + + // Get the previous value from the leaf (it's a copy) + valueHolder = newLeaf.values[pos]; + V replacedValue = null; + + if ( !valueExists && btree.isAllowDuplicates() ) + { + valueHolder.add( value ); + newLeaf.values[pos] = valueHolder; + } + else if ( valueExists && btree.isAllowDuplicates() ) + { + // As strange as it sounds, we need to remove the value to reinject it. + // There are cases where the value retrieval just use one part of the + // value only (typically for LDAP Entries, where we use the DN) + replacedValue = valueHolder.remove( value ); + valueHolder.add( value ); + } + else if ( !btree.isAllowDuplicates() ) + { + replacedValue = valueHolder.replaceValueArray( value ); + } + + // Create the result + InsertResult result = new ModifyResult( newLeaf, replacedValue ); + result.addCopiedPage( this ); + + return result; + } + + + /** + * Adds a new into a copy of the current page at a given position. We return the + * modified page. The new page will have one more element than the current page. + * + * @param revision The revision of the modified page + * @param key The key to insert + * @param value The value to insert + * @param pos The position into the page + * @return The modified page with the element added + */ + private Page addElement( long revision, K key, V value, int pos ) + { + // First copy the current page, but add one element in the copied page + InMemoryLeaf newLeaf = new InMemoryLeaf( btree, revision, nbElems + 1 ); + + // Atm, store the value in memory + InMemoryValueHolder valueHolder = new InMemoryValueHolder( btree, value ); + + // Deal with the special case of an empty page + if ( nbElems == 0 ) + { + newLeaf.setKey( 0, new KeyHolder( key ) ); + newLeaf.values[0] = valueHolder; + } + else + { + // Copy the keys and the values up to the insertion position + System.arraycopy( getKeys(), 0, newLeaf.getKeys(), 0, pos ); + System.arraycopy( values, 0, newLeaf.values, 0, pos ); + + // Add the new element + newLeaf.setKey( pos, new KeyHolder( key ) ); + newLeaf.values[pos] = valueHolder; + + // And copy the remaining elements + System.arraycopy( getKeys(), pos, newLeaf.getKeys(), pos + 1, getKeys().length - pos ); + System.arraycopy( values, pos, newLeaf.values, pos + 1, values.length - pos ); + } + + return newLeaf; + } + + + /** + * Split a full page into two new pages, a left, a right and a pivot element. The new pages will + * each contains half of the original elements.
          + * The pivot will be computed, depending on the place + * we will inject the newly added element.
          + * If the newly added element is in the middle, we will use it + * as a pivot. Otherwise, we will use either the last element in the left page if the element is added + * on the left, or the first element in the right page if it's added on the right. + * + * @param revision The new revision for all the created pages + * @param key The key to add + * @param value The value to add + * @param pos The position of the insertion of the new element + * @return An OverflowPage containing the pivot, and the new left and right pages + */ + private InsertResult addAndSplit( long revision, K key, V value, int pos ) + { + int middle = btree.getPageSize() >> 1; + InMemoryLeaf leftLeaf = null; + InMemoryLeaf rightLeaf = null; + InMemoryValueHolder valueHolder = new InMemoryValueHolder( btree, value ); + + // Determinate where to store the new value + if ( pos <= middle ) + { + // The left page will contain the new value + leftLeaf = new InMemoryLeaf( btree, revision, middle + 1 ); + + // Copy the keys and the values up to the insertion position + System.arraycopy( getKeys(), 0, leftLeaf.getKeys(), 0, pos ); + System.arraycopy( values, 0, leftLeaf.values, 0, pos ); + + // Add the new element + leftLeaf.setKey( pos, new KeyHolder( key ) ); + leftLeaf.values[pos] = valueHolder; + + // And copy the remaining elements + System.arraycopy( getKeys(), pos, leftLeaf.getKeys(), pos + 1, middle - pos ); + System.arraycopy( values, pos, leftLeaf.values, pos + 1, middle - pos ); + + // Now, create the right page + rightLeaf = new InMemoryLeaf( btree, revision, middle ); + + // Copy the keys and the values in the right page + System.arraycopy( getKeys(), middle, rightLeaf.getKeys(), 0, middle ); + System.arraycopy( values, middle, rightLeaf.values, 0, middle ); + } + else + { + // Create the left page + leftLeaf = new InMemoryLeaf( btree, revision, middle ); + + // Copy all the element into the left page + System.arraycopy( getKeys(), 0, leftLeaf.getKeys(), 0, middle ); + System.arraycopy( values, 0, leftLeaf.values, 0, middle ); + + // Now, create the right page + rightLeaf = new InMemoryLeaf( btree, revision, middle + 1 ); + + int rightPos = pos - middle; + + // Copy the keys and the values up to the insertion position + System.arraycopy( getKeys(), middle, rightLeaf.getKeys(), 0, rightPos ); + System.arraycopy( values, middle, rightLeaf.values, 0, rightPos ); + + // Add the new element + rightLeaf.setKey( rightPos, new KeyHolder( key ) ); + rightLeaf.values[rightPos] = valueHolder; + + // And copy the remaining elements + System.arraycopy( getKeys(), pos, rightLeaf.getKeys(), rightPos + 1, nbElems - pos ); + System.arraycopy( values, pos, rightLeaf.values, rightPos + 1, nbElems - pos ); + } + + // Get the pivot + K pivot = rightLeaf.getKey( 0 ); + + // Create the result + InsertResult result = new SplitResult( pivot, leftLeaf, rightLeaf ); + + return result; + } + + + /** + * {@inheritDoc} + */ + public Tuple findLeftMost() throws IOException + { + V val = null; + + val = values[0].getCursor().next(); + + return new Tuple( getKey( 0 ), val ); + } + + + /** + * {@inheritDoc} + */ + public Tuple findRightMost() throws EndOfFileExceededException, IOException + { + V val = null; + + ValueCursor valueCursor = values[nbElems - 1].getCursor(); + valueCursor.afterLast(); + val = valueCursor.prev(); + + return new Tuple( getKey( nbElems - 1 ), val ); + } + + + /** + * {@inheritDoc} + */ + public boolean isLeaf() + { + return true; + } + + + /** + * {@inheritDoc} + */ + public boolean isNode() + { + return false; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Leaf[" ); + sb.append( super.toString() ); + + sb.append( "] -> {" ); + + if ( nbElems > 0 ) + { + boolean isFirst = true; + + for ( int i = 0; i < nbElems; i++ ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( "<" ).append( getKey( i ) ).append( "," ).append( values[i] ).append( ">" ); + } + } + + sb.append( "}" ); + + return sb.toString(); + } + + + /** + * {@inheritDoc} + */ + public String dumpPage( String tabs ) + { + StringBuilder sb = new StringBuilder(); + + sb.append( tabs ); + + if ( nbElems > 0 ) + { + boolean isFirst = true; + + for ( int i = 0; i < nbElems; i++ ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( "<" ).append( getKey( i ) ).append( "," ).append( values[i] ).append( ">" ); + } + } + + sb.append( "\n" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryNode.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryNode.java new file mode 100644 index 000000000..4f5d6818e --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryNode.java @@ -0,0 +1,1084 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.List; + + +/** + * A MVCC Node. It stores the keys and references to its children page. It does not + * contain any value. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier */class InMemoryNode extends AbstractPage +{ + /** + * Creates a new Node which will contain only one key, with references to + * a left and right page. This is a specific constructor used by the btree + * when the root was full when we added a new value. + * + * @param btree the parent BTree + * @param revision the Node revision + * @param nbElems The number of elements in this Node + */ + @SuppressWarnings("unchecked") + InMemoryNode( BTree btree, long revision, int nbElems ) + { + super( btree, revision, nbElems ); + + // Create the children array + children = ( PageHolder[] ) Array.newInstance( PageHolder.class, nbElems + 1 ); + } + + + /** + * Creates a new Node which will contain only one key, with references to + * a left and right page. This is a specific constructor used by the btree + * when the root was full when we added a new value. + * + * @param btree the parent BTree + * @param revision the Node revision + * @param key The new key + * @param leftPage The left page + * @param rightPage The right page + */ + @SuppressWarnings("unchecked") + InMemoryNode( BTree btree, long revision, K key, Page leftPage, Page rightPage ) + { + super( btree, revision, 1 ); + + // Create the children array, and store the left and right children + children = ( PageHolder[] ) Array.newInstance( PageHolder.class, btree.getPageSize() + 1 ); + + children[0] = new PageHolder( btree, leftPage ); + children[1] = new PageHolder( btree, rightPage ); + + // Create the keys array and store the pivot into it + // We get the type of array to create from the btree + // Yes, this is an hack... + setKeys( ( KeyHolder[] ) Array.newInstance( KeyHolder.class, btree.getPageSize() ) ); + + setKey( 0, new KeyHolder( key ) ); + } + + + /** + * {@inheritDoc} + */ + public InsertResult insert( K key, V value, long revision ) throws IOException + { + // Find the key into this leaf + int pos = findPos( key ); + + if ( pos < 0 ) + { + // The key has been found in the page. As it's a Node, that means + // we must go down in the right child to insert the value + pos = -( pos++ ); + } + + // Get the child page into which we will insert the tuple + Page child = children[pos].getValue(); + + // and insert the into this child + InsertResult result = child.insert( key, value, revision ); + + // Ok, now, we have injected the tuple down the tree. Let's check + // the result to see if we have to split the current page + if ( result instanceof ModifyResult ) + { + // The child has been modified. + return replaceChild( revision, ( ModifyResult ) result, pos ); + } + else + { + // The child has been split. We have to insert the new pivot in the + // current page, and to reference the two new pages + SplitResult splitResult = ( SplitResult ) result; + K pivot = splitResult.getPivot(); + Page leftPage = splitResult.getLeftPage(); + Page rightPage = splitResult.getRightPage(); + + // We have to deal with the two cases : + // - the current page is full, we have to split it + // - the current page is not full, we insert the new pivot + if ( nbElems == btree.getPageSize() ) + { + // The page is full + result = addAndSplit( splitResult.getCopiedPages(), revision, pivot, leftPage, rightPage, pos ); + } + else + { + // The page can contain the new pivot, let's insert it + result = insertChild( splitResult.getCopiedPages(), revision, pivot, leftPage, rightPage, pos ); + } + + return result; + } + } + + + /** + * Modifies the current node after a remove has been done in one of its children. + * The node won't be merged with another node. + * + * @param removeResult The result of a remove operation + * @param index the position of the key, not transformed + * @param pos The position of the key, as a positive value + * @param found If the key has been found in the page + * @return The new result + * @throws IOException If we have an error while trying to access the page + */ + private RemoveResult handleRemoveResult( RemoveResult removeResult, int index, int pos, boolean found ) + throws IOException + { + // Simplest case : the element has been removed from the underlying page, + // we just have to copy the current page an modify the reference to link to + // the modified page. + InMemoryNode newPage = copy( revision ); + + Page modifiedPage = removeResult.getModifiedPage(); + + if ( found ) + { + newPage.children[index + 1] = new PageHolder( btree, modifiedPage ); + } + else + { + newPage.children[index] = new PageHolder( btree, modifiedPage ); + } + + if ( pos < 0 ) + { + newPage.setKey( index, new KeyHolder( removeResult.getModifiedPage().getLeftMostKey() ) ); + } + + // Modify the result and return + removeResult.setModifiedPage( newPage ); + removeResult.addCopiedPage( this ); + + return removeResult; + } + + + /** + * Handles the removal of an element from the root page, when two of its children + * have been merged. + * + * @param mergedResult The merge result + * @param pos The position in the current root + * @param found Tells if the removed key is present in the root page + * @return The resulting root page + * @throws IOException If we have an error while trying to access the page + */ + private RemoveResult handleRootRemove( MergedWithSiblingResult mergedResult, int pos, boolean found ) + throws IOException + { + RemoveResult removeResult = null; + + // If the current node contains only one key, then the merged result will be + // the new root. Deal with this case + if ( nbElems == 1 ) + { + removeResult = new RemoveResult( mergedResult.getCopiedPages(), mergedResult.getModifiedPage(), + mergedResult.getRemovedElement() ); + + removeResult.addCopiedPage( this ); + } + else + { + // Remove the element and update the reference to the changed pages + removeResult = removeKey( mergedResult, revision, pos ); + } + + return removeResult; + } + + + /** + * Borrows an element from the right sibling, creating a new sibling with one + * less element and creating a new page where the element to remove has been + * deleted and the borrowed element added on the right. + * + * @param revision The new revision for all the pages + * @param sibling The right sibling + * @param pos The position of the element to remove + * @return The resulting pages + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult borrowFromRight( long revision, MergedWithSiblingResult mergedResult, + InMemoryNode sibling, int pos ) throws IOException + { + // Create the new sibling, with one less element at the beginning + InMemoryNode newSibling = new InMemoryNode( btree, revision, sibling.getNbElems() - 1 ); + + K siblingKey = sibling.children[0].getValue().getLeftMostKey(); + + // Copy the keys and children of the old sibling in the new sibling + System.arraycopy( sibling.getKeys(), 1, newSibling.getKeys(), 0, newSibling.getNbElems() ); + System.arraycopy( sibling.children, 1, newSibling.children, 0, newSibling.getNbElems() + 1 ); + + // Create the new page and add the new element at the end + // First copy the current node, with the same size + InMemoryNode newNode = new InMemoryNode( btree, revision, nbElems ); + + // Copy the keys and the values up to the insertion position + int index = Math.abs( pos ); + + // Copy the key and children from sibling + newNode.setKey( nbElems - 1, new KeyHolder( siblingKey ) ); // 1 + newNode.children[nbElems] = sibling.children[0]; // 8 + + if ( index < 2 ) + { + // Copy the keys + System.arraycopy( getKeys(), 1, newNode.getKeys(), 0, nbElems - 1 ); + + // Inject the modified page + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[0] = new PageHolder( btree, modifiedPage ); + + // Copy the children + System.arraycopy( children, 2, newNode.children, 1, nbElems - 1 ); + } + else + { + if ( index > 2 ) + { + // Copy the keys before the deletion point + System.arraycopy( getKeys(), 0, newNode.getKeys(), 0, index - 2 ); // 4 + } + + // Inject the new modified page key + newNode.setKey( index - 2, new KeyHolder( mergedResult.getModifiedPage().getLeftMostKey() ) ); // 2 + + if ( index < nbElems ) + { + // Copy the remaining keys after the deletion point + System.arraycopy( getKeys(), index, newNode.getKeys(), index - 1, nbElems - index ); // 3 + + // Copy the remaining children after the deletion point + System.arraycopy( children, index + 1, newNode.children, index, nbElems - index ); // 7 + } + + // Copy the children before the deletion point + System.arraycopy( children, 0, newNode.children, 0, index - 1 ); // 5 + + // Inject the modified page + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[index - 1] = new PageHolder( btree, modifiedPage ); // 6 + } + + // Create the result + DeleteResult result = new BorrowedFromRightResult( mergedResult.getCopiedPages(), newNode, + newSibling, mergedResult.getRemovedElement() ); + + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * Borrows an element from the left sibling, creating a new sibling with one + * less element and creating a new page where the element to remove has been + * deleted and the borrowed element added on the left. + * + * @param revision The new revision for all the pages + * @param sibling The left sibling + * @param pos The position of the element to remove + * @return The resulting pages + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult borrowFromLeft( long revision, MergedWithSiblingResult mergedResult, + InMemoryNode sibling, int pos ) throws IOException + { + // The sibling is on the left, borrow the rightmost element + Page siblingChild = sibling.children[sibling.nbElems].getValue(); + + // Create the new sibling, with one less element at the end + InMemoryNode newSibling = new InMemoryNode( btree, revision, sibling.getNbElems() - 1 ); + + // Copy the keys and children of the old sibling in the new sibling + System.arraycopy( sibling.getKeys(), 0, newSibling.getKeys(), 0, newSibling.getNbElems() ); + System.arraycopy( sibling.children, 0, newSibling.children, 0, newSibling.getNbElems() + 1 ); + + // Create the new page and add the new element at the beginning + // First copy the current node, with the same size + InMemoryNode newNode = new InMemoryNode( btree, revision, nbElems ); + + // Sets the first children + newNode.children[0] = new PageHolder( btree, siblingChild ); //1 + + int index = Math.abs( pos ); + + if ( index < 2 ) + { + newNode.setKey( 0, new KeyHolder( mergedResult.getModifiedPage().getLeftMostKey() ) ); + System.arraycopy( getKeys(), 1, newNode.getKeys(), 1, nbElems - 1 ); + + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[1] = new PageHolder( btree, modifiedPage ); + System.arraycopy( children, 2, newNode.children, 2, nbElems - 1 ); + } + else + { + // Set the first key + newNode.setKey( 0, new KeyHolder( children[0].getValue().getLeftMostKey() ) ); //2 + + if ( index > 2 ) + { + // Copy the keys before the deletion point + System.arraycopy( getKeys(), 0, newNode.getKeys(), 1, index - 2 ); // 4 + } + + // Inject the modified key + newNode.setKey( index - 1, new KeyHolder( mergedResult.getModifiedPage().getLeftMostKey() ) ); // 3 + + if ( index < nbElems ) + { + // Add copy the remaining keys after the deletion point + System.arraycopy( getKeys(), index, newNode.getKeys(), index, nbElems - index ); // 5 + + // Copy the remaining children after the insertion point + System.arraycopy( children, index + 1, newNode.children, index + 1, nbElems - index ); // 8 + } + + // Copy the children before the insertion point + System.arraycopy( children, 0, newNode.children, 1, index - 1 ); // 6 + + // Insert the modified page + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[index] = new PageHolder( btree, modifiedPage ); // 7 + } + + // Create the result + DeleteResult result = new BorrowedFromLeftResult( mergedResult.getCopiedPages(), newNode, + newSibling, + mergedResult.getRemovedElement() ); + + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * We have to merge the node with its sibling, both have N/2 elements before the element + * removal. + * + * @param revision The revision + * @param mergedResult The result of the merge + * @param sibling The Page we will merge the current page with + * @param isLeft Tells if the sibling is on the left + * @param pos The position of the key that has been removed + * @return The page resulting of the merge + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult mergeWithSibling( long revision, MergedWithSiblingResult mergedResult, + InMemoryNode sibling, boolean isLeft, int pos ) throws IOException + { + // Create the new node. It will contain N - 1 elements (the maximum number) + // as we merge two nodes that contain N/2 elements minus the one we remove + InMemoryNode newNode = new InMemoryNode( btree, revision, btree.getPageSize() ); + Tuple removedElement = mergedResult.getRemovedElement(); + int half = btree.getPageSize() / 2; + int index = Math.abs( pos ); + + if ( isLeft ) + { + // The sibling is on the left. Copy all of its elements in the new node first + System.arraycopy( sibling.getKeys(), 0, newNode.getKeys(), 0, half ); //1 + System.arraycopy( sibling.children, 0, newNode.children, 0, half + 1 ); //2 + + // Then copy all the elements up to the deletion point + if ( index < 2 ) + { + newNode.setKey( half, new KeyHolder( mergedResult.getModifiedPage().getLeftMostKey() ) ); + System.arraycopy( getKeys(), 1, newNode.getKeys(), half + 1, half - 1 ); + + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[half + 1] = new PageHolder( btree, modifiedPage ); + System.arraycopy( children, 2, newNode.children, half + 2, half - 1 ); + } + else + { + // Copy the left part of the node keys up to the deletion point + // Insert the new key + newNode.setKey( half, new KeyHolder( children[0].getValue().getLeftMostKey() ) ); // 3 + + if ( index > 2 ) + { + System.arraycopy( getKeys(), 0, newNode.getKeys(), half + 1, index - 2 ); //4 + } + + // Inject the new merged key + newNode.setKey( half + index - 1, new KeyHolder( mergedResult.getModifiedPage().getLeftMostKey() ) ); //5 + + if ( index < half ) + { + System.arraycopy( getKeys(), index, newNode.getKeys(), half + index, half - index ); //6 + System.arraycopy( children, index + 1, newNode.children, half + index + 1, half - index ); //9 + } + + // Copy the children before the deletion point + System.arraycopy( children, 0, newNode.children, half + 1, index - 1 ); // 7 + + // Inject the new merged child + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[half + index] = new PageHolder( btree, modifiedPage ); //8 + } + } + else + { + // The sibling is on the right. + if ( index < 2 ) + { + // Copy the keys + System.arraycopy( getKeys(), 1, newNode.getKeys(), 0, half - 1 ); + + // Insert the first child + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[0] = new PageHolder( btree, modifiedPage ); + + // Copy the node children + System.arraycopy( children, 2, newNode.children, 1, half - 1 ); + } + else + { + // Copy the keys and children before the deletion point + if ( index > 2 ) + { + // Copy the first keys + System.arraycopy( getKeys(), 0, newNode.getKeys(), 0, index - 2 ); //1 + } + + // Copy the first children + System.arraycopy( children, 0, newNode.children, 0, index - 1 ); //6 + + // Inject the modified key + newNode.setKey( index - 2, new KeyHolder( mergedResult.getModifiedPage().getLeftMostKey() ) ); //2 + + // Inject the modified children + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[index - 1] = new PageHolder( btree, modifiedPage ); // 7 + + // Add the remaining node's key if needed + if ( index < half ) + { + System.arraycopy( getKeys(), index, newNode.getKeys(), index - 1, half - index ); //5 + + // Add the remining children if below half + System.arraycopy( children, index + 1, newNode.children, index, half - index ); // 8 + } + } + + // Inject the new key from sibling + newNode.setKey( half - 1, new KeyHolder( sibling.findLeftMost().getKey() ) ); //3 + + // Copy the sibling keys + System.arraycopy( sibling.getKeys(), 0, newNode.getKeys(), half, half ); + + // Add the sibling children + System.arraycopy( sibling.children, 0, newNode.children, half, half + 1 ); // 9 + } + + // And create the result + DeleteResult result = new MergedWithSiblingResult( mergedResult.getCopiedPages(), newNode, + removedElement ); + + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */ DeleteResult delete( K key, V value, long revision, Page parent, int parentPos ) + throws IOException + { + // We first try to delete the element from the child it belongs to + // Find the key in the page + int pos = findPos( key ); + boolean found = pos < 0; + int index = pos; + Page child = null; + DeleteResult deleteResult = null; + + if ( found ) + { + index = -( pos + 1 ); + child = children[-pos].getValue(); + deleteResult = ((AbstractPage)child).delete( key, value, revision, this, -pos ); + } + else + { + child = children[pos].getValue(); + deleteResult = ((AbstractPage)child).delete( key, value, revision, this, pos ); + } + + // If the key is not present in the tree, we simply return + if ( deleteResult instanceof NotPresentResult ) + { + // Nothing to do... + return deleteResult; + } + + // If we just modified the child, return a modified page + if ( deleteResult instanceof RemoveResult ) + { + RemoveResult removeResult = handleRemoveResult( ( RemoveResult ) deleteResult, index, pos, + found ); + + return removeResult; + } + + // If we had to borrow an element in the child, then have to update + // the current page + if ( deleteResult instanceof BorrowedFromSiblingResult ) + { + RemoveResult removeResult = handleBorrowedResult( ( BorrowedFromSiblingResult ) deleteResult, + pos ); + + return removeResult; + } + + // Last, not least, we have merged two child pages. We now have to remove + // an element from the local page, and to deal with the result. + if ( deleteResult instanceof MergedWithSiblingResult ) + { + MergedWithSiblingResult mergedResult = ( MergedWithSiblingResult ) deleteResult; + + // If the parent is null, then this page is the root page. + if ( parent == null ) + { + RemoveResult result = handleRootRemove( mergedResult, pos, found ); + + return result; + } + + // We have some parent. Check if the current page is not half full + int halfSize = btree.getPageSize() / 2; + + if ( nbElems > halfSize ) + { + // The page has more than N/2 elements. + // We simply remove the element from the page, and if it was the leftmost, + // we return the new pivot (it will replace any instance of the removed + // key in its parents) + RemoveResult result = removeKey( mergedResult, revision, pos ); + + return result; + } + else + { + // We will remove one element from a page that will have less than N/2 elements, + // which will lead to some reorganization : either we can borrow an element from + // a sibling, or we will have to merge two pages + int siblingPos = selectSibling( parent, parentPos ); + + InMemoryNode sibling = ( InMemoryNode ) ( ( ( InMemoryNode ) parent ).children[siblingPos] + .getValue() ); + + if ( sibling.getNbElems() > halfSize ) + { + // The sibling contains enough elements + // We can borrow the element from the sibling + if ( siblingPos < parentPos ) + { + DeleteResult result = borrowFromLeft( revision, mergedResult, sibling, pos ); + + return result; + } + else + { + // Borrow from the right + DeleteResult result = borrowFromRight( revision, mergedResult, sibling, pos ); + + return result; + } + } + else + { + // We need to merge the sibling with the current page + DeleteResult result = mergeWithSibling( revision, mergedResult, sibling, + ( siblingPos < parentPos ), pos ); + + return result; + } + } + } + + // We should never reach this point + return null; + } + + + /** + * The deletion in a children has moved an element from one of its sibling. The key + * is present in the current node. + * @param borrowedResult The result of the deletion from the children + * @param pos The position the key was found in the current node + * @return The result + * @throws IOException If we have an error while trying to access the page + */ + private RemoveResult handleBorrowedResult( BorrowedFromSiblingResult borrowedResult, int pos ) + throws IOException + { + Page modifiedPage = borrowedResult.getModifiedPage(); + Page modifiedSibling = borrowedResult.getModifiedSibling(); + + InMemoryNode newPage = copy( revision ); + + if ( pos < 0 ) + { + pos = -( pos + 1 ); + + if ( borrowedResult.isFromRight() ) + { + // Update the keys + newPage.setKey( pos, new KeyHolder( modifiedPage.findLeftMost().getKey() ) ); + newPage.setKey( pos + 1, new KeyHolder( modifiedSibling.findLeftMost().getKey() ) ); + + // Update the children + newPage.children[pos + 1] = new PageHolder( btree, modifiedPage ); + newPage.children[pos + 2] = new PageHolder( btree, modifiedSibling ); + } + else + { + // Update the keys + newPage.setKey( pos, new KeyHolder( modifiedPage.findLeftMost().getKey() ) ); + + // Update the children + newPage.children[pos] = new PageHolder( btree, modifiedSibling ); + newPage.children[pos + 1] = new PageHolder( btree, modifiedPage ); + } + } + else + { + if ( borrowedResult.isFromRight() ) + { + // Update the keys + newPage.setKey( pos, new KeyHolder( modifiedSibling.findLeftMost().getKey() ) ); + + // Update the children + newPage.children[pos] = new PageHolder( btree, modifiedPage ); + newPage.children[pos + 1] = new PageHolder( btree, modifiedSibling ); + } + else + { + // Update the keys + newPage.setKey( pos - 1, new KeyHolder( modifiedPage.findLeftMost().getKey() ) ); + + // Update the children + newPage.children[pos - 1] = new PageHolder( btree, modifiedSibling ); + newPage.children[pos] = new PageHolder( btree, modifiedPage ); + } + } + + // Modify the result and return + RemoveResult removeResult = new RemoveResult( borrowedResult.getCopiedPages(), newPage, + borrowedResult.getRemovedElement() ); + + removeResult.addCopiedPage( this ); + + return removeResult; + } + + + /** + * Remove the key at a given position. + * + * @param mergedResult The page we will remove a key from + * @param revision The revision of the modified page + * @param pos The position into the page of the element to remove + * @return The modified page with the element added + * @throws IOException If we have an error while trying to access the page + */ + private RemoveResult removeKey( MergedWithSiblingResult mergedResult, long revision, int pos ) + throws IOException + { + // First copy the current page, but remove one element in the copied page + InMemoryNode newNode = new InMemoryNode( btree, revision, nbElems - 1 ); + + int index = Math.abs( pos ) - 2; + + // + if ( index < 0 ) + { + // Copy the keys and the children + System.arraycopy( getKeys(), 1, newNode.getKeys(), 0, newNode.nbElems ); + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[0] = new PageHolder( btree, modifiedPage ); + System.arraycopy( children, 2, newNode.children, 1, nbElems - 1 ); + } + else + { + // Copy the keys + if ( index > 0 ) + { + System.arraycopy( getKeys(), 0, newNode.getKeys(), 0, index ); + } + + newNode.setKey( index, new KeyHolder( mergedResult.getModifiedPage().findLeftMost().getKey() ) ); + + if ( index < nbElems - 2 ) + { + System.arraycopy( getKeys(), index + 2, newNode.getKeys(), index + 1, nbElems - index - 2 ); + } + + // Copy the children + System.arraycopy( children, 0, newNode.children, 0, index + 1 ); + + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[index + 1] = new PageHolder( btree, modifiedPage ); + + if ( index < nbElems - 2 ) + { + System.arraycopy( children, index + 3, newNode.children, index + 2, nbElems - index - 2 ); + } + } + + // Create the result + RemoveResult result = new RemoveResult( mergedResult.getCopiedPages(), newNode, + mergedResult.getRemovedElement() ); + + result.addCopiedPage( this ); + + return result; + } + + + /** + * Set the value at a give position + * + * @param pos The position in the values array + * @param value the value to inject + */ + /* no qualifier */void setValue( int pos, Page value ) + { + children[pos] = new PageHolder( btree, value ); + } + + + /** + * This method is used when we have to replace a child in a page when we have + * found the key in the tree (the value will be changed, so we have made + * copies of the existing pages). + * + * @param revision The current revision + * @param result The modified page + * @param pos The position of the found key + * @return A modified page + * @throws IOException If we have an error while trying to access the page + */ + private InsertResult replaceChild( long revision, ModifyResult result, int pos ) throws IOException + { + // Just copy the current page and update its revision + Page newPage = copy( revision ); + + // Last, we update the children table of the newly created page + // to point on the modified child + Page modifiedPage = result.getModifiedPage(); + + ( ( InMemoryNode ) newPage ).children[pos] = new PageHolder( btree, modifiedPage ); + + // We can return the result, where we update the modifiedPage, + // to avoid the creation of a new object + result.setModifiedPage( newPage ); + + result.addCopiedPage( this ); + + return result; + } + + + /** + * Adds a new key into a copy of the current page at a given position. We return the + * modified page. The new page will have one more key than the current page. + * + * @param copiedPages the list of copied pages + * @param revision The revision of the modified page + * @param key The key to insert + * @param leftPage The left child + * @param rightPage The right child + * @param pos The position into the page + * @return The modified page with the element added + * @throws IOException If we have an error while trying to access the page + */ + private InsertResult insertChild( List> copiedPages, long revision, K key, Page leftPage, + Page rightPage, int pos ) + throws IOException + { + // First copy the current page, but add one element in the copied page + InMemoryNode newNode = new InMemoryNode( btree, revision, nbElems + 1 ); + + // Copy the keys and the children up to the insertion position + if ( nbElems > 0 ) + { + System.arraycopy( getKeys(), 0, newNode.getKeys(), 0, pos ); + System.arraycopy( children, 0, newNode.children, 0, pos ); + } + + // Add the new key and children + newNode.setKey( pos, new KeyHolder( key ) ); + + // If the BTree is managed, we now have to write the modified page on disk + // and to add this page to the list of modified pages + newNode.children[pos] = new PageHolder( btree, leftPage ); + newNode.children[pos + 1] = new PageHolder( btree, rightPage ); + + // And copy the remaining keys and children + if ( nbElems > 0 ) + { + System.arraycopy( getKeys(), pos, newNode.getKeys(), pos + 1, getKeys().length - pos ); + System.arraycopy( children, pos + 1, newNode.children, pos + 2, children.length - pos - 1 ); + } + + // Create the result + InsertResult result = new ModifyResult( copiedPages, newNode, null ); + result.addCopiedPage( this ); + + return result; + } + + + /** + * Splits a full page into two new pages, a left, a right and a pivot element. The new pages will + * each contains half of the original elements.
          + * The pivot will be computed, depending on the place + * we will inject the newly added element.
          + * If the newly added element is in the middle, we will use it + * as a pivot. Otherwise, we will use either the last element in the left page if the element is added + * on the left, or the first element in the right page if it's added on the right. + * + * @param copiedPages the list of copied pages + * @param revision The new revision for all the created pages + * @param pivot The key that will be move up after the split + * @param leftPage The left child + * @param rightPage The right child + * @param pos The position of the insertion of the new element + * @return An OverflowPage containing the pivot, and the new left and right pages + * @throws IOException If we have an error while trying to access the page + */ + private InsertResult addAndSplit( List> copiedPages, long revision, K pivot, Page leftPage, + Page rightPage, int pos ) throws IOException + { + int middle = btree.getPageSize() >> 1; + + // Create two new pages + InMemoryNode newLeftPage = new InMemoryNode( btree, revision, middle ); + InMemoryNode newRightPage = new InMemoryNode( btree, revision, middle ); + + // Determinate where to store the new value + // If it's before the middle, insert the value on the left, + // the key in the middle will become the new pivot + if ( pos < middle ) + { + // Copy the keys and the children up to the insertion position + System.arraycopy( getKeys(), 0, newLeftPage.getKeys(), 0, pos ); + System.arraycopy( children, 0, newLeftPage.children, 0, pos ); + + // Add the new element + newLeftPage.setKey( pos, new KeyHolder( pivot ) ); + newLeftPage.children[pos] = new PageHolder( btree, leftPage ); + newLeftPage.children[pos + 1] = new PageHolder( btree, rightPage ); + + // And copy the remaining elements minus the new pivot + System.arraycopy( getKeys(), pos, newLeftPage.getKeys(), pos + 1, middle - pos - 1 ); + System.arraycopy( children, pos + 1, newLeftPage.children, pos + 2, middle - pos - 1 ); + + // Copy the keys and the children in the right page + System.arraycopy( getKeys(), middle, newRightPage.getKeys(), 0, middle ); + System.arraycopy( children, middle, newRightPage.children, 0, middle + 1 ); + + // Create the result + InsertResult result = new SplitResult( copiedPages, getKey( middle - 1 ), newLeftPage, + newRightPage ); + result.addCopiedPage( this ); + + return result; + } + else if ( pos == middle ) + { + // A special case : the pivot will be propagated up in the tree + // The left and right pages will be spread on the two new pages + // Copy the keys and the children up to the insertion position (here, middle) + System.arraycopy( getKeys(), 0, newLeftPage.getKeys(), 0, middle ); + System.arraycopy( children, 0, newLeftPage.children, 0, middle ); + newLeftPage.children[middle] = new PageHolder( btree, leftPage ); + + // And process the right page now + System.arraycopy( getKeys(), middle, newRightPage.getKeys(), 0, middle ); + System.arraycopy( children, middle + 1, newRightPage.children, 1, middle ); + newRightPage.children[0] = new PageHolder( btree, rightPage ); + + // Create the result + InsertResult result = new SplitResult( copiedPages, pivot, newLeftPage, newRightPage ); + result.addCopiedPage( this ); + + return result; + } + else + { + // Copy the keys and the children up to the middle + System.arraycopy( getKeys(), 0, newLeftPage.getKeys(), 0, middle ); + System.arraycopy( children, 0, newLeftPage.children, 0, middle + 1 ); + + // Copy the keys and the children in the right page up to the pos + System.arraycopy( getKeys(), middle + 1, newRightPage.getKeys(), 0, pos - middle - 1 ); + System.arraycopy( children, middle + 1, newRightPage.children, 0, pos - middle - 1 ); + + // Add the new element + newRightPage.setKey( pos - middle - 1, new KeyHolder( pivot ) ); + newRightPage.children[pos - middle - 1] = new PageHolder( btree, leftPage ); + newRightPage.children[pos - middle] = new PageHolder( btree, rightPage ); + + // And copy the remaining elements minus the new pivot + System.arraycopy( getKeys(), pos, newRightPage.getKeys(), pos - middle, nbElems - pos ); + System.arraycopy( children, pos + 1, newRightPage.children, pos + 1 - middle, nbElems - pos ); + + // Create the result + InsertResult result = new SplitResult( copiedPages, getKey( middle ), newLeftPage, newRightPage ); + result.addCopiedPage( this ); + + return result; + } + } + + + /** + * Copies the current page and all its keys, with a new revision. + * + * @param revision The new revision + * @return The copied page + */ + protected InMemoryNode copy( long revision ) + { + InMemoryNode newPage = new InMemoryNode( btree, revision, nbElems ); + + // Copy the keys + System.arraycopy( getKeys(), 0, newPage.getKeys(), 0, nbElems ); + + // Copy the children + System.arraycopy( children, 0, newPage.children, 0, nbElems + 1 ); + + return newPage; + } + + + /** + * {@inheritDoc} + */ + public K getLeftMostKey() + { + return children[0].getValue().getLeftMostKey(); + } + + + /** + * {@inheritDoc} + */ + public K getRightMostKey() + { + int index = ( nbElems + 1 ) - 1; + + if ( children[index] != null ) + { + return children[index].getValue().getRightMostKey(); + } + + return children[nbElems - 1].getValue().getRightMostKey(); + } + + + /** + * {@inheritDoc} + */ + public boolean isLeaf() + { + return false; + } + + + /** + * {@inheritDoc} + */ + public boolean isNode() + { + return true; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Node[" ); + sb.append( super.toString() ); + sb.append( "] -> {" ); + + if ( nbElems > 0 ) + { + // Start with the first child + if ( children[0] == null ) + { + sb.append( "null" ); + } + else + { + sb.append( 'r' ).append( children[0].getValue().getRevision() ); + } + + for ( int i = 0; i < nbElems; i++ ) + { + sb.append( "|<" ).append( getKey( i ) ).append( ">|" ); + + if ( children[i + 1] == null ) + { + sb.append( "null" ); + } + else + { + sb.append( 'r' ).append( children[i + 1].getValue().getRevision() ); + } + } + } + + sb.append( "}" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryTransactionManager.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryTransactionManager.java new file mode 100644 index 000000000..ef9afb58b --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryTransactionManager.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * An implementation of a TransactionManager for in-memory B-trees + * + * @author Apache Directory Project + */ +public class InMemoryTransactionManager extends AbstractTransactionManager +{ + /** A lock to protect the transaction handling */ + private Lock transactionLock = new ReentrantLock(); + + /** A ThreadLocalStorage used to store the current transaction */ + private static final ThreadLocal context = new ThreadLocal(); + + /** A Map keeping the latest revisions for each managed BTree */ + private Map> currentBTreeHeaders = new HashMap>(); + + /** A Map storing the new revisions when some change have been made in some BTrees */ + private Map> newBTreeHeaders = new HashMap>(); + + /** A lock to protect the BtreeHeader maps */ + private ReadWriteLock btreeHeadersLock = new ReentrantReadWriteLock(); + + /** + * {@inheritDoc} + */ + @Override + public void beginTransaction() + { + // First, take the lock + transactionLock.lock(); + + // Now, check the TLS state + Integer nbTxnLevel = context.get(); + + if ( nbTxnLevel == null ) + { + context.set( 1 ); + } + else + { + // And increment the counter of inner txn. + context.set( nbTxnLevel + 1 ); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void commit() + { + int nbTxnStarted = context.get(); + + if ( nbTxnStarted == 0 ) + { + // The transaction was rollbacked, quit immediatelly + transactionLock.unlock(); + + return; + } + else + { + + // And decrement the number of started transactions + context.set( nbTxnStarted - 1 ); + } + + // Finally, release the global lock + transactionLock.unlock(); + } + + + /** + * {@inheritDoc} + */ + @Override + public void rollback() + { + // Reset the counter + context.set( 0 ); + + // Finally, release the global lock + transactionLock.unlock(); + } + + + /** + * Get the current BTreeHeader for a given Btree. It might not exist + */ + public BTreeHeader getBTreeHeader( String name ) + { + // Get a lock + btreeHeadersLock.readLock().lock(); + + // get the current BTree Header for this BTree and revision + BTreeHeader btreeHeader = currentBTreeHeaders.get( name ); + + // And unlock + btreeHeadersLock.readLock().unlock(); + + return btreeHeader; + } + + + + + /** + * {@inheritDoc} + */ + public void updateNewBTreeHeaders( BTreeHeader btreeHeader ) + { + newBTreeHeaders.put( btreeHeader.getBtree().getName(), btreeHeader ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryValueHolder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryValueHolder.java new file mode 100644 index 000000000..bfae29cec --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryValueHolder.java @@ -0,0 +1,313 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Comparator; +import java.util.UUID; + +import org.apache.directory.mavibot.btree.exception.BTreeOperationException; +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; + + +/** + * A holder to store the Values + * + * @author Apache Directory Project + * @param The value type + */ +/* No qualifier */class InMemoryValueHolder extends AbstractValueHolder +{ + /** + * Creates a new instance of a ValueHolder, containing the serialized values. + * + * @param parentBtree the parent BTree + * @param valueSerializer The Value's serializer + * @param raw The raw data containing the values + * @param nbValues the number of stored values + * @param raw the byte[] containing either the serialized array of values or the sub-btree offset + */ + InMemoryValueHolder( BTree parentBtree, int nbValues ) + { + valueSerializer = parentBtree.getValueSerializer(); + + if ( nbValues <= 1 ) + { + valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues ); + } + } + + + /** + * Creates a new instance of a ValueHolder, containing Values. This constructor is called + * when we need to create a new ValueHolder with deserialized values. + * + * @param parentBtree The parent BTree + * @param values The Values stored in the ValueHolder + */ + InMemoryValueHolder( BTree parentBtree, V... values ) + { + valueSerializer = parentBtree.getValueSerializer(); + + if ( ( values != null ) && ( values.length > 0 ) ) + { + int nbValues = values.length; + + if ( nbValues == 1 ) + { + // Store the value + valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues ); + valueArray[0] = values[0]; + nbArrayElems = nbValues; + } + else + { + // Use a sub btree, now that we have reached the threshold + createSubTree(); + + // Now inject all the values into it + for ( V value : values ) + { + try + { + valueBtree.insert( value, value ); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + } + } + } + } + + + /** + * {@inheritDoc} + */ + public int size() + { + if ( valueBtree != null ) + { + return ( int ) valueBtree.getNbElems(); + } + else + { + return nbArrayElems; + } + } + + + /** + * Create a new Sub-BTree to store the values. + */ + protected void createSubTree() + { + InMemoryBTreeConfiguration configuration = new InMemoryBTreeConfiguration(); + configuration.setAllowDuplicates( false ); + configuration.setName( UUID.randomUUID().toString() ); + configuration.setKeySerializer( valueSerializer ); + configuration.setValueSerializer( valueSerializer ); + + valueBtree = BTreeFactory.createInMemoryBTree( configuration ); + } + + + /** + * Manage a new Sub-BTree + */ + protected void manageSubTree() + { + // Nothing to do + } + + + /** + * Set the subBtree in the ValueHolder + */ + /* No qualifier*/void setSubBtree( BTree subBtree ) + { + valueBtree = subBtree; + valueArray = null; + } + + + /** + * {@inheritDoc} + */ + public V remove( V value ) + { + V removedValue = null; + + if ( valueArray != null ) + { + removedValue = removeFromArray( value ); + } + else + { + removedValue = removeFromBtree( value ); + } + + return removedValue; + } + + + /** + * Remove the value from a sub btree + */ + private V removeFromBtree( V removedValue ) + { + V returnedValue = null; + + try + { + Tuple removedTuple = valueBtree.delete( removedValue ); + + if ( removedTuple != null ) + { + returnedValue = removedTuple.getKey(); + } + } + catch ( IOException e ) + { + throw new BTreeOperationException( e ); + } + + if ( valueBtree.getNbElems() == 1 ) + { + try + { + valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), 1 ); + valueArray[0] = valueBtree.browse().next().getKey(); + nbArrayElems = 1; + valueBtree.close(); + valueBtree = null; + } + catch ( EndOfFileExceededException e ) + { + throw new BTreeOperationException( e ); + } + catch ( IOException e ) + { + throw new BTreeOperationException( e ); + } + catch ( KeyNotFoundException knfe ) + { + throw new BTreeOperationException( knfe ); + } + } + + return returnedValue; + } + + + /** + * Remove a value from an array + */ + private V removeFromArray( V value ) + { + // First check that the value is not already present in the ValueHolder + Comparator comparator = valueSerializer.getComparator(); + + int result = comparator.compare( valueArray[0], value ); + + if ( result != 0 ) + { + // The value does not exists : nothing to do + return null; + } + else + { + V returnedValue = valueArray[0]; + nbArrayElems = 0; + + return returnedValue; + } + } + + + /** + * {@inheritDoc} + */ + public boolean contains( V checkedValue ) + { + if ( valueBtree != null ) + { + try + { + return valueBtree.hasKey( checkedValue ); + } + catch ( IOException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } + catch ( KeyNotFoundException knfe ) + { + // TODO Auto-generated catch block + knfe.printStackTrace(); + return false; + } + } + else + { + Comparator comparator = valueSerializer.getComparator(); + + int result = comparator.compare( checkedValue, valueArray[0] ); + + return result == 0; + } + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "ValueHolder[" ).append( valueSerializer.getClass().getSimpleName() ); + + if ( valueBtree != null ) + { + sb.append( ", SubBTree" ); + } + else + { + sb.append( ", {" ); + + if ( size() != 0 ) + { + sb.append( valueArray[0] ); + } + + sb.append( "}" ); + } + + sb.append( "]" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InsertResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InsertResult.java new file mode 100644 index 000000000..47290c1f5 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InsertResult.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.directory.mavibot.btree; + + +/** + * The result of an insert operation. This is just a container that stores either + * the new pivot that has been extracted after a page split, or a modified page if + * the child page hasn't been split. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/interface InsertResult extends Result> +{ +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/KeyCursor.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/KeyCursor.java new file mode 100644 index 000000000..4299931a1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/KeyCursor.java @@ -0,0 +1,777 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.util.NoSuchElementException; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; + + +/** + * A Cursor is used to fetch only keys in a BTree and is returned by the + * @see BTree#browseKeys method. The cursor must be closed + * when the user is done with it. + *

          + * + * @param The type for the Key + * + * @author Apache Directory Project + */ +public class KeyCursor +{ + /** A marker to tell that we are before the first element */ + private static final int BEFORE_FIRST = -1; + + /** A marker to tell that we are after the last element */ + private static final int AFTER_LAST = -2; + + /** The stack of pages from the root down to the leaf */ + protected ParentPos[] stack; + + /** The stack's depth */ + protected int depth = 0; + + /** The transaction used for this cursor */ + protected ReadTransaction transaction; + + + /** + * Creates a new instance of Cursor. + */ + protected KeyCursor() + { + } + + + /** + * Creates a new instance of Cursor, starting on a page at a given position. + * + * @param transaction The transaction this operation is protected by + * @param stack The stack of parent's from root to this page + */ + public KeyCursor( ReadTransaction transaction, ParentPos[] stack, int depth ) + { + this.transaction = transaction; + this.stack = stack; + this.depth = depth; + } + + + /** + * Change the position in the current cursor to set it after the last key + */ + public void afterLast() throws IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + return; + } + + Page child = null; + + for ( int i = 0; i < depth; i++ ) + { + ParentPos parentPos = stack[i]; + + if ( child != null ) + { + parentPos.page = child; + parentPos.pos = child.getNbElems(); + } + else + { + // We have N+1 children if the page is a Node, so we don't decrement the nbElems field + parentPos.pos = parentPos.page.getNbElems(); + } + + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos ); + } + + // and leaf + ParentPos parentPos = stack[depth]; + + if ( child == null ) + { + parentPos.pos = parentPos.page.getNbElems() - 1; + } + else + { + parentPos.page = child; + parentPos.pos = child.getNbElems() - 1; + } + + parentPos.pos = AFTER_LAST; + } + + + /** + * Change the position in the current cursor before the first key + */ + public void beforeFirst() throws IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + return; + } + + Page child = null; + + for ( int i = 0; i < depth; i++ ) + { + ParentPos parentPos = stack[i]; + parentPos.pos = 0; + + if ( child != null ) + { + parentPos.page = child; + } + + child = ( ( AbstractPage ) parentPos.page ).getPage( 0 ); + } + + // and leaf + ParentPos parentPos = stack[depth]; + parentPos.pos = BEFORE_FIRST; + + if ( child != null ) + { + parentPos.page = child; + } + } + + + /** + * Tells if the cursor can return a next element + * + * @return true if there are some more elements + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasNext() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + return false; + } + + // Take the leaf and check if we have no mare keys + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // Empty BTree, get out + return false; + } + + if ( parentPos.pos == AFTER_LAST ) + { + return false; + } + + if ( parentPos.pos == BEFORE_FIRST ) + { + return true; + } + + if ( parentPos.pos < parentPos.page.getNbElems() - 1 ) + { + // Not the last position, we have a next key + return true; + } + else + { + // Ok, here, we have reached the last key in the leaf. We have to go up and + // see if we have some remaining keys + int currentDepth = depth - 1; + + while ( currentDepth >= 0 ) + { + parentPos = stack[currentDepth]; + + if ( parentPos.pos < parentPos.page.getNbElems() ) + { + // The parent has some remaining keys on the right, get out + return true; + } + else + { + currentDepth--; + } + } + + // We are done, there are no more key left + return false; + } + } + + + /** + * Find the next key + * + * @return the found key + * @throws IOException + * @throws EndOfFileExceededException + */ + public K next() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + throw new NoSuchElementException( "No Key is present" ); + } + + ParentPos parentPos = stack[depth]; + + if ( ( parentPos.page == null ) || ( parentPos.pos == AFTER_LAST ) ) + { + // This is the end : no more keys + throw new NoSuchElementException( "No more keys present" ); + } + + if ( parentPos.pos == parentPos.page.getNbElems() ) + { + // End of the leaf. We have to go back into the stack up to the + // parent, and down to the leaf + parentPos = findNextParentPos(); + + // we also need to check for the type of page cause + // findNextParentPos will never return a null ParentPos + if ( ( parentPos == null ) || ( parentPos.page == null ) ) + { + // This is the end : no more keys + throw new NoSuchElementException( "No more keys present" ); + } + } + + // Deal with the BeforeFirst case + if ( parentPos.pos == BEFORE_FIRST ) + { + parentPos.pos++; + } + else + { + if ( parentPos.pos == parentPos.page.getNbElems() - 1 ) + { + parentPos = findNextParentPos(); + + if ( ( parentPos == null ) || ( parentPos.page == null ) ) + { + // This is the end : no more keys + throw new NoSuchElementException( "No more keys present" ); + } + } + else + { + parentPos.pos++; + } + } + + AbstractPage leaf = ( AbstractPage ) ( parentPos.page ); + + return leaf.getKey( parentPos.pos ); + } + + + /** + * Get the next key. + */ + public K nextKey() throws EndOfFileExceededException, IOException + { + return next(); + } + + + /** + * Tells if the cursor can return a next key + * + * @return true if there are some more keys + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasNextKey() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + // This is the end : no more key + return false; + } + + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // This is the end : no more key + return false; + } + + if ( parentPos.pos == ( parentPos.page.getNbElems() - 1 ) ) + { + // End of the leaf. We have to go back into the stack up to the + // parent, and down to the next leaf + return hasNextParentPos(); + } + else + { + return true; + } + } + + + /** + * Tells if the cursor can return a previous element + * + * @return true if there are some more elements + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasPrev() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + return false; + } + + // Take the leaf and check if we have no mare keys + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // Empty BTree, get out + return false; + } + + if ( parentPos.pos > 0 ) + { + // get out, we have keys on the left + return true; + } + else + { + // Check that we are not before the first key + if ( parentPos.pos == BEFORE_FIRST ) + { + return false; + } + + if ( parentPos.pos == AFTER_LAST ) + { + return true; + } + + // Ok, here, we have reached the first key in the leaf. We have to go up and + // see if we have some remaining keys + int currentDepth = depth - 1; + + while ( currentDepth >= 0 ) + { + parentPos = stack[currentDepth]; + + if ( parentPos.pos > 0 ) + { + // The parent has some remaining keys on the right, get out + return true; + } + else + { + currentDepth--; + } + } + + return false; + } + } + + + /** + * Find the previous key + * + * @return the found key + * @throws IOException + * @throws EndOfFileExceededException + */ + public K prev() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + throw new NoSuchElementException( "No more keys present" ); + } + + ParentPos parentPos = stack[depth]; + + if ( ( parentPos.page == null ) || ( parentPos.pos == BEFORE_FIRST ) ) + { + // This is the end : no more keys + throw new NoSuchElementException( "No more keys present" ); + } + + // Deal with the AfterLast case + if ( parentPos.pos == AFTER_LAST ) + { + parentPos.pos = parentPos.page.getNbElems() - 1; + } + else + { + if ( parentPos.pos == 0 ) + { + parentPos = findPrevParentPos(); + + if ( ( parentPos == null ) || ( parentPos.page == null ) ) + { + // This is the end : no more keys + throw new NoSuchElementException( "No more keys present" ); + } + } + else + { + parentPos.pos--; + } + } + + AbstractPage leaf = ( AbstractPage ) ( parentPos.page ); + + return leaf.getKey( parentPos.pos ); + } + + + /** + * Get the previous key. + * + * @return the found key + * @throws EndOfFileExceededException + * @throws IOException + */ + public K prevKey() throws EndOfFileExceededException, IOException + { + return prev(); + } + + + /** + * Tells if the cursor can return a previous key + * + * @return true if there are some more keys + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasPrevKey() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + // This is the end : no more key + return false; + } + + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // This is the end : no more key + return false; + } + + switch ( parentPos.pos ) + { + case 0 : + // Beginning of the leaf. We have to go back into the stack up to the + // parent, and down to the leaf + return hasPrevParentPos(); + + case -1 : + // no previous key + return false; + + default : + // we have a previous key + return true; + } + } + + + /** + * Tells if there is a next ParentPos + * + * @return the new ParentPos instance, or null if we have no following leaf + * @throws IOException + * @throws EndOfFileExceededException + */ + private boolean hasNextParentPos() throws EndOfFileExceededException, IOException + { + if ( depth == 0 ) + { + // No need to go any further, there is only one leaf in the btree + return false; + } + + int currentDepth = depth - 1; + Page child = null; + + // First, go up the tree until we find a Node which has some element on the right + while ( currentDepth >= 0 ) + { + // We first go up the tree, until we reach a page whose current position + // is not the last one + ParentPos parentPos = stack[currentDepth]; + + if ( parentPos.pos + 1 > parentPos.page.getNbElems() ) + { + // No more element on the right : go up + currentDepth--; + } + else + { + // We can pick the next element at this level + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos + 1 ); + + // and go down the tree through the nodes + while ( currentDepth < depth - 1 ) + { + currentDepth++; + child = ( ( AbstractPage ) child ).getPage( 0 ); + } + + return true; + } + } + + return false; + } + + + /** + * Find the leaf containing the following elements. + * + * @return the new ParentPos instance, or null if we have no following leaf + * @throws IOException + * @throws EndOfFileExceededException + */ + private ParentPos findNextParentPos() throws EndOfFileExceededException, IOException + { + if ( depth == 0 ) + { + // No need to go any further, there is only one leaf in the btree + return null; + } + + int currentDepth = depth - 1; + Page child = null; + + // First, go up the tree until we find a Node which has some element on the right + while ( currentDepth >= 0 ) + { + // We first go up the tree, until we reach a page whose current position + // is not the last one + ParentPos parentPos = stack[currentDepth]; + + if ( parentPos.pos + 1 > parentPos.page.getNbElems() ) + { + // No more element on the right : go up + currentDepth--; + } + else + { + // We can pick the next element at this level + parentPos.pos++; + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos ); + + // and go down the tree through the nodes + while ( currentDepth < depth - 1 ) + { + currentDepth++; + parentPos = stack[currentDepth]; + parentPos.pos = 0; + parentPos.page = child; + child = ( ( AbstractPage ) child ).getPage( 0 ); + } + + // and the leaf + parentPos = stack[depth]; + parentPos.page = child; + parentPos.pos = 0; + + return parentPos; + } + } + + return null; + } + + + /** + * Find the leaf containing the previous elements. + * + * @return the new ParentPos instance, or null if we have no previous leaf + * @throws IOException + * @throws EndOfFileExceededException + */ + private ParentPos findPrevParentPos() throws EndOfFileExceededException, IOException + { + if ( depth == 0 ) + { + // No need to go any further, there is only one leaf in the btree + return null; + } + + int currentDepth = depth - 1; + Page child = null; + + // First, go up the tree until we find a Node which has some element on the left + while ( currentDepth >= 0 ) + { + // We first go up the tree, until we reach a page whose current position + // is not the last one + ParentPos parentPos = stack[currentDepth]; + + if ( parentPos.pos == 0 ) + { + // No more element on the right : go up + currentDepth--; + } + else + { + // We can pick the next element at this level + parentPos.pos--; + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos ); + + // and go down the tree through the nodes + while ( currentDepth < depth - 1 ) + { + currentDepth++; + parentPos = stack[currentDepth]; + parentPos.pos = child.getNbElems(); + parentPos.page = child; + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.page.getNbElems() ); + } + + // and the leaf + parentPos = stack[depth]; + parentPos.pos = child.getNbElems() - 1; + parentPos.page = child; + + return parentPos; + } + } + + return null; + } + + + /** + * Tells if there is a prev ParentPos + * + * @return the new ParentPos instance, or null if we have no previous leaf + * @throws IOException + * @throws EndOfFileExceededException + */ + private boolean hasPrevParentPos() throws EndOfFileExceededException, IOException + { + if ( depth == 0 ) + { + // No need to go any further, there is only one leaf in the btree + return false; + } + + int currentDepth = depth - 1; + Page child = null; + + // First, go up the tree until we find a Node which has some element on the right + while ( currentDepth >= 0 ) + { + // We first go up the tree, until we reach a page whose current position + // is not the last one + ParentPos parentPos = stack[currentDepth]; + + if ( parentPos.pos == 0 ) + { + // No more element on the left : go up + currentDepth--; + } + else + { + // We can pick the previous element at this level + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos - 1 ); + + // and go down the tree through the nodes + while ( currentDepth < depth - 1 ) + { + currentDepth++; + child = ( ( AbstractPage ) child ).getPage( child.getNbElems() ); + } + + return true; + } + } + + return false; + } + + + /** + * {@inheritDoc} + */ + public void close() + { + transaction.close(); + } + + + /** + * Get the creation date + * @return The creation date for this cursor + */ + public long getCreationDate() + { + return transaction.getCreationDate(); + } + + + /** + * Get the current revision + * + * @return The revision this cursor is based on + */ + public long getRevision() + { + return transaction.getRevision(); + } + + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "KeyCursor, depth = " ).append( depth ).append( "\n" ); + + for ( int i = 0; i <= depth; i++ ) + { + sb.append( " " ).append( stack[i] ).append( "\n" ); + } + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/KeyHolder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/KeyHolder.java new file mode 100644 index 000000000..6a8a31e07 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/KeyHolder.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.directory.mavibot.btree; + + +/** + * The data structure holding a key and the way to access it + * + * @author Apache Directory Project + * + * The key type + */ +/* No qualifier*/class KeyHolder +{ + /** The deserialized key */ + protected K key; + + + /** + * Create a new KeyHolder instance + * + * @param key The key to store + */ + /* no qualifier */KeyHolder( K key ) + { + this.key = key; + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */void setKey( K key ) + { + this.key = key; + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */K getKey() + { + return key; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + return key.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/LevelInfo.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/LevelInfo.java new file mode 100644 index 000000000..73fc43504 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/LevelInfo.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.mavibot.btree; + + +/** + * A class to store informations on a level. We have to keep : + *

            + *
          • The number of elements to store in this level
          • + *
          • A flag that tells if it's a leaf or a node level
          • + *
          • The number of pages necessary to store all the elements in a level
          • + *
          • The number of elements we can store in a complete page (we may have one or two + * incomplete pages at the end)
          • + *
          • A flag that tells if we have some incomplete page at the end
          • + *
          + * + * @author Apache Directory Project + */ +public class LevelInfo +{ + /** The level number */ + private int levelNumber; + + /** Nb of elements for this level */ + private int nbElems; + + /** The number of pages in this level */ + private int nbPages; + + /** Nb of elements before we reach an incomplete page */ + private int nbElemsLimit; + + /** A flag that tells if the level contains nodes or leaves */ + private boolean isNode; + + /** The current page which contains the data until we move it to the resulting BTree */ + private Page currentPage; + + /** The current position in the currentPage */ + private int currentPos; + + /** The number of already added elements for this level */ + private int nbAddedElems; + + + /** + * @return the levelNumber + */ + public int getLevelNumber() + { + return levelNumber; + } + + + /** + * @param levelNumber the levelNumber to set + */ + public void setLevelNumber( int levelNumber ) + { + this.levelNumber = levelNumber; + } + + + /** + * @return the nbElems + */ + public int getNbElems() + { + return nbElems; + } + + + /** + * @param nbElems the nbElems to set + */ + public void setNbElems( int nbElems ) + { + this.nbElems = nbElems; + } + + + /** + * @return the nbPages + */ + public int getNbPages() + { + return nbPages; + } + + + /** + * @param nbPages the nbPages to set + */ + public void setNbPages( int nbPages ) + { + this.nbPages = nbPages; + } + + + /** + * Increment the number of pages + */ + public void incNbPages() + { + this.nbPages++; + } + + + /** + * @return the nbElemsLimit + */ + public int getNbElemsLimit() + { + return nbElemsLimit; + } + + + /** + * @param nbElemsLimit the nbElemsLimit to set + */ + public void setNbElemsLimit( int nbElemsLimit ) + { + this.nbElemsLimit = nbElemsLimit; + } + + + /** + * @return the isNode + */ + public boolean isNode() + { + return isNode; + } + + + /** + * @param isNode the isNode to set + */ + public void setType( boolean isNode ) + { + this.isNode = isNode; + } + + + /** + * @return the currentPage + */ + public Page getCurrentPage() + { + return currentPage; + } + + + /** + * @param currentPage the currentPage to set + */ + public void setCurrentPage( Page currentPage ) + { + this.currentPage = currentPage; + } + + + /** + * @return the currentPos + */ + public int getCurrentPos() + { + return currentPos; + } + + + /** + * @param currentPos the currentPos to set + */ + public void setCurrentPos( int currentPos ) + { + this.currentPos = currentPos; + } + + + /** + * Increment the current position + */ + public void incCurrentPos() + { + this.currentPos++; + } + + + /** + * @return the nbAddedElems + */ + public int getNbAddedElems() + { + return nbAddedElems; + } + + + /** + * @param nbAddedElems the nbAddedElems to set + */ + public void setNbAddedElems( int nbAddedElems ) + { + this.nbAddedElems = nbAddedElems; + } + + + /** + * Increment the number of added elements + */ + public void incNbAddedElems() + { + this.nbAddedElems++; + } + + + /** @see Object#toString() */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if ( isNode ) + { + sb.append( "NodeLevel[" ); + sb.append( levelNumber ); + sb.append( "] :" ); + } + else + { + sb.append( "LeafLevel:" ); + } + + sb.append( "\n nbElems = " ).append( nbElems ); + sb.append( "\n nbPages = " ).append( nbPages ); + sb.append( "\n nbElemsLimit = " ).append( nbElemsLimit ); + sb.append( "\n nbAddedElems = " ).append( nbAddedElems ); + sb.append( "\n currentPos = " ).append( currentPos ); + sb.append( "\n currentPage" ); + sb.append( "\n nbKeys : " ).append( currentPage.getNbElems() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/MavibotInspector.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/MavibotInspector.java new file mode 100644 index 000000000..efbffe759 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/MavibotInspector.java @@ -0,0 +1,1457 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.directory.mavibot.btree.exception.InvalidBTreeException; +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.apache.directory.mavibot.btree.util.Strings; + + +/** + * A class to examine a Mavibot database file. + * + * @author Apache Directory Project + */ +public class MavibotInspector +{ + // The file to be read + private File dbFile; + + // The recordManager + private static RecordManager rm; + + private BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); + + // The name of the two page arrays for the global file and teh free pages + private static final String GLOBAL_PAGES_NAME = "__global__"; + private static final String FREE_PAGES_NAME = "__free-pages__"; + + // The set of page array we already know about + private static Set knownPagesArrays = new HashSet(); + + // Create an array of pages to be checked for each B-tree, plus + // two others for the free pages and the global one + // We use one bit per page. It's 0 when the page + // hasn't been checked, 1 otherwise. + private static Map checkedPages = new HashMap(); + + static + { + knownPagesArrays.add( GLOBAL_PAGES_NAME ); + knownPagesArrays.add( FREE_PAGES_NAME ); + knownPagesArrays.add( RecordManager.BTREE_OF_BTREES_NAME ); + knownPagesArrays.add( RecordManager.COPIED_PAGE_BTREE_NAME ); + } + + + /** + * A private class to store a few informations about a btree + * + + private static BtreeInfo btreeInfo; + + static + { + btreeInfo = new BtreeInfo(); + } + + /** + * Create an instance of MavibotInspector + * @param dbFile The file to read + */ + public MavibotInspector( File dbFile ) + { + this.dbFile = dbFile; + } + + + /** + * Check that the file exists + */ + private boolean checkFilePresence() + { + if ( dbFile == null ) + { + System.out.println( "No mavibot database file was given" ); + return false; + } + + if ( !dbFile.exists() ) + { + System.out.println( "Given mavibot database file " + dbFile + " doesn't exist" ); + return false; + } + + return true; + } + + + /** + * Pretty print the file size + */ + public void printFileSize() throws IOException + { + FileChannel fileChannel = new RandomAccessFile( dbFile, "r" ).getChannel(); + + long l = fileChannel.size(); + + fileChannel.close(); + + String msg; + + if ( l < 1024 ) + { + msg = l + " bytes"; + } + else + { + msg = ( l / 1024 ) + " KB"; + } + + System.out.println( msg ); + + fileChannel.close(); + } + + + /** + * Print the number of B-trees + */ + public void printNumberOfBTrees() + { + int nbBtrees = 0; + + if ( rm != null ) + { + nbBtrees = rm.getNbManagedTrees(); + } + + // The number of trees. It must be at least 2 and > 0 + System.out.println( "Total Number of BTrees: " + nbBtrees ); + } + + + /** + * Print the B-tree's name + */ + public void printBTreeNames() + { + if ( rm == null ) + { + System.out.println( "Couldn't find the number of managed btrees" ); + return; + } + + Set trees = rm.getManagedTrees(); + System.out.println( "\nManaged BTrees:" ); + + for ( String tree : trees ) + { + System.out.println( tree ); + } + + System.out.println(); + } + + + /** + * Check a B-tree + */ + public void inspectBTree() + { + if ( rm == null ) + { + System.out.println( "Cannot check BTree(s)" ); + return; + } + + System.out.print( "BTree Name: " ); + String name = readLine(); + + PersistedBTree pb = ( PersistedBTree ) rm.getManagedTree( name ); + + if ( pb == null ) + { + System.out.println( "No BTree exists with the name '" + name + "'" ); + return; + } + + System.out.println( "\nBTree offset: " + String.format( "0x%1$08x", pb.getBtreeOffset() ) ); + System.out.println( "BTree _info_ offset: " + String.format( "0x%1$08x", pb.getBtreeInfoOffset() ) ); + System.out.println( "BTree root page offset: " + String.format( "0x%1$08x", pb.getRootPageOffset() ) ); + System.out.println( "Number of elements present: " + pb.getNbElems() ); + System.out.println( "BTree Page size: " + pb.getPageSize() ); + System.out.println( "BTree revision: " + pb.getRevision() ); + System.out.println( "Key serializer: " + pb.getKeySerializerFQCN() ); + System.out.println( "Value serializer: " + pb.getValueSerializerFQCN() ); + System.out.println(); + } + + + /** + * Load the full fie into a new RecordManager + */ + private boolean loadRm() + { + try + { + if ( rm != null ) + { + System.out.println( "Closing record manager" ); + rm.close(); + } + + rm = new RecordManager( dbFile.getAbsolutePath() ); + System.out.println( "Loaded record manager" ); + } + catch ( Exception e ) + { + System.out.println( "Given database file seems to be corrupted. " + e.getMessage() ); + return false; + } + + return true; + } + + + /** + * Check the whole file + */ + /* no qualifier */static void check( RecordManager recordManager ) + { + try + { + rm = recordManager; + + // First check the RMheader + ByteBuffer recordManagerHeader = ByteBuffer.allocate( RecordManager.RECORD_MANAGER_HEADER_SIZE ); + long fileSize = recordManager.fileChannel.size(); + + if ( fileSize < RecordManager.RECORD_MANAGER_HEADER_SIZE ) + { + throw new InvalidBTreeException( "File size too small : " + fileSize ); + } + + // Read the RMHeader + recordManager.fileChannel.read( recordManagerHeader, 0L ); + recordManagerHeader.flip(); + + // The page size. It must be a power of 2, and above 16. + int pageSize = recordManagerHeader.getInt(); + + if ( ( pageSize != recordManager.pageSize ) || ( pageSize < 32 ) + || ( ( pageSize & ( ~pageSize + 1 ) ) != pageSize ) ) + { + throw new InvalidBTreeException( "Wrong page size : " + pageSize ); + } + + // Compute the number of pages in this file + long nbPages = ( fileSize - RecordManager.RECORD_MANAGER_HEADER_SIZE ) / pageSize; + + // The number of trees. It must be at least >= 2 + int nbBtrees = recordManagerHeader.getInt(); + + if ( ( nbBtrees < 0 ) || ( nbBtrees != recordManager.nbBtree ) ) + { + throw new InvalidBTreeException( "Wrong nb trees : " + nbBtrees ); + } + + // The first free page offset. It must be either -1 or below file size + // and its value must be a modulo of pageSize + long firstFreePage = recordManagerHeader.getLong(); + + if ( firstFreePage != RecordManager.NO_PAGE ) + { + checkOffset( recordManager, firstFreePage ); + } + + int nbPageBits = ( int ) ( nbPages / 32 ); + + // The global page array + checkedPages.put( GLOBAL_PAGES_NAME, new int[nbPageBits + 1] ); + + // The freePages array + checkedPages.put( FREE_PAGES_NAME, new int[nbPageBits + 1] ); + + // The B-tree of B-trees array + checkedPages.put( RecordManager.BTREE_OF_BTREES_NAME, new int[nbPageBits + 1] ); + + // Last, the Copied Pages B-tree array + checkedPages.put( RecordManager.COPIED_PAGE_BTREE_NAME, new int[nbPageBits + 1] ); + + // Check the free files + checkFreePages( recordManager, checkedPages ); + + // The B-trees offsets + long currentBtreeOfBtreesOffset = recordManagerHeader.getLong(); + long previousBtreeOfBtreesOffset = recordManagerHeader.getLong(); + long currentCopiedPagesBtreeOffset = recordManagerHeader.getLong(); + long previousCopiedPagesBtreeOffset = recordManagerHeader.getLong(); + + // Check that the previous BOB offset is not pointing to something + if ( previousBtreeOfBtreesOffset != RecordManager.NO_PAGE ) + { + System.out.println( "The previous Btree of Btrees offset is not valid : " + + previousBtreeOfBtreesOffset ); + return; + } + + // Check that the previous CPB offset is not pointing to something + if ( previousCopiedPagesBtreeOffset != RecordManager.NO_PAGE ) + { + System.out.println( "The previous Copied Pages Btree offset is not valid : " + + previousCopiedPagesBtreeOffset ); + return; + } + + // Check that the current BOB offset is valid + checkOffset( recordManager, currentBtreeOfBtreesOffset ); + + // Check that the current CPB offset is valid + checkOffset( recordManager, currentCopiedPagesBtreeOffset ); + + // Now, check the BTree of Btrees + checkBtreeOfBtrees( recordManager, checkedPages ); + + // And the Copied Pages BTree + checkBtree( recordManager, currentCopiedPagesBtreeOffset, checkedPages ); + + // We can now dump the checked pages + dumpCheckedPages( recordManager, checkedPages ); + } + catch ( Exception e ) + { + // We catch the exception and rethrow it immediately to be able to + // put a breakpoint here + e.printStackTrace(); + throw new InvalidBTreeException( "Error : " + e.getMessage() ); + } + } + + + /** + * Check the Btree of Btrees + */ + private static void checkBtreeOfBtrees( RecordManager recordManager, Map checkedPages ) + throws Exception + { + // Read the BOB header + PageIO[] bobHeaderPageIos = recordManager + .readPageIOs( recordManager.currentBtreeOfBtreesOffset, Long.MAX_VALUE ); + + // update the checkedPages + updateCheckedPages( checkedPages.get( RecordManager.BTREE_OF_BTREES_NAME ), recordManager.pageSize, + bobHeaderPageIos ); + updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, bobHeaderPageIos ); + + long dataPos = 0L; + + // The B-tree current revision + recordManager.readLong( bobHeaderPageIos, dataPos ); + dataPos += RecordManager.LONG_SIZE; + + // The nb elems in the tree + recordManager.readLong( bobHeaderPageIos, dataPos ); + dataPos += RecordManager.LONG_SIZE; + + // The B-tree rootPage offset + long rootPageOffset = recordManager.readLong( bobHeaderPageIos, dataPos ); + + checkOffset( recordManager, rootPageOffset ); + + dataPos += RecordManager.LONG_SIZE; + + // The B-tree info offset + long btreeInfoOffset = recordManager.readLong( bobHeaderPageIos, dataPos ); + + checkOffset( recordManager, btreeInfoOffset ); + + checkBtreeInfo( recordManager, checkedPages, btreeInfoOffset, -1L ); + + // Check the elements in the btree itself + // We will read every single page + checkBtreeOfBtreesPage( recordManager, checkedPages, rootPageOffset ); + } + + + /** + * Check a user's B-tree + */ + private static void checkBtree( RecordManager recordManager, long btreeOffset, + Map checkedPages ) throws Exception + { + // Read the B-tree header + PageIO[] btreeHeaderPageIos = recordManager.readPageIOs( btreeOffset, Long.MAX_VALUE ); + + long dataPos = 0L; + + // The B-tree current revision + long btreeRevision = recordManager.readLong( btreeHeaderPageIos, dataPos ); + dataPos += RecordManager.LONG_SIZE; + + // The nb elems in the tree + recordManager.readLong( btreeHeaderPageIos, dataPos ); + dataPos += RecordManager.LONG_SIZE; + + // The B-tree rootPage offset + long rootPageOffset = recordManager.readLong( btreeHeaderPageIos, dataPos ); + + checkOffset( recordManager, rootPageOffset ); + + dataPos += RecordManager.LONG_SIZE; + + // The B-tree info offset + long btreeInfoOffset = recordManager.readLong( btreeHeaderPageIos, dataPos ); + + checkOffset( recordManager, btreeInfoOffset ); + + BtreeInfo btreeInfo = checkBtreeInfo( recordManager, checkedPages, btreeInfoOffset, btreeRevision ); + + // Update the checked pages + updateCheckedPages( checkedPages.get( btreeInfo.btreeName ), recordManager.pageSize, btreeHeaderPageIos ); + updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, btreeHeaderPageIos ); + + // And now, process the rootPage + checkBtreePage( recordManager, btreeInfo, checkedPages, rootPageOffset ); + } + + + /** + * Check the Btree of Btrees rootPage + */ + private static void checkBtreePage( RecordManager recordManager, BtreeInfo btreeInfo, + Map checkedPages, long pageOffset ) throws Exception + { + PageIO[] pageIos = recordManager.readPageIOs( pageOffset, Long.MAX_VALUE ); + + // Update the checkedPages array + updateCheckedPages( checkedPages.get( btreeInfo.btreeName ), recordManager.pageSize, pageIos ); + updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, pageIos ); + + // Deserialize the page now + long position = 0L; + + // The revision + long revision = recordManager.readLong( pageIos, position ); + position += RecordManager.LONG_SIZE; + + // The number of elements in the page + int nbElems = recordManager.readInt( pageIos, position ); + position += RecordManager.INT_SIZE; + + // The size of the data containing the keys and values + // Reads the bytes containing all the keys and values, if we have some + // We read big blob of data into ByteBuffer, then we will process + // this ByteBuffer + ByteBuffer byteBuffer = recordManager.readBytes( pageIos, position ); + + // Now, deserialize the data block. If the number of elements + // is positive, it's a Leaf, otherwise it's a Node + // Note that only a leaf can have 0 elements, and it's the root page then. + if ( nbElems >= 0 ) + { + // It's a leaf, process it as we may have sub-btrees + checkBtreeLeaf( recordManager, btreeInfo, checkedPages, nbElems, revision, byteBuffer, pageIos ); + } + else + { + // It's a node + long[] children = checkBtreeNode( recordManager, btreeInfo, checkedPages, -nbElems, revision, byteBuffer, + pageIos ); + + for ( int pos = 0; pos <= -nbElems; pos++ ) + { + // Recursively check the children + checkBtreePage( recordManager, btreeInfo, checkedPages, children[pos] ); + } + } + } + + + /** + * Check the Btree info page + * @throws ClassNotFoundException + */ + private static BtreeInfo checkBtreeInfo( RecordManager recordManager, Map checkedPages, + long btreeInfoOffset, long btreeRevision ) throws IOException + { + BtreeInfo btreeInfo = new BtreeInfo(); + + PageIO[] btreeInfoPagesIos = recordManager.readPageIOs( btreeInfoOffset, Long.MAX_VALUE ); + + long dataPos = 0L; + + // The B-tree page size + recordManager.readInt( btreeInfoPagesIos, dataPos ); + dataPos += RecordManager.INT_SIZE; + + // The tree name + ByteBuffer btreeNameBytes = recordManager.readBytes( btreeInfoPagesIos, dataPos ); + dataPos += RecordManager.INT_SIZE + btreeNameBytes.limit(); + String btreeName = Strings.utf8ToString( btreeNameBytes ); + + // The keySerializer FQCN + ByteBuffer keySerializerBytes = recordManager.readBytes( btreeInfoPagesIos, dataPos ); + + if ( keySerializerBytes != null ) + { + String keySerializerFqcn = Strings.utf8ToString( keySerializerBytes ); + + btreeInfo.keySerializer = getSerializer( keySerializerFqcn ); + } + + dataPos += RecordManager.INT_SIZE + keySerializerBytes.limit(); + + // The valueSerialier FQCN + ByteBuffer valueSerializerBytes = recordManager.readBytes( btreeInfoPagesIos, dataPos ); + + if ( valueSerializerBytes != null ) + { + String valueSerializerFqcn = Strings.utf8ToString( valueSerializerBytes ); + + btreeInfo.valueSerializer = getSerializer( valueSerializerFqcn ); + } + + dataPos += RecordManager.INT_SIZE + valueSerializerBytes.limit(); + + // The B-tree allowDuplicates flag + recordManager.readInt( btreeInfoPagesIos, dataPos ); + dataPos += RecordManager.INT_SIZE; + + // update the checkedPages + if ( !RecordManager.COPIED_PAGE_BTREE_NAME.equals( btreeName ) + && !RecordManager.BTREE_OF_BTREES_NAME.equals( btreeName ) ) + { + //btreeName = btreeName + "<" + btreeRevision + ">"; + } + + btreeInfo.btreeName = btreeName; + + // Update the checkedPages + int[] checkedPagesArray = checkedPages.get( btreeName ); + + if ( checkedPagesArray == null ) + { + // Add the new name in the checkedPage name if it's not already there + checkedPagesArray = createPageArray( recordManager ); + checkedPages.put( btreeName, checkedPagesArray ); + } + + updateCheckedPages( checkedPagesArray, recordManager.pageSize, btreeInfoPagesIos ); + updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, btreeInfoPagesIos ); + + return btreeInfo; + } + + + /** + * Get back the serializer instance + */ + @SuppressWarnings("unchecked") + private static ElementSerializer getSerializer( String serializerFqcn ) + { + try + { + Class serializerClass = Class.forName( serializerFqcn ); + ElementSerializer serializer = null; + + try + { + serializer = ( ElementSerializer ) serializerClass.getDeclaredField( "INSTANCE" ).get( null ); + } + catch ( NoSuchFieldException e ) + { + // ignore + } + + if ( serializer == null ) + { + serializer = ( ElementSerializer ) serializerClass.newInstance(); + } + + return serializer; + } + catch ( Exception e ) + { + throw new InvalidBTreeException( "Error : " + e.getMessage() ); + } + } + + + /** + * Check the Btree of Btrees rootPage + */ + private static void checkBtreeOfBtreesPage( RecordManager recordManager, Map checkedPages, + long pageOffset ) throws Exception + { + PageIO[] pageIos = recordManager.readPageIOs( pageOffset, Long.MAX_VALUE ); + + // Update the checkedPages array + updateCheckedPages( checkedPages.get( RecordManager.BTREE_OF_BTREES_NAME ), recordManager.pageSize, pageIos ); + updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, pageIos ); + + // Deserialize the page now + long position = 0L; + + // The revision + long revision = recordManager.readLong( pageIos, position ); + position += RecordManager.LONG_SIZE; + + // The number of elements in the page + int nbElems = recordManager.readInt( pageIos, position ); + position += RecordManager.INT_SIZE; + + // The size of the data containing the keys and values + // Reads the bytes containing all the keys and values, if we have some + // We read big blob of data into ByteBuffer, then we will process + // this ByteBuffer + ByteBuffer byteBuffer = recordManager.readBytes( pageIos, position ); + + // Now, deserialize the data block. If the number of elements + // is positive, it's a Leaf, otherwise it's a Node + // Note that only a leaf can have 0 elements, and it's the root page then. + if ( nbElems >= 0 ) + { + // It's a leaf, process it as we may have sub-btrees + checkBtreeOfBtreesLeaf( recordManager, checkedPages, nbElems, revision, byteBuffer, pageIos ); + } + else + { + // It's a node + long[] children = checkBtreeOfBtreesNode( recordManager, checkedPages, -nbElems, revision, byteBuffer, + pageIos ); + + for ( int pos = 0; pos <= -nbElems; pos++ ) + { + // Recursively check the children + checkBtreeOfBtreesPage( recordManager, checkedPages, children[pos] ); + } + } + } + + + /** + * Check a Btree of Btrees leaf. It contains -> offset. + */ + private static void checkBtreeOfBtreesLeaf( RecordManager recordManager, Map checkedPages, + int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos ) throws Exception + { + // Read each key and value + for ( int i = 0; i < nbElems; i++ ) + { + try + { + // Read the number of values + int nbValues = byteBuffer.getInt(); + + if ( nbValues != 1 ) + { + throw new InvalidBTreeException( "We should have only one value for a BOB " + nbValues ); + } + + // This is a normal value + // First, the value, which is an offset, which length should be 12 + int valueLength = byteBuffer.getInt(); + + if ( valueLength != RecordManager.LONG_SIZE + RecordManager.INT_SIZE ) + { + throw new InvalidBTreeException( "The BOB value length is invalid " + valueLength ); + } + + // Second, the offset length, which should be 8 + int offsetLength = byteBuffer.getInt(); + + if ( offsetLength != RecordManager.LONG_SIZE ) + { + throw new InvalidBTreeException( "The BOB value offset length is invalid " + offsetLength ); + } + + // Then the offset + long btreeOffset = byteBuffer.getLong(); + + checkOffset( recordManager, btreeOffset ); + + // Now, process the key + // First the key length + int keyLength = byteBuffer.getInt(); + + // The length should be at least 12 bytes long + if ( keyLength < RecordManager.LONG_SIZE + RecordManager.INT_SIZE ) + { + throw new InvalidBTreeException( "The BOB key length is invalid " + keyLength ); + } + + // Read the revision + long btreeRevision = byteBuffer.getLong(); + + // read the btreeName + int btreeNameLength = byteBuffer.getInt(); + + // The length should be equals to the btreeRevision + btreeNameLength + 4 + if ( keyLength != RecordManager.LONG_SIZE + RecordManager.INT_SIZE + btreeNameLength ) + { + throw new InvalidBTreeException( "The BOB key length is not the expected value " + + ( RecordManager.LONG_SIZE + RecordManager.INT_SIZE + btreeNameLength ) + ", expected " + + keyLength ); + } + + byte[] bytes = new byte[btreeNameLength]; + byteBuffer.get( bytes ); + String btreeName = Strings.utf8ToString( bytes ); + + // Add the new name in the checkedPage name if it's not already there + int[] btreePagesArray = createPageArray( recordManager ); + checkedPages.put( btreeName, btreePagesArray ); + + // Now, we can check the Btree we just found + checkBtree( recordManager, btreeOffset, checkedPages ); + + //System.out.println( "read <" + btreeName + "," + btreeRevision + "> : 0x" + Long.toHexString( btreeOffset ) ); + } + catch ( BufferUnderflowException bue ) + { + throw new InvalidBTreeException( "The BOB leaf byte buffer is too short : " + bue.getMessage() ); + } + } + } + + + /** + * Check a Btree leaf. + */ + private static void checkBtreeLeaf( RecordManager recordManager, BtreeInfo btreeInfo, + Map checkedPages, int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos ) + throws Exception + { + // Read each key and value + for ( int i = 0; i < nbElems; i++ ) + { + try + { + // Read the number of values + int nbValues = byteBuffer.getInt(); + + if ( nbValues < 0 ) + { + // This is a sub-btree. Read the offset + long subBtreeOffset = byteBuffer.getLong(); + + // And process the sub-btree + checkBtree( recordManager, subBtreeOffset, checkedPages ); + + // Now, process the key + // The key length + byteBuffer.getInt(); + + // The key itself + btreeInfo.keySerializer.deserialize( byteBuffer ); + } + else + { + // just deserialize the keys and values + // The value + byteBuffer.getInt(); + btreeInfo.valueSerializer.deserialize( byteBuffer ); + + // the key + byteBuffer.getInt(); + + btreeInfo.keySerializer.deserialize( byteBuffer ); + } + } + catch ( BufferUnderflowException bue ) + { + throw new InvalidBTreeException( "The BOB leaf byte buffer is too short : " + bue.getMessage() ); + } + } + } + + + /** + * Check a Btree of Btrees Node + */ + private static long[] checkBtreeOfBtreesNode( RecordManager recordManager, Map checkedPages, + int nbElems, long revision, + ByteBuffer byteBuffer, PageIO[] pageIos ) throws IOException + { + long[] children = new long[nbElems + 1]; + + // Read each value + for ( int i = 0; i < nbElems; i++ ) + { + // The offsets of the child + long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + checkOffset( recordManager, firstOffset ); + + long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + checkOffset( recordManager, lastOffset ); + + children[i] = firstOffset; + + // Read the key length + int keyLength = byteBuffer.getInt(); + + // The length should be at least 12 bytes long + if ( keyLength < RecordManager.LONG_SIZE + RecordManager.INT_SIZE ) + { + throw new InvalidBTreeException( "The BOB key length is invalid " + keyLength ); + } + + // Read the revision + byteBuffer.getLong(); + + // read the btreeName + int btreeNameLength = byteBuffer.getInt(); + + // The length should be equals to the btreeRevision + btreeNameLength + 4 + if ( keyLength != RecordManager.LONG_SIZE + RecordManager.INT_SIZE + btreeNameLength ) + { + throw new InvalidBTreeException( "The BOB key length is not the expected value " + + ( RecordManager.LONG_SIZE + RecordManager.INT_SIZE + btreeNameLength ) + ", expected " + keyLength ); + } + + // Read the Btree name + byte[] bytes = new byte[btreeNameLength]; + byteBuffer.get( bytes ); + } + + // And read the last child + // The offsets of the child + long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + checkOffset( recordManager, firstOffset ); + + long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + checkOffset( recordManager, lastOffset ); + + children[nbElems] = firstOffset; + + // and read the last value, as it's a node + return children; + } + + + /** + * Check a Btree node. + */ + private static long[] checkBtreeNode( RecordManager recordManager, BtreeInfo btreeInfo, + Map checkedPages, int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos ) + throws Exception + { + long[] children = new long[nbElems + 1]; + + // Read each key and value + for ( int i = 0; i < nbElems; i++ ) + { + try + { + // The offsets of the child + long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + checkOffset( recordManager, firstOffset ); + + long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + checkOffset( recordManager, lastOffset ); + + children[i] = firstOffset; + + // Now, read the key + // The key lenth + byteBuffer.getInt(); + + // The key itself + btreeInfo.keySerializer.deserialize( byteBuffer ); + } + catch ( BufferUnderflowException bue ) + { + throw new InvalidBTreeException( "The BOB leaf byte buffer is too short : " + bue.getMessage() ); + } + } + + // The last child + // The offsets of the child + long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + checkOffset( recordManager, firstOffset ); + + long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + checkOffset( recordManager, lastOffset ); + + children[nbElems] = firstOffset; + + return children; + } + + + /** + * Create an array of bits for pages + */ + private static int[] createPageArray( RecordManager recordManager ) throws IOException + { + long fileSize = recordManager.fileChannel.size(); + int pageSize = recordManager.pageSize; + long nbPages = ( fileSize - RecordManager.RECORD_MANAGER_HEADER_SIZE ) / pageSize; + int nbPageBits = ( int ) ( nbPages / 32 ); + + return new int[nbPageBits + 1]; + } + + + /** + * Update the array of seen pages. + */ + private static void updateCheckedPages( int[] checkedPages, int pageSize, PageIO... pageIos ) + { + for ( PageIO pageIO : pageIos ) + { + long offset = pageIO.getOffset(); + + setCheckedPage( rm, checkedPages, offset ); + } + } + + + /** + * Check the offset to be sure it's a valid one : + *
            + *
          • It's >= 0
          • + *
          • It's below the end of the file
          • + *
          • It's a multiple of the pageSize + *
          + */ + private static void checkOffset( RecordManager recordManager, long offset ) throws IOException + { + if ( ( offset == RecordManager.NO_PAGE ) || + ( ( ( offset - RecordManager.RECORD_MANAGER_HEADER_SIZE ) % recordManager.pageSize ) != 0 ) || + ( offset > recordManager.fileChannel.size() ) ) + { + throw new InvalidBTreeException( "Invalid Offset : " + offset ); + } + } + + + /** + * Check the free pages + */ + private static void checkFreePages( RecordManager recordManager, Map checkedPages ) + throws IOException + { + if ( recordManager.firstFreePage == RecordManager.NO_PAGE ) + { + return; + } + + // Now, read all the free pages + long currentOffset = recordManager.firstFreePage; + long fileSize = recordManager.fileChannel.size(); + + while ( currentOffset != RecordManager.NO_PAGE ) + { + if ( currentOffset > fileSize ) + { + System.out.println( "Wrong free page offset, above file size : " + currentOffset ); + return; + } + + try + { + PageIO pageIo = recordManager.fetchPage( currentOffset ); + + if ( currentOffset != pageIo.getOffset() ) + { + System.out.println( "PageIO offset is incorrect : " + currentOffset + "-" + + pageIo.getOffset() ); + return; + } + + setCheckedPage( recordManager, checkedPages.get( GLOBAL_PAGES_NAME ), currentOffset ); + setCheckedPage( recordManager, checkedPages.get( FREE_PAGES_NAME ), currentOffset ); + + long newOffset = pageIo.getNextPage(); + currentOffset = newOffset; + } + catch ( IOException ioe ) + { + throw new InvalidBTreeException( "Cannot fetch page at : " + currentOffset ); + } + } + } + + + /** + * Update the CheckedPages array + */ + private static void setCheckedPage( RecordManager recordManager, int[] checkedPages, long offset ) + { + int pageNumber = ( int ) offset / recordManager.pageSize; + int nbBitsPage = ( RecordManager.INT_SIZE << 3 ); + long pageMask = checkedPages[pageNumber / nbBitsPage]; + long mask = 1L << pageNumber % nbBitsPage; + + if ( ( pageMask & mask ) != 0 ) + { + //throw new InvalidBTreeException( "The page " + offset + " has already been referenced" ); + } + + pageMask |= mask; + + checkedPages[pageNumber / nbBitsPage] |= pageMask; + } + + + /** + * Output the pages that has been seen ('1') and those which has not been seen ('0'). The '.' represent non-pages + * at the end of the file. + */ + private static void dumpCheckedPages( RecordManager recordManager, Map checkedPages ) + throws IOException + { + // First dump the global array + int[] globalArray = checkedPages.get( GLOBAL_PAGES_NAME ); + String result = dumpPageArray( recordManager, globalArray ); + + String dump = String.format( "%1$-40s : %2$s", GLOBAL_PAGES_NAME, result ); + System.out.println( dump ); + + // The free pages array + int[] freePagesArray = checkedPages.get( FREE_PAGES_NAME ); + result = dumpPageArray( recordManager, freePagesArray ); + + dump = String.format( "%1$-40s : %2$s", FREE_PAGES_NAME, result ); + System.out.println( dump ); + + // The B-tree of B-trees pages array + int[] btreeOfBtreesArray = checkedPages.get( RecordManager.BTREE_OF_BTREES_NAME ); + result = dumpPageArray( recordManager, btreeOfBtreesArray ); + + dump = String.format( "%1$-40s : %2$s", RecordManager.BTREE_OF_BTREES_NAME, result ); + System.out.println( dump ); + + // The Copied page B-tree pages array + int[] copiedPagesArray = checkedPages.get( RecordManager.COPIED_PAGE_BTREE_NAME ); + result = dumpPageArray( recordManager, copiedPagesArray ); + + dump = String.format( "%1$-40s : %2$s", RecordManager.COPIED_PAGE_BTREE_NAME, result ); + System.out.println( dump ); + + // And now, all the other btree arrays + for ( String btreeName : checkedPages.keySet() ) + { + // Don't do the array we have already processed + if ( knownPagesArrays.contains( btreeName ) ) + { + continue; + } + + int[] btreePagesArray = checkedPages.get( btreeName ); + result = dumpPageArray( recordManager, btreePagesArray ); + + dump = String.format( "%1$-40s : %2$s", btreeName, result ); + System.out.println( dump ); + } + } + + + /** + * @see #getPageOffsets() + */ + public static List getFreePages() throws IOException + { + return getPageOffsets( FREE_PAGES_NAME ); + } + + + /** + * @see #getPageOffsets() + */ + public static List getGlobalPages() throws IOException + { + return getPageOffsets( GLOBAL_PAGES_NAME ); + } + + + /** + * Gives a list of offsets of pages from the page array associated wit the given name. + * + * This method should always be called after calling check() method. + * + * @return a list of offsets + * @throws IOException + */ + public static List getPageOffsets( String pageArrayName ) throws IOException + { + List lst = new ArrayList(); + + int[] fparry = checkedPages.get( pageArrayName ); + + long nbPagesChecked = 0; // the 0th page will always be of RM header + long fileSize = rm.fileChannel.size(); + long nbPages = ( fileSize - RecordManager.RECORD_MANAGER_HEADER_SIZE ) / rm.pageSize; + + for ( int checkedPage : fparry ) + { + for ( int j = 0; j < 32; j++ ) + { + + if ( nbPagesChecked > nbPages + 1 ) + { + break; + } + else + { + int mask = ( checkedPage & ( 1 << j ) ); + if ( mask != 0 ) + { + lst.add( nbPagesChecked * rm.pageSize); + } + } + + nbPagesChecked++; + } + } + + return lst; + } + + + /** + * Process a page array + */ + private static String dumpPageArray( RecordManager recordManager, int[] checkedPages ) throws IOException + { + StringBuilder sb = new StringBuilder(); + int i = -1; + int nbPagesChecked = 0; + long fileSize = recordManager.fileChannel.size(); + long nbPages = ( fileSize - RecordManager.RECORD_MANAGER_HEADER_SIZE ) / recordManager.pageSize; + + for ( int checkedPage : checkedPages ) + { + if ( i == 0 ) + { + sb.append( " " ); + i++; + } + else + { + i = 0; + } + + sb.append( "[" ).append( i ).append( "] " ); + + for ( int j = 0; j < 32; j++ ) + { + if ( nbPagesChecked >= nbPages + 1 ) + { + sb.append( "." ); + } + else + { + if ( ( checkedPage & ( 1 << j ) ) == 0 ) + { + sb.append( "0" ); + } + else + { + sb.append( "1" ); + } + } + + nbPagesChecked++; + } + } + + return sb.toString(); + } + + + /** + * The entry point method + */ + public void start() throws Exception + { + if ( !checkFilePresence() ) + { + return; + } + + if ( !loadRm() ) + { + return; + } + + boolean stop = false; + + while ( !stop ) + { + System.out.println( "Choose an option:" ); + System.out.println( "n - Print Number of BTrees" ); + System.out.println( "b - Print BTree Names" ); + System.out.println( "i - Inspect BTree" ); + System.out.println( "c - Check Free Pages" ); + System.out.println( "s - Get database file size" ); + System.out.println( "d - Dump RecordManager" ); + System.out.println( "r - Reload RecordManager" ); + System.out.println( "o - Read page at offset" ); + System.out.println( "q - Quit" ); + + char c = readOption(); + + switch ( c ) + { + case 'n': + printNumberOfBTrees(); + break; + + case 'b': + printBTreeNames(); + break; + + case 'i': + inspectBTree(); + break; + + case 'c': + long fileSize = rm.fileChannel.size(); + long nbPages = fileSize / rm.pageSize; + int nbPageBits = ( int ) ( nbPages / RecordManager.INT_SIZE ); + + Map checkedPages = new HashMap( 2 ); + + // The global page array + checkedPages.put( GLOBAL_PAGES_NAME, new int[nbPageBits + 1] ); + + // The freePages array + checkedPages.put( FREE_PAGES_NAME, new int[nbPageBits + 1] ); + + checkFreePages( rm, checkedPages ); + break; + + case 's': + printFileSize(); + break; + + case 'd': + check( rm ); + break; + + case 'r': + loadRm(); + break; + + case 'o': + readPageAt(); + break; + case 'q': + stop = true; + break; + + default: + System.out.println( "Invalid option" ); + //c = readOption( br ); + break; + } + } + + try + { + rm.close(); + br.close(); + } + catch ( Exception e ) + { + //ignore + } + } + + + /** + * Read the user's interaction + */ + private String readLine() + { + try + { + String line = br.readLine(); + + if ( line != null ) + { + return line.trim(); + } + else + { + return ""; + } + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + /** + * Process the input and get back the selected choice + */ + private char readOption() + { + try + { + String s = br.readLine(); + + if ( ( s == null ) || ( s.length() == 0 ) ) + { + return ' '; + } + + return s.charAt( 0 ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + private void readPageAt() throws IOException + { + System.out.println(); + System.out.print( "Offset: " ); + + String s = readLine(); + + long offset = -1; + + try + { + offset = Long.parseLong( s.trim() ); + } + catch( Exception e ) + { + offset = -1; + } + + if( offset < 0 || offset > (rm.fileChannel.size() - rm.DEFAULT_PAGE_SIZE) ) + { + System.out.println( "Invalid offset " + s ); + return; + } + + PageIO io = rm.fetchPage( offset ); + + List ll = new ArrayList(); + ll.add( offset ); + + do + { +// System.out.println( "Next Page: " + next ); +// System.out.println( "Size: " + io.getSize() ); +// ByteBuffer data = io.getData(); + + long next = io.getNextPage(); + ll.add( next ); + if ( next == -1 ) + { + break; + } + + io = rm.fetchPage( next ); + } + while( true ); + + int i = 0; + for ( ; i < ll.size() - 2; i++ ) + { + System.out.print( ll.get( i ) + " --> "); + } + + System.out.println( ll.get( i ) ); + } + + + /** + * Main method + */ + public static void main( String[] args ) throws Exception + { + + if ( args.length == 0 ) + { + System.out.println( "Usage java MavibotInspector " ); + System.exit( 0 ); + } + + File f = new File( args[0] ); + + MavibotInspector mi = new MavibotInspector( f ); + mi.start(); + } +} + +/** + * A class used to store some information about the Btree + */ +final class BtreeInfo +{ + // The btree name + /* no qualifier */String btreeName; + + // The key serializer + /* no qualifier */ElementSerializer keySerializer; + + // The value serializer + /* no qualifier */ElementSerializer valueSerializer; + + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "B-tree Info :" ); + sb.append( "\n name : " ).append( btreeName ); + sb.append( "\n key serializer : " ).append( keySerializer.getClass().getName() ); + sb.append( "\n value serializer : " ).append( valueSerializer.getClass().getName() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/MergedWithSiblingResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/MergedWithSiblingResult.java new file mode 100644 index 000000000..79a4fa080 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/MergedWithSiblingResult.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.directory.mavibot.btree; + + +import java.util.List; + + +/** + * The result of a delete operation, when the child has not been merged. It contains the + * reference to the modified page, and the removed element. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier*/class MergedWithSiblingResult extends AbstractDeleteResult +{ + /** + * The default constructor for RemoveResult. + * + * @param modifiedPage The modified page + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + public MergedWithSiblingResult( Page modifiedPage, Tuple removedElement ) + { + super( modifiedPage, removedElement ); + } + + + /** + * A constructor for RemoveResult which takes a list of copied page. + * + * @param copiedPages the list of copied pages + * @param modifiedPage The modified page + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + public MergedWithSiblingResult( List> copiedPages, Page modifiedPage, + Tuple removedElement ) + { + super( copiedPages, modifiedPage, removedElement ); + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "MergedWithSiblingResult" ); + sb.append( "\n removed element : " ).append( getRemovedElement() ); + sb.append( "\n modifiedPage : " ).append( getModifiedPage() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Modification.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Modification.java new file mode 100644 index 000000000..1138b4719 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Modification.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +/** + * An abstract class used to store a modification done on a BTree. + * + * @param The key type + * @param The value type + * + * @author Apache Directory Project + */ +/* No qualifier*/abstract class Modification extends Tuple +{ + /** The byte used to define an Addition in the serialized journal */ + public static final byte ADDITION = 0; + + /** The byte used to define a Deletion in the serialized journal */ + public static final byte DELETION = 1; + + + /** + * Create a new Modification instance. + * + * @param key The key being modified + * @param value The value being modified + */ + protected Modification( K key, V value ) + { + super( key, value ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ModifyResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ModifyResult.java new file mode 100644 index 000000000..a21883e23 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ModifyResult.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.List; + + +/** + * The result of an insert operation, when the child has not been split. It contains the + * reference to the modified page. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/class ModifyResult extends AbstractResult implements InsertResult +{ + /** The modified page reference */ + protected Page modifiedPage; + + /** The modified value if the key was found in the tree*/ + protected V modifiedValue; + + + /** + * The default constructor for ModifyResult. + * + * @param modifiedPage The modified page + * @param modifiedvalue The modified value (can be null if the key wasn't present in the tree) + */ + public ModifyResult( Page modifiedPage, V modifiedValue ) + { + super(); + this.modifiedPage = modifiedPage; + this.modifiedValue = modifiedValue; + } + + + /** + * A constructor for ModifyResult which takes a list of copied pages. + * + * @param copiedPages the list of copied pages + * @param modifiedPage The modified page + * @param modifiedvalue The modified value (can be null if the key wasn't present in the tree) + */ + public ModifyResult( List> copiedPages, Page modifiedPage, V modifiedValue ) + { + super( copiedPages ); + this.modifiedPage = modifiedPage; + this.modifiedValue = modifiedValue; + } + + + /** + * @return the modifiedPage + */ + public Page getModifiedPage() + { + return modifiedPage; + } + + + /** + * Set the modified page + * @param modifiedPage The new modified page + */ + public void setModifiedPage( Page modifiedPage ) + { + this.modifiedPage = modifiedPage; + } + + + /** + * @return the modifiedValue + */ + public V getModifiedValue() + { + return modifiedValue; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "ModifyResult, old value = " ).append( modifiedValue ); + sb.append( ", modifiedPage = " ).append( modifiedPage ); + sb.append( super.toString() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevision.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevision.java new file mode 100644 index 000000000..c40d1ee33 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevision.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.directory.mavibot.btree; + + +/** + * A data structure that stores a Btree name associated with a revision. We use + * it to manage Btree of Btrees. + * + * @author Apache Directory Project + */ +/* no qualifier*/class NameRevision extends Tuple +{ + /** + * A constructor for the RevisionName class + * @param revision The revision + * @param name The BTree name + */ + /* no qualifier*/NameRevision( String name, long revision ) + { + super( name, revision ); + } + + + /** + * @return the revision + */ + /* no qualifier*/long getRevision() + { + return getValue(); + } + + + /** + * @param revision the revision to set + */ + /* no qualifier*/void setRevision( long revision ) + { + setValue( revision ); + } + + + /** + * @return the btree name + */ + /* no qualifier*/String getName() + { + return getKey(); + } + + + /** + * @param name the btree name to set + */ + /* no qualifier*/void setName( String name ) + { + setKey( name ); + } + + + /** + * @see Object#equals(Object) + */ + public boolean equals( Object that ) + { + if ( this == that ) + { + return true; + } + + if ( !( that instanceof NameRevision ) ) + { + return false; + } + + NameRevision revisionName = ( NameRevision ) that; + + if ( getRevision() != revisionName.getRevision() ) + { + return false; + } + + if ( getName() == null ) + { + return revisionName.getName() == null; + } + + return ( getName().equals( revisionName.getName() ) ); + + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ( ( getName() == null ) ? 0 : getName().hashCode() ); + result = prime * result + ( int ) ( getRevision() ^ ( getRevision() >>> 32 ) ); + return result; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + return "[" + getName() + ":" + getRevision() + "]"; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevisionComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevisionComparator.java new file mode 100644 index 000000000..00ea577b7 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevisionComparator.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.Comparator; + + +/** + * A comparator for the RevisionName class + * + * @author Apache Directory Project + */ +/* no qualifier*/class NameRevisionComparator implements Comparator +{ + /** A static instance of a NameRevisionComparator */ + public static final NameRevisionComparator INSTANCE = new NameRevisionComparator(); + + /** + * A private constructor of the NameRevisionComparator class + */ + private NameRevisionComparator() + { + } + + + /** + * {@inheritDoc} + */ + public int compare( NameRevision rn1, NameRevision rn2 ) + { + if ( rn1 == rn2 ) + { + return 0; + } + + // First compare the name + int comp = rn1.getName().compareTo( rn2.getName() ); + + if ( comp < 0 ) + { + return -1; + } + else if ( comp > 0 ) + { + return 1; + } + + if ( rn1.getRevision() < rn2.getRevision() ) + { + return -1; + } + else if ( rn1.getRevision() > rn2.getRevision() ) + { + return 1; + } + + // The name are equal : check the revision + if ( rn1.getRevision() < rn2.getRevision() ) + { + return -1; + } + else if ( rn1.getRevision() > rn2.getRevision() ) + { + return 1; + } + + return 0; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevisionSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevisionSerializer.java new file mode 100644 index 000000000..a6c7a9df4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevisionSerializer.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.directory.mavibot.btree; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; +import org.apache.directory.mavibot.btree.serializer.AbstractElementSerializer; +import org.apache.directory.mavibot.btree.serializer.BufferHandler; +import org.apache.directory.mavibot.btree.serializer.ByteArraySerializer; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.apache.directory.mavibot.btree.util.Strings; + + +/** + * A serializer for the NameRevision object. The NameRevision will be serialized + * as a String ( the Name) followed by the revision as a Long. + * + * @author Apache Directory Project + */ +/* no qualifier*/class NameRevisionSerializer extends AbstractElementSerializer +{ + /** A static instance of a NameRevisionSerializer */ + /*No qualifier*/ final static NameRevisionSerializer INSTANCE = new NameRevisionSerializer(); + + /** + * Create a new instance of a NameRevisionSerializer + */ + private NameRevisionSerializer() + { + super( NameRevisionComparator.INSTANCE ); + } + + + /** + * A static method used to deserialize a NameRevision from a byte array. + * + * @param in The byte array containing the NameRevision + * @return A NameRevision instance + */ + /* no qualifier*/static NameRevision deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a NameRevision from a byte array. + * + * @param in The byte array containing the NameRevision + * @param start the position in the byte[] we will deserialize the NameRevision from + * @return A NameRevision instance + */ + /* no qualifier*/static NameRevision deserialize( byte[] in, int start ) + { + // The buffer must be 8 bytes plus 4 bytes long (the revision is a long, and the name is a String + if ( ( in == null ) || ( in.length < 12 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a NameRevision from a buffer with not enough bytes" ); + } + + long revision = LongSerializer.deserialize( in, start ); + String name = StringSerializer.deserialize( in, 8 + start ); + + NameRevision revisionName = new NameRevision( name, revision ); + + return revisionName; + } + + + /** + * A static method used to deserialize a NameRevision from a byte array. + * + * @param in The byte array containing the NameRevision + * @return A NameRevision instance + */ + public NameRevision fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a NameRevision from a byte array. + * + * @param in The byte array containing the NameRevision + * @param start the position in the byte[] we will deserialize the NameRevision from + * @return A NameRevision instance + */ + public NameRevision fromBytes( byte[] in, int start ) + { + // The buffer must be 8 bytes plus 4 bytes long (the revision is a long, and the name is a String + if ( ( in == null ) || ( in.length < 12 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a NameRevision from a buffer with not enough bytes" ); + } + + long revision = LongSerializer.deserialize( in, start ); + String name = StringSerializer.deserialize( in, 8 + start ); + + NameRevision revisionName = new NameRevision( name, revision ); + + return revisionName; + } + + + /** + * {@inheritDoc} + */ + @Override + public byte[] serialize( NameRevision revisionName ) + { + if ( revisionName == null ) + { + throw new SerializerCreationException( "The revisionName instance should not be null " ); + } + + byte[] result = null; + + if ( revisionName.getName() != null ) + { + byte[] stringBytes = Strings.getBytesUtf8( revisionName.getName() ); + int stringLen = stringBytes.length; + result = new byte[8 + 4 + stringBytes.length]; + LongSerializer.serialize( result, 0, revisionName.getRevision() ); + + if ( stringLen > 0 ) + { + ByteArraySerializer.serialize( result, 8, stringBytes ); + } + } + else + { + result = new byte[8 + 4]; + LongSerializer.serialize( result, 0, revisionName.getRevision() ); + StringSerializer.serialize( result, 8, null ); + } + + return result; + } + + + /** + * Serialize a NameRevision + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized NameRevision + * @param value the value to serialize + * @return The byte[] containing the serialized NameRevision + */ + /* no qualifier*/static byte[] serialize( byte[] buffer, int start, NameRevision revisionName ) + { + if ( revisionName.getName() != null ) + { + byte[] stringBytes = Strings.getBytesUtf8( revisionName.getName() ); + int stringLen = stringBytes.length; + LongSerializer.serialize( buffer, start, revisionName.getRevision() ); + IntSerializer.serialize( buffer, 8 + start, stringLen ); + ByteArraySerializer.serialize( buffer, 12 + start, stringBytes ); + } + else + { + LongSerializer.serialize( buffer, start, revisionName.getRevision() ); + StringSerializer.serialize( buffer, 8, null ); + } + + return buffer; + } + + + /** + * {@inheritDoc} + */ + @Override + public NameRevision deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] revisionBytes = bufferHandler.read( 8 ); + long revision = LongSerializer.deserialize( revisionBytes ); + + byte[] lengthBytes = bufferHandler.read( 4 ); + + int len = IntSerializer.deserialize( lengthBytes ); + + switch ( len ) + { + case 0: + return new NameRevision( "", revision ); + + case -1: + return new NameRevision( null, revision ); + + default: + byte[] nameBytes = bufferHandler.read( len ); + + return new NameRevision( Strings.utf8ToString( nameBytes ), revision ); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public NameRevision deserialize( ByteBuffer buffer ) throws IOException + { + // The revision + long revision = buffer.getLong(); + + // The name's length + int len = buffer.getInt(); + + switch ( len ) + { + case 0: + return new NameRevision( "", revision ); + + case -1: + return new NameRevision( null, revision ); + + default: + byte[] nameBytes = new byte[len]; + buffer.get( nameBytes ); + + return new NameRevision( Strings.utf8ToString( nameBytes ), revision ); + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NotPresentResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NotPresentResult.java new file mode 100644 index 000000000..cda62bae1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NotPresentResult.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.directory.mavibot.btree; + + +/** + * The result of an delete operation, when the key to delete is not present in the tree. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/class NotPresentResult extends AbstractResult implements DeleteResult +{ + /** The unique instance for this class */ + @SuppressWarnings("rawtypes") + public static final NotPresentResult NOT_PRESENT = new NotPresentResult(); + + + /** + * A private void constructor, as we won't have any other instance. + */ + private NotPresentResult() + { + // Do nothing + } + + + /** + * {@inheritDoc} + */ + public Page getModifiedPage() + { + return null; + } + + + /** + * {@inheritDoc} + */ + public Tuple getRemovedElement() + { + return null; + } + + + /** + * {@inheritDoc} + */ + public K getNewLeftMost() + { + return null; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Page.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Page.java new file mode 100644 index 000000000..8fb415080 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Page.java @@ -0,0 +1,285 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; + + +/** + * A MVCC Page interface. A Page can be either a Leaf (containing keys and values) or a Node + * (containing keys and references to child pages).
          + * A Page can be stored on disk. If so, we store the serialized value of this Page into + * one or more {@link PageIO} (they will be linked) + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier*/interface Page +{ + /** + * @return The number of keys present in this page + */ + int getNbElems(); + + + /** + * Inserts the given key and value into this page. We first find the place were to + * inject the into the tree, by recursively browsing the pages :
          + *
            + *
          • If the index is below zero, the key is present in the Page : we modify the + * value and return
          • + *
          • If the page is a node, we have to go down to the right child page
          • + *
          • If the page is a leaf, we insert the new element into the page, and if + * the Page is full, we split it and propagate the new pivot up into the tree
          • + *
          + *

          + * + * @param key Inserted key + * @param value Inserted value + * @param revision The new revision for the modified pages + * @return Either a modified Page or an Overflow element if the Page was full + * @throws IOException If we have an error while trying to access the page + */ + InsertResult insert( K key, V value, long revision ) throws IOException; + + + /** + * Deletes the value from an entry associated with the given key in this page. We first find + * the place were to remove the into the tree, by recursively browsing the pages. + * If the value is present, it will be deleted first, later if there are no other values associated with + * this key(which can happen when duplicates are enabled), we will remove the key from the tree. + * + * @param revision The new revision for the modified pages + * @param key The key to delete + * @param value The value to delete (can be null) + * @param parent The parent page + * @param parentPos The position of the current page in it's parent + * @return Either a modified Page if the key has been removed from the page, or a NotPresentResult. + * @throws IOException If we have an error while trying to access the page + */ + DeleteResult delete( K key, V value, long revision /*, Page parent, int parentPos*/ ) throws IOException; + + + /** + * Gets the value associated with the given key, if any. If we don't have + * one, this method will throw a KeyNotFoundException.
          + * Note that we may get back null if a null value has been associated + * with the key. + * + * @param key The key we are looking for + * @return The associated value, which can be null + * @throws KeyNotFoundException If no entry with the given key can be found + * @throws IOException If we have an error while trying to access the page + */ + V get( K key ) throws KeyNotFoundException, IOException; + + + /** + * Gets the values associated with the given key, if any. If we don't have + * the key, this method will throw a KeyNotFoundException.
          + * Note that we may get back null if a null value has been associated + * with the key. + * + * @param key The key we are looking for + * @return The associated value, which can be null + * @throws KeyNotFoundException If no entry with the given key can be found + * @throws IOException If we have an error while trying to access the page + * @throws IllegalArgumentException If duplicates are not enabled + */ + ValueCursor getValues( K key ) throws KeyNotFoundException, IOException, IllegalArgumentException; + + + /** + * Checks if the page contains the given key with the given value. + * + * @param key The key we are looking for + * @param value The value associated with the given key + * @return true if the key and value are associated with each other, false otherwise + */ + boolean contains( K key, V value ) throws IOException; + + + /** + * Browses the tree, looking for the given key, and creates a Cursor on top + * of the found result. + * + * @param key The key we are looking for. + * @param transaction The started transaction for this operation + * @param stack The stack of parents we go through to get to this page + * @return A Cursor to browse the next elements + * @throws IOException If we have an error while trying to access the page + */ + TupleCursor browse( K key, ReadTransaction transaction, ParentPos[] stack, int depth ) + throws IOException; + + + /** + * Browses the whole tree, and creates a Cursor on top of it. + * + * @param transaction The started transaction for this operation + * @param stack The stack of parents we go through to get to this page + * @return A Cursor to browse the next elements + * @throws IOException If we have an error while trying to access the page + */ + TupleCursor browse( ReadTransaction transaction, ParentPos[] stack, int depth ) + throws EndOfFileExceededException, IOException; + + + /** + * Browses the keys of whole tree, and creates a Cursor on top of it. + * + * @param transaction The started transaction for this operation + * @param stack The stack of parents we go through to get to this page + * @return A Cursor to browse the keys + * @throws IOException If we have an error while trying to access the page + */ + KeyCursor browseKeys( ReadTransaction transaction, ParentPos[] stack, int depth ) + throws EndOfFileExceededException, IOException; + + + /** + * @return the revision + */ + long getRevision(); + + + /** + * Returns the key at a given position + * + * @param pos The position of the key we want to retrieve + * @return The key found at the given position + */ + K getKey( int pos ); + + + /** + * Finds the leftmost key in this page. If the page is a node, it will go + * down in the leftmost children to recursively find the leftmost key. + * + * @return The leftmost key in the tree + */ + K getLeftMostKey(); + + + /** + * Finds the rightmost key in this page. If the page is a node, it will go + * down in the rightmost children to recursively find the rightmost key. + * + * @return The rightmost key in the tree + */ + K getRightMostKey(); + + + /** + * Finds the leftmost element in this page. If the page is a node, it will go + * down in the leftmost children to recursively find the leftmost element. + * + * @return The leftmost element in the tree + * @throws IOException If we have an error while trying to access the page + */ + Tuple findLeftMost() throws IOException; + + + /** + * Finds the rightmost element in this page. If the page is a node, it will go + * down in the rightmost children to recursively find the rightmost element. + * + * @return The rightmost element in the tree + * @throws IOException If we have an error while trying to access the page + */ + Tuple findRightMost() throws EndOfFileExceededException, IOException; + + + /** + * Pretty-prints the tree with tabs + * @param tabs The tabs to add in front of each node + * @return A pretty-print dump of the tree + */ + String dumpPage( String tabs ); + + + /** + * Find the position of the given key in the page. If we have found the key, + * we will return its position as a negative value. + *

          + * Assuming that the array is zero-indexed, the returned value will be :
          + * position = - ( position + 1) + *
          + * So for the following table of keys :
          + *

          +     * +---+---+---+---+
          +     * | b | d | f | h |
          +     * +---+---+---+---+
          +     *   0   1   2   3
          +     * 
          + * looking for 'b' will return -1 (-(0+1)) and looking for 'f' will return -3 (-(2+1)).
          + * Computing the real position is just a matter to get -(position++). + *

          + * If we don't find the key in the table, we will return the position of the key + * immediately above the key we are looking for.
          + * For instance, looking for : + *

            + *
          • 'a' will return 0
          • + *
          • 'b' will return -1
          • + *
          • 'c' will return 1
          • + *
          • 'd' will return -2
          • + *
          • 'e' will return 2
          • + *
          • 'f' will return -3
          • + *
          • 'g' will return 3
          • + *
          • 'h' will return -4
          • + *
          • 'i' will return 4
          • + *
          + * + * + * @param key The key to find + * @return The position in the page. + */ + int findPos( K key ); + + + /** + * Checks if the given key exists. + * + * @param key The key we are looking at + * @return true if the key is present, false otherwise + * @throws IOException If we have an error while trying to access the page + */ + boolean hasKey( K key ) throws IOException; + + + /** + * Tells if the page is a leaf or not + * @return true if the page is a leaf + */ + boolean isLeaf(); + + + /** + * Tells if the page is a node or not + * @return true if the page is a node + */ + boolean isNode(); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageHolder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageHolder.java new file mode 100644 index 000000000..4efc7ab79 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageHolder.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +/** + * A Page holder. It stores the page and provide a way to access it. + * + * @param The type of the BTree key + * @param The type of the BTree value + * + * @author Apache Directory Project + */ +/* No qualifier*/class PageHolder +{ + /** The BTree */ + protected BTree btree; + + /** The stored page */ + private Page page; + + + /** + * Create a new holder storing an offset and a SoftReference containing the element. + * + * @param btree The associated BTree + * @param page The element to store into a SoftReference + **/ + /* no qualifier */PageHolder( BTree btree, Page page ) + { + this.btree = btree; + this.page = page; + } + + + /** + * @return the stored page + */ + /* no qualifier */Page getValue() + { + return page; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageIO.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageIO.java new file mode 100644 index 000000000..dbff0edbc --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageIO.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.util.Strings; + + +/** + * A structure containing a Page on disk. It's a byte[PageSize] plus a few more details like + * the page offset on disk and a link to the next page.
          + * As we may need more than one Page to store some data, the PageIO are linked so that + * the list of all the PageIO contain the full data.
          + * The first PageIO contains the size of the data.
          + * Here is the logical structure of a PageIO : + *
          + * For a first page :
          + *
          + * +----------+------+----------------------+
          + * | nextPage | size | XXXXXXXXXXXXXXXXXXXX |
          + * +----------+------+----------------------+
          + *
          + * for any page but the first :
          + *
          + * +----------+-----------------------------+
          + * | nextPage | XXXXXXXXXXXXXXXXXXXXXXXXXXX |
          + * +----------+-----------------------------+
          + *
          + * for the last page :
          + * +----------+-----------------------------+
          + * |    -1    | XXXXXXXXXXXXXXXXXXXXXXXXXXX |
          + * +----------+-----------------------------+
          + *
          + * In any case, the page length is always PageSize.
          + * 
          + * + * @author Apache Directory Project + */ +/* No qualifier*/class PageIO +{ + /** The contain data */ + private ByteBuffer data; + + /** A pointer to the next pageIO */ + private long nextPage; + + /** The offset on disk */ + private int size; + + /** The position of the page on disk */ + private long offset; + + + /** + * A default constructor for a PageIO + */ + /* no qualifier */PageIO() + { + nextPage = -2L; + size = -1; + offset = -1L; + } + + + /** + * A constructor for a PageIO when we know the offset of this page on disk + */ + /* no qualifier */PageIO( long offset ) + { + nextPage = -2L; + size = -1; + this.offset = offset; + } + + + /** + * @return the data + */ + /* no qualifier */ByteBuffer getData() + { + return data; + } + + + /** + * @param data the data to set + */ + /* no qualifier */void setData( ByteBuffer data ) + { + this.data = data; + nextPage = data.getLong( 0 ); + } + + + /** + * Get the NextPage value from the PageIO. If it's -1, there is no next page
          + * @return the nextPage + */ + /* no qualifier */long getNextPage() + { + return nextPage; + } + + + /** + * @param nextPage the nextPage to set + */ + /* no qualifier */void setNextPage( long nextPage ) + { + this.nextPage = nextPage; + + data.putLong( 0, nextPage ); + } + + + /** + * @return the size + */ + /* no qualifier */long getSize() + { + return size; + } + + + /** + * @param size the size to set + */ + /* no qualifier */void setSize( int size ) + { + data.putInt( 8, size ); + + this.size = size; + } + + + /** + * @param size the size to set + */ + /* no qualifier */void setSize() + { + size = data.getInt( 8 ); + } + + + /** + * @return the offset + */ + /* no qualifier */long getOffset() + { + return offset; + } + + + /** + * @param offset the offset to set + */ + /* no qualifier */void setOffset( long offset ) + { + this.offset = offset; + } + + + /* no qualifier */PageIO copy( PageIO copy ) + { + // The data + if ( data.isDirect() ) + { + copy.data = ByteBuffer.allocateDirect( data.capacity() ); + } + else + { + copy.data = ByteBuffer.allocate( data.capacity() ); + } + + // Save the original buffer position and limit + int start = data.position(); + int limit = data.limit(); + + // The data is extended to get all the bytes in it + data.position( 0 ); + data.limit( data.capacity() ); + + // Copy the data + copy.data.put( data ); + + // Restore the original buffer to the initial position and limit + data.position( start ); + data.limit( limit ); + + // Set those position and limit in the copied buffer + copy.data.position( start ); + copy.data.limit( limit ); + + // The size + copy.size = size; + + // The offset and next page pointers are not copied. + return copy; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "PageIO[offset:0x" ).append( Long.toHexString( offset ) ); + + if ( size != -1 ) + { + sb.append( ", size:" ).append( size ); + } + + if ( nextPage != -1L ) + { + sb.append( ", next:0x" ).append( Long.toHexString( nextPage ) ); + } + + sb.append( "]" ); + + int start = 0; + + byte[] array = null; + + data.mark(); + data.position( 0 ); + + if ( data.isDirect() ) + { + array = new byte[data.capacity()]; + data.get( array ); + } + else + { + array = data.array(); + } + + data.reset(); + + for ( int i = start; i < array.length; i++ ) + { + if ( ( ( i - start ) % 16 ) == 0 ) + { + sb.append( "\n " ); + } + + sb.append( Strings.dumpByte( array[i] ) ).append( " " ); + } + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageReclaimer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageReclaimer.java new file mode 100644 index 000000000..c2bfe380e --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageReclaimer.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * A class used for reclaiming the copied pages. + * + * @author Apache Directory Project + */ +public class PageReclaimer +{ + /** the record manager */ + private RecordManager rm; + + /** The LoggerFactory used by this class */ + protected static final Logger LOG = LoggerFactory.getLogger( PageReclaimer.class ); + + /** a flag to detect the running state */ + private boolean running = false; + + /** + * Creates a new instance of PageReclaimer. + * + * @param rm the record manager + */ + public PageReclaimer( RecordManager rm ) + { + this.rm = rm; + } + + + /** + * relcaims the copied pages + */ + /* no qualifier */ void reclaim() + { + //System.out.println( "reclaiming pages" ); + try + { + if ( running ) + { + return; + } + + running = true; + + Set managed = rm.getManagedTrees(); + + for ( String name : managed ) + { + PersistedBTree tree = ( PersistedBTree ) rm.getManagedTree( name ); + + long latestRev = tree.getRevision(); + + Set inUseRevisions = new TreeSet(); + + // the tree might have been removed + if ( tree != null ) + { + Iterator txnItr = tree.getReadTransactions().iterator(); + while ( txnItr.hasNext() ) + { + inUseRevisions.add( txnItr.next().getRevision() ); + } + } + + List copiedRevisions = getRevisions( name ); + + // the revision last removed from copiedPage BTree + long lastRemovedRev = -1; + + List freeList = new ArrayList(); + + for ( RevisionOffset ro : copiedRevisions ) + { + long rv = ro.getRevision(); + if ( inUseRevisions.contains( rv ) ) + { + //System.out.println( "Revision " + rv + " of BTree " + name + " is in use, not reclaiming pages" ); + break; + } + + long[] offsets = ro.getOffsets(); + + //System.out.println( "Reclaiming " + Arrays.toString( offsets ) + "( " + offsets.length + " ) pages of the revision " + rv + " of BTree " + name ); + + for( long l : offsets ) + { + freeList.add( l ); + } + + RevisionName key = new RevisionName( rv, name ); + + //System.out.println( "delete cpb key " + key ); + rm.copiedPageBtree.delete( key ); + lastRemovedRev = rv; + } + + // no new txn is needed for the operations on BoB + // and also no need to traverse BoB if the tree is a sub-btree + if ( ( lastRemovedRev != -1 ) && !tree.isAllowDuplicates() ) + { + // we SHOULD NOT delete the latest revision from BoB + NameRevision nr = new NameRevision( name, latestRev ); + TupleCursor cursor = rm.btreeOfBtrees.browseFrom( nr ); + + List btreeHeaderOffsets = new ArrayList(); + + while ( cursor.hasPrev() ) + { + Tuple t = cursor.prev(); + //System.out.println( "deleting BoB rev " + t.getKey() + " latest rev " + latestRev ); + rm.btreeOfBtrees.delete( t.getKey() ); + btreeHeaderOffsets.add( t.value ); + } + + cursor.close(); + + for( Long l : btreeHeaderOffsets ) + { + // the offset may have already been present while + // clearing CPB so skip it here, otherwise it will result in OOM + // due to the attempt to free and already freed page + if(freeList.contains( l )) + { + //System.out.println( "bob duplicate offset " + l ); + continue; + } + + freeList.add( l ); + } + } + + for( Long offset : freeList ) + { + PageIO[] pageIos = rm.readPageIOs( offset, -1L ); + + for ( PageIO pageIo : pageIos ) + { + rm.free( pageIo ); + } + } + + } + + running = false; + } + catch ( Exception e ) + { + running = false; + rm.rollback(); + LOG.warn( "Errors while reclaiming", e ); + throw new RuntimeException( e ); + } + } + + + /** + * gets a list of all the copied pages of a given B-Tree. + * + * @param name the name of the B-Tree + * @return list of RevisionOffset + * @throws Exception + */ + private List getRevisions( String name ) throws Exception + { + TupleCursor cursor = rm.copiedPageBtree.browse(); + + List lst = new ArrayList(); + + while ( cursor.hasNext() ) + { + Tuple t = cursor.next(); + RevisionName rn = t.getKey(); + if ( name.equals( rn.getName() ) ) + { + //System.out.println( t.getValue() ); + lst.add( new RevisionOffset( rn.getRevision(), t.getValue() ) ); + } + } + + cursor.close(); + + return lst; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ParentPos.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ParentPos.java new file mode 100644 index 000000000..e787db45a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ParentPos.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.directory.mavibot.btree; + + +/** + * This class is used to store the parent page and the position in it during + * a browse operation. We have as many ParentPos instance than the depth of the tree. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier*/class ParentPos +{ + /** The page we are browsing */ + /* no qualifier */Page page; + + /** The current position in the page */ + /* no qualifier */int pos; + + /** The current position of the duplicate container in the page */ + /* no qualifier */int dupPos; + + /** The current position of the duplicate container in the page */ + /* no qualifier */ValueCursor valueCursor; + + + /** + * Creates a new instance of ParentPos + * @param page The current Page + * @param pos The current position in the page + */ + /* no qualifier */ParentPos( Page page, int pos ) + { + this.page = page; + this.pos = pos; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + return "<" + pos + "," + page + ">"; + } +} \ No newline at end of file diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTree.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTree.java new file mode 100644 index 000000000..09083d6f4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTree.java @@ -0,0 +1,817 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The B+Tree MVCC data structure. + * + * @param The type for the keys + * @param The type for the stored values + * + * @author Apache Directory Project + */ +public class PersistedBTree extends AbstractBTree implements Closeable +{ + /** The LoggerFactory used by this class */ + protected static final Logger LOG = LoggerFactory.getLogger( PersistedBTree.class ); + + protected static final Logger LOG_PAGES = LoggerFactory.getLogger( "org.apache.directory.mavibot.LOG_PAGES" ); + + /** The cache associated with this B-tree */ + protected LRUMap cache; + + /** The default number of pages to keep in memory */ + public static final int DEFAULT_CACHE_SIZE = 1000; + + /** The cache size, default to 1000 elements */ + protected int cacheSize = DEFAULT_CACHE_SIZE; + + /** The number of stored Values before we switch to a B-tree */ + private static final int DEFAULT_VALUE_THRESHOLD_UP = 8; + + /** The number of stored Values before we switch back to an array */ + private static final int DEFAULT_VALUE_THRESHOLD_LOW = 1; + + /** The configuration for the array <-> B-tree switch */ + /*No qualifier*/static int valueThresholdUp = DEFAULT_VALUE_THRESHOLD_UP; + /*No qualifier*/static int valueThresholdLow = DEFAULT_VALUE_THRESHOLD_LOW; + + /** The BtreeInfo offset */ + private long btreeInfoOffset = RecordManager.NO_PAGE; + + /** The internal recordManager */ + private RecordManager recordManager; + + + /** + * Creates a new BTree, with no initialization. + */ + /* no qualifier */PersistedBTree() + { + setType( BTreeTypeEnum.PERSISTED ); + } + + + /** + * Creates a new persisted B-tree using the BTreeConfiguration to initialize the + * BTree + * + * @param configuration The configuration to use + */ + /* no qualifier */PersistedBTree( PersistedBTreeConfiguration configuration ) + { + super(); + String name = configuration.getName(); + + if ( name == null ) + { + throw new IllegalArgumentException( "BTree name cannot be null" ); + } + + setName( name ); + setPageSize( configuration.getPageSize() ); + setKeySerializer( configuration.getKeySerializer() ); + setValueSerializer( configuration.getValueSerializer() ); + setAllowDuplicates( configuration.isAllowDuplicates() ); + setType( configuration.getBtreeType() ); + + readTimeOut = configuration.getReadTimeOut(); + writeBufferSize = configuration.getWriteBufferSize(); + cacheSize = configuration.getCacheSize(); + + if ( keySerializer.getComparator() == null ) + { + throw new IllegalArgumentException( "Comparator should not be null" ); + } + + // Create the first root page, with revision 0L. It will be empty + // and increment the revision at the same time + Page rootPage = new PersistedLeaf( this ); + + // Create a B-tree header, and initialize it + BTreeHeader btreeHeader = new BTreeHeader(); + btreeHeader.setRootPage( rootPage ); + btreeHeader.setBtree( this ); + + switch ( btreeType ) + { + case BTREE_OF_BTREES: + case COPIED_PAGES_BTREE: + // We will create a new cache and a new readTransactions map + init( null ); + currentBtreeHeader = btreeHeader; + break; + + case PERSISTED_SUB: + init( ( PersistedBTree ) configuration.getParentBTree() ); + btreeRevisions.put( 0L, btreeHeader ); + currentBtreeHeader = btreeHeader; + break; + + default: + // We will create a new cache and a new readTransactions map + init( null ); + btreeRevisions.put( 0L, btreeHeader ); + currentBtreeHeader = btreeHeader; + break; + } + } + + + /** + * Initialize the BTree. + * + * @throws IOException If we get some exception while initializing the BTree + */ + public void init( BTree parentBTree ) + { + if ( parentBTree == null ) + { + // This is not a subBtree, we have to initialize the cache + + // Create the queue containing the pending read transactions + readTransactions = new ConcurrentLinkedQueue>(); + + if ( cacheSize < 1 ) + { + cacheSize = DEFAULT_CACHE_SIZE; + } + + cache = new LRUMap( cacheSize ); + } + else + { + this.cache = ( ( PersistedBTree ) parentBTree ).getCache(); + this.readTransactions = ( ( PersistedBTree ) parentBTree ).getReadTransactions(); + } + + // Initialize the txnManager thread + //FIXME we should NOT create a new transaction manager thread for each BTree + //createTransactionManager(); + } + + + /** + * Return the cache we use in this BTree + */ + /* No qualifier */LRUMap getCache() + { + return cache; + } + + + /** + * Return the cache we use in this BTree + */ + /* No qualifier */ConcurrentLinkedQueue> getReadTransactions() + { + return readTransactions; + } + + + /** + * Close the BTree, cleaning up all the data structure + */ + public void close() throws IOException + { + // Stop the readTransaction thread + // readTransactionsThread.interrupt(); + // readTransactions.clear(); + + // Clean the cache + cache.clear(); + } + + + /** + * @return the btreeOffset + */ + /* No qualifier*/long getBtreeOffset() + { + return getBTreeHeader( getName() ).getBTreeHeaderOffset(); + } + + + /** + * @param btreeOffset the B-tree header Offset to set + */ + /* No qualifier*/void setBtreeHeaderOffset( long btreeHeaderOffset ) + { + getBTreeHeader( getName() ).setBTreeHeaderOffset( btreeHeaderOffset ); + } + + + /** + * @return the rootPageOffset + */ + /* No qualifier*/long getRootPageOffset() + { + return getBTreeHeader( getName() ).getRootPageOffset(); + } + + + /** + * Gets the RecordManager for a managed BTree + * + * @return The recordManager if the B-tree is managed + */ + /* No qualifier */RecordManager getRecordManager() + { + return recordManager; + } + + + /** + * Inject a RecordManager for a managed BTree + * + * @param recordManager The injected RecordManager + */ + /* No qualifier */void setRecordManager( RecordManager recordManager ) + { + // The RecordManager is also the TransactionManager + transactionManager = recordManager; + this.recordManager = recordManager; + } + + + /** + * + * Deletes the given pair if both key and value match. If the given value is null + * and there is no null value associated with the given key then the entry with the given key + * will be removed. + * + * @param key The key to be removed + * @param value The value to be removed (can be null, and when no null value exists the key will be removed irrespective of the value) + * @param revision The revision to be associated with this operation + * @return + * @throws IOException + */ + /* no qualifier */Tuple delete( K key, V value, long revision ) throws IOException + { + // We have to start a new transaction, which will be committed or rollbacked + // locally. This will duplicate the current BtreeHeader during this phase. + if ( revision == -1L ) + { + revision = currentRevision.get() + 1; + } + + try + { + // Try to delete the entry starting from the root page. Here, the root + // page may be either a Node or a Leaf + DeleteResult result = processDelete( key, value, revision ); + + // Check that we have found the element to delete + if ( result instanceof NotPresentResult ) + { + // We haven't found the element in the B-tree, just get out + // without updating the recordManager + + return null; + } + + // The element was found, and removed + AbstractDeleteResult deleteResult = ( AbstractDeleteResult ) result; + + Tuple tuple = deleteResult.getRemovedElement(); + + // If the B-tree is managed, we have to update the rootPage on disk + // Update the RecordManager header + + // Return the value we have found if it was modified + return tuple; + } + catch ( IOException ioe ) + { + // if we've got an error, we have to rollback + throw ioe; + } + } + + + /** + * Insert the tuple into the B-tree rootPage, get back the new rootPage + */ + private DeleteResult processDelete( K key, V value, long revision ) throws IOException + { + // Get the current B-tree header, and delete the value from it + BTreeHeader btreeHeader = getBTreeHeader( getName() ); + + // Try to delete the entry starting from the root page. Here, the root + // page may be either a Node or a Leaf + DeleteResult result = btreeHeader.getRootPage().delete( key, value, revision ); + + if ( result instanceof NotPresentResult ) + { + // Key not found. + return result; + } + + // Create a new BTreeHeader + BTreeHeader newBtreeHeader = btreeHeader.copy(); + + // Inject the old B-tree header into the pages to be freed + // if we are deleting an element from a management BTree + if ( ( btreeType == BTreeTypeEnum.BTREE_OF_BTREES ) || ( btreeType == BTreeTypeEnum.COPIED_PAGES_BTREE ) ) + { + PageIO[] pageIos = recordManager.readPageIOs( btreeHeader.getBTreeHeaderOffset(), -1L ); + + for ( PageIO pageIo : pageIos ) + { + recordManager.freedPages.add( pageIo ); + } + } + + // The element was found, and removed + AbstractDeleteResult removeResult = ( AbstractDeleteResult ) result; + + // This is a new root + Page newRootPage = removeResult.getModifiedPage(); + + // Write the modified page on disk + // Note that we don't use the holder, the new root page will + // remain in memory. + writePage( newRootPage, revision ); + + // Decrease the number of elements in the current tree + newBtreeHeader.decrementNbElems(); + newBtreeHeader.setRootPage( newRootPage ); + newBtreeHeader.setRevision( revision ); + + // Write down the data on disk + long newBtreeHeaderOffset = recordManager.writeBtreeHeader( this, newBtreeHeader ); + + // Update the B-tree of B-trees with this new offset, if we are not already doing so + switch ( btreeType ) + { + case PERSISTED: + // We have a new B-tree header to inject into the B-tree of btrees + recordManager.addInBtreeOfBtrees( getName(), revision, newBtreeHeaderOffset ); + + recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); + + // Store the new revision + storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); + + break; + + case PERSISTED_SUB: + // Sub-B-trees are only updating the CopiedPage B-tree + recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); + + //btreeRevisions.put( revision, newBtreeHeader ); + + currentRevision.set( revision ); + + break; + + case BTREE_OF_BTREES: + // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters + recordManager.updateRecordManagerHeader( newBtreeHeaderOffset, -1L ); + + // We can free the copied pages + recordManager.freePages( this, revision, result.getCopiedPages() ); + + // Store the new revision + storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); + + break; + + case COPIED_PAGES_BTREE: + // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters + recordManager.updateRecordManagerHeader( -1L, newBtreeHeaderOffset ); + + // We can free the copied pages + recordManager.freePages( this, revision, result.getCopiedPages() ); + + // Store the new revision + storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); + + break; + + default: + // Nothing to do for sub-btrees + break; + } + + // Return the value we have found if it was modified + return result; + } + + + /** + * Insert an entry in the BTree. + *

          + * We will replace the value if the provided key already exists in the + * btree. + *

          + * The revision number is the revision to use to insert the data. + * + * @param key Inserted key + * @param value Inserted value + * @param revision The revision to use + * @return an instance of the InsertResult. + */ + /* no qualifier */InsertResult insert( K key, V value, long revision ) throws IOException + { + // We have to start a new transaction, which will be committed or rollbacked + // locally. This will duplicate the current BtreeHeader during this phase. + if ( revision == -1L ) + { + revision = currentRevision.get() + 1; + } + + try + { + // Try to insert the new value in the tree at the right place, + // starting from the root page. Here, the root page may be either + // a Node or a Leaf + InsertResult result = processInsert( key, value, revision ); + + // Return the value we have found if it was modified + return result; + } + catch ( IOException ioe ) + { + throw ioe; + } + } + + + private BTreeHeader getBTreeHeader( String name ) + { + switch ( btreeType ) + { + case PERSISTED_SUB: + return getBtreeHeader(); + + case BTREE_OF_BTREES: + return recordManager.getNewBTreeHeader( RecordManager.BTREE_OF_BTREES_NAME ); + + case COPIED_PAGES_BTREE: + return recordManager.getNewBTreeHeader( RecordManager.COPIED_PAGE_BTREE_NAME ); + + default: + return recordManager.getBTreeHeader( name ); + } + } + + + private BTreeHeader getNewBTreeHeader( String name ) + { + if ( btreeType == BTreeTypeEnum.PERSISTED_SUB ) + { + return getBtreeHeader(); + } + + BTreeHeader btreeHeader = recordManager.getNewBTreeHeader( getName() ); + + return btreeHeader; + } + + + /** + * Insert the tuple into the B-tree rootPage, get back the new rootPage + */ + private InsertResult processInsert( K key, V value, long revision ) throws IOException + { + // Get the current B-tree header, and insert the value into it + BTreeHeader btreeHeader = getBTreeHeader( getName() ); + InsertResult result = btreeHeader.getRootPage().insert( key, value, revision ); + + if ( result instanceof ExistsResult ) + { + return result; + } + + // Create a new BTreeHeader + BTreeHeader newBtreeHeader = btreeHeader.copy(); + + // Inject the old B-tree header into the pages to be freed + // if we are inserting an element in a management BTree + if ( ( btreeType == BTreeTypeEnum.BTREE_OF_BTREES ) || ( btreeType == BTreeTypeEnum.COPIED_PAGES_BTREE ) ) + { + PageIO[] pageIos = recordManager.readPageIOs( btreeHeader.getBTreeHeaderOffset(), -1L ); + + for ( PageIO pageIo : pageIos ) + { + recordManager.freedPages.add( pageIo ); + } + } + + Page newRootPage; + + if ( result instanceof ModifyResult ) + { + ModifyResult modifyResult = ( ( ModifyResult ) result ); + + newRootPage = modifyResult.getModifiedPage(); + + // Increment the counter if we have inserted a new value + if ( modifyResult.getModifiedValue() == null ) + { + newBtreeHeader.incrementNbElems(); + } + } + else + { + // We have split the old root, create a new one containing + // only the pivotal we got back + SplitResult splitResult = ( ( SplitResult ) result ); + + K pivot = splitResult.getPivot(); + Page leftPage = splitResult.getLeftPage(); + Page rightPage = splitResult.getRightPage(); + + // If the B-tree is managed, we have to write the two pages that were created + // and to keep a track of the two offsets for the upper node + PageHolder holderLeft = writePage( leftPage, revision ); + + PageHolder holderRight = writePage( rightPage, revision ); + + // Create the new rootPage + newRootPage = new PersistedNode( this, revision, pivot, holderLeft, holderRight ); + + // Always increment the counter : we have added a new value + newBtreeHeader.incrementNbElems(); + } + + // Write the new root page on disk + LOG_PAGES.debug( "Writing the new rootPage revision {} for {}", revision, name ); + writePage( newRootPage, revision ); + + // Update the new B-tree header + newBtreeHeader.setRootPage( newRootPage ); + newBtreeHeader.setRevision( revision ); + + // Write down the data on disk + long newBtreeHeaderOffset = recordManager.writeBtreeHeader( this, newBtreeHeader ); + + // Update the B-tree of B-trees with this new offset, if we are not already doing so + switch ( btreeType ) + { + case PERSISTED: + // We have a new B-tree header to inject into the B-tree of btrees + recordManager.addInBtreeOfBtrees( getName(), revision, newBtreeHeaderOffset ); + + recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); + + // Store the new revision + storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); + + break; + + case PERSISTED_SUB: + // Sub-B-trees are only updating the CopiedPage B-tree + recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); + + // Store the new revision + storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); + + currentRevision.set( revision ); + + break; + + case BTREE_OF_BTREES: + // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters + recordManager.updateRecordManagerHeader( newBtreeHeaderOffset, -1L ); + + // We can free the copied pages + recordManager.freePages( this, revision, result.getCopiedPages() ); + + // Store the new revision + storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); + + break; + + case COPIED_PAGES_BTREE: + // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters + recordManager.updateRecordManagerHeader( -1L, newBtreeHeaderOffset ); + + // We can free the copied pages + recordManager.freePages( this, revision, result.getCopiedPages() ); + + // Store the new revision + storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); + + break; + + default: + // Nothing to do for sub-btrees + break; + } + + // Get the new root page and make it the current root page + return result; + } + + + /** + * Write the data in the ByteBuffer, and eventually on disk if needed. + * + * @param channel The channel we want to write to + * @param bb The ByteBuffer we want to feed + * @param buffer The data to inject + * @throws IOException If the write failed + */ + private void writeBuffer( FileChannel channel, ByteBuffer bb, byte[] buffer ) throws IOException + { + int size = buffer.length; + int pos = 0; + + // Loop until we have written all the data + do + { + if ( bb.remaining() >= size ) + { + // No flush, as the ByteBuffer is big enough + bb.put( buffer, pos, size ); + size = 0; + } + else + { + // Flush the data on disk, reinitialize the ByteBuffer + int len = bb.remaining(); + size -= len; + bb.put( buffer, pos, len ); + pos += len; + + bb.flip(); + + channel.write( bb ); + + bb.clear(); + } + } + while ( size > 0 ); + } + + + /** + * Write a page either in the pending pages if the transaction is started, + * or directly on disk. + */ + private PageHolder writePage( Page modifiedPage, long revision ) throws IOException + { + PageHolder pageHolder = recordManager.writePage( this, modifiedPage, revision ); + + return pageHolder; + } + + + /** + * Get the rootPzge associated to a give revision. + * + * @param revision The revision we are looking for + * @return The rootPage associated to this revision + * @throws IOException If we had an issue while accessing the underlying file + * @throws KeyNotFoundException If the revision does not exist for this Btree + */ + public Page getRootPage( long revision ) throws IOException, KeyNotFoundException + { + return recordManager.getRootPage( this, revision ); + } + + + /** + * Get the current rootPage + * + * @return The rootPage + */ + public Page getRootPage() + { + return getBTreeHeader( getName() ).getRootPage(); + } + + + /* no qualifier */void setRootPage( Page root ) + { + getBTreeHeader( getName() ).setRootPage( root ); + } + + + /** + * @return the btreeInfoOffset + */ + public long getBtreeInfoOffset() + { + return btreeInfoOffset; + } + + + /** + * @param btreeInfoOffset the btreeInfoOffset to set + */ + public void setBtreeInfoOffset( long btreeInfoOffset ) + { + this.btreeInfoOffset = btreeInfoOffset; + } + + + /** + * {@inheritDoc} + */ + protected ReadTransaction beginReadTransaction() + { + BTreeHeader btreeHeader = getBTreeHeader( getName() ); + + ReadTransaction readTransaction = new ReadTransaction( recordManager, btreeHeader, readTransactions ); + + readTransactions.add( readTransaction ); + + return readTransaction; + } + + + /** + * {@inheritDoc} + */ + protected ReadTransaction beginReadTransaction( long revision ) + { + BTreeHeader btreeHeader = getBtreeHeader( revision ); + + if ( btreeHeader != null ) + { + ReadTransaction readTransaction = new ReadTransaction( recordManager, btreeHeader, + readTransactions ); + + readTransactions.add( readTransaction ); + + return readTransaction; + } + else + { + return null; + } + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Managed BTree" ); + sb.append( "[" ).append( getName() ).append( "]" ); + sb.append( "( pageSize:" ).append( getPageSize() ); + + if ( getBTreeHeader( getName() ).getRootPage() != null ) + { + sb.append( ", nbEntries:" ).append( getBTreeHeader( getName() ).getNbElems() ); + } + else + { + sb.append( ", nbEntries:" ).append( 0 ); + } + + sb.append( ", comparator:" ); + + if ( keySerializer.getComparator() == null ) + { + sb.append( "null" ); + } + else + { + sb.append( keySerializer.getComparator().getClass().getSimpleName() ); + } + + sb.append( ", DuplicatesAllowed: " ).append( isAllowDuplicates() ); + + sb.append( ") : \n" ); + sb.append( getBTreeHeader( getName() ).getRootPage().dumpPage( "" ) ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTreeBuilder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTreeBuilder.java new file mode 100644 index 000000000..2f8df9a3a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTreeBuilder.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.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * A B-tree builder that builds a tree from the bottom. + * + * @author Apache Directory Project + */ +public class PersistedBTreeBuilder +{ + private String name; + + private int numKeysInNode; + + private ElementSerializer keySerializer; + + private ElementSerializer valueSerializer; + + private RecordManager rm; + + + public PersistedBTreeBuilder( RecordManager rm, String name, int numKeysInNode, ElementSerializer keySerializer, + ElementSerializer valueSerializer ) + { + this.rm = rm; + this.name = name; + this.numKeysInNode = numKeysInNode; + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + } + + + @SuppressWarnings("unchecked") + public BTree build( Iterator> sortedTupleItr ) throws Exception + { + BTree btree = BTreeFactory.createPersistedBTree( name, keySerializer, valueSerializer ); + + rm.manage( btree ); + + List> lstLeaves = new ArrayList>(); + + int totalTupleCount = 0; + + PersistedLeaf leaf1 = ( PersistedLeaf ) BTreeFactory.createLeaf( btree, 0, numKeysInNode ); + lstLeaves.add( leaf1 ); + + int leafIndex = 0; + + while ( sortedTupleItr.hasNext() ) + { + Tuple tuple = sortedTupleItr.next(); + + BTreeFactory.setKey( btree, leaf1, leafIndex, tuple.getKey() ); + + PersistedValueHolder eh = new PersistedValueHolder( btree, tuple.getValue() ); + + BTreeFactory.setValue( btree, leaf1, leafIndex, eh ); + + leafIndex++; + totalTupleCount++; + if ( ( totalTupleCount % numKeysInNode ) == 0 ) + { + leafIndex = 0; + + PageHolder pageHolder = rm.writePage( btree, leaf1, 1 ); + + leaf1 = ( PersistedLeaf ) BTreeFactory.createLeaf( btree, 0, numKeysInNode ); + lstLeaves.add( leaf1 ); + } + + //TODO build the whole tree in chunks rather than processing *all* leaves at first + } + + if ( lstLeaves.isEmpty() ) + { + return btree; + } + + // remove null keys and values from the last leaf and resize + PersistedLeaf lastLeaf = ( PersistedLeaf ) lstLeaves.get( lstLeaves.size() - 1 ); + for ( int i = 0; i < lastLeaf.getNbElems(); i++ ) + { + if ( lastLeaf.getKey( i ) == null ) + { + int n = i; + lastLeaf.setNbElems( n ); + KeyHolder[] keys = lastLeaf.getKeys(); + + lastLeaf.setKeys( ( KeyHolder[] ) Array.newInstance( PersistedKeyHolder.class, n ) ); + System.arraycopy( keys, 0, lastLeaf.getKeys(), 0, n ); + + ValueHolder[] values = lastLeaf.values; + lastLeaf.values = ( PersistedValueHolder[] ) Array.newInstance( PersistedValueHolder.class, n ); + System.arraycopy( values, 0, lastLeaf.values, 0, n ); + + PageHolder pageHolder = rm.writePage( btree, lastLeaf, 1 ); + + break; + } + } + + // make sure either one of the root pages is reclaimed, cause when we call rm.manage() + // there is already a root page created + Page rootPage = attachNodes( lstLeaves, btree ); + + //System.out.println("built rootpage : " + rootPage); + ( ( PersistedBTree ) btree ).setNbElems( totalTupleCount ); + + rm.updateBtreeHeader( btree, ( ( AbstractPage ) rootPage ).getOffset() ); + + rm.freePages( btree, btree.getRootPage().getRevision(), Arrays.asList( btree.getRootPage() ) ); + + ( ( AbstractBTree ) btree ).setRootPage( rootPage ); + + return btree; + } + + + @SuppressWarnings("unchecked") + private Page attachNodes( List> children, BTree btree ) throws IOException + { + if ( children.size() == 1 ) + { + return children.get( 0 ); + } + + List> lstNodes = new ArrayList>(); + + int numChildren = numKeysInNode + 1; + + PersistedNode node = ( PersistedNode ) BTreeFactory.createNode( btree, 0, numKeysInNode ); + lstNodes.add( node ); + int i = 0; + int totalNodes = 0; + + for ( Page page : children ) + { + if ( i != 0 ) + { + BTreeFactory.setKey( btree, node, i - 1, page.getLeftMostKey() ); + } + + BTreeFactory.setPage( btree, node, i, page ); + + i++; + totalNodes++; + + if ( ( totalNodes % numChildren ) == 0 ) + { + i = 0; + + rm.writePage( btree, node, 1 ); + + node = ( PersistedNode ) BTreeFactory.createNode( btree, 0, numKeysInNode ); + lstNodes.add( node ); + } + } + + // remove null keys and values from the last node and resize + AbstractPage lastNode = ( AbstractPage ) lstNodes.get( lstNodes.size() - 1 ); + + for ( int j = 0; j < lastNode.getNbElems(); j++ ) + { + if ( lastNode.getKey( j ) == null ) + { + int n = j; + lastNode.setNbElems( n ); + KeyHolder[] keys = lastNode.getKeys(); + + lastNode.setKeys( ( KeyHolder[] ) Array.newInstance( KeyHolder.class, n ) ); + System.arraycopy( keys, 0, lastNode.getKeys(), 0, n ); + + rm.writePage( btree, lastNode, 1 ); + + break; + } + } + + return attachNodes( lstNodes, btree ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTreeConfiguration.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTreeConfiguration.java new file mode 100644 index 000000000..20c702095 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTreeConfiguration.java @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * The B+Tree Configuration. This class can be used to store all the configurable + * parameters used by the B-tree class + * + * @param The type for the keys + * @param The type for the stored values + * + * @author Apache Directory Project + */ +public class PersistedBTreeConfiguration +{ + /** Number of entries in each Page. */ + private int pageSize = BTree.DEFAULT_PAGE_SIZE; + + /** The size of the buffer used to write data in disk */ + private int writeBufferSize = BTree.DEFAULT_WRITE_BUFFER_SIZE; + + /** The Key and Value serializer used for this tree. If none is provided, + * the B-tree will deduce the serializer to use from the generic type, and + * use the default Java serialization */ + private ElementSerializer keySerializer; + private ElementSerializer valueSerializer; + + /** The B-tree name */ + private String name; + + /** The path where the B-tree file will be stored. Default to the local + * temporary directory. + */ + private String filePath; + + /** + * The maximum delay to wait before a revision is considered as unused. + * This delay is necessary so that a read that does not ends does not + * hold a revision in memory forever. + * The default value is 10000 (10 seconds). If the value is 0 or below, + * the delay is considered as infinite + */ + private long readTimeOut = PersistedBTree.DEFAULT_READ_TIMEOUT; + + /** Flag to enable duplicate key support */ + private boolean allowDuplicates; + + /** The B-tree type */ + private BTreeTypeEnum btreeType = BTreeTypeEnum.PERSISTED; + + /** The cache size, if it's <= 0, we don't have cache */ + private int cacheSize; + + /** The inherited B-tree if we create a sub B-tree */ + private BTree parentBTree; + + + /** + * @return the pageSize + */ + public int getPageSize() + { + return pageSize; + } + + + /** + * @param pageSize the pageSize to set + */ + public void setPageSize( int pageSize ) + { + this.pageSize = pageSize; + } + + + /** + * @return the key serializer + */ + public ElementSerializer getKeySerializer() + { + return keySerializer; + } + + + /** + * @return the value serializer + */ + public ElementSerializer getValueSerializer() + { + return valueSerializer; + } + + + /** + * @param keySerializer the key serializer to set + * @param valueSerializer the value serializer to set + */ + public void setSerializers( ElementSerializer keySerializer, ElementSerializer valueSerializer ) + { + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + } + + + /** + * @param serializer the key serializer to set + */ + public void setKeySerializer( ElementSerializer keySerializer ) + { + this.keySerializer = keySerializer; + } + + + /** + * @param serializer the key serializer to set + */ + public void setValueSerializer( ElementSerializer valueSerializer ) + { + this.valueSerializer = valueSerializer; + } + + + /** + * @return the readTimeOut + */ + public long getReadTimeOut() + { + return readTimeOut; + } + + + /** + * @param readTimeOut the readTimeOut to set + */ + public void setReadTimeOut( long readTimeOut ) + { + this.readTimeOut = readTimeOut; + } + + + /** + * @return the filePath + */ + public String getFilePath() + { + return filePath; + } + + + /** + * @param filePath the filePath to set + */ + public void setFilePath( String filePath ) + { + this.filePath = filePath; + } + + + /** + * @return the writeBufferSize + */ + public int getWriteBufferSize() + { + return writeBufferSize; + } + + + /** + * @param writeBufferSize the writeBufferSize to set + */ + public void setWriteBufferSize( int writeBufferSize ) + { + this.writeBufferSize = writeBufferSize; + } + + + /** + * @return the name + */ + public String getName() + { + return name; + } + + + /** + * @param name the name to set + */ + public void setName( String name ) + { + this.name = name.trim(); + } + + + /** + * @return true if duplicate key support is enabled + */ + public boolean isAllowDuplicates() + { + return allowDuplicates; + } + + + /** + * enable duplicate key support + * + * @param allowDuplicates + * @throws IllegalStateException if the B-tree was already initialized or when tried to turn off duplicate suport on + * an existing B-tree containing duplicate keys + */ + public void setAllowDuplicates( boolean allowDuplicates ) + { + this.allowDuplicates = allowDuplicates; + } + + + /** + * @return the cacheSize + */ + public int getCacheSize() + { + return cacheSize; + } + + + /** + * @param cacheSize the cacheSize to set. + */ + public void setCacheSize( int cacheSize ) + { + this.cacheSize = cacheSize; + } + + + /** + * @return the cache + */ + public BTree getParentBTree() + { + return parentBTree; + } + + + /** + * @param cache the cache to set. + */ + public void setParentBTree( BTree parentBTree ) + { + this.parentBTree = parentBTree; + } + + + /** + * @return The BtreeType for this Btree + */ + public BTreeTypeEnum getBtreeType() + { + return btreeType; + } + + + /** + * @param btreeType The BtreeType + */ + public void setBtreeType( BTreeTypeEnum btreeType ) + { + this.btreeType = btreeType; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedKeyHolder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedKeyHolder.java new file mode 100644 index 000000000..1c6b9415a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedKeyHolder.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; + + +/** + * A class storing either a key, or an offset to the key on the page's byte[] + * + * @author Apache Directory Project + * + * The key type + */ +/* No qualifier */class PersistedKeyHolder extends KeyHolder +{ + /** The ByteBuffer storing the key */ + private byte[] raw; + + /** The Key serializer */ + private ElementSerializer keySerializer; + + + /** + * Create a new KeyHolder instance + * @param keySerializer The KeySerializer instance + * @param key The key to store + */ + /* no qualifier */PersistedKeyHolder( ElementSerializer keySerializer, K key ) + { + super( key ); + this.keySerializer = keySerializer; + raw = keySerializer.serialize( key ); + } + + + /** + * Create a new KeyHolder instance + * @param keySerializer The KeySerializer instance + * @param raw the bytes representing the serialized key + */ + /* no qualifier */PersistedKeyHolder( ElementSerializer keySerializer, byte[] raw ) + { + super( null ); + this.keySerializer = keySerializer; + this.raw = raw; + } + + + /** + * @return the key + */ + /* no qualifier */K getKey() + { + if ( key == null ) + { + try + { + key = keySerializer.fromBytes( raw ); + } + catch ( IOException ioe ) + { + // Nothing we can do here... + } + } + + return key; + } + + + /** + * @param key the Key to store in into the KeyHolder + */ + /* no qualifier */void setKey( K key ) + { + this.key = key; + raw = keySerializer.serialize( key ); + } + + + /** + * @return The internal serialized byte[] + */ + /* No qualifier */byte[] getRaw() + { + return raw; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "PersistedKeyHolder[" ); + + if ( key != null ) + { + sb.append( key ); + sb.append( ", " ); + } + else if ( raw != null ) + { + K key = getKey(); + sb.append( ":" ).append( key ).append( ":," ); + } + else + { + sb.append( "null," ); + } + + if ( raw != null ) + { + sb.append( raw.length ); + } + else + { + sb.append( "null" ); + } + + sb.append( "]" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedLeaf.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedLeaf.java new file mode 100644 index 000000000..a6a0ed92b --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedLeaf.java @@ -0,0 +1,1447 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.apache.directory.mavibot.btree.BTreeTypeEnum.PERSISTED_SUB; + +import java.io.IOException; +import java.lang.reflect.Array; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; + + +/** + * A MVCC Leaf. It stores the keys and values. It does not have any children. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier */class PersistedLeaf extends AbstractPage +{ + /** Values associated with keys */ + protected ValueHolder[] values; + + + /** + * Constructor used to create a new Leaf when we read it from a file. + * + * @param btree The BTree this page belongs to. + */ + PersistedLeaf( BTree btree ) + { + super( btree ); + } + + + /** + * Internal constructor used to create Page instance used when a page is being copied or overflow + * + * @param btree The BTree this page belongs to. + * @param revision The page revision + * @param nbElems The number of elements this page will contain + */ + @SuppressWarnings("unchecked") + PersistedLeaf( BTree btree, long revision, int nbElems ) + { + super( btree, revision, nbElems ); + if ( btree.getType() != BTreeTypeEnum.PERSISTED_SUB ) + { + values = ( ValueHolder[] ) Array.newInstance( PersistedValueHolder.class, nbElems ); + } + } + + + /** + * {@inheritDoc} + */ + public InsertResult insert( K key, V value, long revision ) throws IOException + { + // Find the key into this leaf + int pos = findPos( key ); + + boolean isSubTree = ( btree.getType() == PERSISTED_SUB ); + + if ( pos < 0 ) + { + // We already have the key in the page : replace the value + // into a copy of this page, unless the page has already be copied + int index = -( pos + 1 ); + + if ( isSubTree ) + { + return ExistsResult.EXISTS; + } + + // Replace the existing value in a copy of the current page + InsertResult result = replaceElement( revision, key, value, index ); + + return result; + } + + // The key is not present in the leaf. We have to add it in the page + if ( nbElems < btree.getPageSize() ) + { + // The current page is not full, it can contain the added element. + // We insert it into a copied page and return the result + Page modifiedPage = null; + + if ( isSubTree ) + { + modifiedPage = addSubTreeElement( revision, key, pos ); + } + else + { + modifiedPage = addElement( revision, key, value, pos ); + } + + InsertResult result = new ModifyResult( modifiedPage, null ); + result.addCopiedPage( this ); + + return result; + } + else + { + // The Page is already full : we split it and return the overflow element, + // after having created two pages. + InsertResult result = null; + + if ( isSubTree ) + { + result = addAndSplitSubTree( revision, key, pos ); + } + else + { + result = addAndSplit( revision, key, value, pos ); + } + + result.addCopiedPage( this ); + + return result; + } + } + + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + /* no qualifier */DeleteResult delete( K key, V value, long revision, Page parent, int parentPos ) + throws IOException + { + // Check that the leaf is not empty + if ( nbElems == 0 ) + { + // Empty leaf + return NotPresentResult.NOT_PRESENT; + } + + // Find the key in the page + int pos = findPos( key ); + + if ( pos >= 0 ) + { + // Not found : return the not present result. + return NotPresentResult.NOT_PRESENT; + } + + // Get the removed element + Tuple removedElement = null; + + // flag to detect if a key was completely removed + boolean keyRemoved = false; + + int index = -( pos + 1 ); + + boolean isNotSubTree = ( btree.getType() != PERSISTED_SUB ); + + ValueHolder valueHolder = null; + + if ( isNotSubTree ) + { + valueHolder = values[index]; + } + else + // set value to null, just incase if a non-null value passed while deleting a key from from sub-btree + { + value = null; + } + + if ( value == null ) + { + // we have to delete the whole value + removedElement = new Tuple( keys[index].getKey(), value ); // the entire value was removed + keyRemoved = true; + } + else + { + if ( valueHolder.contains( value ) ) + { + keyRemoved = ( valueHolder.size() == 1 ); + + removedElement = new Tuple( keys[index].getKey(), value ); // only one value was removed + } + else + { + return NotPresentResult.NOT_PRESENT; + } + } + + PersistedLeaf newLeaf = null; + + if ( keyRemoved ) + { + // No value, we can remove the key + newLeaf = new PersistedLeaf( btree, revision, nbElems - 1 ); + } + else + { + // Copy the page as we will delete a value from a ValueHolder + newLeaf = new PersistedLeaf( btree, revision, nbElems ); + } + + // Create the result + DeleteResult defaultResult = new RemoveResult( newLeaf, removedElement ); + + // If the parent is null, then this page is the root page. + if ( parent == null ) + { + // Just remove the entry if it's present, or replace it if we have more than one value in the ValueHolder + copyAfterRemovingElement( keyRemoved, value, newLeaf, index ); + + // The current page is added in the copied page list + defaultResult.addCopiedPage( this ); + + return defaultResult; + } + else if ( keyRemoved ) + { + // The current page is not the root. Check if the leaf has more than N/2 + // elements + int halfSize = btree.getPageSize() / 2; + + if ( nbElems == halfSize ) + { + // We have to find a sibling now, and either borrow an entry from it + // if it has more than N/2 elements, or to merge the two pages. + // Check in both next and previous page, if they have the same parent + // and select the biggest page with the same parent to borrow an element. + int siblingPos = selectSibling( parent, parentPos ); + PersistedLeaf sibling = ( PersistedLeaf ) ( ( ( PersistedNode ) parent ) + .getPage( siblingPos ) ); + + if ( sibling.getNbElems() == halfSize ) + { + // We will merge the current page with its sibling + DeleteResult result = mergeWithSibling( removedElement, revision, sibling, + ( siblingPos < parentPos ), index ); + + return result; + } + else + { + // We can borrow the element from the left sibling + if ( siblingPos < parentPos ) + { + DeleteResult result = borrowFromLeft( removedElement, revision, sibling, index ); + + return result; + } + else + { + // Borrow from the right sibling + DeleteResult result = borrowFromRight( removedElement, revision, sibling, index ); + + return result; + } + } + } + else + { + // The page has more than N/2 elements. + // We simply remove the element from the page, and if it was the leftmost, + // we return the new pivot (it will replace any instance of the removed + // key in its parents) + copyAfterRemovingElement( true, value, newLeaf, index ); + + // The current page is added in the copied page list + defaultResult.addCopiedPage( this ); + + return defaultResult; + } + } + else + { + // Last, not least : we can copy the full page + // Copy the keys and the values + System.arraycopy( keys, 0, newLeaf.keys, 0, nbElems ); + System.arraycopy( values, 0, newLeaf.values, 0, nbElems ); + + // Replace the ValueHolder now + try + { + ValueHolder newValueHolder = valueHolder.clone(); + newValueHolder.remove( value ); + + newLeaf.values[pos] = newValueHolder; + } + catch ( CloneNotSupportedException e ) + { + throw new RuntimeException( e ); + } + + // The current page is added in the copied page list + defaultResult.addCopiedPage( this ); + + return defaultResult; + } + } + + + /** + * Merges the sibling with the current leaf, after having removed the element in the page. + * + * @param revision The new revision + * @param sibling The sibling we will merge with + * @param isLeft Tells if the sibling is on the left or on the right + * @param pos The position of the removed element + * @return The new created leaf containing the sibling and the old page. + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult mergeWithSibling( Tuple removedElement, long revision, + PersistedLeaf sibling, + boolean isLeft, int pos ) + throws EndOfFileExceededException, IOException + { + boolean isNotSubTree = ( btree.getType() != PERSISTED_SUB ); + + // Create the new page. It will contain N - 1 elements (the maximum number) + // as we merge two pages that contain N/2 elements minus the one we remove + PersistedLeaf newLeaf = new PersistedLeaf( btree, revision, btree.getPageSize() - 1 ); + + if ( isLeft ) + { + // The sibling is on the left + // Copy all the elements from the sibling first + System.arraycopy( sibling.keys, 0, newLeaf.keys, 0, sibling.nbElems ); + if ( isNotSubTree ) + { + System.arraycopy( sibling.values, 0, newLeaf.values, 0, sibling.nbElems ); + } + + // Copy all the elements from the page up to the deletion position + System.arraycopy( keys, 0, newLeaf.keys, sibling.nbElems, pos ); + if ( isNotSubTree ) + { + System.arraycopy( values, 0, newLeaf.values, sibling.nbElems, pos ); + } + + // And copy the remaining elements after the deletion point + System.arraycopy( keys, pos + 1, newLeaf.keys, sibling.nbElems + pos, nbElems - pos - 1 ); + if ( isNotSubTree ) + { + System.arraycopy( values, pos + 1, newLeaf.values, sibling.nbElems + pos, nbElems - pos - 1 ); + } + } + else + { + // The sibling is on the right + // Copy all the elements from the page up to the deletion position + System.arraycopy( keys, 0, newLeaf.keys, 0, pos ); + if ( isNotSubTree ) + { + System.arraycopy( values, 0, newLeaf.values, 0, pos ); + } + + // Then copy the remaining elements after the deletion point + System.arraycopy( keys, pos + 1, newLeaf.keys, pos, nbElems - pos - 1 ); + if ( isNotSubTree ) + { + System.arraycopy( values, pos + 1, newLeaf.values, pos, nbElems - pos - 1 ); + } + + // And copy all the elements from the sibling + System.arraycopy( sibling.keys, 0, newLeaf.keys, nbElems - 1, sibling.nbElems ); + if ( isNotSubTree ) + { + System.arraycopy( sibling.values, 0, newLeaf.values, nbElems - 1, sibling.nbElems ); + } + } + + // And create the result + DeleteResult result = new MergedWithSiblingResult( newLeaf, removedElement ); + + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * Borrows an element from the left sibling, creating a new sibling with one + * less element and creating a new page where the element to remove has been + * deleted and the borrowed element added on the left. + * + * @param revision The new revision for all the pages + * @param sibling The left sibling + * @param pos The position of the element to remove + * @return The resulting pages + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult borrowFromLeft( Tuple removedElement, long revision, PersistedLeaf sibling, + int pos ) + throws IOException + { + boolean isNotSubTree = ( btree.getType() != PERSISTED_SUB ); + + // The sibling is on the left, borrow the rightmost element + K siblingKey = sibling.keys[sibling.getNbElems() - 1].getKey(); + ValueHolder siblingValue = null; + if ( isNotSubTree ) + { + siblingValue = sibling.values[sibling.getNbElems() - 1]; + } + + // Create the new sibling, with one less element at the end + PersistedLeaf newSibling = ( PersistedLeaf ) sibling.copy( revision, sibling.getNbElems() - 1 ); + + // Create the new page and add the new element at the beginning + // First copy the current page, with the same size + PersistedLeaf newLeaf = new PersistedLeaf( btree, revision, nbElems ); + + // Insert the borrowed element + newLeaf.keys[0] = new PersistedKeyHolder( btree.getKeySerializer(), siblingKey ); + if ( isNotSubTree ) + { + newLeaf.values[0] = siblingValue; + } + + // Copy the keys and the values up to the insertion position, + System.arraycopy( keys, 0, newLeaf.keys, 1, pos ); + if ( isNotSubTree ) + { + System.arraycopy( values, 0, newLeaf.values, 1, pos ); + } + + // And copy the remaining elements + System.arraycopy( keys, pos + 1, newLeaf.keys, pos + 1, keys.length - pos - 1 ); + if ( isNotSubTree ) + { + System.arraycopy( values, pos + 1, newLeaf.values, pos + 1, values.length - pos - 1 ); + } + + DeleteResult result = new BorrowedFromLeftResult( newLeaf, newSibling, removedElement ); + + // Add the copied pages to the list + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * Borrows an element from the right sibling, creating a new sibling with one + * less element and creating a new page where the element to remove has been + * deleted and the borrowed element added on the right. + * + * @param revision The new revision for all the pages + * @param sibling The right sibling + * @param pos The position of the element to remove + * @return The resulting pages + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult borrowFromRight( Tuple removedElement, long revision, PersistedLeaf sibling, + int pos ) + throws IOException + { + boolean isNotSubTree = ( btree.getType() != PERSISTED_SUB ); + + // The sibling is on the left, borrow the rightmost element + K siblingKey = sibling.keys[0].getKey(); + ValueHolder siblingHolder = null; + if ( isNotSubTree ) + { + siblingHolder = sibling.values[0]; + } + + // Create the new sibling + PersistedLeaf newSibling = new PersistedLeaf( btree, revision, sibling.getNbElems() - 1 ); + + // Copy the keys and the values from 1 to N in the new sibling + System.arraycopy( sibling.keys, 1, newSibling.keys, 0, sibling.nbElems - 1 ); + if ( isNotSubTree ) + { + System.arraycopy( sibling.values, 1, newSibling.values, 0, sibling.nbElems - 1 ); + } + + // Create the new page and add the new element at the end + // First copy the current page, with the same size + PersistedLeaf newLeaf = new PersistedLeaf( btree, revision, nbElems ); + + // Insert the borrowed element at the end + newLeaf.keys[nbElems - 1] = new PersistedKeyHolder( btree.getKeySerializer(), siblingKey ); + if ( isNotSubTree ) + { + newLeaf.values[nbElems - 1] = siblingHolder; + } + + // Copy the keys and the values up to the deletion position, + System.arraycopy( keys, 0, newLeaf.keys, 0, pos ); + if ( isNotSubTree ) + { + System.arraycopy( values, 0, newLeaf.values, 0, pos ); + } + + // And copy the remaining elements + System.arraycopy( keys, pos + 1, newLeaf.keys, pos, keys.length - pos - 1 ); + if ( isNotSubTree ) + { + System.arraycopy( values, pos + 1, newLeaf.values, pos, values.length - pos - 1 ); + } + + DeleteResult result = new BorrowedFromRightResult( newLeaf, newSibling, removedElement ); + + // Add the copied pages to the list + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * Copies the elements of the current page to a new page + * + * @param keyRemoved a flag stating if the key was removed + * @param newLeaf The new page into which the remaining keys and values will be copied + * @param pos The position into the page of the element to remove + * @throws IOException If we have an error while trying to access the page + */ + private void copyAfterRemovingElement( boolean keyRemoved, V removedValue, PersistedLeaf newLeaf, int pos ) + throws IOException + { + boolean isNotSubTree = ( btree.getType() != PERSISTED_SUB ); + + if ( keyRemoved ) + { + // Deal with the special case of a page with only one element by skipping + // the copy, as we won't have any remaining element in the page + if ( nbElems == 1 ) + { + return; + } + + // Copy the keys and the values up to the insertion position + System.arraycopy( keys, 0, newLeaf.keys, 0, pos ); + if ( isNotSubTree ) + { + System.arraycopy( values, 0, newLeaf.values, 0, pos ); + } + + // And copy the elements after the position + System.arraycopy( keys, pos + 1, newLeaf.keys, pos, keys.length - pos - 1 ); + if ( isNotSubTree ) + { + System.arraycopy( values, pos + 1, newLeaf.values, pos, values.length - pos - 1 ); + } + } + else + // one of the many values of the same key was removed, no change in the number of keys + { + System.arraycopy( keys, 0, newLeaf.keys, 0, nbElems ); + System.arraycopy( values, 0, newLeaf.values, 0, nbElems ); + + // We still have to clone the modified value holder + ValueHolder valueHolder = newLeaf.values[pos]; + + try + { + ValueHolder newValueHolder = valueHolder.clone(); + + newValueHolder.remove( removedValue ); + + newLeaf.values[pos] = newValueHolder; + } + catch ( CloneNotSupportedException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + + /** + * {@inheritDoc} + */ + public V get( K key ) throws KeyNotFoundException, IOException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + ValueHolder valueHolder = values[-( pos + 1 )]; + + ValueCursor cursor = valueHolder.getCursor(); + + cursor.beforeFirst(); + + if ( cursor.hasNext() ) + { + V value = cursor.next(); + + return value; + } + else + { + return null; + } + } + else + { + throw KeyNotFoundException.INSTANCE; + } + } + + + /** + * {@inheritDoc} + */ + /* No qualifier */KeyHolder getKeyHolder( int pos ) + { + if ( pos < nbElems ) + { + return keys[pos]; + } + else + { + return null; + } + } + + + /** + * {@inheritDoc} + */ + @Override + public ValueCursor getValues( K key ) throws KeyNotFoundException, IOException, IllegalArgumentException + { + if ( !btree.isAllowDuplicates() ) + { + throw new IllegalArgumentException( "Duplicates are not allowed in this tree" ); + } + + int pos = findPos( key ); + + if ( pos < 0 ) + { + ValueHolder valueHolder = values[-( pos + 1 )]; + + return valueHolder.getCursor(); + } + else + { + throw KeyNotFoundException.INSTANCE; + } + } + + + /** + * {@inheritDoc} + */ + public boolean hasKey( K key ) + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + return true; + } + + return false; + } + + + @Override + public boolean contains( K key, V value ) throws IOException + { + int pos = findPos( key ); + + if ( pos < 0 ) + { + ValueHolder valueHolder = values[-( pos + 1 )]; + + return valueHolder.contains( value ); + } + else + { + return false; + } + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */ValueHolder getValue( int pos ) + { + if ( pos < nbElems ) + { + return values[pos]; + } + else + { + return null; + } + } + + + /** + * Sets the value at a give position + * @param pos The position in the values array + * @param value the value to inject + */ + /* no qualifier */void setValue( int pos, ValueHolder value ) + { + values[pos] = value; + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browse( K key, ReadTransaction transaction, ParentPos[] stack, int depth ) + { + int pos = findPos( key ); + + // First use case : the leaf is empty (this is a root page) + if ( nbElems == 0 ) + { + // We have to return an empty cursor + return new EmptyTupleCursor(); + } + + // The cursor we will return + TupleCursor cursor = new TupleCursor( transaction, stack, depth ); + + // Depending on the position, we will proceed differently : + // 1) if the key is found in the page, the cursor will be + // set to this position. + // 2) The key has not been found, but is in the middle of the + // page (ie, other keys above the one we are looking for exist), + // the cursor will be set to the current position + // 3) The key has not been found, and we are at the end of + // the page. We have to fetch the next key in yhe B-tree + if ( pos < 0 ) + { + // The key has been found. + pos = -( pos + 1 ); + + // Start at the found position in the page + ParentPos parentPos = new ParentPos( this, pos ); + + // Create the value cursor + parentPos.valueCursor = values[pos].getCursor(); + + // And store this position in the stack + stack[depth] = parentPos; + + return cursor; + } + else + { + // The key has not been found, there are keys above this one. + // Select the value just above + if ( pos < nbElems ) + { + // There is at least one key above the one we are looking for. + // This will be the starting point. + ParentPos parentPos = new ParentPos( this, pos ); + + // Create the value cursor + parentPos.valueCursor = values[pos].getCursor(); + + stack[depth] = parentPos; + + return cursor; + } + else + { + // We are at the end of a leaf. We have to see if we have other + // keys on the right. + if ( depth == 0 ) + { + // No children, we are at the end of the root page + stack[depth] = new ParentPos( this, pos ); + + // As we are done, set the cursor at the end + try + { + cursor.afterLast(); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + + return cursor; + } + else + { + // We have to find the adjacent key in the B-tree + boolean isLast = true; + stack[depth] = new ParentPos( this, pos ); + + // Check each upper node, starting from the direct parent + int stackIndex = depth - 1; + + for ( int i = stackIndex; i >= 0; i-- ) + { + if ( stack[i].pos < stack[i].page.getNbElems() ) + { + isLast = false; + break; + } + + stackIndex--; + } + + if ( isLast ) + { + // We don't have any more elements + try + { + cursor.afterLast(); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + + return cursor; + } + + return cursor; + } + } + } + } + + + /** + * {@inheritDoc} + */ + public TupleCursor browse( ReadTransaction transaction, ParentPos[] stack, int depth ) + { + int pos = 0; + TupleCursor cursor = null; + + if ( nbElems == 0 ) + { + // The tree is empty, it's the root, we have nothing to return + stack[depth] = new ParentPos( null, -1 ); + + return new TupleCursor( transaction, stack, depth ); + } + else + { + // Start at the beginning of the page + ParentPos parentPos = new ParentPos( this, pos ); + + // Create the value cursor + parentPos.valueCursor = values[0].getCursor(); + + stack[depth] = parentPos; + + cursor = new TupleCursor( transaction, stack, depth ); + } + + return cursor; + } + + + /** + * Copy the current page and all of the keys, values and children, if it's not a leaf. + * + * @param revision The new revision + * @param nbElems The number of elements to copy + * @return The copied page + */ + private Page copy( long revision, int nbElems ) + { + PersistedLeaf newLeaf = new PersistedLeaf( btree, revision, nbElems ); + + // Copy the keys and the values + System.arraycopy( keys, 0, newLeaf.keys, 0, nbElems ); + + if ( values != null ) + { + // It' not enough to copy the ValueHolder, we have to clone them + // as ValueHolders are mutable + int pos = 0; + + for ( ValueHolder valueHolder : values ) + { + try + { + newLeaf.values[pos++] = valueHolder.clone(); + } + catch ( CloneNotSupportedException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + // Stop when we have copied nbElems values + if ( pos == nbElems ) + { + break; + } + } + } + + return newLeaf; + } + + + /** + * Copy the current page if needed, and replace the value at the position we have found the key. + * + * @param revision The new page revision + * @param key The new key + * @param value the new value + * @param pos The position of the key in the page + * @return The copied page + * @throws IOException If we have an error while trying to access the page + */ + private InsertResult replaceElement( long revision, K key, V value, int pos ) + throws IOException + { + PersistedLeaf newLeaf = this; + + // Get the previous value from the leaf (it's a copy) + ValueHolder valueHolder = values[pos]; + + boolean valueExists = valueHolder.contains( value ); + + if ( this.revision != revision ) + { + // The page hasn't been modified yet, we need to copy it first + newLeaf = ( PersistedLeaf ) copy( revision, nbElems ); + } + + // Get the previous value from the leaf (it's a copy) + valueHolder = newLeaf.values[pos]; + V replacedValue = null; + + if ( !valueExists && btree.isAllowDuplicates() ) + { + valueHolder.add( value ); + newLeaf.values[pos] = valueHolder; + } + else if ( valueExists && btree.isAllowDuplicates() ) + { + // As strange as it sounds, we need to remove the value to reinject it. + // There are cases where the value retrieval just use one part of the + // value only (typically for LDAP Entries, where we use the DN) + replacedValue = valueHolder.remove( value ); + valueHolder.add( value ); + } + else if ( !btree.isAllowDuplicates() ) + { + replacedValue = valueHolder.replaceValueArray( value ); + } + + // Create the result + InsertResult result = new ModifyResult( newLeaf, replacedValue ); + result.addCopiedPage( this ); + + return result; + } + + + /** + * Adds a new into a copy of the current page at a given position. We return the + * modified page. The new page will have one more element than the current page. + * + * @param revision The revision of the modified page + * @param key The key to insert + * @param value The value to insert + * @param pos The position into the page + * @return The modified page with the element added + */ + private Page addElement( long revision, K key, V value, int pos ) + { + // First copy the current page, but add one element in the copied page + PersistedLeaf newLeaf = new PersistedLeaf( btree, revision, nbElems + 1 ); + + // Create the value holder + ValueHolder valueHolder = new PersistedValueHolder( btree, value ); + + // Deal with the special case of an empty page + if ( nbElems == 0 ) + { + newLeaf.keys[0] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + + newLeaf.values[0] = valueHolder; + } + else + { + // Copy the keys and the values up to the insertion position + System.arraycopy( keys, 0, newLeaf.keys, 0, pos ); + System.arraycopy( values, 0, newLeaf.values, 0, pos ); + + // Add the new element + newLeaf.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + newLeaf.values[pos] = valueHolder; + + // And copy the remaining elements + System.arraycopy( keys, pos, newLeaf.keys, pos + 1, keys.length - pos ); + System.arraycopy( values, pos, newLeaf.values, pos + 1, values.length - pos ); + } + + return newLeaf; + } + + + /** + * Split a full page into two new pages, a left, a right and a pivot element. The new pages will + * each contains half of the original elements.
          + * The pivot will be computed, depending on the place + * we will inject the newly added element.
          + * If the newly added element is in the middle, we will use it + * as a pivot. Otherwise, we will use either the last element in the left page if the element is added + * on the left, or the first element in the right page if it's added on the right. + * + * @param revision The new revision for all the created pages + * @param key The key to add + * @param value The value to add + * @param pos The position of the insertion of the new element + * @return An OverflowPage containing the pivot, and the new left and right pages + */ + private InsertResult addAndSplit( long revision, K key, V value, int pos ) + { + int middle = btree.getPageSize() >> 1; + PersistedLeaf leftLeaf = null; + PersistedLeaf rightLeaf = null; + ValueHolder valueHolder = new PersistedValueHolder( btree, value ); + + // Determinate where to store the new value + if ( pos <= middle ) + { + // The left page will contain the new value + leftLeaf = new PersistedLeaf( btree, revision, middle + 1 ); + + // Copy the keys and the values up to the insertion position + System.arraycopy( keys, 0, leftLeaf.keys, 0, pos ); + System.arraycopy( values, 0, leftLeaf.values, 0, pos ); + + // Add the new element + leftLeaf.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + leftLeaf.values[pos] = valueHolder; + + // And copy the remaining elements + System.arraycopy( keys, pos, leftLeaf.keys, pos + 1, middle - pos ); + System.arraycopy( values, pos, leftLeaf.values, pos + 1, middle - pos ); + + // Now, create the right page + rightLeaf = new PersistedLeaf( btree, revision, middle ); + + // Copy the keys and the values in the right page + System.arraycopy( keys, middle, rightLeaf.keys, 0, middle ); + System.arraycopy( values, middle, rightLeaf.values, 0, middle ); + } + else + { + // Create the left page + leftLeaf = new PersistedLeaf( btree, revision, middle ); + + // Copy all the element into the left page + System.arraycopy( keys, 0, leftLeaf.keys, 0, middle ); + System.arraycopy( values, 0, leftLeaf.values, 0, middle ); + + // Now, create the right page + rightLeaf = new PersistedLeaf( btree, revision, middle + 1 ); + + int rightPos = pos - middle; + + // Copy the keys and the values up to the insertion position + System.arraycopy( keys, middle, rightLeaf.keys, 0, rightPos ); + System.arraycopy( values, middle, rightLeaf.values, 0, rightPos ); + + // Add the new element + rightLeaf.keys[rightPos] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + rightLeaf.values[rightPos] = valueHolder; + + // And copy the remaining elements + System.arraycopy( keys, pos, rightLeaf.keys, rightPos + 1, nbElems - pos ); + System.arraycopy( values, pos, rightLeaf.values, rightPos + 1, nbElems - pos ); + } + + // Get the pivot + K pivot = rightLeaf.keys[0].getKey(); + + // Create the result + InsertResult result = new SplitResult( pivot, leftLeaf, rightLeaf ); + + return result; + } + + + /** + * {@inheritDoc} + */ + public K getLeftMostKey() + { + return keys[0].getKey(); + } + + + /** + * {@inheritDoc} + */ + public K getRightMostKey() + { + return keys[nbElems - 1].getKey(); + } + + + /** + * {@inheritDoc} + */ + public Tuple findLeftMost() throws IOException + { + K key = keys[0].getKey(); + + boolean isSubTree = ( btree.getType() == PERSISTED_SUB ); + + if ( isSubTree ) + { + return new Tuple( key, null ); + } + + ValueCursor cursor = values[0].getCursor(); + + try + { + cursor.beforeFirst(); + if ( cursor.hasNext() ) + { + return new Tuple( key, cursor.next() ); + } + else + { + // Null value + return new Tuple( key, null ); + } + } + finally + { + cursor.close(); + } + } + + + /** + * {@inheritDoc} + */ + public Tuple findRightMost() throws EndOfFileExceededException, IOException + { + + K key = keys[nbElems - 1].getKey(); + + boolean isSubTree = ( btree.getType() == PERSISTED_SUB ); + + if ( isSubTree ) + { + return new Tuple( key, null ); + } + + ValueCursor cursor = values[nbElems - 1].getCursor(); + + try + { + cursor.afterLast(); + + if ( cursor.hasPrev() ) + { + return new Tuple( key, cursor.prev() ); + } + else + { + // Null value + return new Tuple( key, null ); + } + } + finally + { + cursor.close(); + } + } + + + /** + * {@inheritDoc} + */ + public boolean isLeaf() + { + return true; + } + + + /** + * {@inheritDoc} + */ + public boolean isNode() + { + return false; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Leaf[" ); + sb.append( super.toString() ); + + sb.append( "] -> {" ); + + if ( nbElems > 0 ) + { + boolean isFirst = true; + + for ( int i = 0; i < nbElems; i++ ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( "<" ).append( keys[i] ).append( "," ); + + if ( values != null ) + { + sb.append( values[i] ); + } + else + { + sb.append( "null" ); + } + + sb.append( ">" ); + } + } + + sb.append( "}" ); + + return sb.toString(); + } + + + /** + * same as {@link #addElement(long, Object, Object, int)} except the values are not copied. + * This method is only used while inserting an element into a sub-BTree. + */ + private Page addSubTreeElement( long revision, K key, int pos ) + { + // First copy the current page, but add one element in the copied page + PersistedLeaf newLeaf = new PersistedLeaf( btree, revision, nbElems + 1 ); + + // Deal with the special case of an empty page + if ( nbElems == 0 ) + { + newLeaf.keys[0] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + } + else + { + // Copy the keys and the values up to the insertion position + System.arraycopy( keys, 0, newLeaf.keys, 0, pos ); + + // Add the new element + newLeaf.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + + // And copy the remaining elements + System.arraycopy( keys, pos, newLeaf.keys, pos + 1, keys.length - pos ); + } + + return newLeaf; + } + + + /** + * same as {@link #addAndSplit(long, Object, Object, int)} except the values are not copied. + * This method is only used while inserting an element into a sub-BTree. + */ + private InsertResult addAndSplitSubTree( long revision, K key, int pos ) + { + int middle = btree.getPageSize() >> 1; + PersistedLeaf leftLeaf = null; + PersistedLeaf rightLeaf = null; + + // Determinate where to store the new value + if ( pos <= middle ) + { + // The left page will contain the new value + leftLeaf = new PersistedLeaf( btree, revision, middle + 1 ); + + // Copy the keys and the values up to the insertion position + System.arraycopy( keys, 0, leftLeaf.keys, 0, pos ); + + // Add the new element + leftLeaf.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + + // And copy the remaining elements + System.arraycopy( keys, pos, leftLeaf.keys, pos + 1, middle - pos ); + + // Now, create the right page + rightLeaf = new PersistedLeaf( btree, revision, middle ); + + // Copy the keys and the values in the right page + System.arraycopy( keys, middle, rightLeaf.keys, 0, middle ); + } + else + { + // Create the left page + leftLeaf = new PersistedLeaf( btree, revision, middle ); + + // Copy all the element into the left page + System.arraycopy( keys, 0, leftLeaf.keys, 0, middle ); + + // Now, create the right page + rightLeaf = new PersistedLeaf( btree, revision, middle + 1 ); + + int rightPos = pos - middle; + + // Copy the keys and the values up to the insertion position + System.arraycopy( keys, middle, rightLeaf.keys, 0, rightPos ); + + // Add the new element + rightLeaf.keys[rightPos] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + + // And copy the remaining elements + System.arraycopy( keys, pos, rightLeaf.keys, rightPos + 1, nbElems - pos ); + } + + // Get the pivot + K pivot = rightLeaf.keys[0].getKey(); + + // Create the result + InsertResult result = new SplitResult( pivot, leftLeaf, rightLeaf ); + + return result; + } + + + /** + * {@inheritDoc} + */ + public KeyCursor browseKeys( ReadTransaction transaction, ParentPos[] stack, int depth ) + { + int pos = 0; + KeyCursor cursor = null; + + if ( nbElems == 0 ) + { + // The tree is empty, it's the root, we have nothing to return + stack[depth] = new ParentPos( null, -1 ); + + return new KeyCursor( transaction, stack, depth ); + } + else + { + // Start at the beginning of the page + ParentPos parentPos = new ParentPos( this, pos ); + + stack[depth] = parentPos; + + cursor = new KeyCursor( transaction, stack, depth ); + } + + return cursor; + } + + + /** + * sets the values to null + * WARNING: only used by the internal API (especially during the bulk loading) + */ + /* no qualifier */void _clearValues_() + { + values = null; + } + + + /** + * {@inheritDoc} + */ + public String dumpPage( String tabs ) + { + StringBuilder sb = new StringBuilder(); + + sb.append( tabs ); + + if ( nbElems > 0 ) + { + boolean isFirst = true; + + for ( int i = 0; i < nbElems; i++ ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( "<" ).append( keys[i] ).append( "," ); + + if ( values != null ) + { + sb.append( values[i] ); + } + else + { + sb.append( "null" ); + } + + sb.append( ">" ); + } + } + + sb.append( "\n" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedNode.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedNode.java new file mode 100644 index 000000000..09a4efab4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedNode.java @@ -0,0 +1,1188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.List; + + +/** + * A MVCC Node. It stores the keys and references to its children page. It does not + * contain any value. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier */class PersistedNode extends AbstractPage +{ + /** + * Creates a new Node which will contain only one key, with references to + * a left and right page. This is a specific constructor used by the btree + * when the root was full when we added a new value. + * + * @param btree the parent BTree + * @param revision the Node revision + * @param nbElems The number of elements in this Node + */ + @SuppressWarnings("unchecked") + PersistedNode( BTree btree, long revision, int nbElems ) + { + super( btree, revision, nbElems ); + + // Create the children array + children = ( PersistedPageHolder[] ) Array.newInstance( PersistedPageHolder.class, nbElems + 1 ); + } + + + /** + * Creates a new Node which will contain only one key, with references to + * a left and right page. This is a specific constructor used by the btree + * when the root was full when we added a new value. + * + * @param btree the parent BTree + * @param revision the Node revision + * @param key The new key + * @param leftPage The left page + * @param rightPage The right page + */ + @SuppressWarnings("unchecked") + PersistedNode( BTree btree, long revision, K key, Page leftPage, Page rightPage ) + { + super( btree, revision, 1 ); + + // Create the children array, and store the left and right children + children = ( PersistedPageHolder[] ) Array.newInstance( PersistedPageHolder.class, + btree.getPageSize() + 1 ); + + children[0] = new PersistedPageHolder( btree, leftPage ); + children[1] = new PersistedPageHolder( btree, rightPage ); + + // Create the keys array and store the pivot into it + // We get the type of array to create from the btree + // Yes, this is an hack... + keys = ( KeyHolder[] ) Array.newInstance( PersistedKeyHolder.class, btree.getPageSize() ); + + keys[0] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + } + + + /** + * Creates a new Node which will contain only one key, with references to + * a left and right page. This is a specific constructor used by the btree + * when the root was full when we added a new value. + * + * @param btree the parent BTree + * @param revision the Node revision + * @param key The new key + * @param leftPage The left page + * @param rightPage The right page + */ + @SuppressWarnings("unchecked") + PersistedNode( BTree btree, long revision, K key, PageHolder leftPage, PageHolder rightPage ) + { + super( btree, revision, 1 ); + + // Create the children array, and store the left and right children + children = ( PageHolder[] ) Array.newInstance( PageHolder.class, + btree.getPageSize() + 1 ); + + children[0] = leftPage; + children[1] = rightPage; + + // Create the keys array and store the pivot into it + keys = ( KeyHolder[] ) Array.newInstance( KeyHolder.class, btree.getPageSize() ); + + keys[0] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + } + + + /** + * {@inheritDoc} + */ + public InsertResult insert( K key, V value, long revision ) throws IOException + { + // Find the key into this leaf + int pos = findPos( key ); + + if ( pos < 0 ) + { + // The key has been found in the page. As it's a Node, that means + // we must go down in the right child to insert the value + pos = -( pos++ ); + } + + // Get the child page into which we will insert the tuple + Page child = children[pos].getValue(); + + // and insert the into this child + InsertResult result = child.insert( key, value, revision ); + + // Ok, now, we have injected the tuple down the tree. Let's check + // the result to see if we have to split the current page + if ( result instanceof ExistsResult ) + { + return result; + } + else if ( result instanceof ModifyResult ) + { + // The child has been modified. + return replaceChild( revision, ( ModifyResult ) result, pos ); + } + else + { + // The child has been split. We have to insert the new pivot in the + // current page, and to reference the two new pages + SplitResult splitResult = ( SplitResult ) result; + K pivot = splitResult.getPivot(); + Page leftPage = splitResult.getLeftPage(); + Page rightPage = splitResult.getRightPage(); + + // We have to deal with the two cases : + // - the current page is full, we have to split it + // - the current page is not full, we insert the new pivot + if ( nbElems == btree.getPageSize() ) + { + // The page is full + result = addAndSplit( splitResult.getCopiedPages(), revision, pivot, leftPage, rightPage, pos ); + } + else + { + // The page can contain the new pivot, let's insert it + result = insertChild( splitResult.getCopiedPages(), revision, pivot, leftPage, rightPage, pos ); + } + + return result; + } + } + + + /** + * Modifies the current node after a remove has been done in one of its children. + * The node won't be merged with another node. + * + * @param removeResult The result of a remove operation + * @param index the position of the key, not transformed + * @param pos The position of the key, as a positive value + * @param found If the key has been found in the page + * @return The new result + * @throws IOException If we have an error while trying to access the page + */ + private RemoveResult handleRemoveResult( RemoveResult removeResult, int index, int pos, boolean found ) + throws IOException + { + // Simplest case : the element has been removed from the underlying page, + // we just have to copy the current page an modify the reference to link to + // the modified page. + PersistedNode newPage = copy( revision ); + + Page modifiedPage = removeResult.getModifiedPage(); + + if ( found ) + { + newPage.children[index + 1] = createHolder( modifiedPage ); + } + else + { + newPage.children[index] = createHolder( modifiedPage ); + } + + if ( pos < 0 ) + { + newPage.keys[index].setKey( removeResult.getModifiedPage().getLeftMostKey() ); + } + + // Modify the result and return + removeResult.setModifiedPage( newPage ); + removeResult.addCopiedPage( this ); + + return removeResult; + } + + + /** + * Handles the removal of an element from the root page, when two of its children + * have been merged. + * + * @param mergedResult The merge result + * @param pos The position in the current root + * @param found Tells if the removed key is present in the root page + * @return The resulting root page + * @throws IOException If we have an error while trying to access the page + */ + private RemoveResult handleRootRemove( MergedWithSiblingResult mergedResult, int pos, boolean found ) + throws IOException + { + RemoveResult removeResult = null; + + // If the current node contains only one key, then the merged result will be + // the new root. Deal with this case + if ( nbElems == 1 ) + { + removeResult = new RemoveResult( mergedResult.getCopiedPages(), mergedResult.getModifiedPage(), + mergedResult.getRemovedElement() ); + + removeResult.addCopiedPage( this ); + } + else + { + // Remove the element and update the reference to the changed pages + removeResult = removeKey( mergedResult, revision, pos ); + } + + return removeResult; + } + + + /** + * Borrows an element from the right sibling, creating a new sibling with one + * less element and creating a new page where the element to remove has been + * deleted and the borrowed element added on the right. + * + * @param revision The new revision for all the pages + * @param sibling The right sibling + * @param pos The position of the element to remove + * @return The resulting pages + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult borrowFromRight( long revision, MergedWithSiblingResult mergedResult, + PersistedNode sibling, int pos ) throws IOException + { + // Create the new sibling, with one less element at the beginning + PersistedNode newSibling = new PersistedNode( btree, revision, sibling.getNbElems() - 1 ); + + K siblingKey = sibling.children[0].getValue().getLeftMostKey(); + + // Copy the keys and children of the old sibling in the new sibling + System.arraycopy( sibling.keys, 1, newSibling.keys, 0, newSibling.getNbElems() ); + System.arraycopy( sibling.children, 1, newSibling.children, 0, newSibling.getNbElems() + 1 ); + + // Create the new page and add the new element at the end + // First copy the current node, with the same size + PersistedNode newNode = new PersistedNode( btree, revision, nbElems ); + + // Copy the keys and the values up to the insertion position + int index = Math.abs( pos ); + + // Copy the key and children from sibling + newNode.keys[nbElems - 1] = new PersistedKeyHolder( btree.getKeySerializer(), siblingKey ); // 1 + newNode.children[nbElems] = sibling.children[0]; // 8 + + if ( index < 2 ) + { + // Copy the keys + System.arraycopy( keys, 1, newNode.keys, 0, nbElems - 1 ); + + // Inject the modified page + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[0] = createHolder( modifiedPage ); + + // Copy the children + System.arraycopy( children, 2, newNode.children, 1, nbElems - 1 ); + } + else + { + if ( index > 2 ) + { + // Copy the keys before the deletion point + System.arraycopy( keys, 0, newNode.keys, 0, index - 2 ); // 4 + } + + // Inject the new modified page key + newNode.keys[index - 2] = new PersistedKeyHolder( btree.getKeySerializer(), mergedResult + .getModifiedPage() + .getLeftMostKey() ); // 2 + + if ( index < nbElems ) + { + // Copy the remaining keys after the deletion point + System.arraycopy( keys, index, newNode.keys, index - 1, nbElems - index ); // 3 + + // Copy the remaining children after the deletion point + System.arraycopy( children, index + 1, newNode.children, index, nbElems - index ); // 7 + } + + // Copy the children before the deletion point + System.arraycopy( children, 0, newNode.children, 0, index - 1 ); // 5 + + // Inject the modified page + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[index - 1] = createHolder( modifiedPage ); // 6 + } + + // Create the result + DeleteResult result = new BorrowedFromRightResult( mergedResult.getCopiedPages(), newNode, + newSibling, mergedResult.getRemovedElement() ); + + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * Borrows an element from the left sibling, creating a new sibling with one + * less element and creating a new page where the element to remove has been + * deleted and the borrowed element added on the left. + * + * @param revision The new revision for all the pages + * @param sibling The left sibling + * @param pos The position of the element to remove + * @return The resulting pages + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult borrowFromLeft( long revision, MergedWithSiblingResult mergedResult, + PersistedNode sibling, int pos ) throws IOException + { + // The sibling is on the left, borrow the rightmost element + Page siblingChild = sibling.children[sibling.nbElems].getValue(); + + // Create the new sibling, with one less element at the end + PersistedNode newSibling = new PersistedNode( btree, revision, sibling.getNbElems() - 1 ); + + // Copy the keys and children of the old sibling in the new sibling + System.arraycopy( sibling.keys, 0, newSibling.keys, 0, newSibling.getNbElems() ); + System.arraycopy( sibling.children, 0, newSibling.children, 0, newSibling.getNbElems() + 1 ); + + // Create the new page and add the new element at the beginning + // First copy the current node, with the same size + PersistedNode newNode = new PersistedNode( btree, revision, nbElems ); + + // Sets the first children + newNode.children[0] = createHolder( siblingChild ); //1 + + int index = Math.abs( pos ); + + if ( index < 2 ) + { + newNode.keys[0] = new PersistedKeyHolder( btree.getKeySerializer(), mergedResult.getModifiedPage() + .getLeftMostKey() ); + System.arraycopy( keys, 1, newNode.keys, 1, nbElems - 1 ); + + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[1] = createHolder( modifiedPage ); + System.arraycopy( children, 2, newNode.children, 2, nbElems - 1 ); + } + else + { + // Set the first key + newNode.keys[0] = new PersistedKeyHolder( btree.getKeySerializer(), children[0].getValue() + .getLeftMostKey() ); //2 + + if ( index > 2 ) + { + // Copy the keys before the deletion point + System.arraycopy( keys, 0, newNode.keys, 1, index - 2 ); // 4 + } + + // Inject the modified key + newNode.keys[index - 1] = new PersistedKeyHolder( btree.getKeySerializer(), mergedResult + .getModifiedPage() + .getLeftMostKey() ); // 3 + + if ( index < nbElems ) + { + // Add copy the remaining keys after the deletion point + System.arraycopy( keys, index, newNode.keys, index, nbElems - index ); // 5 + + // Copy the remaining children after the insertion point + System.arraycopy( children, index + 1, newNode.children, index + 1, nbElems - index ); // 8 + } + + // Copy the children before the insertion point + System.arraycopy( children, 0, newNode.children, 1, index - 1 ); // 6 + + // Insert the modified page + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[index] = createHolder( modifiedPage ); // 7 + } + + // Create the result + DeleteResult result = new BorrowedFromLeftResult( mergedResult.getCopiedPages(), newNode, + newSibling, + mergedResult.getRemovedElement() ); + + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * We have to merge the node with its sibling, both have N/2 elements before the element + * removal. + * + * @param revision The revision + * @param mergedResult The result of the merge + * @param sibling The Page we will merge the current page with + * @param isLeft Tells if the sibling is on the left + * @param pos The position of the key that has been removed + * @return The page resulting of the merge + * @throws IOException If we have an error while trying to access the page + */ + private DeleteResult mergeWithSibling( long revision, MergedWithSiblingResult mergedResult, + PersistedNode sibling, boolean isLeft, int pos ) throws IOException + { + // Create the new node. It will contain N - 1 elements (the maximum number) + // as we merge two nodes that contain N/2 elements minus the one we remove + PersistedNode newNode = new PersistedNode( btree, revision, btree.getPageSize() ); + Tuple removedElement = mergedResult.getRemovedElement(); + int half = btree.getPageSize() / 2; + int index = Math.abs( pos ); + + if ( isLeft ) + { + // The sibling is on the left. Copy all of its elements in the new node first + System.arraycopy( sibling.keys, 0, newNode.keys, 0, half ); //1 + System.arraycopy( sibling.children, 0, newNode.children, 0, half + 1 ); //2 + + // Then copy all the elements up to the deletion point + if ( index < 2 ) + { + newNode.keys[half] = new PersistedKeyHolder( btree.getKeySerializer(), mergedResult + .getModifiedPage() + .getLeftMostKey() ); + System.arraycopy( keys, 1, newNode.keys, half + 1, half - 1 ); + + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[half + 1] = createHolder( modifiedPage ); + System.arraycopy( children, 2, newNode.children, half + 2, half - 1 ); + } + else + { + // Copy the left part of the node keys up to the deletion point + // Insert the new key + newNode.keys[half] = new PersistedKeyHolder( btree.getKeySerializer(), children[0].getValue() + .getLeftMostKey() ); // 3 + + if ( index > 2 ) + { + System.arraycopy( keys, 0, newNode.keys, half + 1, index - 2 ); //4 + } + + // Inject the new merged key + newNode.keys[half + index - 1] = new PersistedKeyHolder( btree.getKeySerializer(), mergedResult + .getModifiedPage().getLeftMostKey() ); //5 + + if ( index < half ) + { + System.arraycopy( keys, index, newNode.keys, half + index, half - index ); //6 + System.arraycopy( children, index + 1, newNode.children, half + index + 1, half - index ); //9 + } + + // Copy the children before the deletion point + System.arraycopy( children, 0, newNode.children, half + 1, index - 1 ); // 7 + + // Inject the new merged child + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[half + index] = createHolder( modifiedPage ); //8 + } + } + else + { + // The sibling is on the right. + if ( index < 2 ) + { + // Copy the keys + System.arraycopy( keys, 1, newNode.keys, 0, half - 1 ); + + // Insert the first child + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[0] = createHolder( modifiedPage ); + + // Copy the node children + System.arraycopy( children, 2, newNode.children, 1, half - 1 ); + } + else + { + // Copy the keys and children before the deletion point + if ( index > 2 ) + { + // Copy the first keys + System.arraycopy( keys, 0, newNode.keys, 0, index - 2 ); //1 + } + + // Copy the first children + System.arraycopy( children, 0, newNode.children, 0, index - 1 ); //6 + + // Inject the modified key + newNode.keys[index - 2] = new PersistedKeyHolder( btree.getKeySerializer(), mergedResult + .getModifiedPage() + .getLeftMostKey() ); //2 + + // Inject the modified children + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[index - 1] = createHolder( modifiedPage ); // 7 + + // Add the remaining node's key if needed + if ( index < half ) + { + System.arraycopy( keys, index, newNode.keys, index - 1, half - index ); //5 + + // Add the remining children if below half + System.arraycopy( children, index + 1, newNode.children, index, half - index ); // 8 + } + } + + // Inject the new key from sibling + newNode.keys[half - 1] = new PersistedKeyHolder( btree.getKeySerializer(), sibling.findLeftMost() + .getKey() ); //3 + + // Copy the sibling keys + System.arraycopy( sibling.keys, 0, newNode.keys, half, half ); + + // Add the sibling children + System.arraycopy( sibling.children, 0, newNode.children, half, half + 1 ); // 9 + } + + // And create the result + DeleteResult result = new MergedWithSiblingResult( mergedResult.getCopiedPages(), newNode, + removedElement ); + + result.addCopiedPage( this ); + result.addCopiedPage( sibling ); + + return result; + } + + + /** + * {@inheritDoc} + */ + /* no qualifier */ DeleteResult delete( K key, V value, long revision, Page parent, int parentPos ) + throws IOException + { + // We first try to delete the element from the child it belongs to + // Find the key in the page + int pos = findPos( key ); + boolean found = pos < 0; + int index = pos; + Page child = null; + DeleteResult deleteResult = null; + + if ( found ) + { + index = -( pos + 1 ); + child = children[-pos].getValue(); + deleteResult = ((AbstractPage)child).delete( key, value, revision, this, -pos ); + } + else + { + child = children[pos].getValue(); + deleteResult = ((AbstractPage)child).delete( key, value, revision, this, pos ); + } + + // If the key is not present in the tree, we simply return + if ( deleteResult instanceof NotPresentResult ) + { + // Nothing to do... + return deleteResult; + } + + // If we just modified the child, return a modified page + if ( deleteResult instanceof RemoveResult ) + { + RemoveResult removeResult = handleRemoveResult( ( RemoveResult ) deleteResult, index, pos, + found ); + + return removeResult; + } + + // If we had to borrow an element in the child, then have to update + // the current page + if ( deleteResult instanceof BorrowedFromSiblingResult ) + { + RemoveResult removeResult = handleBorrowedResult( ( BorrowedFromSiblingResult ) deleteResult, + pos ); + + return removeResult; + } + + // Last, not least, we have merged two child pages. We now have to remove + // an element from the local page, and to deal with the result. + if ( deleteResult instanceof MergedWithSiblingResult ) + { + MergedWithSiblingResult mergedResult = ( MergedWithSiblingResult ) deleteResult; + + // If the parent is null, then this page is the root page. + if ( parent == null ) + { + RemoveResult result = handleRootRemove( mergedResult, pos, found ); + + return result; + } + + // We have some parent. Check if the current page is not half full + int halfSize = btree.getPageSize() / 2; + + if ( nbElems > halfSize ) + { + // The page has more than N/2 elements. + // We simply remove the element from the page, and if it was the leftmost, + // we return the new pivot (it will replace any instance of the removed + // key in its parents) + RemoveResult result = removeKey( mergedResult, revision, pos ); + + return result; + } + else + { + // We will remove one element from a page that will have less than N/2 elements, + // which will lead to some reorganization : either we can borrow an element from + // a sibling, or we will have to merge two pages + int siblingPos = selectSibling( parent, parentPos ); + + PersistedNode sibling = ( PersistedNode ) ( ( ( PersistedNode ) parent ).children[siblingPos] + .getValue() ); + + if ( sibling.getNbElems() > halfSize ) + { + // The sibling contains enough elements + // We can borrow the element from the sibling + if ( siblingPos < parentPos ) + { + DeleteResult result = borrowFromLeft( revision, mergedResult, sibling, pos ); + + return result; + } + else + { + // Borrow from the right + DeleteResult result = borrowFromRight( revision, mergedResult, sibling, pos ); + + return result; + } + } + else + { + // We need to merge the sibling with the current page + DeleteResult result = mergeWithSibling( revision, mergedResult, sibling, + ( siblingPos < parentPos ), pos ); + + return result; + } + } + } + + // We should never reach this point + return null; + } + + + /** + * The deletion in a children has moved an element from one of its sibling. The key + * is present in the current node. + * @param borrowedResult The result of the deletion from the children + * @param pos The position the key was found in the current node + * @return The result + * @throws IOException If we have an error while trying to access the page + */ + private RemoveResult handleBorrowedResult( BorrowedFromSiblingResult borrowedResult, int pos ) + throws IOException + { + Page modifiedPage = borrowedResult.getModifiedPage(); + Page modifiedSibling = borrowedResult.getModifiedSibling(); + + PersistedNode newPage = copy( revision ); + + if ( pos < 0 ) + { + pos = -( pos + 1 ); + + if ( borrowedResult.isFromRight() ) + { + // Update the keys + newPage.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), modifiedPage.findLeftMost() + .getKey() ); + newPage.keys[pos + 1] = new PersistedKeyHolder( btree.getKeySerializer(), modifiedSibling + .findLeftMost() + .getKey() ); + + // Update the children + newPage.children[pos + 1] = createHolder( modifiedPage ); + newPage.children[pos + 2] = createHolder( modifiedSibling ); + } + else + { + // Update the keys + newPage.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), modifiedPage.findLeftMost() + .getKey() ); + + // Update the children + newPage.children[pos] = createHolder( modifiedSibling ); + newPage.children[pos + 1] = createHolder( modifiedPage ); + } + } + else + { + if ( borrowedResult.isFromRight() ) + { + // Update the keys + newPage.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), modifiedSibling.findLeftMost() + .getKey() ); + + // Update the children + newPage.children[pos] = createHolder( modifiedPage ); + newPage.children[pos + 1] = createHolder( modifiedSibling ); + } + else + { + // Update the keys + newPage.keys[pos - 1] = new PersistedKeyHolder( btree.getKeySerializer(), modifiedPage + .findLeftMost() + .getKey() ); + + // Update the children + newPage.children[pos - 1] = createHolder( modifiedSibling ); + newPage.children[pos] = createHolder( modifiedPage ); + } + } + + // Modify the result and return + RemoveResult removeResult = new RemoveResult( borrowedResult.getCopiedPages(), newPage, + borrowedResult.getRemovedElement() ); + + removeResult.addCopiedPage( this ); + + return removeResult; + } + + + /** + * Remove the key at a given position. + * + * @param mergedResult The page we will remove a key from + * @param revision The revision of the modified page + * @param pos The position into the page of the element to remove + * @return The modified page with the element added + * @throws IOException If we have an error while trying to access the page + */ + private RemoveResult removeKey( MergedWithSiblingResult mergedResult, long revision, int pos ) + throws IOException + { + // First copy the current page, but remove one element in the copied page + PersistedNode newNode = new PersistedNode( btree, revision, nbElems - 1 ); + + int index = Math.abs( pos ) - 2; + + // + if ( index < 0 ) + { + // Copy the keys and the children + System.arraycopy( keys, 1, newNode.keys, 0, newNode.nbElems ); + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[0] = createHolder( modifiedPage ); + System.arraycopy( children, 2, newNode.children, 1, nbElems - 1 ); + } + else + { + // Copy the keys + if ( index > 0 ) + { + System.arraycopy( keys, 0, newNode.keys, 0, index ); + } + + newNode.keys[index] = new PersistedKeyHolder( btree.getKeySerializer(), mergedResult.getModifiedPage() + .findLeftMost().getKey() ); + + if ( index < nbElems - 2 ) + { + System.arraycopy( keys, index + 2, newNode.keys, index + 1, nbElems - index - 2 ); + } + + // Copy the children + System.arraycopy( children, 0, newNode.children, 0, index + 1 ); + + Page modifiedPage = mergedResult.getModifiedPage(); + newNode.children[index + 1] = createHolder( modifiedPage ); + + if ( index < nbElems - 2 ) + { + System.arraycopy( children, index + 3, newNode.children, index + 2, nbElems - index - 2 ); + } + } + + // Create the result + RemoveResult result = new RemoveResult( mergedResult.getCopiedPages(), newNode, + mergedResult.getRemovedElement() ); + + result.addCopiedPage( this ); + + return result; + } + + + /** + * {@inheritDoc} + */ + /* No qualifier */KeyHolder getKeyHolder( int pos ) + { + if ( pos < nbElems ) + { + return keys[pos]; + } + else + { + return null; + } + } + + + /** + * Set the value at a give position + * + * @param pos The position in the values array + * @param value the value to inject + */ + /* no qualifier */void setValue( int pos, PersistedPageHolder value ) + { + children[pos] = value; + } + + + /** + * This method is used when we have to replace a child in a page when we have + * found the key in the tree (the value will be changed, so we have made + * copies of the existing pages). + * + * @param revision The current revision + * @param result The modified page + * @param pos The position of the found key + * @return A modified page + * @throws IOException If we have an error while trying to access the page + */ + private InsertResult replaceChild( long revision, ModifyResult result, int pos ) throws IOException + { + // Just copy the current page and update its revision + Page newPage = copy( revision ); + + // Last, we update the children table of the newly created page + // to point on the modified child + Page modifiedPage = result.getModifiedPage(); + + ( ( PersistedNode ) newPage ).children[pos] = createHolder( modifiedPage ); + + // We can return the result, where we update the modifiedPage, + // to avoid the creation of a new object + result.setModifiedPage( newPage ); + + result.addCopiedPage( this ); + + return result; + } + + + /** + * Creates a new holder containing a reference to a Page + * + * @param page The page we will refer to + * @return A holder containing a reference to the child page + * @throws IOException If we have an error while trying to access the page + */ + private PageHolder createHolder( Page page ) throws IOException + { + PageHolder holder = ( ( PersistedBTree ) btree ).getRecordManager().writePage( btree, + page, + revision ); + + return holder; + } + + + /** + * Adds a new key into a copy of the current page at a given position. We return the + * modified page. The new page will have one more key than the current page. + * + * @param copiedPages the list of copied pages + * @param revision The revision of the modified page + * @param key The key to insert + * @param leftPage The left child + * @param rightPage The right child + * @param pos The position into the page + * @return The modified page with the element added + * @throws IOException If we have an error while trying to access the page + */ + private InsertResult insertChild( List> copiedPages, long revision, K key, Page leftPage, + Page rightPage, int pos ) + throws IOException + { + // First copy the current page, but add one element in the copied page + PersistedNode newNode = new PersistedNode( btree, revision, nbElems + 1 ); + + // Copy the keys and the children up to the insertion position + if ( nbElems > 0 ) + { + System.arraycopy( keys, 0, newNode.keys, 0, pos ); + System.arraycopy( children, 0, newNode.children, 0, pos ); + } + + // Add the new key and children + newNode.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), key ); + + // If the BTree is managed, we now have to write the modified page on disk + // and to add this page to the list of modified pages + newNode.children[pos] = createHolder( leftPage ); + newNode.children[pos + 1] = createHolder( rightPage ); + + // And copy the remaining keys and children + if ( nbElems > 0 ) + { + System.arraycopy( keys, pos, newNode.keys, pos + 1, keys.length - pos ); + System.arraycopy( children, pos + 1, newNode.children, pos + 2, children.length - pos - 1 ); + } + + // Create the result + InsertResult result = new ModifyResult( copiedPages, newNode, null ); + result.addCopiedPage( this ); + + return result; + } + + + /** + * Splits a full page into two new pages, a left, a right and a pivot element. The new pages will + * each contains half of the original elements.
          + * The pivot will be computed, depending on the place + * we will inject the newly added element.
          + * If the newly added element is in the middle, we will use it + * as a pivot. Otherwise, we will use either the last element in the left page if the element is added + * on the left, or the first element in the right page if it's added on the right. + * + * @param copiedPages the list of copied pages + * @param revision The new revision for all the created pages + * @param pivot The key that will be move up after the split + * @param leftPage The left child + * @param rightPage The right child + * @param pos The position of the insertion of the new element + * @return An OverflowPage containing the pivot, and the new left and right pages + * @throws IOException If we have an error while trying to access the page + */ + private InsertResult addAndSplit( List> copiedPages, long revision, K pivot, Page leftPage, + Page rightPage, int pos ) throws IOException + { + int middle = btree.getPageSize() >> 1; + + // Create two new pages + PersistedNode newLeftPage = new PersistedNode( btree, revision, middle ); + PersistedNode newRightPage = new PersistedNode( btree, revision, middle ); + + // Determinate where to store the new value + // If it's before the middle, insert the value on the left, + // the key in the middle will become the new pivot + if ( pos < middle ) + { + // Copy the keys and the children up to the insertion position + System.arraycopy( keys, 0, newLeftPage.keys, 0, pos ); + System.arraycopy( children, 0, newLeftPage.children, 0, pos ); + + // Add the new element + newLeftPage.keys[pos] = new PersistedKeyHolder( btree.getKeySerializer(), pivot ); + newLeftPage.children[pos] = createHolder( leftPage ); + newLeftPage.children[pos + 1] = createHolder( rightPage ); + + // And copy the remaining elements minus the new pivot + System.arraycopy( keys, pos, newLeftPage.keys, pos + 1, middle - pos - 1 ); + System.arraycopy( children, pos + 1, newLeftPage.children, pos + 2, middle - pos - 1 ); + + // Copy the keys and the children in the right page + System.arraycopy( keys, middle, newRightPage.keys, 0, middle ); + System.arraycopy( children, middle, newRightPage.children, 0, middle + 1 ); + + // Create the result + pivot = keys[middle - 1].getKey(); + + if ( pivot == null ) + { + pivot = keys[middle - 1].getKey(); + } + + InsertResult result = new SplitResult( copiedPages, pivot, newLeftPage, + newRightPage ); + result.addCopiedPage( this ); + + return result; + } + else if ( pos == middle ) + { + // A special case : the pivot will be propagated up in the tree + // The left and right pages will be spread on the two new pages + // Copy the keys and the children up to the insertion position (here, middle) + System.arraycopy( keys, 0, newLeftPage.keys, 0, middle ); + System.arraycopy( children, 0, newLeftPage.children, 0, middle ); + newLeftPage.children[middle] = createHolder( leftPage ); + + // And process the right page now + System.arraycopy( keys, middle, newRightPage.keys, 0, middle ); + System.arraycopy( children, middle + 1, newRightPage.children, 1, middle ); + newRightPage.children[0] = createHolder( rightPage ); + + // Create the result + InsertResult result = new SplitResult( copiedPages, pivot, newLeftPage, newRightPage ); + result.addCopiedPage( this ); + + return result; + } + else + { + // Copy the keys and the children up to the middle + System.arraycopy( keys, 0, newLeftPage.keys, 0, middle ); + System.arraycopy( children, 0, newLeftPage.children, 0, middle + 1 ); + + // Copy the keys and the children in the right page up to the pos + System.arraycopy( keys, middle + 1, newRightPage.keys, 0, pos - middle - 1 ); + System.arraycopy( children, middle + 1, newRightPage.children, 0, pos - middle - 1 ); + + // Add the new element + newRightPage.keys[pos - middle - 1] = new PersistedKeyHolder( btree.getKeySerializer(), pivot ); + newRightPage.children[pos - middle - 1] = createHolder( leftPage ); + newRightPage.children[pos - middle] = createHolder( rightPage ); + + // And copy the remaining elements minus the new pivot + System.arraycopy( keys, pos, newRightPage.keys, pos - middle, nbElems - pos ); + System.arraycopy( children, pos + 1, newRightPage.children, pos + 1 - middle, nbElems - pos ); + + // Create the result + pivot = keys[middle].getKey(); + + if ( pivot == null ) + { + pivot = keys[middle].getKey(); + } + + InsertResult result = new SplitResult( copiedPages, pivot, newLeftPage, + newRightPage ); + result.addCopiedPage( this ); + + return result; + } + } + + + /** + * Copies the current page and all its keys, with a new revision. + * + * @param revision The new revision + * @return The copied page + */ + protected PersistedNode copy( long revision ) + { + PersistedNode newPage = new PersistedNode( btree, revision, nbElems ); + + // Copy the keys + System.arraycopy( keys, 0, newPage.keys, 0, nbElems ); + + // Copy the children + System.arraycopy( children, 0, newPage.children, 0, nbElems + 1 ); + + return newPage; + } + + + /** + * {@inheritDoc} + */ + public K getLeftMostKey() + { + return children[0].getValue().getLeftMostKey(); + } + + + /** + * {@inheritDoc} + */ + public K getRightMostKey() + { + int index = ( nbElems + 1 ) - 1; + + if ( children[index] != null ) + { + return children[index].getValue().getRightMostKey(); + } + + return children[nbElems - 1].getValue().getRightMostKey(); + } + + + /** + * {@inheritDoc} + */ + public boolean isLeaf() + { + return false; + } + + + /** + * {@inheritDoc} + */ + public boolean isNode() + { + return true; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Node[" ); + sb.append( super.toString() ); + sb.append( "] -> {" ); + + if ( nbElems > 0 ) + { + // Start with the first child + if ( children[0] == null ) + { + sb.append( "null" ); + } + else + { + sb.append( 'r' ).append( children[0].getValue().getRevision() ); + } + + for ( int i = 0; i < nbElems; i++ ) + { + sb.append( "|<" ).append( keys[i] ).append( ">|" ); + + if ( children[i + 1] == null ) + { + sb.append( "null" ); + } + else + { + sb.append( 'r' ).append( children[i + 1].getValue().getRevision() ); + } + } + } + + sb.append( "}" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedPageHolder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedPageHolder.java new file mode 100644 index 000000000..6d78aa8fe --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedPageHolder.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.directory.mavibot.btree.exception.BTreeOperationException; +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; + + +/** + * A Value holder. As we may not store all the values in memory (except for an in-memory + * BTree), we will use a SoftReference to keep a reference to a Value, and if it's null, + * then we will load the Value from the underlying physical support, using the offset. + * + * @param The type for the stored element (either a value or a page) + * @param The type of the BTree key + * @param The type of the BTree value + * + * @author Apache Directory Project + */ +/* No qualifier */class PersistedPageHolder extends PageHolder +{ + /** The RecordManager */ + private RecordManager recordManager; + + /** The cache */ + private LRUMap cache; + + /** The offset of the first {@link PageIO} storing the page on disk */ + private long offset; + + /** The offset of the last {@link PageIO} storing the page on disk */ + private long lastOffset; + + + /** + * Create a new holder storing an offset and a SoftReference containing the element. + * + * @param page The element to store into a SoftReference + */ + public PersistedPageHolder( BTree btree, Page page ) + { + // DO NOT keep the reference to Page, it will be fetched from cache when needed + super( btree, null ); + cache = ( ( PersistedBTree ) btree ).getCache(); + recordManager = ( ( PersistedBTree ) btree ).getRecordManager(); + offset = ( ( AbstractPage ) page ).getOffset(); + lastOffset = ( ( AbstractPage ) page ).getLastOffset(); + + ( ( AbstractPage ) page ).setOffset( offset ); + ( ( AbstractPage ) page ).setLastOffset( lastOffset ); + + cache.put( offset, page ); + } + + + /** + * Create a new holder storing an offset and a SoftReference containing the element. + * + * @param page The element to store into a SoftReference + */ + public PersistedPageHolder( BTree btree, Page page, long offset, long lastOffset ) + { + // DO NOT keep the reference to Page, it will be fetched from cache when needed + super( btree, null ); + cache = ( ( PersistedBTree ) btree ).getCache(); + recordManager = ( ( PersistedBTree ) btree ).getRecordManager(); + this.offset = offset; + this.lastOffset = lastOffset; + + if ( page != null ) + { + ( ( AbstractPage ) page ).setOffset( offset ); + ( ( AbstractPage ) page ).setLastOffset( lastOffset ); + } + + cache.put( offset, page ); + } + + + /** + * {@inheritDoc} + * @throws IOException + * @throws EndOfFileExceededException + */ + public Page getValue() + { + Page page = ( Page ) cache.get( offset ); + + if ( page == null ) + { + // We have to fetch the element from disk, using the offset now + page = fetchElement(); + + ( ( AbstractPage ) page ).setOffset( offset ); + ( ( AbstractPage ) page ).setLastOffset( lastOffset ); + + cache.put( offset, page ); + } + + return page; + } + + + /** + * Retrieve the value from the disk, using the BTree and offset + * @return The deserialized element ( + */ + private Page fetchElement() + { + try + { + Page element = recordManager.deserialize( btree, offset ); + + return element; + } + catch ( EndOfFileExceededException eofee ) + { + throw new BTreeOperationException( eofee.getMessage() ); + } + catch ( IOException ioe ) + { + throw new BTreeOperationException( ioe.getMessage() ); + } + } + + + /** + * @return The offset of the first {@link PageIO} storing the data on disk + */ + /* No qualifier */long getOffset() + { + return offset; + } + + + /** + * @return The offset of the last {@link PageIO} storing the data on disk + */ + /* No qualifier */long getLastOffset() + { + return lastOffset; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + Page page = getValue(); + + sb.append( btree.getName() ).append( "[" ).append( offset ).append( ", " ).append( lastOffset ) + .append( "]:" ).append( page ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedValueHolder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedValueHolder.java new file mode 100644 index 000000000..78c18bfd0 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedValueHolder.java @@ -0,0 +1,801 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Comparator; +import java.util.Iterator; +import java.util.UUID; + +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyCreatedException; +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.BTreeCreationException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * A holder to store the Values + * + * @author Apache Directory Project + * @param The value type + */ +/* No qualifier */class PersistedValueHolder extends AbstractValueHolder +{ + /** The LoggerFactory used by this class */ + protected static final Logger LOG = LoggerFactory.getLogger( PersistedValueHolder.class ); + + /** The parent BTree */ + protected PersistedBTree parentBtree; + + /** The serialized value */ + private byte[] raw; + + /** A flag set to true when the raw value has been deserialized */ + private boolean isDeserialized = false; + + /** A flag to signal that the raw value represent the serialized values in their last state */ + private boolean isRawUpToDate = false; + + + /** + * Creates a new instance of a ValueHolder, containing the serialized values. + * + * @param parentBtree the parent BTree + * @param raw The raw data containing the values + * @param nbValues the number of stored values + * @param raw the byte[] containing either the serialized array of values or the sub-btree offset + */ + PersistedValueHolder( BTree parentBtree, int nbValues, byte[] raw ) + { + this.parentBtree = ( PersistedBTree ) parentBtree; + this.valueSerializer = parentBtree.getValueSerializer(); + this.raw = raw; + isRawUpToDate = true; + valueThresholdUp = PersistedBTree.valueThresholdUp; + valueThresholdLow = PersistedBTree.valueThresholdLow; + + // We create the array of values if they fit in an array. If they are stored in a + // BTree, we do nothing atm. + if ( nbValues <= valueThresholdUp ) + { + // The values are contained into an array + valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues ); + } + } + + + /** + * Creates a new instance of a ValueHolder, containing Values. This constructor is called + * when we need to create a new ValueHolder with deserialized values. + * + * @param parentBtree The parent BTree + * @param values The Values stored in the ValueHolder + */ + PersistedValueHolder( BTree parentBtree, V... values ) + { + this.parentBtree = ( PersistedBTree ) parentBtree; + this.valueSerializer = parentBtree.getValueSerializer(); + valueThresholdUp = PersistedBTree.valueThresholdUp; + valueThresholdLow = PersistedBTree.valueThresholdLow; + + if ( values != null ) + { + int nbValues = values.length; + + if ( nbValues < PersistedBTree.valueThresholdUp ) + { + // Keep an array + valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues ); + + try + { + System.arraycopy( values, 0, valueArray, 0, values.length ); + } + catch ( ArrayStoreException ase ) + { + ase.printStackTrace(); + throw ase; + } + } + else + { + // Use a sub btree, now that we have reached the threshold + createSubTree(); + + try + { + build( ( PersistedBTree ) valueBtree, values ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + + manageSubTree(); + } + } + else + { + // No value, we create an empty array + valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), 0 ); + } + + isDeserialized = true; + } + + + /** + * @return a cursor on top of the values + */ + public ValueCursor getCursor() + { + // Check that the values are deserialized before doing anything + checkAndDeserialize(); + + return super.getCursor(); + } + + + /** + * @return the raw representation of the value holder. The serialized value will not be the same + * if the values are stored in an array or in a btree.
          + * If they are stored in a BTree, the raw value will contain the offset of the btree, otherwise + * it will contain a byte[] which will contain each serialized value, prefixed by their length. + * + */ + /* No qualifier*/byte[] getRaw() + { + if ( isRawUpToDate ) + { + // Just have to return the raw value + return raw; + } + + if ( isSubBtree() ) + { + // The values are stored into a subBtree, return the offset of this subBtree + long btreeOffset = ( ( PersistedBTree ) valueBtree ).getBtreeOffset(); + raw = LongSerializer.serialize( btreeOffset ); + } + else + { + // Create as many byte[] as we have length and serialized values to store + byte[][] valueBytes = new byte[valueArray.length * 2][]; + int length = 0; + int pos = 0; + + // Process each value now + for ( V value : valueArray ) + { + // Serialize the value + byte[] bytes = valueSerializer.serialize( value ); + length += bytes.length; + + // Serialize the value's length + byte[] sizeBytes = IntSerializer.serialize( bytes.length ); + length += sizeBytes.length; + + // And store the two byte[] + valueBytes[pos++] = sizeBytes; + valueBytes[pos++] = bytes; + } + + // Last, not least, create a buffer large enough to contain all the created byte[], + // and copy all those byte[] into this buffer + raw = new byte[length]; + pos = 0; + + for ( byte[] bytes : valueBytes ) + { + System.arraycopy( bytes, 0, raw, pos, bytes.length ); + pos += bytes.length; + } + } + + // Update the flags + isRawUpToDate = true; + + return raw; + } + + + /** + * {@inheritDoc} + */ + public int size() + { + checkAndDeserialize(); + + if ( valueArray == null ) + { + return ( int ) valueBtree.getNbElems(); + } + else + { + return valueArray.length; + } + } + + + /** + * Create a new Sub-BTree to store the values. + */ + protected void createSubTree() + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + configuration.setAllowDuplicates( false ); + configuration.setKeySerializer( valueSerializer ); + configuration.setName( UUID.randomUUID().toString() ); + configuration.setValueSerializer( valueSerializer ); + configuration.setParentBTree( parentBtree ); + configuration.setBtreeType( BTreeTypeEnum.PERSISTED_SUB ); + + valueBtree = BTreeFactory.createPersistedBTree( configuration ); + ( ( PersistedBTree ) valueBtree ).setRecordManager( parentBtree.getRecordManager() ); + } + + + /** + * Push the sub-BTree into the RecordManager + */ + protected void manageSubTree() + { + try + { + parentBtree.getRecordManager().manageSubBtree( valueBtree ); + raw = null; + } + catch ( BTreeAlreadyManagedException e ) + { + // should never happen + throw new BTreeAlreadyCreatedException( e ); + } + catch ( IOException e ) + { + throw new BTreeCreationException( e ); + } + } + + + /** + * Set the subBtree in the ValueHolder + */ + /* No qualifier*/void setSubBtree( BTree subBtree ) + { + valueBtree = subBtree; + raw = null; + valueArray = null; + isDeserialized = true; + isRawUpToDate = false; + } + + + /** + * Check that the values are stored as raw value + */ + private void checkAndDeserialize() + { + if ( !isDeserialized ) + { + if ( valueArray == null ) + { + // the values are stored into a sub-btree. Read it now if it's not already done + deserializeSubBtree(); + } + else + { + // The values are stored into an array. Deserialize it now + deserializeArray(); + } + + // Change the flag + isDeserialized = true; + } + } + + + /** + * {@inheritDoc} + */ + public void add( V value ) + { + // First check that we have a loaded BTree + checkAndDeserialize(); + + super.add( value ); + + // The raw value is not anymore up to date with the content + isRawUpToDate = false; + raw = null; + } + + + /** + * Remove a value from an array + */ + private V removeFromArray( V value ) + { + checkAndDeserialize(); + + // First check that the value is not already present in the ValueHolder + int pos = findPos( value ); + + if ( pos < 0 ) + { + // The value does not exists : nothing to do + return null; + } + + // Ok, we just have to delete the new element at the right position + // First, copy the array + V[] newValueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length - 1 ); + + System.arraycopy( valueArray, 0, newValueArray, 0, pos ); + System.arraycopy( valueArray, pos + 1, newValueArray, pos, valueArray.length - pos - 1 ); + + // Get the removed element + V removedValue = valueArray[pos]; + + // And switch the arrays + valueArray = newValueArray; + + return removedValue; + } + + + /** + * Remove the value from a sub btree + */ + private V removeFromBtree( V removedValue ) + { + // First check that we have a loaded BTree + checkAndDeserialize(); + + if ( btreeContains( removedValue ) ) + { + try + { + if ( valueBtree.getNbElems() - 1 < PersistedBTree.valueThresholdLow ) + { + int nbValues = ( int ) ( valueBtree.getNbElems() - 1 ); + + // We have to switch to an Array of values + valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues ); + + // Now copy all the value but the one we have removed + TupleCursor cursor = valueBtree.browse(); + V returnedValue = null; + int pos = 0; + + while ( cursor.hasNext() ) + { + Tuple tuple = cursor.next(); + + V value = tuple.getKey(); + + if ( valueSerializer.getComparator().compare( removedValue, value ) == 0 ) + { + // This is the removed value : skip it + returnedValue = value; + } + else + { + valueArray[pos++] = value; + } + } + + cursor.close(); + + return returnedValue; + } + else + { + Tuple removedTuple = valueBtree.delete( removedValue ); + + if ( removedTuple != null ) + { + return removedTuple.getKey(); + } + else + { + return null; + } + } + } + catch ( IOException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + catch ( KeyNotFoundException knfe ) + { + // TODO Auto-generated catch block + knfe.printStackTrace(); + return null; + } + } + else + { + return null; + } + } + + + /** + * {@inheritDoc} + */ + public V remove( V value ) + { + V removedValue = null; + + if ( valueArray != null ) + { + removedValue = removeFromArray( value ); + } + else + { + removedValue = removeFromBtree( value ); + } + + // The raw value is not anymore up to date wth the content + isRawUpToDate = false; + raw = null; + + return removedValue; + } + + + /** + * {@inheritDoc} + */ + public boolean contains( V checkedValue ) + { + // First, deserialize the value if it's still a byte[] + checkAndDeserialize(); + + return super.contains( checkedValue ); + } + + + /** + * Find the position of a given value in the array, or the position where we + * would insert the element (in this case, the position will be negative). + * As we use a 0-based array, the negative position for 0 is -1. + * -1 means the element can be added in position 0 + * -2 means the element can be added in position 1 + * ... + */ + private int findPos( V value ) + { + if ( valueArray.length == 0 ) + { + return -1; + } + + // Do a search using dichotomy + int pivot = valueArray.length / 2; + int low = 0; + int high = valueArray.length - 1; + Comparator comparator = valueSerializer.getComparator(); + + while ( high > low ) + { + switch ( high - low ) + { + case 1: + // We have 2 elements + int result = comparator.compare( value, valueArray[pivot] ); + + if ( result == 0 ) + { + return pivot; + } + + if ( result < 0 ) + { + if ( pivot == low ) + { + return -( low + 1 ); + } + else + { + result = comparator.compare( value, valueArray[low] ); + + if ( result == 0 ) + { + return low; + } + + if ( result < 0 ) + { + return -( low + 1 ); + } + else + { + return -( low + 2 ); + } + } + } + else + { + if ( pivot == high ) + { + return -( high + 2 ); + } + else + { + result = comparator.compare( value, valueArray[high] ); + + if ( result == 0 ) + { + return high; + } + + if ( result < 0 ) + { + return -( high + 1 ); + } + else + { + return -( high + 2 ); + } + } + } + + default: + // We have 3 elements + result = comparator.compare( value, valueArray[pivot] ); + + if ( result == 0 ) + { + return pivot; + } + + if ( result < 0 ) + { + high = pivot - 1; + } + else + { + low = pivot + 1; + } + + pivot = ( high + low ) / 2; + + continue; + } + } + + int result = comparator.compare( value, valueArray[pivot] ); + + if ( result == 0 ) + { + return pivot; + } + + if ( result < 0 ) + { + return -( pivot + 1 ); + } + else + { + return -( pivot + 2 ); + } + } + + + /** + * Create a clone of this instance + */ + public ValueHolder clone() throws CloneNotSupportedException + { + PersistedValueHolder copy = ( PersistedValueHolder ) super.clone(); + + // copy the valueArray if it's not null + // We don't clone the BTree, as we will create new revisions when + // modifying it + if ( valueArray != null ) + { + copy.valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length ); + System.arraycopy( valueArray, 0, copy.valueArray, 0, valueArray.length ); + } + + // Also clone the raw value if its up to date + if ( isRawUpToDate ) + { + copy.raw = new byte[raw.length]; + System.arraycopy( raw, 0, copy.raw, 0, raw.length ); + } + + return copy; + } + + + @Override + public V replaceValueArray( V newValue ) + { + V val = super.replaceValueArray( newValue ); + // The raw value is not anymore up to date with the content + isRawUpToDate = false; + + return val; + } + + + /** + * Deserialize the values stored in an array + */ + private void deserializeArray() + { + // We haven't yet deserialized the values. Let's do it now. The values are + // necessarily stored in an array at this point + int index = 0; + int pos = 0; + + while ( pos < raw.length ) + { + try + { + int size = IntSerializer.deserialize( raw, pos ); + pos += 4; + + V value = valueSerializer.fromBytes( raw, pos ); + pos += size; + valueArray[index++] = value; + } + catch ( IOException e ) + { + e.printStackTrace(); + } + } + } + + + /** + * Deserialize the values stored in a sub-btree + */ + private void deserializeSubBtree() + { + // Get the sub-btree offset + long offset = LongSerializer.deserialize( raw ); + + // and reload the sub btree + valueBtree = parentBtree.getRecordManager().loadDupsBtree( offset, parentBtree ); + } + + + /** + * @return The sub-btree offset + */ + /* No qualifier */long getOffset() + { + if ( valueArray == null ) + { + return ( ( PersistedBTree ) valueBtree ).getBtreeOffset(); + } + else + { + return -1L; + } + } + + + /** + * Constructs the sub-BTree using bulkload instead of performing sequential inserts. + * + * @param btree the sub-BTtree to be constructed + * @param dupKeyValues the array of values to be inserted as keys + * @return The created BTree + * @throws Exception + */ + private BTree build( PersistedBTree btree, final V[] dupKeyValues ) throws Exception + { + Iterator> valueIterator = new Iterator>() + { + int pos = 0; + + + @Override + public Tuple next() + { + // We can now return the found value + V value = dupKeyValues[pos]; + pos++; + + return new Tuple( value, value ); + } + + + @Override + public boolean hasNext() + { + // Check that we have at least one element to read + return pos < dupKeyValues.length; + } + + + @Override + public void remove() + { + } + + }; + + BulkLoader.load( btree, valueIterator, dupKeyValues.length ); + + return btree; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "ValueHolder[" ).append( valueSerializer.getClass().getSimpleName() ); + + if ( !isDeserialized ) + { + sb.append( ", isRaw[" ).append( raw.length ).append( "]" ); + } + else + { + if ( valueArray == null ) + { + sb.append( ", SubBTree" ); + } + else + { + sb.append( ", array{" ); + + boolean isFirst = true; + + for ( V value : valueArray ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( "/" ); + } + + sb.append( value ); + } + + sb.append( "}" ); + } + } + + sb.append( "]" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PoisonPill.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PoisonPill.java new file mode 100644 index 000000000..cd96416c6 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PoisonPill.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +/** + * This is special class which is injected into the journal queue to tell + * the journal thread that it should stop. + * + * @param The key type + * @param The value type + * + * @author Apache Directory Project + */ +/* No qualifier*/class PoisonPill extends Modification +{ + /** + * Create a new PoisonPill instance. + * + * @param key The key being added + * @param value The value being added + */ + /* no qualifier */PoisonPill() + { + super( null, null ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ReadTransaction.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ReadTransaction.java new file mode 100644 index 000000000..5128e2884 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ReadTransaction.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.Date; +import java.util.concurrent.ConcurrentLinkedQueue; + + +/** + * The Transaction is used to protect the BTree against concurrent modification, + * and insure that a read is always done against one single revision. It's also + * used to gather many modifications under one single revision, if needed. + *

          + * A Transaction should be closed when the user is done with it, otherwise the + * pages associated with the given revision, and all the referenced pages, will + * remain on the storage. + *

          + * A Transaction can be hold for quite a long time, for instance while doing + * a browse against a big BTree. At some point, transactions which are pending + * for too long will be closed by the transaction manager. + * + * @author Apache Directory Project + * + * @param The type for the Key + * @param The type for the stored value + */ +public class ReadTransaction +{ + /** The associated revision */ + private long revision; + + /** The date of creation */ + private long creationDate; + + /** The associated B-tree header */ + private BTreeHeader btreeHeader; + + /** A flag used to tell if a transaction is closed or not */ + private volatile boolean closed; + + /** The list of read transactions being executed */ + private ConcurrentLinkedQueue> readTransactions; + + /** The reference to the recordManager, if any */ + private RecordManager recordManager; + + /** + * Creates a new transaction instance + * + * @param btreeHeader The BtreeHeader we will use for this read transaction + */ + public ReadTransaction( RecordManager recordManager, BTreeHeader btreeHeader, ConcurrentLinkedQueue> readTransactions ) + { + if ( btreeHeader != null ) + { + this.revision = btreeHeader.getRevision(); + this.creationDate = System.currentTimeMillis(); + this.btreeHeader = btreeHeader; + this.recordManager = recordManager; + closed = false; + } + + this.readTransactions = readTransactions; + } + + + /** + * Creates a new transaction instance + * + * @param btreeHeader The BtreeHeader we will use for this read transaction + */ + public ReadTransaction( BTreeHeader btreeHeader, ConcurrentLinkedQueue> readTransactions ) + { + if ( btreeHeader != null ) + { + this.revision = btreeHeader.getRevision(); + this.creationDate = System.currentTimeMillis(); + this.btreeHeader = btreeHeader; + closed = false; + } + + this.readTransactions = readTransactions; + } + + + /** + * @return the associated revision + */ + public long getRevision() + { + return revision; + } + + + /** + * @return the creationDate + */ + public long getCreationDate() + { + return creationDate; + } + + + /** + * @return the btreeHeader + */ + public BTreeHeader getBtreeHeader() + { + return btreeHeader; + } + + + /** + * Close the transaction, releasing the revision it was using. + */ + public void close() + { + closed = true; + + // Remove the transaction from the list of opened transactions + readTransactions.remove( this ); + + // and push the + if ( recordManager != null ) + { + recordManager.releaseTransaction( this ); + } + + // Now, get back the copied pages + } + + + /** + * @return true if this transaction has been closed + */ + public boolean isClosed() + { + return closed; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + return "Transaction[" + revision + ":" + new Date( creationDate ) + ", closed :" + closed + "]"; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RecordManager.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RecordManager.java new file mode 100644 index 000000000..91a5841dc --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RecordManager.java @@ -0,0 +1,4158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.BTreeCreationException; +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.FileException; +import org.apache.directory.mavibot.btree.exception.InvalidOffsetException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.exception.RecordManagerException; +import org.apache.directory.mavibot.btree.serializer.ElementSerializer; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.LongArraySerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.util.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The RecordManager is used to manage the file in which we will store the B-trees. + * A RecordManager will manage more than one B-tree.
          + * + * It stores data in fixed size pages (default size is 512 bytes), which may be linked one to + * the other if the data we want to store is too big for a page. + * + * @author Apache Directory Project + */ +public class RecordManager extends AbstractTransactionManager +{ + /** The LoggerFactory used by this class */ + protected static final Logger LOG = LoggerFactory.getLogger( RecordManager.class ); + + /** The LoggerFactory used to trace TXN operations */ + protected static final Logger TXN_LOG = LoggerFactory.getLogger( "TXN_LOG" ); + + /** The LoggerFactory used by this class */ + protected static final Logger LOG_PAGES = LoggerFactory.getLogger( "org.apache.directory.mavibot.LOG_PAGES" ); + + /** A dedicated logger for the check */ + protected static final Logger LOG_CHECK = LoggerFactory.getLogger( "org.apache.directory.mavibot.LOG_CHECK" ); + + /** The associated file */ + private File file; + + /** The channel used to read and write data */ + /* no qualifier */FileChannel fileChannel; + + /** The number of managed B-trees */ + /* no qualifier */int nbBtree; + + /** The first and last free page */ + /* no qualifier */long firstFreePage; + + /** Some counters to track the number of free pages */ + public AtomicLong nbFreedPages = new AtomicLong( 0 ); + public AtomicLong nbCreatedPages = new AtomicLong( 0 ); + public AtomicLong nbReusedPages = new AtomicLong( 0 ); + public AtomicLong nbUpdateRMHeader = new AtomicLong( 0 ); + public AtomicLong nbUpdateBtreeHeader = new AtomicLong( 0 ); + public AtomicLong nbUpdatePageIOs = new AtomicLong( 0 ); + + /** The offset of the end of the file */ + private long endOfFileOffset; + + /** + * A B-tree used to manage the page that has been copied in a new version. + * Those pages can be reclaimed when the associated version is dead. + **/ + /* no qualifier */BTree copiedPageBtree; + + /** A constant for an offset on a non existing page */ + public static final long NO_PAGE = -1L; + + /** The number of bytes used to store the size of a page */ + private static final int PAGE_SIZE = 4; + + /** The size of the link to next page */ + private static final int LINK_SIZE = 8; + + /** Some constants */ + private static final int BYTE_SIZE = 1; + /* no qualifier */static final int INT_SIZE = 4; + /* no qualifier */static final int LONG_SIZE = 8; + + /** The default page size */ + public static final int DEFAULT_PAGE_SIZE = 512; + + /** The minimal page size. Can't be below 64, as we have to store many thing sin the RMHeader */ + private static final int MIN_PAGE_SIZE = 64; + + /** The RecordManager header size */ + /* no qualifier */static int RECORD_MANAGER_HEADER_SIZE = DEFAULT_PAGE_SIZE; + + /** A global buffer used to store the RecordManager header */ + private ByteBuffer RECORD_MANAGER_HEADER_BUFFER; + + /** A static buffer used to store the RecordManager header */ + private byte[] RECORD_MANAGER_HEADER_BYTES; + + /** The length of an Offset, as a negative value */ + //private byte[] LONG_LENGTH = new byte[] + // { ( byte ) 0xFF, ( byte ) 0xFF, ( byte ) 0xFF, ( byte ) 0xF8 }; + + /** The RecordManager underlying page size. */ + /* no qualifier */int pageSize = DEFAULT_PAGE_SIZE; + + /** The set of managed B-trees */ + private Map> managedBtrees; + + /** The queue of recently closed transactions */ + private Queue closedTransactionsQueue = new LinkedBlockingQueue(); + + /** The default file name */ + private static final String DEFAULT_FILE_NAME = "mavibot.db"; + + /** A flag set to true if we want to keep old revisions */ + private boolean keepRevisions; + + /** A flag used by internal btrees */ + public static final boolean INTERNAL_BTREE = true; + + /** A flag used by internal btrees */ + public static final boolean NORMAL_BTREE = false; + + /** The B-tree of B-trees */ + /* no qualifier */BTree btreeOfBtrees; + + /** The B-tree of B-trees management btree name */ + /* no qualifier */static final String BTREE_OF_BTREES_NAME = "_btree_of_btrees_"; + + /** The CopiedPages management btree name */ + /* no qualifier */static final String COPIED_PAGE_BTREE_NAME = "_copiedPageBtree_"; + + /** The current B-tree of B-trees header offset */ + /* no qualifier */long currentBtreeOfBtreesOffset; + + /** The previous B-tree of B-trees header offset */ + private long previousBtreeOfBtreesOffset = NO_PAGE; + + /** The offset on the current copied pages B-tree */ + /* no qualifier */long currentCopiedPagesBtreeOffset = NO_PAGE; + + /** The offset on the previous copied pages B-tree */ + private long previousCopiedPagesBtreeOffset = NO_PAGE; + + /** A lock to protect the transaction handling */ + private ReentrantLock transactionLock = new ReentrantLock(); + + /** A ThreadLocalStorage used to store the current transaction */ + private static final ThreadLocal CONTEXT = new ThreadLocal(); + + /** The list of PageIO that can be freed after a commit */ + List freedPages = new ArrayList(); + + /** The list of PageIO that can be freed after a roolback */ + private List allocatedPages = new ArrayList(); + + /** A Map keeping the latest revisions for each managed BTree */ + private Map> currentBTreeHeaders = new HashMap>(); + + /** A Map storing the new revisions when some change have been made in some BTrees */ + private Map> newBTreeHeaders = new HashMap>(); + + /** A lock to protect the BtreeHeader maps */ + private ReadWriteLock btreeHeadersLock = new ReentrantReadWriteLock(); + + /** A value stored into the transaction context for rollbacked transactions */ + private static final int ROLLBACKED_TXN = 0; + + /** A lock to protect the freepage pointers */ + private ReentrantLock freePageLock = new ReentrantLock(); + + /** the space reclaimer */ + private PageReclaimer reclaimer; + + /** variable to keep track of the write commit count */ + private int commitCount = 0; + + /** the threshold at which the PageReclaimer will be run to free the copied pages */ + // FIXME the below value is derived after seeing that anything higher than that + // is resulting in a "This thread does not hold the transactionLock" error + private int pageReclaimerThreshold = 70; + + /* a flag used to disable the free page reclaimer (used for internal testing only) */ + private boolean disableReclaimer = false; + + public Map writeCounter = new HashMap(); + + + /** + * Create a Record manager which will either create the underlying file + * or load an existing one. If a folder is provided, then we will create + * a file with a default name : mavibot.db + * + * @param name The file name, or a folder name + */ + public RecordManager( String fileName ) + { + this( fileName, DEFAULT_PAGE_SIZE ); + } + + + /** + * Create a Record manager which will either create the underlying file + * or load an existing one. If a folder is provider, then we will create + * a file with a default name : mavibot.db + * + * @param name The file name, or a folder name + * @param pageSize the size of a page on disk, in bytes + */ + public RecordManager( String fileName, int pageSize ) + { + managedBtrees = new LinkedHashMap>(); + + if ( pageSize < MIN_PAGE_SIZE ) + { + this.pageSize = MIN_PAGE_SIZE; + } + else + { + this.pageSize = pageSize; + } + + RECORD_MANAGER_HEADER_BUFFER = ByteBuffer.allocate( this.pageSize ); + RECORD_MANAGER_HEADER_BYTES = new byte[this.pageSize]; + RECORD_MANAGER_HEADER_SIZE = this.pageSize; + + // Open the file or create it + File tmpFile = new File( fileName ); + + if ( tmpFile.isDirectory() ) + { + // It's a directory. Check that we don't have an existing mavibot file + tmpFile = new File( tmpFile, DEFAULT_FILE_NAME ); + } + + // We have to create a new file, if it does not already exist + boolean isNewFile = createFile( tmpFile ); + + try + { + RandomAccessFile randomFile = new RandomAccessFile( file, "rw" ); + fileChannel = randomFile.getChannel(); + + // get the current end of file offset + endOfFileOffset = fileChannel.size(); + + if ( isNewFile ) + { + initRecordManager(); + } + else + { + loadRecordManager(); + } + + reclaimer = new PageReclaimer( this ); + runReclaimer(); + } + catch ( Exception e ) + { + LOG.error( "Error while initializing the RecordManager : {}", e.getMessage() ); + LOG.error( "", e ); + throw new RecordManagerException( e ); + } + } + + + /** + * runs the PageReclaimer to free the copied pages + */ + private void runReclaimer() + { + if ( disableReclaimer ) + { + LOG.warn( "Free page reclaimer is disabled, this should not be disabled on production systems." ); + return; + } + + try + { + commitCount = 0; + reclaimer.reclaim(); + // must update the headers after reclaim operation + updateRecordManagerHeader(); + } + catch ( Exception e ) + { + LOG.warn( "PageReclaimer failed to free the pages", e ); + } + } + + + /** + * Create the mavibot file if it does not exist + */ + private boolean createFile( File mavibotFile ) + { + try + { + boolean creation = mavibotFile.createNewFile(); + + file = mavibotFile; + + if ( mavibotFile.length() == 0 ) + { + return true; + } + else + { + return creation; + } + } + catch ( IOException ioe ) + { + LOG.error( "Cannot create the file {}", mavibotFile.getName() ); + return false; + } + } + + + /** + * We will create a brand new RecordManager file, containing nothing, but the RecordManager header, + * a B-tree to manage the old revisions we want to keep and + * a B-tree used to manage pages associated with old versions. + *
          + * The RecordManager header contains the following details : + *

          +     * +--------------------------+
          +     * | PageSize                 | 4 bytes : The size of a physical page (default to 4096)
          +     * +--------------------------+
          +     * |  NbTree                  | 4 bytes : The number of managed B-trees (zero or more)
          +     * +--------------------------+
          +     * | FirstFree                | 8 bytes : The offset of the first free page
          +     * +--------------------------+
          +     * | current BoB offset       | 8 bytes : The offset of the current BoB
          +     * +--------------------------+
          +     * | previous BoB offset      | 8 bytes : The offset of the previous BoB
          +     * +--------------------------+
          +     * | current CP btree offset  | 8 bytes : The offset of the current BoB
          +     * +--------------------------+
          +     * | previous CP btree offset | 8 bytes : The offset of the previous BoB
          +     * +--------------------------+
          +     * 
          + * + * We then store the B-tree managing the pages that have been copied when we have added + * or deleted an element in the B-tree. They are associated with a version. + * + * Last, we add the bTree that keep a track on each revision we can have access to. + */ + private void initRecordManager() throws IOException + { + // Create a new Header + nbBtree = 0; + firstFreePage = NO_PAGE; + currentBtreeOfBtreesOffset = NO_PAGE; + + updateRecordManagerHeader(); + + // Set the offset of the end of the file + endOfFileOffset = fileChannel.size(); + + // First, create the btree of btrees + createBtreeOfBtrees(); + + // Now, initialize the Copied Page B-tree + createCopiedPagesBtree(); + + // Inject these B-trees into the RecordManager. They are internal B-trees. + try + { + manageSubBtree( btreeOfBtrees ); + + currentBtreeOfBtreesOffset = ( ( PersistedBTree ) btreeOfBtrees ).getBtreeHeader() + .getBTreeHeaderOffset(); + updateRecordManagerHeader(); + + // Inject the BtreeOfBtrees into the currentBtreeHeaders map + currentBTreeHeaders.put( BTREE_OF_BTREES_NAME, + ( ( PersistedBTree ) btreeOfBtrees ).getBtreeHeader() ); + newBTreeHeaders.put( BTREE_OF_BTREES_NAME, + ( ( PersistedBTree ) btreeOfBtrees ).getBtreeHeader() ); + + // The FreePage B-tree + manageSubBtree( copiedPageBtree ); + + currentCopiedPagesBtreeOffset = ( ( PersistedBTree ) copiedPageBtree ) + .getBtreeHeader().getBTreeHeaderOffset(); + updateRecordManagerHeader(); + + // Inject the CopiedPagesBTree into the currentBtreeHeaders map + currentBTreeHeaders.put( COPIED_PAGE_BTREE_NAME, + ( ( PersistedBTree ) copiedPageBtree ).getBtreeHeader() ); + newBTreeHeaders.put( COPIED_PAGE_BTREE_NAME, + ( ( PersistedBTree ) copiedPageBtree ).getBtreeHeader() ); + } + catch ( BTreeAlreadyManagedException btame ) + { + // Can't happen here. + } + + // We are all set ! Verify the file + if ( LOG_CHECK.isDebugEnabled() ) + { + MavibotInspector.check( this ); + } + + } + + + /** + * Create the B-treeOfBtrees + */ + private void createBtreeOfBtrees() + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + configuration.setKeySerializer( NameRevisionSerializer.INSTANCE ); + configuration.setName( BTREE_OF_BTREES_NAME ); + configuration.setValueSerializer( LongSerializer.INSTANCE ); + configuration.setBtreeType( BTreeTypeEnum.BTREE_OF_BTREES ); + configuration.setCacheSize( PersistedBTree.DEFAULT_CACHE_SIZE ); + + btreeOfBtrees = BTreeFactory.createPersistedBTree( configuration ); + } + + + /** + * Create the CopiedPagesBtree + */ + private void createCopiedPagesBtree() + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + configuration.setKeySerializer( RevisionNameSerializer.INSTANCE ); + configuration.setName( COPIED_PAGE_BTREE_NAME ); + configuration.setValueSerializer( LongArraySerializer.INSTANCE ); + configuration.setBtreeType( BTreeTypeEnum.COPIED_PAGES_BTREE ); + configuration.setCacheSize( PersistedBTree.DEFAULT_CACHE_SIZE ); + + copiedPageBtree = BTreeFactory.createPersistedBTree( configuration ); + } + + + /** + * Load the BTrees from the disk. + * + * @throws InstantiationException + * @throws IllegalAccessException + * @throws ClassNotFoundException + * @throws NoSuchFieldException + * @throws SecurityException + * @throws IllegalArgumentException + */ + private void loadRecordManager() throws IOException, ClassNotFoundException, IllegalAccessException, + InstantiationException, IllegalArgumentException, SecurityException, NoSuchFieldException, KeyNotFoundException + { + if ( fileChannel.size() != 0 ) + { + ByteBuffer recordManagerHeader = ByteBuffer.allocate( RECORD_MANAGER_HEADER_SIZE ); + + // The file exists, we have to load the data now + fileChannel.read( recordManagerHeader ); + + recordManagerHeader.rewind(); + + // read the RecordManager Header : + // +---------------------+ + // | PageSize | 4 bytes : The size of a physical page (default to 4096) + // +---------------------+ + // | NbTree | 4 bytes : The number of managed B-trees (at least 1) + // +---------------------+ + // | FirstFree | 8 bytes : The offset of the first free page + // +---------------------+ + // | current BoB offset | 8 bytes : The offset of the current B-tree of B-trees + // +---------------------+ + // | previous BoB offset | 8 bytes : The offset of the previous B-tree of B-trees + // +---------------------+ + // | current CP offset | 8 bytes : The offset of the current Copied Pages B-tree + // +---------------------+ + // | previous CP offset | 8 bytes : The offset of the previous Copied Pages B-tree + // +---------------------+ + + // The page size + pageSize = recordManagerHeader.getInt(); + + // The number of managed B-trees + nbBtree = recordManagerHeader.getInt(); + + // The first and last free page + firstFreePage = recordManagerHeader.getLong(); + + // Read all the free pages + checkFreePages(); + + // The current BOB offset + currentBtreeOfBtreesOffset = recordManagerHeader.getLong(); + + // The previous BOB offset + previousBtreeOfBtreesOffset = recordManagerHeader.getLong(); + + // The current Copied Pages B-tree offset + currentCopiedPagesBtreeOffset = recordManagerHeader.getLong(); + + // The previous Copied Pages B-tree offset + previousCopiedPagesBtreeOffset = recordManagerHeader.getLong(); + + // read the B-tree of B-trees + PageIO[] bobHeaderPageIos = readPageIOs( currentBtreeOfBtreesOffset, Long.MAX_VALUE ); + + btreeOfBtrees = BTreeFactory. createPersistedBTree( BTreeTypeEnum.BTREE_OF_BTREES ); + + loadBtree( bobHeaderPageIos, btreeOfBtrees ); + + // read the copied page B-tree + PageIO[] copiedPagesPageIos = readPageIOs( currentCopiedPagesBtreeOffset, Long.MAX_VALUE ); + + copiedPageBtree = BTreeFactory + . createPersistedBTree( BTreeTypeEnum.COPIED_PAGES_BTREE ); + + loadBtree( copiedPagesPageIos, copiedPageBtree ); + + // Now, read all the B-trees from the btree of btrees + TupleCursor btreeCursor = btreeOfBtrees.browse(); + Map loadedBtrees = new HashMap(); + + // loop on all the btrees we have, and keep only the latest revision + long currentRevision = -1L; + + while ( btreeCursor.hasNext() ) + { + Tuple btreeTuple = btreeCursor.next(); + NameRevision nameRevision = btreeTuple.getKey(); + long btreeOffset = btreeTuple.getValue(); + long revision = nameRevision.getValue(); + + // Check if we already have processed this B-tree + Long loadedBtreeRevision = loadedBtrees.get( nameRevision.getName() ); + + if ( loadedBtreeRevision != null ) + { + // The btree has already been loaded. The revision is necessarily higher + if ( revision > currentRevision ) + { + // We have a newer revision : switch to the new revision (we keep the offset atm) + loadedBtrees.put( nameRevision.getName(), btreeOffset ); + currentRevision = revision; + } + } + else + { + // This is a new B-tree + loadedBtrees.put( nameRevision.getName(), btreeOffset ); + currentRevision = nameRevision.getRevision(); + } + } + + // TODO : clean up the old revisions... + + // Now, we can load the real btrees using the offsets + for ( String btreeName : loadedBtrees.keySet() ) + { + long btreeOffset = loadedBtrees.get( btreeName ); + + PageIO[] btreePageIos = readPageIOs( btreeOffset, Long.MAX_VALUE ); + + BTree btree = BTreeFactory. createPersistedBTree(); + //( ( PersistedBTree ) btree ).setBtreeHeaderOffset( btreeOffset ); + loadBtree( btreePageIos, btree ); + + // Add the btree into the map of managed B-trees + managedBtrees.put( btreeName, ( BTree ) btree ); + } + + // We are done ! Let's finish with the last initialization parts + endOfFileOffset = fileChannel.size(); + } + } + + + /** + * Starts a transaction + */ + public void beginTransaction() + { + if ( TXN_LOG.isDebugEnabled() ) + { + TXN_LOG.debug( "Begining a new transaction on thread {}, TxnLevel {}", + Thread.currentThread().getName(), getTxnLevel() ); + } + + // First, take the lock if it's not already taken + if ( !( ( ReentrantLock ) transactionLock ).isHeldByCurrentThread() ) + { + TXN_LOG.debug( "--> Lock taken" ); + transactionLock.lock(); + } + else + { + TXN_LOG.debug( "..o The current thread already holds the lock" ); + } + + // Now, check the TLS state + incrementTxnLevel(); + } + + + /** + * Commits a transaction + */ + public void commit() + { + // We *must* own the transactionLock + if ( !transactionLock.isHeldByCurrentThread() ) + { + String name = Thread.currentThread().getName(); + String err = "This thread, '" + name + "' does not hold the transactionLock "; + TXN_LOG.error( err ); + throw new RecordManagerException( err ); + } + + if ( TXN_LOG.isDebugEnabled() ) + { + TXN_LOG.debug( "Committing a transaction on thread {}, TxnLevel {}", + Thread.currentThread().getName(), getTxnLevel() ); + } + + if ( !fileChannel.isOpen() ) + { + // Still we have to decrement the TransactionLevel + int txnLevel = decrementTxnLevel(); + + if ( txnLevel == 0 ) + { + // We can safely release the lock + // The file has been closed, nothing remains to commit, let's get out + transactionLock.unlock(); + } + + return; + } + + int nbTxnStarted = CONTEXT.get(); + + switch ( nbTxnStarted ) + { + case ROLLBACKED_TXN: + // The transaction was rollbacked, quit immediatelly + transactionLock.unlock(); + + return; + + case 1: + // We are done with the transaction, we can update the RMHeader and swap the BTreeHeaders + // First update the RMHeader to be sure that we have a way to restore from a crash + updateRecordManagerHeader(); + + // Swap the BtreeHeaders maps + swapCurrentBtreeHeaders(); + + // We can now free pages + for ( PageIO pageIo : freedPages ) + { + try + { + free( pageIo ); + } + catch ( IOException ioe ) + { + throw new RecordManagerException( ioe.getMessage() ); + } + } + + // Release the allocated and freed pages list + freedPages.clear(); + allocatedPages.clear(); + + // And update the RMHeader again, removing the old references to BOB and CPB b-tree headers + // here, we have to erase the old references to keep only the new ones. + updateRecordManagerHeader(); + + commitCount++; + + if ( commitCount >= pageReclaimerThreshold ) + { + runReclaimer(); + } + + // Finally, decrement the number of started transactions + // and release the global lock if possible + int txnLevel = decrementTxnLevel(); + + if ( txnLevel == 0 ) + { + transactionLock.unlock(); + } + + return; + + default: + // We are inner an existing transaction. Just update the necessary elements + // Update the RMHeader to be sure that we have a way to restore from a crash + updateRecordManagerHeader(); + + // Swap the BtreeHeaders maps + //swapCurrentBtreeHeaders(); + + // We can now free pages + for ( PageIO pageIo : freedPages ) + { + try + { + free( pageIo ); + } + catch ( IOException ioe ) + { + throw new RecordManagerException( ioe.getMessage() ); + } + } + + // Release the allocated and freed pages list + freedPages.clear(); + allocatedPages.clear(); + + // And update the RMHeader again, removing the old references to BOB and CPB b-tree headers + // here, we have to erase the old references to keep only the new ones. + updateRecordManagerHeader(); + + commitCount++; + + if ( commitCount >= pageReclaimerThreshold ) + { + runReclaimer(); + } + + // Finally, decrement the number of started transactions + // and release the global lock + txnLevel = decrementTxnLevel(); + + if ( txnLevel == 0 ) + { + transactionLock.unlock(); + } + + return; + } + } + + + public boolean isContextOk() + { + return ( CONTEXT == null ? true : ( CONTEXT.get() == 0 ) ); + } + + + /** + * Get the transactionLevel, ie the number of encapsulated update ops + */ + private int getTxnLevel() + { + Integer nbTxnLevel = CONTEXT.get(); + + if ( nbTxnLevel == null ) + { + return -1; + } + + return nbTxnLevel; + } + + + /** + * Increment the transactionLevel + */ + private void incrementTxnLevel() + { + Integer nbTxnLevel = CONTEXT.get(); + + if ( nbTxnLevel == null ) + { + CONTEXT.set( 1 ); + } + else + { + // And increment the counter of inner txn. + CONTEXT.set( nbTxnLevel + 1 ); + } + + if ( TXN_LOG.isDebugEnabled() ) + { + TXN_LOG.debug( "Incrementing the TxnLevel : {}", CONTEXT.get() ); + } + } + + + /** + * Decrement the transactionLevel + */ + private int decrementTxnLevel() + { + int nbTxnStarted = CONTEXT.get() - 1; + + CONTEXT.set( nbTxnStarted ); + + if ( TXN_LOG.isDebugEnabled() ) + { + TXN_LOG.debug( "Decrementing the TxnLevel : {}", CONTEXT.get() ); + } + + return nbTxnStarted; + } + + + /** + * Rollback a transaction + */ + public void rollback() + { + // We *must* own the transactionLock + if ( !transactionLock.isHeldByCurrentThread() ) + { + TXN_LOG.error( "This thread does not hold the transactionLock" ); + throw new RecordManagerException( "This thread does not hold the transactionLock" ); + } + + if ( TXN_LOG.isDebugEnabled() ) + { + TXN_LOG.debug( "Rollbacking a new transaction on thread {}, TxnLevel {}", + Thread.currentThread().getName(), getTxnLevel() ); + } + + // Reset the counter + CONTEXT.set( ROLLBACKED_TXN ); + + // We can now free allocated pages, this is the end of the transaction + for ( PageIO pageIo : allocatedPages ) + { + try + { + free( pageIo ); + } + catch ( IOException ioe ) + { + throw new RecordManagerException( ioe.getMessage() ); + } + } + + // Release the allocated and freed pages list + freedPages.clear(); + allocatedPages.clear(); + + // And update the RMHeader + updateRecordManagerHeader(); + + // And restore the BTreeHeaders new Map to the current state + revertBtreeHeaders(); + + // This is an all-of-nothing operation : we can't have a transaction within + // a transaction that would survive an inner transaction rollback. + transactionLock.unlock(); + } + + + /** + * Reads all the PageIOs that are linked to the page at the given position, including + * the first page. + * + * @param position The position of the first page + * @param limit The maximum bytes to read. Set this value to -1 when the size is unknown. + * @return An array of pages + */ + /*no qualifier*/PageIO[] readPageIOs( long position, long limit ) throws IOException, EndOfFileExceededException + { + LOG.debug( "Read PageIOs at position {}", position ); + + if ( limit <= 0 ) + { + limit = Long.MAX_VALUE; + } + + PageIO firstPage = fetchPage( position ); + firstPage.setSize(); + List listPages = new ArrayList(); + listPages.add( firstPage ); + long dataRead = pageSize - LONG_SIZE - INT_SIZE; + + // Iterate on the pages, if needed + long nextPage = firstPage.getNextPage(); + + if ( ( dataRead < limit ) && ( nextPage != NO_PAGE ) ) + { + while ( dataRead < limit ) + { + PageIO page = fetchPage( nextPage ); + listPages.add( page ); + nextPage = page.getNextPage(); + dataRead += pageSize - LONG_SIZE; + + if ( nextPage == NO_PAGE ) + { + page.setNextPage( NO_PAGE ); + break; + } + } + } + + LOG.debug( "Nb of PageIOs read : {}", listPages.size() ); + + // Return + return listPages.toArray( new PageIO[] + {} ); + } + + + /** + * Check the offset to be sure it's a valid one : + *
            + *
          • It's >= 0
          • + *
          • It's below the end of the file
          • + *
          • It's a multipl of the pageSize + *
          + * @param offset The offset to check + * @throws InvalidOffsetException If the offset is not valid + */ + /* no qualifier */void checkOffset( long offset ) + { + if ( ( offset < 0 ) || ( offset > endOfFileOffset ) || ( ( offset % pageSize ) != 0 ) ) + { + throw new InvalidOffsetException( "Bad Offset : " + offset ); + } + } + + + /** + * Read a B-tree from the disk. The meta-data are at the given position in the list of pages. + * We load a B-tree in two steps : first, we load the B-tree header, then the common informations + * + * @param pageIos The list of pages containing the meta-data + * @param btree The B-tree we have to initialize + * @throws InstantiationException + * @throws IllegalAccessException + * @throws ClassNotFoundException + * @throws NoSuchFieldException + * @throws SecurityException + * @throws IllegalArgumentException + */ + private void loadBtree( PageIO[] pageIos, BTree btree ) throws EndOfFileExceededException, + IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, IllegalArgumentException, + SecurityException, NoSuchFieldException + { + loadBtree( pageIos, btree, null ); + } + + + /** + * Read a B-tree from the disk. The meta-data are at the given position in the list of pages. + * We load a B-tree in two steps : first, we load the B-tree header, then the common informations + * + * @param pageIos The list of pages containing the meta-data + * @param btree The B-tree we have to initialize + * @throws InstantiationException + * @throws IllegalAccessException + * @throws ClassNotFoundException + * @throws NoSuchFieldException + * @throws SecurityException + * @throws IllegalArgumentException + */ + /* no qualifier */ void loadBtree( PageIO[] pageIos, BTree btree, BTree parentBTree ) + throws EndOfFileExceededException, + IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, IllegalArgumentException, + SecurityException, NoSuchFieldException + { + long dataPos = 0L; + + // Process the B-tree header + BTreeHeader btreeHeader = new BTreeHeader(); + btreeHeader.setBtree( btree ); + + // The BtreeHeader offset + btreeHeader.setBTreeHeaderOffset( pageIos[0].getOffset() ); + + // The B-tree current revision + long revision = readLong( pageIos, dataPos ); + btreeHeader.setRevision( revision ); + dataPos += LONG_SIZE; + + // The nb elems in the tree + long nbElems = readLong( pageIos, dataPos ); + btreeHeader.setNbElems( nbElems ); + dataPos += LONG_SIZE; + + // The B-tree rootPage offset + long rootPageOffset = readLong( pageIos, dataPos ); + btreeHeader.setRootPageOffset( rootPageOffset ); + dataPos += LONG_SIZE; + + // The B-tree information offset + long btreeInfoOffset = readLong( pageIos, dataPos ); + + // Now, process the common informations + PageIO[] infoPageIos = readPageIOs( btreeInfoOffset, Long.MAX_VALUE ); + ( ( PersistedBTree ) btree ).setBtreeInfoOffset( infoPageIos[0].getOffset() ); + dataPos = 0L; + + // The B-tree page size + int btreePageSize = readInt( infoPageIos, dataPos ); + BTreeFactory.setPageSize( btree, btreePageSize ); + dataPos += INT_SIZE; + + // The tree name + ByteBuffer btreeNameBytes = readBytes( infoPageIos, dataPos ); + dataPos += INT_SIZE + btreeNameBytes.limit(); + String btreeName = Strings.utf8ToString( btreeNameBytes ); + BTreeFactory.setName( btree, btreeName ); + + // The keySerializer FQCN + ByteBuffer keySerializerBytes = readBytes( infoPageIos, dataPos ); + dataPos += INT_SIZE + keySerializerBytes.limit(); + + String keySerializerFqcn = ""; + + if ( keySerializerBytes != null ) + { + keySerializerFqcn = Strings.utf8ToString( keySerializerBytes ); + } + + BTreeFactory.setKeySerializer( btree, keySerializerFqcn ); + + // The valueSerialier FQCN + ByteBuffer valueSerializerBytes = readBytes( infoPageIos, dataPos ); + + String valueSerializerFqcn = ""; + dataPos += INT_SIZE + valueSerializerBytes.limit(); + + if ( valueSerializerBytes != null ) + { + valueSerializerFqcn = Strings.utf8ToString( valueSerializerBytes ); + } + + BTreeFactory.setValueSerializer( btree, valueSerializerFqcn ); + + // The B-tree allowDuplicates flag + int allowDuplicates = readInt( infoPageIos, dataPos ); + ( ( PersistedBTree ) btree ).setAllowDuplicates( allowDuplicates != 0 ); + dataPos += INT_SIZE; + + // Set the recordManager in the btree + ( ( PersistedBTree ) btree ).setRecordManager( this ); + + // Set the current revision to the one stored in the B-tree header + // Here, we have to tell the BTree to keep this revision in the + // btreeRevisions Map, thus the 'true' parameter at the end. + ( ( PersistedBTree ) btree ).storeRevision( btreeHeader, true ); + + // Now, init the B-tree + ( ( PersistedBTree ) btree ).init( parentBTree ); + + // Update the BtreeHeaders Maps + currentBTreeHeaders.put( btree.getName(), ( ( PersistedBTree ) btree ).getBtreeHeader() ); + newBTreeHeaders.put( btree.getName(), ( ( PersistedBTree ) btree ).getBtreeHeader() ); + + // Read the rootPage pages on disk + PageIO[] rootPageIos = readPageIOs( rootPageOffset, Long.MAX_VALUE ); + + Page btreeRoot = readPage( btree, rootPageIos ); + BTreeFactory.setRecordManager( btree, this ); + + BTreeFactory.setRootPage( btree, btreeRoot ); + } + + + /** + * Deserialize a Page from a B-tree at a give position + * + * @param btree The B-tree we want to read a Page from + * @param offset The position in the file for this page + * @return The read page + * @throws EndOfFileExceededException If we have reached the end of the file while reading the page + */ + public Page deserialize( BTree btree, long offset ) throws EndOfFileExceededException, + IOException + { + checkOffset( offset ); + PageIO[] rootPageIos = readPageIOs( offset, Long.MAX_VALUE ); + + Page page = readPage( btree, rootPageIos ); + + return page; + } + + + /** + * Read a page from some PageIO for a given B-tree + * @param btree The B-tree we want to read a page for + * @param pageIos The PageIO containing the raw data + * @return The read Page if successful + * @throws IOException If the deserialization failed + */ + private Page readPage( BTree btree, PageIO[] pageIos ) throws IOException + { + // Deserialize the rootPage now + long position = 0L; + + // The revision + long revision = readLong( pageIos, position ); + position += LONG_SIZE; + + // The number of elements in the page + int nbElems = readInt( pageIos, position ); + position += INT_SIZE; + + // The size of the data containing the keys and values + Page page = null; + + // Reads the bytes containing all the keys and values, if we have some + // We read big blog of data into ByteBuffer, then we will process + // this ByteBuffer + ByteBuffer byteBuffer = readBytes( pageIos, position ); + + // Now, deserialize the data block. If the number of elements + // is positive, it's a Leaf, otherwise it's a Node + // Note that only a leaf can have 0 elements, and it's the root page then. + if ( nbElems >= 0 ) + { + // It's a leaf + page = readLeafKeysAndValues( btree, nbElems, revision, byteBuffer, pageIos ); + } + else + { + // It's a node + page = readNodeKeysAndValues( btree, -nbElems, revision, byteBuffer, pageIos ); + } + + ( ( AbstractPage ) page ).setOffset( pageIos[0].getOffset() ); + + if ( pageIos.length > 1 ) + { + ( ( AbstractPage ) page ).setLastOffset( pageIos[pageIos.length - 1].getOffset() ); + } + + return page; + } + + + /** + * Deserialize a Leaf from some PageIOs + */ + private PersistedLeaf readLeafKeysAndValues( BTree btree, int nbElems, long revision, + ByteBuffer byteBuffer, PageIO[] pageIos ) + { + // Its a leaf, create it + PersistedLeaf leaf = ( PersistedLeaf ) BTreeFactory.createLeaf( btree, revision, nbElems ); + + // Store the page offset on disk + leaf.setOffset( pageIos[0].getOffset() ); + leaf.setLastOffset( pageIos[pageIos.length - 1].getOffset() ); + + int[] keyLengths = new int[nbElems]; + int[] valueLengths = new int[nbElems]; + + boolean isNotSubTree = ( btree.getType() != BTreeTypeEnum.PERSISTED_SUB ); + + // Read each key and value + for ( int i = 0; i < nbElems; i++ ) + { + if ( isNotSubTree ) + { + // Read the number of values + int nbValues = byteBuffer.getInt(); + PersistedValueHolder valueHolder = null; + + if ( nbValues < 0 ) + { + // This is a sub-btree + byte[] btreeOffsetBytes = new byte[LONG_SIZE]; + byteBuffer.get( btreeOffsetBytes ); + + // Create the valueHolder. As the number of values is negative, we have to switch + // to a positive value but as we start at -1 for 0 value, add 1. + valueHolder = new PersistedValueHolder( btree, 1 - nbValues, btreeOffsetBytes ); + } + else + { + // This is an array + // Read the value's array length + valueLengths[i] = byteBuffer.getInt(); + + // This is an Array of values, read the byte[] associated with it + byte[] arrayBytes = new byte[valueLengths[i]]; + byteBuffer.get( arrayBytes ); + valueHolder = new PersistedValueHolder( btree, nbValues, arrayBytes ); + } + + BTreeFactory.setValue( btree, leaf, i, valueHolder ); + } + + keyLengths[i] = byteBuffer.getInt(); + byte[] data = new byte[keyLengths[i]]; + byteBuffer.get( data ); + BTreeFactory.setKey( btree, leaf, i, data ); + } + + return leaf; + } + + + /** + * Deserialize a Node from some PageIos + */ + private PersistedNode readNodeKeysAndValues( BTree btree, int nbElems, long revision, + ByteBuffer byteBuffer, PageIO[] pageIos ) throws IOException + { + PersistedNode node = ( PersistedNode ) BTreeFactory.createNode( btree, revision, nbElems ); + + // Read each value and key + for ( int i = 0; i < nbElems; i++ ) + { + // This is an Offset + long offset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + PersistedPageHolder valueHolder = new PersistedPageHolder( btree, null, offset, lastOffset ); + node.setValue( i, valueHolder ); + + // Read the key length + int keyLength = byteBuffer.getInt(); + + int currentPosition = byteBuffer.position(); + + // and the key value + K key = btree.getKeySerializer().deserialize( byteBuffer ); + + // Set the new position now + byteBuffer.position( currentPosition + keyLength ); + + BTreeFactory.setKey( btree, node, i, key ); + } + + // and read the last value, as it's a node + long offset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer ); + + PersistedPageHolder valueHolder = new PersistedPageHolder( btree, null, offset, lastOffset ); + node.setValue( nbElems, valueHolder ); + + return node; + } + + + /** + * Read a byte[] from pages. + * + * @param pageIos The pages we want to read the byte[] from + * @param position The position in the data stored in those pages + * @return The byte[] we have read + */ + /* no qualifier */ByteBuffer readBytes( PageIO[] pageIos, long position ) + { + // Read the byte[] length first + int length = readInt( pageIos, position ); + position += INT_SIZE; + + // Compute the page in which we will store the data given the + // current position + int pageNb = computePageNb( position ); + + // Compute the position in the current page + int pagePos = ( int ) ( position + ( pageNb + 1 ) * LONG_SIZE + INT_SIZE ) - pageNb * pageSize; + + // Check that the length is correct : it should fit in the provided pageIos + int pageEnd = computePageNb( position + length ); + + if ( pageEnd > pageIos.length ) + { + // This is wrong... + LOG.error( "Wrong size : {}, it's larger than the number of provided pages {}", length, pageIos.length ); + throw new ArrayIndexOutOfBoundsException(); + } + + ByteBuffer pageData = pageIos[pageNb].getData(); + int remaining = pageData.capacity() - pagePos; + + if ( length == 0 ) + { + // No bytes to read : return null; + return null; + } + else + { + ByteBuffer bytes = ByteBuffer.allocate( length ); + + while ( length > 0 ) + { + if ( length <= remaining ) + { + pageData.mark(); + pageData.position( pagePos ); + int oldLimit = pageData.limit(); + pageData.limit( pagePos + length ); + bytes.put( pageData ); + pageData.limit( oldLimit ); + pageData.reset(); + bytes.rewind(); + + return bytes; + } + + pageData.mark(); + pageData.position( pagePos ); + int oldLimit = pageData.limit(); + pageData.limit( pagePos + remaining ); + bytes.put( pageData ); + pageData.limit( oldLimit ); + pageData.reset(); + pageNb++; + pagePos = LINK_SIZE; + pageData = pageIos[pageNb].getData(); + length -= remaining; + remaining = pageData.capacity() - pagePos; + } + + bytes.rewind(); + + return bytes; + } + } + + + /** + * Read an int from pages + * @param pageIos The pages we want to read the int from + * @param position The position in the data stored in those pages + * @return The int we have read + */ + /* no qualifier */int readInt( PageIO[] pageIos, long position ) + { + // Compute the page in which we will store the data given the + // current position + int pageNb = computePageNb( position ); + + // Compute the position in the current page + int pagePos = ( int ) ( position + ( pageNb + 1 ) * LONG_SIZE + INT_SIZE ) - pageNb * pageSize; + + ByteBuffer pageData = pageIos[pageNb].getData(); + int remaining = pageData.capacity() - pagePos; + int value = 0; + + if ( remaining >= INT_SIZE ) + { + value = pageData.getInt( pagePos ); + } + else + { + value = 0; + + switch ( remaining ) + { + case 3: + value += ( ( pageData.get( pagePos + 2 ) & 0x00FF ) << 8 ); + // Fallthrough !!! + + case 2: + value += ( ( pageData.get( pagePos + 1 ) & 0x00FF ) << 16 ); + // Fallthrough !!! + + case 1: + value += ( pageData.get( pagePos ) << 24 ); + break; + } + + // Now deal with the next page + pageData = pageIos[pageNb + 1].getData(); + pagePos = LINK_SIZE; + + switch ( remaining ) + { + case 1: + value += ( pageData.get( pagePos ) & 0x00FF ) << 16; + // fallthrough !!! + + case 2: + value += ( pageData.get( pagePos + 2 - remaining ) & 0x00FF ) << 8; + // fallthrough !!! + + case 3: + value += ( pageData.get( pagePos + 3 - remaining ) & 0x00FF ); + break; + } + } + + return value; + } + + + /** + * Read a byte from pages + * @param pageIos The pages we want to read the byte from + * @param position The position in the data stored in those pages + * @return The byte we have read + */ + private byte readByte( PageIO[] pageIos, long position ) + { + // Compute the page in which we will store the data given the + // current position + int pageNb = computePageNb( position ); + + // Compute the position in the current page + int pagePos = ( int ) ( position + ( pageNb + 1 ) * LONG_SIZE + INT_SIZE ) - pageNb * pageSize; + + ByteBuffer pageData = pageIos[pageNb].getData(); + byte value = 0; + + value = pageData.get( pagePos ); + + return value; + } + + + /** + * Read a long from pages + * @param pageIos The pages we want to read the long from + * @param position The position in the data stored in those pages + * @return The long we have read + */ + /* no qualifier */long readLong( PageIO[] pageIos, long position ) + { + // Compute the page in which we will store the data given the + // current position + int pageNb = computePageNb( position ); + + // Compute the position in the current page + int pagePos = ( int ) ( position + ( pageNb + 1 ) * LONG_SIZE + INT_SIZE ) - pageNb * pageSize; + + ByteBuffer pageData = pageIos[pageNb].getData(); + int remaining = pageData.capacity() - pagePos; + long value = 0L; + + if ( remaining >= LONG_SIZE ) + { + value = pageData.getLong( pagePos ); + } + else + { + switch ( remaining ) + { + case 7: + value += ( ( ( long ) pageData.get( pagePos + 6 ) & 0x00FF ) << 8 ); + // Fallthrough !!! + + case 6: + value += ( ( ( long ) pageData.get( pagePos + 5 ) & 0x00FF ) << 16 ); + // Fallthrough !!! + + case 5: + value += ( ( ( long ) pageData.get( pagePos + 4 ) & 0x00FF ) << 24 ); + // Fallthrough !!! + + case 4: + value += ( ( ( long ) pageData.get( pagePos + 3 ) & 0x00FF ) << 32 ); + // Fallthrough !!! + + case 3: + value += ( ( ( long ) pageData.get( pagePos + 2 ) & 0x00FF ) << 40 ); + // Fallthrough !!! + + case 2: + value += ( ( ( long ) pageData.get( pagePos + 1 ) & 0x00FF ) << 48 ); + // Fallthrough !!! + + case 1: + value += ( ( long ) pageData.get( pagePos ) << 56 ); + break; + } + + // Now deal with the next page + pageData = pageIos[pageNb + 1].getData(); + pagePos = LINK_SIZE; + + switch ( remaining ) + { + case 1: + value += ( ( long ) pageData.get( pagePos ) & 0x00FF ) << 48; + // fallthrough !!! + + case 2: + value += ( ( long ) pageData.get( pagePos + 2 - remaining ) & 0x00FF ) << 40; + // fallthrough !!! + + case 3: + value += ( ( long ) pageData.get( pagePos + 3 - remaining ) & 0x00FF ) << 32; + // fallthrough !!! + + case 4: + value += ( ( long ) pageData.get( pagePos + 4 - remaining ) & 0x00FF ) << 24; + // fallthrough !!! + + case 5: + value += ( ( long ) pageData.get( pagePos + 5 - remaining ) & 0x00FF ) << 16; + // fallthrough !!! + + case 6: + value += ( ( long ) pageData.get( pagePos + 6 - remaining ) & 0x00FF ) << 8; + // fallthrough !!! + + case 7: + value += ( ( long ) pageData.get( pagePos + 7 - remaining ) & 0x00FF ); + break; + } + } + + return value; + } + + + /** + * Manage a B-tree. The btree will be added and managed by this RecordManager. We will create a + * new RootPage for this added B-tree, which will contain no data.
          + * This method is threadsafe. + * Managing a btree is a matter of storing an reference to the managed B-tree in the B-tree Of B-trees. + * We store a tuple of NameRevision (where revision is 0L) and a offset to the B-tree header. + * At the same time, we keep a track of the managed B-trees in a Map. + * + * @param btree The new B-tree to manage. + * @param treeType flag indicating if this is an internal tree + * + * @throws BTreeAlreadyManagedException If the B-tree is already managed + * @throws IOException if there was a problem while accessing the file + */ + public synchronized void manage( BTree btree ) throws BTreeAlreadyManagedException, IOException + { + beginTransaction(); + + try + { + LOG.debug( "Managing the btree {}", btree.getName() ); + BTreeFactory.setRecordManager( btree, this ); + + String name = btree.getName(); + + if ( managedBtrees.containsKey( name ) ) + { + // There is already a B-tree with this name in the recordManager... + LOG.error( "There is already a B-tree named '{}' managed by this recordManager", name ); + rollback(); + throw new BTreeAlreadyManagedException( name ); + } + + // Now, write the B-tree informations + long btreeInfoOffset = writeBtreeInfo( btree ); + BTreeHeader btreeHeader = ( ( AbstractBTree ) btree ).getBtreeHeader(); + ( ( PersistedBTree ) btree ).setBtreeInfoOffset( btreeInfoOffset ); + + // Serialize the B-tree root page + Page rootPage = btreeHeader.getRootPage(); + + PageIO[] rootPageIos = serializePage( btree, btreeHeader.getRevision(), rootPage ); + + // Get the reference on the first page + long rootPageOffset = rootPageIos[0].getOffset(); + + // Store the rootPageOffset into the Btree header and into the rootPage + btreeHeader.setRootPageOffset( rootPageOffset ); + ( ( PersistedLeaf ) rootPage ).setOffset( rootPageOffset ); + + LOG.debug( "Flushing the newly managed '{}' btree rootpage", btree.getName() ); + flushPages( rootPageIos ); + + // And the B-tree header + long btreeHeaderOffset = writeBtreeHeader( btree, btreeHeader ); + + // Now, if this is a new B-tree, add it to the B-tree of B-trees + // Add the btree into the map of managed B-trees + managedBtrees.put( name, ( BTree ) btree ); + + // And in the Map of currentBtreeHeaders and newBtreeHeaders + currentBTreeHeaders.put( name, btreeHeader ); + newBTreeHeaders.put( name, btreeHeader ); + + // We can safely increment the number of managed B-trees + nbBtree++; + + // Create the new NameRevision + NameRevision nameRevision = new NameRevision( name, 0L ); + + // Inject it into the B-tree of B-tree + btreeOfBtrees.insert( nameRevision, btreeHeaderOffset ); + commit(); + } + catch ( IOException ioe ) + { + rollback(); + throw ioe; + } + } + + + /** + * Managing a btree is a matter of storing an reference to the managed B-tree in the B-tree Of B-trees. + * We store a tuple of NameRevision (where revision is 0L) and a offset to the B-tree header. + * At the same time, we keep a track of the managed B-trees in a Map. + * + * @param btree The new B-tree to manage. + * @param treeType flag indicating if this is an internal tree + * + * @throws BTreeAlreadyManagedException If the B-tree is already managed + * @throws IOException + */ + public synchronized void manageSubBtree( BTree btree ) + throws BTreeAlreadyManagedException, IOException + { + LOG.debug( "Managing the sub-btree {}", btree.getName() ); + BTreeFactory.setRecordManager( btree, this ); + + String name = btree.getName(); + + if ( managedBtrees.containsKey( name ) ) + { + // There is already a subB-tree with this name in the recordManager... + LOG.error( "There is already a sub-B-tree named '{}' managed by this recordManager", name ); + throw new BTreeAlreadyManagedException( name ); + } + + // Now, write the subB-tree informations + long btreeInfoOffset = writeBtreeInfo( btree ); + BTreeHeader btreeHeader = ( ( AbstractBTree ) btree ).getBtreeHeader(); + ( ( PersistedBTree ) btree ).setBtreeInfoOffset( btreeInfoOffset ); + + // Serialize the B-tree root page + Page rootPage = btreeHeader.getRootPage(); + + PageIO[] rootPageIos = serializePage( btree, btreeHeader.getRevision(), rootPage ); + + // Get the reference on the first page + long rootPageOffset = rootPageIos[0].getOffset(); + + // Store the rootPageOffset into the Btree header and into the rootPage + btreeHeader.setRootPageOffset( rootPageOffset ); + + ( ( AbstractPage ) rootPage ).setOffset( rootPageOffset ); + + LOG.debug( "Flushing the newly managed '{}' btree rootpage", btree.getName() ); + flushPages( rootPageIos ); + + // And the B-tree header + long btreeHeaderOffset = writeBtreeHeader( btree, btreeHeader ); + + // Now, if this is a new B-tree, add it to the B-tree of B-trees + // Add the btree into the map of managed B-trees + if ( ( btree.getType() != BTreeTypeEnum.BTREE_OF_BTREES ) && + ( btree.getType() != BTreeTypeEnum.COPIED_PAGES_BTREE ) && + ( btree.getType() != BTreeTypeEnum.PERSISTED_SUB ) ) + { + managedBtrees.put( name, ( BTree ) btree ); + } + + // And in the Map of currentBtreeHeaders and newBtreeHeaders + currentBTreeHeaders.put( name, btreeHeader ); + newBTreeHeaders.put( name, btreeHeader ); + + // Create the new NameRevision + NameRevision nameRevision = new NameRevision( name, 0L ); + + // Inject it into the B-tree of B-tree + if ( ( btree.getType() != BTreeTypeEnum.BTREE_OF_BTREES ) && + ( btree.getType() != BTreeTypeEnum.COPIED_PAGES_BTREE ) && + ( btree.getType() != BTreeTypeEnum.PERSISTED_SUB ) ) + { + // We can safely increment the number of managed B-trees + nbBtree++; + + btreeOfBtrees.insert( nameRevision, btreeHeaderOffset ); + } + + updateRecordManagerHeader(); + } + + + /** + * Serialize a new Page. It will contain the following data :
          + *
            + *
          • the revision : a long
          • + *
          • the number of elements : an int (if <= 0, it's a Node, otherwise it's a Leaf)
          • + *
          • the size of the values/keys when serialized + *
          • the keys : an array of serialized keys
          • + *
          • the values : an array of references to the children pageIO offset (stored as long) + * if it's a Node, or a list of values if it's a Leaf
          • + *
          • + *
          + * + * @param revision The node revision + * @param keys The keys to serialize + * @param children The references to the children + * @return An array of pages containing the serialized node + * @throws IOException + */ + private PageIO[] serializePage( BTree btree, long revision, Page page ) throws IOException + { + int nbElems = page.getNbElems(); + + boolean isNotSubTree = ( btree.getType() != BTreeTypeEnum.PERSISTED_SUB ); + + if ( nbElems == 0 ) + { + return serializeRootPage( revision ); + } + else + { + // Prepare a list of byte[] that will contain the serialized page + int nbBuffers = 1 + 1 + 1 + nbElems * 3; + int dataSize = 0; + int serializedSize = 0; + + if ( page.isNode() ) + { + // A Node has one more value to store + nbBuffers++; + } + + // Now, we can create the list with the right size + List serializedData = new ArrayList( nbBuffers ); + + // The revision + byte[] buffer = LongSerializer.serialize( revision ); + serializedData.add( buffer ); + serializedSize += buffer.length; + + // The number of elements + // Make it a negative value if it's a Node + int pageNbElems = nbElems; + + if ( page.isNode() ) + { + pageNbElems = -nbElems; + } + + buffer = IntSerializer.serialize( pageNbElems ); + serializedData.add( buffer ); + serializedSize += buffer.length; + + // Iterate on the keys and values. We first serialize the value, then the key + // until we are done with all of them. If we are serializing a page, we have + // to serialize one more value + for ( int pos = 0; pos < nbElems; pos++ ) + { + // Start with the value + if ( page.isNode() ) + { + dataSize += serializeNodeValue( ( PersistedNode ) page, pos, serializedData ); + dataSize += serializeNodeKey( ( PersistedNode ) page, pos, serializedData ); + } + else + { + if ( isNotSubTree ) + { + dataSize += serializeLeafValue( ( PersistedLeaf ) page, pos, serializedData ); + } + + dataSize += serializeLeafKey( ( PersistedLeaf ) page, pos, serializedData ); + } + } + + // Nodes have one more value to serialize + if ( page.isNode() ) + { + dataSize += serializeNodeValue( ( PersistedNode ) page, nbElems, serializedData ); + } + + // Store the data size + buffer = IntSerializer.serialize( dataSize ); + serializedData.add( 2, buffer ); + serializedSize += buffer.length; + + serializedSize += dataSize; + + // We are done. Allocate the pages we need to store the data + PageIO[] pageIos = getFreePageIOs( serializedSize ); + + // And store the data into those pages + long position = 0L; + + for ( byte[] bytes : serializedData ) + { + position = storeRaw( position, bytes, pageIos ); + } + + return pageIos; + } + } + + + /** + * Serialize a Node's key + */ + private int serializeNodeKey( PersistedNode node, int pos, List serializedData ) + { + KeyHolder holder = node.getKeyHolder( pos ); + byte[] buffer = ( ( PersistedKeyHolder ) holder ).getRaw(); + + // We have to store the serialized key length + byte[] length = IntSerializer.serialize( buffer.length ); + serializedData.add( length ); + + // And store the serialized key now if not null + if ( buffer.length != 0 ) + { + serializedData.add( buffer ); + } + + return buffer.length + INT_SIZE; + } + + + /** + * Serialize a Node's Value. We store the two offsets of the child page. + */ + private int serializeNodeValue( PersistedNode node, int pos, List serializedData ) + throws IOException + { + // For a node, we just store the children's offsets + Page child = node.getReference( pos ); + + // The first offset + byte[] buffer = LongSerializer.serialize( ( ( AbstractPage ) child ).getOffset() ); + serializedData.add( buffer ); + int dataSize = buffer.length; + + // The last offset + buffer = LongSerializer.serialize( ( ( AbstractPage ) child ).getLastOffset() ); + serializedData.add( buffer ); + dataSize += buffer.length; + + return dataSize; + } + + + /** + * Serialize a Leaf's key + */ + private int serializeLeafKey( PersistedLeaf leaf, int pos, List serializedData ) + { + int dataSize = 0; + KeyHolder keyHolder = leaf.getKeyHolder( pos ); + byte[] keyData = ( ( PersistedKeyHolder ) keyHolder ).getRaw(); + + if ( keyData != null ) + { + // We have to store the serialized key length + byte[] length = IntSerializer.serialize( keyData.length ); + serializedData.add( length ); + + // And the key data + serializedData.add( keyData ); + dataSize += keyData.length + INT_SIZE; + } + else + { + serializedData.add( IntSerializer.serialize( 0 ) ); + dataSize += INT_SIZE; + } + + return dataSize; + } + + + /** + * Serialize a Leaf's Value. + */ + private int serializeLeafValue( PersistedLeaf leaf, int pos, List serializedData ) + throws IOException + { + // The value can be an Array or a sub-btree, but we don't care + // we just iterate on all the values + ValueHolder valueHolder = leaf.getValue( pos ); + int dataSize = 0; + int nbValues = valueHolder.size(); + + if ( nbValues == 0 ) + { + // No value. + byte[] buffer = IntSerializer.serialize( nbValues ); + serializedData.add( buffer ); + + return buffer.length; + } + + if ( !valueHolder.isSubBtree() ) + { + // Write the nb elements first + byte[] buffer = IntSerializer.serialize( nbValues ); + serializedData.add( buffer ); + dataSize = INT_SIZE; + + // We have a serialized value. Just flush it + byte[] data = ( ( PersistedValueHolder ) valueHolder ).getRaw(); + dataSize += data.length; + + // Store the data size + buffer = IntSerializer.serialize( data.length ); + serializedData.add( buffer ); + dataSize += INT_SIZE; + + // and add the data if it's not 0 + if ( data.length > 0 ) + { + serializedData.add( data ); + } + } + else + { + // Store the nbVlues as a negative number. We add 1 so that 0 is not confused with an Array value + byte[] buffer = IntSerializer.serialize( -( nbValues + 1 ) ); + serializedData.add( buffer ); + dataSize += buffer.length; + + // the B-tree offset + buffer = LongSerializer.serialize( ( ( PersistedValueHolder ) valueHolder ).getOffset() ); + serializedData.add( buffer ); + dataSize += buffer.length; + } + + return dataSize; + } + + + /** + * Write a root page with no elements in it + */ + private PageIO[] serializeRootPage( long revision ) throws IOException + { + // We will have 1 single page if we have no elements + PageIO[] pageIos = new PageIO[1]; + + // This is either a new root page or a new page that will be filled later + PageIO newPage = fetchNewPage(); + + // We need first to create a byte[] that will contain all the data + // For the root page, this is easy, as we only have to store the revision, + // and the number of elements, which is 0. + long position = 0L; + + position = store( position, revision, newPage ); + position = store( position, 0, newPage ); + + // Update the page size now + newPage.setSize( ( int ) position ); + + // Insert the result into the array of PageIO + pageIos[0] = newPage; + + return pageIos; + } + + + /** + * Update the RecordManager header, injecting the following data : + * + *
          +     * +---------------------+
          +     * | PageSize            | 4 bytes : The size of a physical page (default to 4096)
          +     * +---------------------+
          +     * | NbTree              | 4 bytes : The number of managed B-trees (at least 1)
          +     * +---------------------+
          +     * | FirstFree           | 8 bytes : The offset of the first free page
          +     * +---------------------+
          +     * | current BoB offset  | 8 bytes : The offset of the current B-tree of B-trees
          +     * +---------------------+
          +     * | previous BoB offset | 8 bytes : The offset of the previous B-tree of B-trees
          +     * +---------------------+
          +     * | current CP offset   | 8 bytes : The offset of the current CopiedPages B-tree
          +     * +---------------------+
          +     * | previous CP offset  | 8 bytes : The offset of the previous CopiedPages B-tree
          +     * +---------------------+
          +     * 
          + */ + public void updateRecordManagerHeader() + { + // The page size + int position = writeData( RECORD_MANAGER_HEADER_BYTES, 0, pageSize ); + + // The number of managed B-tree + position = writeData( RECORD_MANAGER_HEADER_BYTES, position, nbBtree ); + + // The first free page + position = writeData( RECORD_MANAGER_HEADER_BYTES, position, firstFreePage ); + + // The offset of the current B-tree of B-trees + position = writeData( RECORD_MANAGER_HEADER_BYTES, position, currentBtreeOfBtreesOffset ); + + // The offset of the copied pages B-tree + position = writeData( RECORD_MANAGER_HEADER_BYTES, position, previousBtreeOfBtreesOffset ); + + // The offset of the current B-tree of B-trees + position = writeData( RECORD_MANAGER_HEADER_BYTES, position, currentCopiedPagesBtreeOffset ); + + // The offset of the copied pages B-tree + position = writeData( RECORD_MANAGER_HEADER_BYTES, position, previousCopiedPagesBtreeOffset ); + + // Write the RecordManager header on disk + RECORD_MANAGER_HEADER_BUFFER.put( RECORD_MANAGER_HEADER_BYTES ); + RECORD_MANAGER_HEADER_BUFFER.flip(); + + LOG.debug( "Update RM header" ); + + if ( LOG_PAGES.isDebugEnabled() ) + { + StringBuilder sb = new StringBuilder(); + + sb.append( "First free page : 0x" ).append( Long.toHexString( firstFreePage ) ).append( "\n" ); + sb.append( "Current BOB header : 0x" ).append( Long.toHexString( currentBtreeOfBtreesOffset ) ) + .append( "\n" ); + sb.append( "Previous BOB header : 0x" ).append( Long.toHexString( previousBtreeOfBtreesOffset ) ) + .append( "\n" ); + sb.append( "Current CPB header : 0x" ).append( Long.toHexString( currentCopiedPagesBtreeOffset ) ) + .append( "\n" ); + sb.append( "Previous CPB header : 0x" ).append( Long.toHexString( previousCopiedPagesBtreeOffset ) ) + .append( "\n" ); + + if ( firstFreePage != NO_PAGE ) + { + long freePage = firstFreePage; + sb.append( "free pages list : " ); + + boolean isFirst = true; + + while ( freePage != NO_PAGE ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( " -> " ); + } + + sb.append( "0x" ).append( Long.toHexString( freePage ) ); + + try + { + PageIO[] freePageIO = readPageIOs( freePage, 8 ); + + freePage = freePageIO[0].getNextPage(); + } + catch ( EndOfFileExceededException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch ( IOException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } + + LOG_PAGES.debug( "Update RM Header : \n{}", sb.toString() ); + } + + try + { + + Integer nbTxnStarted = CONTEXT.get(); + + if ( ( nbTxnStarted == null ) || ( nbTxnStarted <= 1 ) ) + { + //System.out.println( "Writing page at 0000" ); + writeCounter.put( 0L, writeCounter.containsKey( 0L ) ? writeCounter.get( 0L ) + 1 : 1 ); + fileChannel.write( RECORD_MANAGER_HEADER_BUFFER, 0 ); + } + } + catch ( IOException ioe ) + { + throw new FileException( ioe.getMessage() ); + } + + RECORD_MANAGER_HEADER_BUFFER.clear(); + + // Reset the old versions + previousBtreeOfBtreesOffset = -1L; + previousCopiedPagesBtreeOffset = -1L; + + nbUpdateRMHeader.incrementAndGet(); + } + + + /** + * Update the RecordManager header, injecting the following data : + * + *
          +     * +---------------------+
          +     * | PageSize            | 4 bytes : The size of a physical page (default to 4096)
          +     * +---------------------+
          +     * | NbTree              | 4 bytes : The number of managed B-trees (at least 1)
          +     * +---------------------+
          +     * | FirstFree           | 8 bytes : The offset of the first free page
          +     * +---------------------+
          +     * | current BoB offset  | 8 bytes : The offset of the current B-tree of B-trees
          +     * +---------------------+
          +     * | previous BoB offset | 8 bytes : The offset of the previous B-tree of B-trees
          +     * +---------------------+
          +     * | current CP offset   | 8 bytes : The offset of the current CopiedPages B-tree
          +     * +---------------------+
          +     * | previous CP offset  | 8 bytes : The offset of the previous CopiedPages B-tree
          +     * +---------------------+
          +     * 
          + */ + public void updateRecordManagerHeader( long newBtreeOfBtreesOffset, long newCopiedPageBtreeOffset ) + { + if ( newBtreeOfBtreesOffset != -1L ) + { + previousBtreeOfBtreesOffset = currentBtreeOfBtreesOffset; + currentBtreeOfBtreesOffset = newBtreeOfBtreesOffset; + } + + if ( newCopiedPageBtreeOffset != -1L ) + { + previousCopiedPagesBtreeOffset = currentCopiedPagesBtreeOffset; + currentCopiedPagesBtreeOffset = newCopiedPageBtreeOffset; + } + } + + + /** + * Inject an int into a byte[] at a given position. + */ + private int writeData( byte[] buffer, int position, int value ) + { + RECORD_MANAGER_HEADER_BYTES[position] = ( byte ) ( value >>> 24 ); + RECORD_MANAGER_HEADER_BYTES[position + 1] = ( byte ) ( value >>> 16 ); + RECORD_MANAGER_HEADER_BYTES[position + 2] = ( byte ) ( value >>> 8 ); + RECORD_MANAGER_HEADER_BYTES[position + 3] = ( byte ) ( value ); + + return position + 4; + } + + + /** + * Inject a long into a byte[] at a given position. + */ + private int writeData( byte[] buffer, int position, long value ) + { + RECORD_MANAGER_HEADER_BYTES[position] = ( byte ) ( value >>> 56 ); + RECORD_MANAGER_HEADER_BYTES[position + 1] = ( byte ) ( value >>> 48 ); + RECORD_MANAGER_HEADER_BYTES[position + 2] = ( byte ) ( value >>> 40 ); + RECORD_MANAGER_HEADER_BYTES[position + 3] = ( byte ) ( value >>> 32 ); + RECORD_MANAGER_HEADER_BYTES[position + 4] = ( byte ) ( value >>> 24 ); + RECORD_MANAGER_HEADER_BYTES[position + 5] = ( byte ) ( value >>> 16 ); + RECORD_MANAGER_HEADER_BYTES[position + 6] = ( byte ) ( value >>> 8 ); + RECORD_MANAGER_HEADER_BYTES[position + 7] = ( byte ) ( value ); + + return position + 8; + } + + + /** + * Add a new tuple into the B-tree of B-trees. + * + * @param name The B-tree name + * @param revision The B-tree revision + * @param btreeHeaderOffset The B-tree offset + * @throws IOException If the update failed + */ + /* no qualifier */ void addInBtreeOfBtrees( String name, long revision, long btreeHeaderOffset ) + throws IOException + { + checkOffset( btreeHeaderOffset ); + NameRevision nameRevision = new NameRevision( name, revision ); + + btreeOfBtrees.insert( nameRevision, btreeHeaderOffset ); + + // Update the B-tree of B-trees offset + currentBtreeOfBtreesOffset = getNewBTreeHeader( BTREE_OF_BTREES_NAME ).getBTreeHeaderOffset(); + } + + + /** + * Add a new tuple into the CopiedPages B-tree. + * + * @param name The B-tree name + * @param revision The B-tree revision + * @param btreeHeaderOffset The B-tree offset + * @throws IOException If the update failed + */ + /* no qualifier */ void addInCopiedPagesBtree( String name, long revision, List> pages ) + throws IOException + { + RevisionName revisionName = new RevisionName( revision, name ); + + long[] pageOffsets = new long[pages.size()]; + int pos = 0; + + for ( Page page : pages ) + { + pageOffsets[pos++] = ( ( AbstractPage ) page ).getOffset(); + } + + copiedPageBtree.insert( revisionName, pageOffsets ); + + // Update the CopiedPageBtree offset + currentCopiedPagesBtreeOffset = ( ( AbstractBTree ) copiedPageBtree ).getBtreeHeader() + .getBTreeHeaderOffset(); + } + + + /** + * Internal method used to update the B-tree of B-trees offset + * @param btreeOfBtreesOffset The new offset + */ + /* no qualifier */void setBtreeOfBtreesOffset( long btreeOfBtreesOffset ) + { + checkOffset( btreeOfBtreesOffset ); + this.currentBtreeOfBtreesOffset = btreeOfBtreesOffset; + } + + + /** + * Write the B-tree header on disk. We will write the following informations : + *
          +     * +------------+
          +     * | revision   | The B-tree revision
          +     * +------------+
          +     * | nbElems    | The B-tree number of elements
          +     * +------------+
          +     * | rootPage   | The root page offset
          +     * +------------+
          +     * | BtreeInfo  | The B-tree info offset
          +     * +------------+
          +     * 
          + * @param btree The B-tree which header has to be written + * @param btreeInfoOffset The offset of the B-tree informations + * @return The B-tree header offset + * @throws IOException If we weren't able to write the B-tree header + */ + /* no qualifier */ long writeBtreeHeader( BTree btree, BTreeHeader btreeHeader ) + throws IOException + { + int bufferSize = + LONG_SIZE + // The revision + LONG_SIZE + // the number of element + LONG_SIZE + // The root page offset + LONG_SIZE; // The B-tree info page offset + + // Get the pageIOs we need to store the data. We may need more than one. + PageIO[] btreeHeaderPageIos = getFreePageIOs( bufferSize ); + + // Store the B-tree header Offset into the B-tree + long btreeHeaderOffset = btreeHeaderPageIos[0].getOffset(); + + // Now store the B-tree data in the pages : + // - the B-tree revision + // - the B-tree number of elements + // - the B-tree root page offset + // - the B-tree info page offset + // Starts at 0 + long position = 0L; + + // The B-tree current revision + position = store( position, btreeHeader.getRevision(), btreeHeaderPageIos ); + + // The nb elems in the tree + position = store( position, btreeHeader.getNbElems(), btreeHeaderPageIos ); + + // Now, we can inject the B-tree rootPage offset into the B-tree header + position = store( position, btreeHeader.getRootPageOffset(), btreeHeaderPageIos ); + + // The B-tree info page offset + position = store( position, ( ( PersistedBTree ) btree ).getBtreeInfoOffset(), btreeHeaderPageIos ); + + // And flush the pages to disk now + LOG.debug( "Flushing the newly managed '{}' btree header", btree.getName() ); + + if ( LOG_PAGES.isDebugEnabled() ) + { + LOG_PAGES.debug( "Writing BTreeHeader revision {} for {}", btreeHeader.getRevision(), btree.getName() ); + StringBuilder sb = new StringBuilder(); + + sb.append( "Offset : " ).append( Long.toHexString( btreeHeaderOffset ) ).append( "\n" ); + sb.append( " Revision : " ).append( btreeHeader.getRevision() ).append( "\n" ); + sb.append( " NbElems : " ).append( btreeHeader.getNbElems() ).append( "\n" ); + sb.append( " RootPage : 0x" ).append( Long.toHexString( btreeHeader.getRootPageOffset() ) ) + .append( "\n" ); + sb.append( " Info : 0x" ) + .append( Long.toHexString( ( ( PersistedBTree ) btree ).getBtreeInfoOffset() ) ).append( "\n" ); + + LOG_PAGES.debug( "Btree Header[{}]\n{}", btreeHeader.getRevision(), sb.toString() ); + } + + flushPages( btreeHeaderPageIos ); + + btreeHeader.setBTreeHeaderOffset( btreeHeaderOffset ); + + return btreeHeaderOffset; + } + + + /** + * Write the B-tree informations on disk. We will write the following informations : + *
          +     * +------------+
          +     * | pageSize   | The B-tree page size (ie, the number of elements per page max)
          +     * +------------+
          +     * | nameSize   | The B-tree name size
          +     * +------------+
          +     * | name       | The B-tree name
          +     * +------------+
          +     * | keySerSize | The keySerializer FQCN size
          +     * +------------+
          +     * | keySerFQCN | The keySerializer FQCN
          +     * +------------+
          +     * | valSerSize | The Value serializer FQCN size
          +     * +------------+
          +     * | valSerKQCN | The valueSerializer FQCN
          +     * +------------+
          +     * | dups       | The flags that tell if the dups are allowed
          +     * +------------+
          +     * 
          + * @param btree The B-tree which header has to be written + * @return The B-tree header offset + * @throws IOException If we weren't able to write the B-tree header + */ + private long writeBtreeInfo( BTree btree ) throws IOException + { + // We will add the newly managed B-tree at the end of the header. + byte[] btreeNameBytes = Strings.getBytesUtf8( btree.getName() ); + byte[] keySerializerBytes = Strings.getBytesUtf8( btree.getKeySerializerFQCN() ); + byte[] valueSerializerBytes = Strings.getBytesUtf8( btree.getValueSerializerFQCN() ); + + int bufferSize = + INT_SIZE + // The page size + INT_SIZE + // The name size + btreeNameBytes.length + // The name + INT_SIZE + // The keySerializerBytes size + keySerializerBytes.length + // The keySerializerBytes + INT_SIZE + // The valueSerializerBytes size + valueSerializerBytes.length + // The valueSerializerBytes + INT_SIZE; // The allowDuplicates flag + + // Get the pageIOs we need to store the data. We may need more than one. + PageIO[] btreeHeaderPageIos = getFreePageIOs( bufferSize ); + + // Keep the B-tree header Offset into the B-tree + long btreeInfoOffset = btreeHeaderPageIos[0].getOffset(); + + // Now store the B-tree information data in the pages : + // - the B-tree page size + // - the B-tree name + // - the keySerializer FQCN + // - the valueSerializer FQCN + // - the flags that tell if the dups are allowed + // Starts at 0 + long position = 0L; + + // The B-tree page size + position = store( position, btree.getPageSize(), btreeHeaderPageIos ); + + // The tree name + position = store( position, btreeNameBytes, btreeHeaderPageIos ); + + // The keySerializer FQCN + position = store( position, keySerializerBytes, btreeHeaderPageIos ); + + // The valueSerialier FQCN + position = store( position, valueSerializerBytes, btreeHeaderPageIos ); + + // The allowDuplicates flag + position = store( position, ( btree.isAllowDuplicates() ? 1 : 0 ), btreeHeaderPageIos ); + + // And flush the pages to disk now + LOG.debug( "Flushing the newly managed '{}' btree header", btree.getName() ); + flushPages( btreeHeaderPageIos ); + + return btreeInfoOffset; + } + + + /** + * Update the B-tree header after a B-tree modification. This will make the latest modification + * visible.
          + * We update the following fields : + *
            + *
          • the revision
          • + *
          • the number of elements
          • + *
          • the B-tree root page offset
          • + *
          + *
          + * As a result, a new version of the BtreHeader will be created, which will replace the previous + * B-tree header + * @param btree TheB-tree to update + * @param btreeHeaderOffset The offset of the modified btree header + * @return The offset of the new B-tree Header + * @throws IOException If we weren't able to write the file on disk + * @throws EndOfFileExceededException If we tried to write after the end of the file + */ + /* no qualifier */ long updateBtreeHeader( BTree btree, long btreeHeaderOffset ) + throws EndOfFileExceededException, IOException + { + return updateBtreeHeader( btree, btreeHeaderOffset, false ); + } + + + /** + * Update the B-tree header after a B-tree modification. This will make the latest modification + * visible.
          + * We update the following fields : + *
            + *
          • the revision
          • + *
          • the number of elements
          • + *
          • the reference to the current B-tree revisions
          • + *
          • the reference to the old B-tree revisions
          • + *
          + *
          + * As a result, we new version of the BtreHeader will be created + * @param btree The B-tree to update + * @param btreeHeaderOffset The offset of the modified btree header + * @return The offset of the new B-tree Header if it has changed (ie, when the onPlace flag is set to true) + * @throws IOException + * @throws EndOfFileExceededException + */ + /* no qualifier */ void updateBtreeHeaderOnPlace( BTree btree, long btreeHeaderOffset ) + throws EndOfFileExceededException, + IOException + { + updateBtreeHeader( btree, btreeHeaderOffset, true ); + } + + + /** + * Update the B-tree header after a B-tree modification. This will make the latest modification + * visible.
          + * We update the following fields : + *
            + *
          • the revision
          • + *
          • the number of elements
          • + *
          • the reference to the current B-tree revisions
          • + *
          • the reference to the old B-tree revisions
          • + *
          + *
          + * As a result, a new version of the BtreHeader will be created, which may replace the previous + * B-tree header (if the onPlace flag is set to true) or a new set of pageIos will contain the new + * version. + * + * @param btree The B-tree to update + * @param rootPageOffset The offset of the modified rootPage + * @param onPlace Tells if we modify the B-tree on place, or if we create a copy + * @return The offset of the new B-tree Header if it has changed (ie, when the onPlace flag is set to true) + * @throws EndOfFileExceededException If we tried to write after the end of the file + * @throws IOException If tehre were some error while writing the data on disk + */ + private long updateBtreeHeader( BTree btree, long btreeHeaderOffset, boolean onPlace ) + throws EndOfFileExceededException, IOException + { + // Read the pageIOs associated with this B-tree + PageIO[] pageIos; + long newBtreeHeaderOffset = NO_PAGE; + long offset = ( ( PersistedBTree ) btree ).getBtreeOffset(); + + if ( onPlace ) + { + // We just have to update the existing BTreeHeader + long headerSize = LONG_SIZE + LONG_SIZE + LONG_SIZE; + + pageIos = readPageIOs( offset, headerSize ); + + // Now, update the revision + long position = 0; + + position = store( position, btree.getRevision(), pageIos ); + position = store( position, btree.getNbElems(), pageIos ); + position = store( position, btreeHeaderOffset, pageIos ); + + // Write the pages on disk + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "-----> Flushing the '{}' B-treeHeader", btree.getName() ); + LOG.debug( " revision : " + btree.getRevision() + ", NbElems : " + btree.getNbElems() + + ", btreeHeader offset : 0x" + + Long.toHexString( btreeHeaderOffset ) ); + } + + // Get new place on disk to store the modified BTreeHeader if it's not onPlace + // Rewrite the pages at the same place + LOG.debug( "Rewriting the B-treeHeader on place for B-tree " + btree.getName() ); + flushPages( pageIos ); + } + else + { + // We have to read and copy the existing BTreeHeader and to create a new one + pageIos = readPageIOs( offset, Long.MAX_VALUE ); + + // Now, copy every read page + PageIO[] newPageIOs = new PageIO[pageIos.length]; + int pos = 0; + + for ( PageIO pageIo : pageIos ) + { + // Fetch a free page + newPageIOs[pos] = fetchNewPage(); + + // keep a track of the allocated and copied pages so that we can + // free them when we do a commit or rollback, if the btree is an management one + if ( ( btree.getType() == BTreeTypeEnum.BTREE_OF_BTREES ) + || ( btree.getType() == BTreeTypeEnum.COPIED_PAGES_BTREE ) ) + { + freedPages.add( pageIo ); + allocatedPages.add( newPageIOs[pos] ); + } + + pageIo.copy( newPageIOs[pos] ); + + if ( pos > 0 ) + { + newPageIOs[pos - 1].setNextPage( newPageIOs[pos].getOffset() ); + } + + pos++; + } + + // store the new btree header offset + // and update the revision + long position = 0; + + position = store( position, btree.getRevision(), newPageIOs ); + position = store( position, btree.getNbElems(), newPageIOs ); + position = store( position, btreeHeaderOffset, newPageIOs ); + + // Get new place on disk to store the modified BTreeHeader if it's not onPlace + // Flush the new B-treeHeader on disk + LOG.debug( "Rewriting the B-treeHeader on place for B-tree " + btree.getName() ); + flushPages( newPageIOs ); + + newBtreeHeaderOffset = newPageIOs[0].getOffset(); + } + + nbUpdateBtreeHeader.incrementAndGet(); + + if ( LOG_CHECK.isDebugEnabled() ) + { + MavibotInspector.check( this ); + } + + return newBtreeHeaderOffset; + } + + + /** + * Write the pages on disk, either at the end of the file, or at + * the position they were taken from. + * + * @param pageIos The list of pages to write + * @throws IOException If the write failed + */ + private void flushPages( PageIO... pageIos ) throws IOException + { + if ( LOG.isDebugEnabled() ) + { + for ( PageIO pageIo : pageIos ) + { + dump( pageIo ); + } + } + + for ( PageIO pageIo : pageIos ) + { + pageIo.getData().rewind(); + long pos = pageIo.getOffset(); + + if ( fileChannel.size() < ( pageIo.getOffset() + pageSize ) ) + { + LOG.debug( "Adding a page at the end of the file" ); + // This is a page we have to add to the file + pos = fileChannel.size(); + fileChannel.write( pageIo.getData(), pos ); + //fileChannel.force( false ); + } + else + { + LOG.debug( "Writing a page at position {}", pageIo.getOffset() ); + fileChannel.write( pageIo.getData(), pageIo.getOffset() ); + //fileChannel.force( false ); + } + + //System.out.println( "Writing page at " + Long.toHexString( pos ) ); + writeCounter.put( pos, writeCounter.containsKey( pos ) ? writeCounter.get( pos ) + 1 : 1 ); + + nbUpdatePageIOs.incrementAndGet(); + + pageIo.getData().rewind(); + } + } + + + /** + * Compute the page in which we will store data given an offset, when + * we have a list of pages. + * + * @param offset The position in the data + * @return The page number in which the offset will start + */ + private int computePageNb( long offset ) + { + long pageNb = 0; + + offset -= pageSize - LINK_SIZE - PAGE_SIZE; + + if ( offset < 0 ) + { + return ( int ) pageNb; + } + + pageNb = 1 + offset / ( pageSize - LINK_SIZE ); + + return ( int ) pageNb; + } + + + /** + * Stores a byte[] into one ore more pageIO (depending if the long is stored + * across a boundary or not) + * + * @param position The position in a virtual byte[] if all the pages were contiguous + * @param bytes The byte[] to serialize + * @param pageIos The pageIOs we have to store the data in + * @return The new offset + */ + private long store( long position, byte[] bytes, PageIO... pageIos ) + { + if ( bytes != null ) + { + // Write the bytes length + position = store( position, bytes.length, pageIos ); + + // Compute the page in which we will store the data given the + // current position + int pageNb = computePageNb( position ); + + // Get back the buffer in this page + ByteBuffer pageData = pageIos[pageNb].getData(); + + // Compute the position in the current page + int pagePos = ( int ) ( position + ( pageNb + 1 ) * LONG_SIZE + INT_SIZE ) - pageNb * pageSize; + + // Compute the remaining size in the page + int remaining = pageData.capacity() - pagePos; + int nbStored = bytes.length; + + // And now, write the bytes until we have none + while ( nbStored > 0 ) + { + if ( remaining > nbStored ) + { + pageData.mark(); + pageData.position( pagePos ); + pageData.put( bytes, bytes.length - nbStored, nbStored ); + pageData.reset(); + nbStored = 0; + } + else + { + pageData.mark(); + pageData.position( pagePos ); + pageData.put( bytes, bytes.length - nbStored, remaining ); + pageData.reset(); + pageNb++; + pageData = pageIos[pageNb].getData(); + pagePos = LINK_SIZE; + nbStored -= remaining; + remaining = pageData.capacity() - pagePos; + } + } + + // We are done + position += bytes.length; + } + else + { + // No bytes : write 0 and return + position = store( position, 0, pageIos ); + } + + return position; + } + + + /** + * Stores a byte[] into one ore more pageIO (depending if the long is stored + * across a boundary or not). We don't add the byte[] size, it's already present + * in the received byte[]. + * + * @param position The position in a virtual byte[] if all the pages were contiguous + * @param bytes The byte[] to serialize + * @param pageIos The pageIOs we have to store the data in + * @return The new offset + */ + private long storeRaw( long position, byte[] bytes, PageIO... pageIos ) + { + if ( bytes != null ) + { + // Compute the page in which we will store the data given the + // current position + int pageNb = computePageNb( position ); + + // Get back the buffer in this page + ByteBuffer pageData = pageIos[pageNb].getData(); + + // Compute the position in the current page + int pagePos = ( int ) ( position + ( pageNb + 1 ) * LONG_SIZE + INT_SIZE ) - pageNb * pageSize; + + // Compute the remaining size in the page + int remaining = pageData.capacity() - pagePos; + int nbStored = bytes.length; + + // And now, write the bytes until we have none + while ( nbStored > 0 ) + { + if ( remaining > nbStored ) + { + pageData.mark(); + pageData.position( pagePos ); + pageData.put( bytes, bytes.length - nbStored, nbStored ); + pageData.reset(); + nbStored = 0; + } + else + { + pageData.mark(); + pageData.position( pagePos ); + pageData.put( bytes, bytes.length - nbStored, remaining ); + pageData.reset(); + pageNb++; + + if ( pageNb == pageIos.length ) + { + // We can stop here : we have reach the end of the page + break; + } + + pageData = pageIos[pageNb].getData(); + pagePos = LINK_SIZE; + nbStored -= remaining; + remaining = pageData.capacity() - pagePos; + } + } + + // We are done + position += bytes.length; + } + else + { + // No bytes : write 0 and return + position = store( position, 0, pageIos ); + } + + return position; + } + + + /** + * Stores an Integer into one ore more pageIO (depending if the int is stored + * across a boundary or not) + * + * @param position The position in a virtual byte[] if all the pages were contiguous + * @param value The int to serialize + * @param pageIos The pageIOs we have to store the data in + * @return The new offset + */ + private long store( long position, int value, PageIO... pageIos ) + { + // Compute the page in which we will store the data given the + // current position + int pageNb = computePageNb( position ); + + // Compute the position in the current page + int pagePos = ( int ) ( position + ( pageNb + 1 ) * LONG_SIZE + INT_SIZE ) - pageNb * pageSize; + + // Get back the buffer in this page + ByteBuffer pageData = pageIos[pageNb].getData(); + + // Compute the remaining size in the page + int remaining = pageData.capacity() - pagePos; + + if ( remaining < INT_SIZE ) + { + // We have to copy the serialized length on two pages + + switch ( remaining ) + { + case 3: + pageData.put( pagePos + 2, ( byte ) ( value >>> 8 ) ); + // Fallthrough !!! + + case 2: + pageData.put( pagePos + 1, ( byte ) ( value >>> 16 ) ); + // Fallthrough !!! + + case 1: + pageData.put( pagePos, ( byte ) ( value >>> 24 ) ); + break; + } + + // Now deal with the next page + pageData = pageIos[pageNb + 1].getData(); + pagePos = LINK_SIZE; + + switch ( remaining ) + { + case 1: + pageData.put( pagePos, ( byte ) ( value >>> 16 ) ); + // fallthrough !!! + + case 2: + pageData.put( pagePos + 2 - remaining, ( byte ) ( value >>> 8 ) ); + // fallthrough !!! + + case 3: + pageData.put( pagePos + 3 - remaining, ( byte ) ( value ) ); + break; + } + } + else + { + // Store the value in the page at the selected position + pageData.putInt( pagePos, value ); + } + + // Increment the position to reflect the addition of an Int (4 bytes) + position += INT_SIZE; + + return position; + } + + + /** + * Stores a Long into one ore more pageIO (depending if the long is stored + * across a boundary or not) + * + * @param position The position in a virtual byte[] if all the pages were contiguous + * @param value The long to serialize + * @param pageIos The pageIOs we have to store the data in + * @return The new offset + */ + private long store( long position, long value, PageIO... pageIos ) + { + // Compute the page in which we will store the data given the + // current position + int pageNb = computePageNb( position ); + + // Compute the position in the current page + int pagePos = ( int ) ( position + ( pageNb + 1 ) * LONG_SIZE + INT_SIZE ) - pageNb * pageSize; + + // Get back the buffer in this page + ByteBuffer pageData = pageIos[pageNb].getData(); + + // Compute the remaining size in the page + int remaining = pageData.capacity() - pagePos; + + if ( remaining < LONG_SIZE ) + { + // We have to copy the serialized length on two pages + + switch ( remaining ) + { + case 7: + pageData.put( pagePos + 6, ( byte ) ( value >>> 8 ) ); + // Fallthrough !!! + + case 6: + pageData.put( pagePos + 5, ( byte ) ( value >>> 16 ) ); + // Fallthrough !!! + + case 5: + pageData.put( pagePos + 4, ( byte ) ( value >>> 24 ) ); + // Fallthrough !!! + + case 4: + pageData.put( pagePos + 3, ( byte ) ( value >>> 32 ) ); + // Fallthrough !!! + + case 3: + pageData.put( pagePos + 2, ( byte ) ( value >>> 40 ) ); + // Fallthrough !!! + + case 2: + pageData.put( pagePos + 1, ( byte ) ( value >>> 48 ) ); + // Fallthrough !!! + + case 1: + pageData.put( pagePos, ( byte ) ( value >>> 56 ) ); + break; + } + + // Now deal with the next page + pageData = pageIos[pageNb + 1].getData(); + pagePos = LINK_SIZE; + + switch ( remaining ) + { + case 1: + pageData.put( pagePos, ( byte ) ( value >>> 48 ) ); + // fallthrough !!! + + case 2: + pageData.put( pagePos + 2 - remaining, ( byte ) ( value >>> 40 ) ); + // fallthrough !!! + + case 3: + pageData.put( pagePos + 3 - remaining, ( byte ) ( value >>> 32 ) ); + // fallthrough !!! + + case 4: + pageData.put( pagePos + 4 - remaining, ( byte ) ( value >>> 24 ) ); + // fallthrough !!! + + case 5: + pageData.put( pagePos + 5 - remaining, ( byte ) ( value >>> 16 ) ); + // fallthrough !!! + + case 6: + pageData.put( pagePos + 6 - remaining, ( byte ) ( value >>> 8 ) ); + // fallthrough !!! + + case 7: + pageData.put( pagePos + 7 - remaining, ( byte ) ( value ) ); + break; + } + } + else + { + // Store the value in the page at the selected position + pageData.putLong( pagePos, value ); + } + + // Increment the position to reflect the addition of an Long (8 bytes) + position += LONG_SIZE; + + return position; + } + + + /** + * Write the page in a serialized form. + * + * @param btree The persistedBtree we will create a new PageHolder for + * @param newPage The page to write on disk + * @param newRevision The page's revision + * @return A PageHolder containing the copied page + * @throws IOException If the page can't be written on disk + */ + /* No qualifier*/ PageHolder writePage( BTree btree, Page newPage, + long newRevision ) throws IOException + { + // We first need to save the new page on disk + PageIO[] pageIos = serializePage( btree, newRevision, newPage ); + + if ( LOG_PAGES.isDebugEnabled() ) + { + LOG_PAGES.debug( "Write data for '{}' btree", btree.getName() ); + + logPageIos( pageIos ); + } + + // Write the page on disk + flushPages( pageIos ); + + // Build the resulting reference + long offset = pageIos[0].getOffset(); + long lastOffset = pageIos[pageIos.length - 1].getOffset(); + PersistedPageHolder pageHolder = new PersistedPageHolder( btree, newPage, offset, + lastOffset ); + + return pageHolder; + } + + + /* No qualifier */static void logPageIos( PageIO[] pageIos ) + { + int pageNb = 0; + + for ( PageIO pageIo : pageIos ) + { + StringBuilder sb = new StringBuilder(); + sb.append( "PageIO[" ).append( pageNb ).append( "]:0x" ); + sb.append( Long.toHexString( pageIo.getOffset() ) ).append( "/" ); + sb.append( pageIo.getSize() ); + pageNb++; + + ByteBuffer data = pageIo.getData(); + + int position = data.position(); + int dataLength = ( int ) pageIo.getSize() + 12; + + if ( dataLength > data.limit() ) + { + dataLength = data.limit(); + } + + byte[] bytes = new byte[dataLength]; + + data.get( bytes ); + data.position( position ); + int pos = 0; + + for ( byte b : bytes ) + { + int mod = pos % 16; + + switch ( mod ) + { + case 0: + sb.append( "\n " ); + // No break + case 4: + case 8: + case 12: + sb.append( " " ); + case 1: + case 2: + case 3: + case 5: + case 6: + case 7: + case 9: + case 10: + case 11: + case 13: + case 14: + case 15: + sb.append( Strings.dumpByte( b ) ).append( " " ); + } + pos++; + } + + LOG_PAGES.debug( sb.toString() ); + } + } + + + /** + * Compute the number of pages needed to store some specific size of data. + * + * @param dataSize The size of the data we want to store in pages + * @return The number of pages needed + */ + private int computeNbPages( int dataSize ) + { + if ( dataSize <= 0 ) + { + return 0; + } + + // Compute the number of pages needed. + // Considering that each page can contain PageSize bytes, + // but that the first 8 bytes are used for links and we + // use 4 bytes to store the data size, the number of needed + // pages is : + // NbPages = ( (dataSize - (PageSize - 8 - 4 )) / (PageSize - 8) ) + 1 + // NbPages += ( if (dataSize - (PageSize - 8 - 4 )) % (PageSize - 8) > 0 : 1 : 0 ) + int availableSize = ( pageSize - LONG_SIZE ); + int nbNeededPages = 1; + + // Compute the number of pages that will be full but the first page + if ( dataSize > availableSize - INT_SIZE ) + { + int remainingSize = dataSize - ( availableSize - INT_SIZE ); + nbNeededPages += remainingSize / availableSize; + int remain = remainingSize % availableSize; + + if ( remain > 0 ) + { + nbNeededPages++; + } + } + + return nbNeededPages; + } + + + /** + * Get as many pages as needed to store the data of the given size. The returned + * PageIOs are all linked together. + * + * @param dataSize The data size + * @return An array of pages, enough to store the full data + */ + private PageIO[] getFreePageIOs( int dataSize ) throws IOException + { + if ( dataSize == 0 ) + { + return new PageIO[] + {}; + } + + int nbNeededPages = computeNbPages( dataSize ); + + PageIO[] pageIOs = new PageIO[nbNeededPages]; + + // The first page : set the size + pageIOs[0] = fetchNewPage(); + pageIOs[0].setSize( dataSize ); + + for ( int i = 1; i < nbNeededPages; i++ ) + { + pageIOs[i] = fetchNewPage(); + + // Create the link + pageIOs[i - 1].setNextPage( pageIOs[i].getOffset() ); + } + + return pageIOs; + } + + + /** + * Return a new Page. We take one of the existing free pages, or we create + * a new page at the end of the file. + * + * @return The fetched PageIO + */ + private PageIO fetchNewPage() throws IOException + { + //System.out.println( "Fetching new page" ); + if ( firstFreePage == NO_PAGE ) + { + nbCreatedPages.incrementAndGet(); + + // We don't have any free page. Reclaim some new page at the end + // of the file + PageIO newPage = new PageIO( endOfFileOffset ); + + endOfFileOffset += pageSize; + + ByteBuffer data = ByteBuffer.allocateDirect( pageSize ); + + newPage.setData( data ); + newPage.setNextPage( NO_PAGE ); + newPage.setSize( 0 ); + + LOG.debug( "Requiring a new page at offset {}", newPage.getOffset() ); + + return newPage; + } + else + { + nbReusedPages.incrementAndGet(); + + freePageLock.lock(); + + // We have some existing free page. Fetch it from disk + PageIO pageIo = fetchPage( firstFreePage ); + + // Update the firstFreePage pointer + firstFreePage = pageIo.getNextPage(); + + freePageLock.unlock(); + + // overwrite the data of old page + ByteBuffer data = ByteBuffer.allocateDirect( pageSize ); + pageIo.setData( data ); + + pageIo.setNextPage( NO_PAGE ); + pageIo.setSize( 0 ); + + LOG.debug( "Reused page at offset {}", pageIo.getOffset() ); + + return pageIo; + } + } + + + /** + * fetch a page from disk, knowing its position in the file. + * + * @param offset The position in the file + * @return The found page + */ + /* no qualifier */PageIO fetchPage( long offset ) throws IOException, EndOfFileExceededException + { + checkOffset( offset ); + + if ( fileChannel.size() < offset + pageSize ) + { + // Error : we are past the end of the file + throw new EndOfFileExceededException( "We are fetching a page on " + offset + + " when the file's size is " + fileChannel.size() ); + } + else + { + // Read the page + fileChannel.position( offset ); + + ByteBuffer data = ByteBuffer.allocate( pageSize ); + fileChannel.read( data ); + data.rewind(); + + PageIO readPage = new PageIO( offset ); + readPage.setData( data ); + + return readPage; + } + } + + + /** + * @return the pageSize + */ + public int getPageSize() + { + return pageSize; + } + + + /** + * Set the page size, ie the number of bytes a page can store. + * + * @param pageSize The number of bytes for a page + */ + /* no qualifier */void setPageSize( int pageSize ) + { + if ( this.pageSize >= 13 ) + { + this.pageSize = pageSize; + } + else + { + this.pageSize = DEFAULT_PAGE_SIZE; + } + } + + + /** + * Close the RecordManager and flush everything on disk + */ + public void close() throws IOException + { + beginTransaction(); + + // Close all the managed B-trees + for ( BTree tree : managedBtrees.values() ) + { + tree.close(); + } + + // Close the management B-trees + copiedPageBtree.close(); + btreeOfBtrees.close(); + + managedBtrees.clear(); + + // Write the data + fileChannel.force( true ); + + // And close the channel + fileChannel.close(); + + commit(); + } + + /** Hex chars */ + private static final byte[] HEX_CHAR = new byte[] + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + + public static String dump( byte octet ) + { + return new String( new byte[] + { HEX_CHAR[( octet & 0x00F0 ) >> 4], HEX_CHAR[octet & 0x000F] } ); + } + + + /** + * Dump a pageIO + */ + private void dump( PageIO pageIo ) + { + ByteBuffer buffer = pageIo.getData(); + buffer.mark(); + byte[] longBuffer = new byte[LONG_SIZE]; + byte[] intBuffer = new byte[INT_SIZE]; + + // get the next page offset + buffer.get( longBuffer ); + long nextOffset = LongSerializer.deserialize( longBuffer ); + + // Get the data size + buffer.get( intBuffer ); + int size = IntSerializer.deserialize( intBuffer ); + + buffer.reset(); + + System.out.println( "PageIO[" + Long.toHexString( pageIo.getOffset() ) + "], size = " + size + ", NEXT PageIO:" + + Long.toHexString( nextOffset ) ); + System.out.println( " 0 1 2 3 4 5 6 7 8 9 A B C D E F " ); + System.out.println( "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" ); + + for ( int i = 0; i < buffer.limit(); i += 16 ) + { + System.out.print( "|" ); + + for ( int j = 0; j < 16; j++ ) + { + System.out.print( dump( buffer.get() ) ); + + if ( j == 15 ) + { + System.out.println( "|" ); + } + else + { + System.out.print( " " ); + } + } + } + + System.out.println( "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" ); + + buffer.reset(); + } + + + /** + * Dump the RecordManager file + * @throws IOException + */ + public void dump() + { + System.out.println( "/---------------------------- Dump ----------------------------\\" ); + + try + { + RandomAccessFile randomFile = new RandomAccessFile( file, "r" ); + FileChannel fileChannel = randomFile.getChannel(); + + ByteBuffer recordManagerHeader = ByteBuffer.allocate( RECORD_MANAGER_HEADER_SIZE ); + + // load the RecordManager header + fileChannel.read( recordManagerHeader ); + + recordManagerHeader.rewind(); + + // The page size + long fileSize = fileChannel.size(); + int pageSize = recordManagerHeader.getInt(); + long nbPages = fileSize / pageSize; + + // The number of managed B-trees + int nbBtree = recordManagerHeader.getInt(); + + // The first free page + long firstFreePage = recordManagerHeader.getLong(); + + // The current B-tree of B-trees + long currentBtreeOfBtreesPage = recordManagerHeader.getLong(); + + // The previous B-tree of B-trees + long previousBtreeOfBtreesPage = recordManagerHeader.getLong(); + + // The current CopiedPages B-tree + long currentCopiedPagesBtreePage = recordManagerHeader.getLong(); + + // The previous CopiedPages B-tree + long previousCopiedPagesBtreePage = recordManagerHeader.getLong(); + + System.out.println( " RecordManager" ); + System.out.println( " -------------" ); + System.out.println( " Size = 0x" + Long.toHexString( fileSize ) ); + System.out.println( " NbPages = " + nbPages ); + System.out.println( " Header " ); + System.out.println( " page size : " + pageSize ); + System.out.println( " nbTree : " + nbBtree ); + System.out.println( " firstFreePage : 0x" + Long.toHexString( firstFreePage ) ); + System.out.println( " current BOB : 0x" + Long.toHexString( currentBtreeOfBtreesPage ) ); + System.out.println( " previous BOB : 0x" + Long.toHexString( previousBtreeOfBtreesPage ) ); + System.out.println( " current CopiedPages : 0x" + Long.toHexString( currentCopiedPagesBtreePage ) ); + System.out.println( " previous CopiedPages : 0x" + Long.toHexString( previousCopiedPagesBtreePage ) ); + + // Dump the Free pages list + dumpFreePages( firstFreePage ); + + // Dump the B-tree of B-trees + dumpBtreeHeader( currentBtreeOfBtreesPage ); + + // Dump the previous B-tree of B-trees if any + if ( previousBtreeOfBtreesPage != NO_PAGE ) + { + dumpBtreeHeader( previousBtreeOfBtreesPage ); + } + + // Dump the CopiedPages B-tree + dumpBtreeHeader( currentCopiedPagesBtreePage ); + + // Dump the previous B-tree of B-trees if any + if ( previousCopiedPagesBtreePage != NO_PAGE ) + { + dumpBtreeHeader( previousCopiedPagesBtreePage ); + } + + // Dump all the user's B-tree + randomFile.close(); + System.out.println( "\\---------------------------- Dump ----------------------------/" ); + } + catch ( IOException ioe ) + { + System.out.println( "Exception while dumping the file : " + ioe.getMessage() ); + } + } + + + /** + * Dump the free pages + */ + private void dumpFreePages( long freePageOffset ) throws EndOfFileExceededException, IOException + { + System.out.println( "\n FreePages : " ); + int pageNb = 1; + + while ( freePageOffset != NO_PAGE ) + { + PageIO pageIo = fetchPage( freePageOffset ); + + System.out.println( " freePage[" + pageNb + "] : 0x" + Long.toHexString( pageIo.getOffset() ) ); + + freePageOffset = pageIo.getNextPage(); + pageNb++; + } + } + + + /** + * Dump a B-tree Header + */ + private long dumpBtreeHeader( long btreeOffset ) throws EndOfFileExceededException, IOException + { + // First read the B-tree header + PageIO[] pageIos = readPageIOs( btreeOffset, Long.MAX_VALUE ); + + long dataPos = 0L; + + // The B-tree current revision + long revision = readLong( pageIos, dataPos ); + dataPos += LONG_SIZE; + + // The nb elems in the tree + long nbElems = readLong( pageIos, dataPos ); + dataPos += LONG_SIZE; + + // The B-tree rootPage offset + long rootPageOffset = readLong( pageIos, dataPos ); + dataPos += LONG_SIZE; + + // The B-tree page size + int btreePageSize = readInt( pageIos, dataPos ); + dataPos += INT_SIZE; + + // The tree name + ByteBuffer btreeNameBytes = readBytes( pageIos, dataPos ); + dataPos += INT_SIZE + btreeNameBytes.limit(); + String btreeName = Strings.utf8ToString( btreeNameBytes ); + + // The keySerializer FQCN + ByteBuffer keySerializerBytes = readBytes( pageIos, dataPos ); + dataPos += INT_SIZE + keySerializerBytes.limit(); + + String keySerializerFqcn = ""; + + if ( keySerializerBytes != null ) + { + keySerializerFqcn = Strings.utf8ToString( keySerializerBytes ); + } + + // The valueSerialier FQCN + ByteBuffer valueSerializerBytes = readBytes( pageIos, dataPos ); + + String valueSerializerFqcn = ""; + dataPos += INT_SIZE + valueSerializerBytes.limit(); + + if ( valueSerializerBytes != null ) + { + valueSerializerFqcn = Strings.utf8ToString( valueSerializerBytes ); + } + + // The B-tree allowDuplicates flag + int allowDuplicates = readInt( pageIos, dataPos ); + boolean dupsAllowed = allowDuplicates != 0; + + dataPos += INT_SIZE; + + // System.out.println( "\n B-Tree " + btreeName ); + // System.out.println( " ------------------------- " ); + + // System.out.println( " nbPageIOs[" + pageIos.length + "] = " + pageIoList ); + if ( LOG.isDebugEnabled() ) + { + StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + + for ( PageIO pageIo : pageIos ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( "0x" ).append( Long.toHexString( pageIo.getOffset() ) ); + } + + String pageIoList = sb.toString(); + + LOG.debug( " PageIOs[{}] = {}", pageIos.length, pageIoList ); + + // System.out.println( " dataSize = "+ pageIos[0].getSize() ); + LOG.debug( " dataSize = {}", pageIos[0].getSize() ); + + LOG.debug( " B-tree '{}'", btreeName ); + LOG.debug( " revision : {}", revision ); + LOG.debug( " nbElems : {}", nbElems ); + LOG.debug( " rootPageOffset : 0x{}", Long.toHexString( rootPageOffset ) ); + LOG.debug( " B-tree page size : {}", btreePageSize ); + LOG.debug( " keySerializer : '{}'", keySerializerFqcn ); + LOG.debug( " valueSerializer : '{}'", valueSerializerFqcn ); + LOG.debug( " dups allowed : {}", dupsAllowed ); + // + // System.out.println( " B-tree '" + btreeName + "'" ); + // System.out.println( " revision : " + revision ); + // System.out.println( " nbElems : " + nbElems ); + // System.out.println( " rootPageOffset : 0x" + Long.toHexString( rootPageOffset ) ); + // System.out.println( " B-tree page size : " + btreePageSize ); + // System.out.println( " keySerializer : " + keySerializerFqcn ); + // System.out.println( " valueSerializer : " + valueSerializerFqcn ); + // System.out.println( " dups allowed : " + dupsAllowed ); + } + + return rootPageOffset; + } + + + /** + * Get the number of managed trees. We don't count the CopiedPage B-tree and the B-tree of B-trees + * + * @return The number of managed B-trees + */ + public int getNbManagedTrees() + { + return nbBtree; + } + + + /** + * Get the managed B-trees. We don't return the CopiedPage B-tree nor the B-tree of B-trees. + * + * @return The managed B-trees + */ + public Set getManagedTrees() + { + Set btrees = new HashSet( managedBtrees.keySet() ); + + return btrees; + } + + + /** + * Stores the copied pages into the CopiedPages B-tree + * + * @param name The B-tree name + * @param revision The revision + * @param copiedPages The pages that have been copied while creating this revision + * @throws IOException If we weren't able to store the data on disk + */ + /* No Qualifier */void storeCopiedPages( String name, long revision, long[] copiedPages ) throws IOException + { + RevisionName revisionName = new RevisionName( revision, name ); + + copiedPageBtree.insert( revisionName, copiedPages ); + } + + + /** + * Store a reference to an old rootPage into the Revision B-tree + * + * @param btree The B-tree we want to keep an old RootPage for + * @param rootPage The old rootPage + * @throws IOException If we have an issue while writing on disk + */ + /* No qualifier */ void storeRootPage( BTree btree, Page rootPage ) throws IOException + { + if ( !isKeepRevisions() ) + { + return; + } + + if ( btree == copiedPageBtree ) + { + return; + } + + NameRevision nameRevision = new NameRevision( btree.getName(), rootPage.getRevision() ); + + ( ( AbstractBTree ) btreeOfBtrees ).insert( nameRevision, + ( ( AbstractPage ) rootPage ).getOffset(), 0 ); + + if ( LOG_CHECK.isDebugEnabled() ) + { + MavibotInspector.check( this ); + } + } + + + /** + * Fetch the rootPage of a given B-tree for a given revision. + * + * @param btree The B-tree we are interested in + * @param revision The revision we want to get back + * @return The rootPage for this B-tree and this revision, if any + * @throws KeyNotFoundException If we can't find the rootPage for this revision and this B-tree + * @throws IOException If we had an ise while accessing the data on disk + */ + /* No qualifier */ Page getRootPage( BTree btree, long revision ) throws KeyNotFoundException, + IOException + { + if ( btree.getRevision() == revision ) + { + // We are asking for the current revision + return btree.getRootPage(); + } + + // Get the B-tree header offset + NameRevision nameRevision = new NameRevision( btree.getName(), revision ); + long btreeHeaderOffset = btreeOfBtrees.get( nameRevision ); + + // get the B-tree rootPage + Page btreeRoot = readRootPage( btree, btreeHeaderOffset ); + + return btreeRoot; + } + + + /** + * Read a root page from the B-tree header offset + */ + private Page readRootPage( BTree btree, long btreeHeaderOffset ) + throws EndOfFileExceededException, IOException + { + // Read the B-tree header pages on disk + PageIO[] btreeHeaderPageIos = readPageIOs( btreeHeaderOffset, Long.MAX_VALUE ); + long dataPos = LONG_SIZE + LONG_SIZE; + + // The B-tree rootPage offset + long rootPageOffset = readLong( btreeHeaderPageIos, dataPos ); + + // Read the rootPage pages on disk + PageIO[] rootPageIos = readPageIOs( rootPageOffset, Long.MAX_VALUE ); + + // Now, convert it to a Page + Page btreeRoot = readPage( btree, rootPageIos ); + + return btreeRoot; + } + + + /** + * Get one managed trees, knowing its name. + * + * @param name The B-tree name we are looking for + * @return The managed B-trees + */ + public BTree getManagedTree( String name ) + { + return ( BTree ) managedBtrees.get( name ); + } + + + /** + * Move a list of pages to the free page list. A logical page is associated with one + * or more physical PageIOs, which are on the disk. We have to move all those PagIO instances + * to the free list, and do the same in memory (we try to keep a reference to a set of + * free pages. + * + * @param btree The B-tree which were owning the pages + * @param revision The current revision + * @param pages The pages to free + * @throws IOException If we had a problem while updating the file + * @throws EndOfFileExceededException If we tried to write after the end of the file + */ + /* Package protected */ void freePages( BTree btree, long revision, List> pages ) + throws EndOfFileExceededException, IOException + { + if ( ( pages == null ) || pages.isEmpty() ) + { + return; + } + + if ( !keepRevisions ) + { + // if the B-tree doesn't keep revisions, we can safely move + // the pages to the freed page list. + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Freeing the following pages :" ); + + for ( Page page : pages ) + { + LOG.debug( " {}", page ); + } + } + + for ( Page page : pages ) + { + long pageOffset = ( ( AbstractPage ) page ).getOffset(); + + PageIO[] pageIos = readPageIOs( pageOffset, Long.MAX_VALUE ); + + for ( PageIO pageIo : pageIos ) + { + freedPages.add( pageIo ); + } + } + } + else + { + // We are keeping revisions of standard B-trees, so we move the pages to the CopiedPages B-tree + // but only for non managed B-trees + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Moving the following pages to the CopiedBtree :" ); + + for ( Page page : pages ) + { + LOG.debug( " {}", page ); + } + } + + long[] pageOffsets = new long[pages.size()]; + int pos = 0; + + for ( Page page : pages ) + { + pageOffsets[pos++] = ( ( AbstractPage ) page ).offset; + } + + if ( ( btree.getType() != BTreeTypeEnum.BTREE_OF_BTREES ) + && ( btree.getType() != BTreeTypeEnum.COPIED_PAGES_BTREE ) ) + { + // Deal with standard B-trees + RevisionName revisionName = new RevisionName( revision, btree.getName() ); + + copiedPageBtree.insert( revisionName, pageOffsets ); + + // Update the RecordManager Copiedpage Offset + currentCopiedPagesBtreeOffset = ( ( PersistedBTree ) copiedPageBtree ) + .getBtreeOffset(); + } + else + { + // Managed B-trees : we simply free the copied pages + for ( long pageOffset : pageOffsets ) + { + PageIO[] pageIos = readPageIOs( pageOffset, Long.MAX_VALUE ); + + for ( PageIO pageIo : pageIos ) + { + freedPages.add( pageIo ); + } + } + } + } + } + + + /** + * Add a PageIO to the list of free PageIOs + * + * @param pageIo The page to free + * @throws IOException If we weren't capable of updating the file + */ + /* no qualifier */ void free( PageIO pageIo ) throws IOException + { + freePageLock.lock(); + + // We add the Page's PageIOs before the + // existing free pages. + // Link it to the first free page + pageIo.setNextPage( firstFreePage ); + + LOG.debug( "Flushing the first free page" ); + + // And flush it to disk + //FIXME can be flushed last after releasing the lock + flushPages( pageIo ); + + // We can update the firstFreePage offset + firstFreePage = pageIo.getOffset(); + + freePageLock.unlock(); + } + + + /** + * Add an array of PageIOs to the list of free PageIOs + * + * @param offsets The offsets of the pages whose associated PageIOs will be fetched and freed. + * @throws IOException If we weren't capable of updating the file + */ + /*no qualifier*/ void free( long... offsets ) throws IOException + { + freePageLock.lock(); + + List pageIos = new ArrayList(); + int pageIndex = 0; + for ( int i = 0; i < offsets.length; i++ ) + { + PageIO[] ios = readPageIOs( offsets[i], Long.MAX_VALUE ); + + for ( PageIO io : ios ) + { + pageIos.add( io ); + + if ( pageIndex > 0 ) + { + pageIos.get( pageIndex - 1 ).setNextPage( io.getOffset() ); + } + + pageIndex++; + } + } + + // We add the Page's PageIOs before the + // existing free pages. + // Link it to the first free page + pageIos.get( pageIndex - 1 ).setNextPage( firstFreePage ); + + LOG.debug( "Flushing the first free page" ); + + // And flush it to disk + //FIXME can be flushed last after releasing the lock + flushPages( pageIos.toArray( new PageIO[0] ) ); + + // We can update the firstFreePage offset + firstFreePage = pageIos.get( 0 ).getOffset(); + + freePageLock.unlock(); + } + + + /** + * @return the keepRevisions flag + */ + public boolean isKeepRevisions() + { + return keepRevisions; + } + + + /** + * @param keepRevisions the keepRevisions flag to set + */ + public void setKeepRevisions( boolean keepRevisions ) + { + this.keepRevisions = keepRevisions; + } + + + /** + * Creates a B-tree and automatically adds it to the list of managed btrees + * + * @param name the name of the B-tree + * @param keySerializer key serializer + * @param valueSerializer value serializer + * @param allowDuplicates flag for allowing duplicate keys + * @return a managed B-tree + * @throws IOException If we weren't able to update the file on disk + * @throws BTreeAlreadyManagedException If the B-tree is already managed + */ + @SuppressWarnings("all") + public BTree addBTree( String name, ElementSerializer keySerializer, + ElementSerializer valueSerializer, boolean allowDuplicates ) + throws IOException, BTreeAlreadyManagedException + { + PersistedBTreeConfiguration config = new PersistedBTreeConfiguration(); + + config.setName( name ); + config.setKeySerializer( keySerializer ); + config.setValueSerializer( valueSerializer ); + config.setAllowDuplicates( allowDuplicates ); + + BTree btree = new PersistedBTree( config ); + manage( btree ); + + if ( LOG_CHECK.isDebugEnabled() ) + { + MavibotInspector.check( this ); + } + + return btree; + } + + + /** + * Add a newly closd transaction into the closed transaction queue + */ + /* no qualifier */ void releaseTransaction( ReadTransaction readTransaction ) + { + RevisionName revisionName = new RevisionName( + readTransaction.getRevision(), + readTransaction.getBtreeHeader().getBtree().getName() ); + //closedTransactionsQueue.add( revisionName ); + } + + + /** + * Get the current BTreeHeader for a given Btree. It might not exist + */ + public BTreeHeader getBTreeHeader( String name ) + { + // Get a lock + btreeHeadersLock.readLock().lock(); + + // get the current BTree Header for this BTree and revision + BTreeHeader btreeHeader = currentBTreeHeaders.get( name ); + + // And unlock + btreeHeadersLock.readLock().unlock(); + + return btreeHeader; + } + + + /** + * Get the new BTreeHeader for a given Btree. It might not exist + */ + public BTreeHeader getNewBTreeHeader( String name ) + { + // get the current BTree Header for this BTree and revision + BTreeHeader btreeHeader = newBTreeHeaders.get( name ); + + return btreeHeader; + } + + + /** + * {@inheritDoc} + */ + public void updateNewBTreeHeaders( BTreeHeader btreeHeader ) + { + newBTreeHeaders.put( btreeHeader.getBtree().getName(), btreeHeader ); + } + + + /** + * Swap the current BtreeHeader map with the new one. This method will only + * be called in a single trhead, when the current transaction will be committed. + */ + private void swapCurrentBtreeHeaders() + { + // Copy the reference to the current BtreeHeader Map + Map> tmp = currentBTreeHeaders; + + // Get a write lock + btreeHeadersLock.writeLock().lock(); + + // Swap the new BTreeHeader Map + currentBTreeHeaders = newBTreeHeaders; + + // And unlock + btreeHeadersLock.writeLock().unlock(); + + // Last, not least, clear the Map and reinject the latest revision in it + tmp.clear(); + tmp.putAll( currentBTreeHeaders ); + + // And update the new BTreeHeader map + newBTreeHeaders = tmp; + } + + + /** + * revert the new BTreeHeaders Map to the current BTreeHeader Map. This method + * is called when we have to rollback a transaction. + */ + private void revertBtreeHeaders() + { + // Clean up teh new BTreeHeaders Map + newBTreeHeaders.clear(); + + // Reinject the latest revision in it + newBTreeHeaders.putAll( currentBTreeHeaders ); + } + + + /** + * Loads a B-tree holding the values of a duplicate key + * This tree is also called as dups tree or sub tree + * + * @param offset the offset of the B-tree header + * @return the deserialized B-tree + */ + /* No qualifier */ BTree loadDupsBtree( long btreeHeaderOffset, BTree parentBtree ) + { + PageIO[] pageIos = null; + try + { + pageIos = readPageIOs( btreeHeaderOffset, Long.MAX_VALUE ); + + BTree subBtree = BTreeFactory. createPersistedBTree( BTreeTypeEnum.PERSISTED_SUB ); + loadBtree( pageIos, subBtree, parentBtree ); + + return subBtree; + } + catch ( Exception e ) + { + // should not happen + throw new BTreeCreationException( e ); + } + } + + + private void checkFreePages() throws EndOfFileExceededException, IOException + { + //System.out.println( "Checking the free pages, starting from " + Long.toHexString( firstFreePage ) ); + + // read all the free pages, add them into a set, to be sure we don't have a cycle + Set freePageOffsets = new HashSet(); + + long currentFreePageOffset = firstFreePage; + + while ( currentFreePageOffset != NO_PAGE ) + { + //System.out.println( "Next page offset :" + Long.toHexString( currentFreePageOffset ) ); + + if ( ( currentFreePageOffset % pageSize ) != 0 ) + { + throw new InvalidOffsetException( "Wrong offset : " + Long.toHexString( currentFreePageOffset ) ); + } + + if ( freePageOffsets.contains( currentFreePageOffset ) ) + { + throw new InvalidOffsetException( "Offset : " + Long.toHexString( currentFreePageOffset ) + + " already read, there is a cycle" ); + } + + freePageOffsets.add( currentFreePageOffset ); + PageIO pageIO = fetchPage( currentFreePageOffset ); + + currentFreePageOffset = pageIO.getNextPage(); + } + + return; + } + + + /** + * sets the threshold of the number of commits to be performed before + * reclaiming the free pages. + * + * @param pageReclaimerThreshold the number of commits before the reclaimer runs + */ + /* no qualifier */ void setPageReclaimerThreshold( int pageReclaimerThreshold ) + { + this.pageReclaimerThreshold = pageReclaimerThreshold; + } + + + /* no qualifier */void _disableReclaimer( boolean toggle ) + { + this.disableReclaimer = toggle; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "RM free pages : [" ); + + if ( firstFreePage != NO_PAGE ) + { + long current = firstFreePage; + boolean isFirst = true; + + while ( current != NO_PAGE ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + PageIO pageIo; + + try + { + pageIo = fetchPage( current ); + sb.append( pageIo.getOffset() ); + current = pageIo.getNextPage(); + } + catch ( EndOfFileExceededException e ) + { + e.printStackTrace(); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + + } + } + + sb.append( "]" ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RemoveResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RemoveResult.java new file mode 100644 index 000000000..afd7a4927 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RemoveResult.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.directory.mavibot.btree; + + +import java.util.List; + + +/** + * The result of a delete operation, when the child has not been merged. It contains the + * reference to the modified page, and the removed element. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/class RemoveResult extends AbstractDeleteResult +{ + /** + * The default constructor for RemoveResult. + * + * @param modifiedPage The modified page + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + public RemoveResult( Page modifiedPage, Tuple removedElement ) + { + super( modifiedPage, removedElement ); + } + + + /** + * A constructor for RemoveResult which takes a list of copied pages. + * + * @param copiedPages the list of copied pages + * @param modifiedPage The modified page + * @param removedElement The removed element (can be null if the key wasn't present in the tree) + */ + public RemoveResult( List> copiedPages, Page modifiedPage, Tuple removedElement ) + { + super( copiedPages, modifiedPage, removedElement ); + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "RemoveResult :" ); + sb.append( "\n removed element = " ).append( getRemovedElement() ); + sb.append( "\n modifiedPage = " ).append( getModifiedPage() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Result.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Result.java new file mode 100644 index 000000000..9f6b19514 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Result.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.List; + + +/** + * The result of an insert or delete operation. + * + * @param The type for the Key + * @param The type for the stored value + + * @author Apache Directory Project + */ +/* No qualifier*/interface Result

          +{ + /** + * @return the copiedPage + */ + List

          getCopiedPages(); + + + /** + * Add a new copied page + * @param copiedPage the added page + */ + void addCopiedPage( P copiedPage ); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionName.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionName.java new file mode 100644 index 000000000..1483168e1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionName.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + +import java.io.Serializable; + + +/** + * A data structure that stores a revision associated to a BTree name. We use + * it to allow the access to old revisions. + * + * @author Apache Directory Project + */ +/* no qualifier*/class RevisionName extends Tuple implements Serializable +{ + /** + * for serialization purpose + */ + protected RevisionName() + { + } + + + /** + * A constructor for the RevisionName class + * @param revision The revision + * @param name The BTree name + */ + /* no qualifier*/RevisionName( long revision, String name ) + { + super( revision, name ); + } + + + /** + * @return the revision + */ + /* no qualifier*/long getRevision() + { + return getKey(); + } + + + /** + * @param revision the revision to set + */ + /* no qualifier*/void setRevision( long revision ) + { + setKey( revision ); + } + + + /** + * @return the btree name + */ + /* no qualifier*/String getName() + { + return getValue(); + } + + + /** + * @param name the btree name to set + */ + /* no qualifier*/void setName( String name ) + { + setValue( name ); + } + + + /** + * @see Object#equals(Object) + */ + public boolean equals( Object that ) + { + if ( this == that ) + { + return true; + } + + if ( !( that instanceof RevisionName ) ) + { + return false; + } + + RevisionName revisionName = ( RevisionName ) that; + + if ( getRevision() != revisionName.getRevision() ) + { + return false; + } + + if ( getName() == null ) + { + return revisionName.getName() == null; + } + + return ( getName().equals( revisionName.getName() ) ); + + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ( ( getName() == null ) ? 0 : getName().hashCode() ); + result = prime * result + ( int ) ( getRevision() ^ ( getRevision() >>> 32 ) ); + return result; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + return "[" + getRevision() + ":" + getName() + "]"; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionNameComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionNameComparator.java new file mode 100644 index 000000000..1d3e62d3a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionNameComparator.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.Comparator; + + +/** + * A comparator for the RevisionName class + * + * @author Apache Directory Project + */ +/* no qualifier*/class RevisionNameComparator implements Comparator +{ + /** A static instance of a RevisionNameComparator */ + public static final RevisionNameComparator INSTANCE = new RevisionNameComparator(); + + /** + * A private constructor of the RevisionNameComparator class + */ + private RevisionNameComparator() + { + } + + + /** + * {@inheritDoc} + */ + public int compare( RevisionName rn1, RevisionName rn2 ) + { + if ( rn1 == rn2 ) + { + return 0; + } + + // First compare the revisions + if ( rn1.getRevision() < rn2.getRevision() ) + { + return -1; + } + else if ( rn1.getRevision() > rn2.getRevision() ) + { + return 1; + } + + // The revision are equal : check the name + return rn1.getName().compareTo( rn2.getName() ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionNameSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionNameSerializer.java new file mode 100644 index 000000000..b5920e305 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionNameSerializer.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.directory.mavibot.btree; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; +import org.apache.directory.mavibot.btree.serializer.AbstractElementSerializer; +import org.apache.directory.mavibot.btree.serializer.BufferHandler; +import org.apache.directory.mavibot.btree.serializer.ByteArraySerializer; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.apache.directory.mavibot.btree.util.Strings; + + +/** + * A serializer for the RevisionName object. The RevisionName will be serialized + * as a long (the revision), followed by the String. + * + * @author Apache Directory Project + */ +/* no qualifier*/class RevisionNameSerializer extends AbstractElementSerializer +{ + /** A static instance of a RevisionNameSerializer */ + /*No qualifier*/ final static RevisionNameSerializer INSTANCE = new RevisionNameSerializer(); + + /** + * Create a new instance of a RevisionNameSerializer + */ + private RevisionNameSerializer() + { + super( RevisionNameComparator.INSTANCE ); + } + + + /** + * A static method used to deserialize a RevisionName from a byte array. + * + * @param in The byte array containing the RevisionName + * @return A RevisionName instance + */ + /* no qualifier*/static RevisionName deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a RevisionName from a byte array. + * + * @param in The byte array containing the RevisionName + * @param start the position in the byte[] we will deserialize the RevisionName from + * @return A RevisionName instance + */ + /* no qualifier*/static RevisionName deserialize( byte[] in, int start ) + { + // The buffer must be 8 bytes plus 4 bytes long (the revision is a long, and the name is a String + if ( ( in == null ) || ( in.length < 12 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a RevisionName from a buffer with not enough bytes" ); + } + + long revision = LongSerializer.deserialize( in, start ); + String name = StringSerializer.deserialize( in, 8 + start ); + + RevisionName revisionName = new RevisionName( revision, name ); + + return revisionName; + } + + + /** + * A static method used to deserialize a RevisionName from a byte array. + * + * @param in The byte array containing the RevisionName + * @return A RevisionName instance + */ + public RevisionName fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a RevisionName from a byte array. + * + * @param in The byte array containing the RevisionName + * @param start the position in the byte[] we will deserialize the RevisionName from + * @return A RevisionName instance + */ + public RevisionName fromBytes( byte[] in, int start ) + { + // The buffer must be 8 bytes plus 4 bytes long (the revision is a long, and the name is a String + if ( ( in == null ) || ( in.length < 12 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a RevisionName from a buffer with not enough bytes" ); + } + + long revision = LongSerializer.deserialize( in, start ); + String name = StringSerializer.deserialize( in, 8 + start ); + + RevisionName revisionName = new RevisionName( revision, name ); + + return revisionName; + } + + + /** + * {@inheritDoc} + */ + @Override + public byte[] serialize( RevisionName revisionName ) + { + if ( revisionName == null ) + { + throw new SerializerCreationException( "The revisionName instance should not be null " ); + } + + byte[] result = null; + + if ( revisionName.getName() != null ) + { + byte[] stringBytes = Strings.getBytesUtf8( revisionName.getName() ); + int stringLen = stringBytes.length; + result = new byte[8 + 4 + stringBytes.length]; + LongSerializer.serialize( result, 0, revisionName.getRevision() ); + + if ( stringLen > 0 ) + { + ByteArraySerializer.serialize( result, 8, stringBytes ); + } + } + else + { + result = new byte[8 + 4]; + LongSerializer.serialize( result, 0, revisionName.getRevision() ); + StringSerializer.serialize( result, 8, null ); + } + + return result; + } + + + /** + * Serialize a RevisionName + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized RevisionName + * @param value the value to serialize + * @return The byte[] containing the serialized RevisionName + */ + /* no qualifier*/static byte[] serialize( byte[] buffer, int start, RevisionName revisionName ) + { + if ( revisionName.getName() != null ) + { + byte[] stringBytes = Strings.getBytesUtf8( revisionName.getName() ); + int stringLen = stringBytes.length; + LongSerializer.serialize( buffer, start, revisionName.getRevision() ); + IntSerializer.serialize( buffer, 8 + start, stringLen ); + ByteArraySerializer.serialize( buffer, 12 + start, stringBytes ); + } + else + { + LongSerializer.serialize( buffer, start, revisionName.getRevision() ); + StringSerializer.serialize( buffer, 8, null ); + } + + return buffer; + } + + + /** + * {@inheritDoc} + */ + @Override + public RevisionName deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] revisionBytes = bufferHandler.read( 8 ); + long revision = LongSerializer.deserialize( revisionBytes ); + + byte[] lengthBytes = bufferHandler.read( 4 ); + + int len = IntSerializer.deserialize( lengthBytes ); + + switch ( len ) + { + case 0: + return new RevisionName( revision, "" ); + + case -1: + return new RevisionName( revision, null ); + + default: + byte[] nameBytes = bufferHandler.read( len ); + + return new RevisionName( revision, Strings.utf8ToString( nameBytes ) ); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public RevisionName deserialize( ByteBuffer buffer ) throws IOException + { + // The revision + long revision = buffer.getLong(); + + // The name's length + int len = buffer.getInt(); + + switch ( len ) + { + case 0: + return new RevisionName( revision, "" ); + + case -1: + return new RevisionName( revision, null ); + + default: + byte[] nameBytes = new byte[len]; + buffer.get( nameBytes ); + + return new RevisionName( revision, Strings.utf8ToString( nameBytes ) ); + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffset.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffset.java new file mode 100644 index 000000000..695e9fdab --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffset.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.directory.mavibot.btree; + + +import java.util.Arrays; + + +/** + * A class to hold name, revision, and copied page offsets of a B-Tree. + * + * @author Apache Directory Project + */ +public class RevisionOffset +{ + /** the revision number */ + private long revision; + + /** offsets of copied pages */ + private long[] offsets; + + + /** + * Creates a new instance of RevisionOffset. + * + * @param revision the revision number + * @param offsets array of copied page offsets + */ + public RevisionOffset( long revision, long[] offsets ) + { + this.revision = revision; + this.offsets = offsets; + } + + + public long getRevision() + { + return revision; + } + + + /* no qualifier */void setRevision( long revision ) + { + this.revision = revision; + } + + + public long[] getOffsets() + { + return offsets; + } + + + /* no qualifier */void setOffsets( long[] offsets ) + { + this.offsets = offsets; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ( int ) ( revision ^ ( revision >>> 32 ) ); + return result; + } + + + @Override + public boolean equals( Object obj ) + { + if ( this == obj ) + { + return true; + } + + if ( obj == null ) + { + return false; + } + + RevisionOffset other = ( RevisionOffset ) obj; + + if ( revision != other.revision ) + { + return false; + } + + return true; + } + + + @Override + public String toString() + { + return "RevisionOffset [revision=" + revision + ", offsets=" + Arrays.toString( offsets ) + "]"; + } + +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffsetComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffsetComparator.java new file mode 100644 index 000000000..cb2940dcb --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffsetComparator.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.Arrays; +import java.util.Comparator; + + +/** + * A comparator for the RevisionOffset class + * + * @author Apache Directory Project + */ +/* no qualifier*/class RevisionOffsetComparator implements Comparator +{ + /** A static instance of a RevisionOffsetComparator */ + public static final RevisionOffsetComparator INSTANCE = new RevisionOffsetComparator(); + + public static final RevisionOffsetComparator INSTANCE_DESC_ORDER = new RevisionOffsetComparator( true ); + + private boolean desc; + + /** + * A private constructor of the RevisionOffsetComparator class + */ + private RevisionOffsetComparator() + { + } + + + private RevisionOffsetComparator( boolean desc ) + { + this.desc = desc; + } + + + /** + * {@inheritDoc} + */ + public int compare( RevisionOffset rn1, RevisionOffset rn2 ) + { + if ( rn1 == rn2 ) + { + return 0; + } + + // the RevisionOffset will never be used as a key + + // First compare the revisions + if ( rn1.getRevision() < rn2.getRevision() ) + { + return desc ? 1 : -1; + } + else if ( rn1.getRevision() > rn2.getRevision() ) + { + return desc ? -1 : 1; + } + + // ignore the offsets + return 0; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffsetSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffsetSerializer.java new file mode 100644 index 000000000..57a8bbb2c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffsetSerializer.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; +import org.apache.directory.mavibot.btree.serializer.AbstractElementSerializer; +import org.apache.directory.mavibot.btree.serializer.BufferHandler; +import org.apache.directory.mavibot.btree.serializer.LongArraySerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; + + +/** + * A serializer for the RevisionOffset object. The RevisionOffset will be serialized + * as a long (the revision), followed by the long[]. + * + * @author Apache Directory Project + */ +/* no qualifier*/class RevisionOffsetSerializer extends AbstractElementSerializer +{ + /** A static instance of a RevisionOffsetSerializer */ + /*No qualifier*/ final static RevisionOffsetSerializer INSTANCE = new RevisionOffsetSerializer(); + + /** + * Create a new instance of a RevisionOffsetSerializer + */ + private RevisionOffsetSerializer() + { + super( RevisionOffsetComparator.INSTANCE ); + } + + + /** + * A static method used to deserialize a RevisionOffset from a byte array. + * + * @param in The byte array containing the RevisionOffset + * @return A RevisionOffset instance + */ + /* no qualifier*/static RevisionOffset deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a RevisionOffset from a byte array. + * + * @param in The byte array containing the RevisionOffset + * @param start the position in the byte[] we will deserialize the RevisionOffset from + * @return A RevisionOffset instance + */ + /* no qualifier*/static RevisionOffset deserialize( byte[] in, int start ) + { + // The buffer must be 8 bytes + if ( ( in == null ) || ( in.length < 8 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a RevisionOffset from a buffer with not enough bytes" ); + } + + long revision = LongSerializer.deserialize( in, start ); + + try + { + long[] offsets = LongArraySerializer.INSTANCE.fromBytes( in, 8 + start ); + + RevisionOffset RevisionOffset = new RevisionOffset( revision, offsets ); + + return RevisionOffset; + } + catch( IOException e ) + { + throw new RuntimeException( e ); + } + } + + + /** + * A static method used to deserialize a RevisionOffset from a byte array. + * + * @param in The byte array containing the RevisionOffset + * @return A RevisionOffset instance + */ + public RevisionOffset fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a RevisionOffset from a byte array. + * + * @param in The byte array containing the RevisionOffset + * @param start the position in the byte[] we will deserialize the RevisionOffset from + * @return A RevisionOffset instance + */ + public RevisionOffset fromBytes( byte[] in, int start ) + { + // The buffer must be 8 bytes long (the revision is a long, and the name is a String + if ( ( in == null ) || ( in.length < 8 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a RevisionOffset from a buffer with not enough bytes" ); + } + + return deserialize( in, start ); + } + + + /** + * {@inheritDoc} + */ + @Override + public byte[] serialize( RevisionOffset RevisionOffset ) + { + if ( RevisionOffset == null ) + { + throw new SerializerCreationException( "The RevisionOffset instance should not be null " ); + } + + byte[] result = null; + + byte[] offsets = LongArraySerializer.INSTANCE.serialize( RevisionOffset.getOffsets() ); + result = new byte[8 + offsets.length]; + LongSerializer.serialize( result, 0, RevisionOffset.getRevision() ); + + System.arraycopy( offsets, 0, result, 8, offsets.length ); + + return result; + } + + + /** + * Serialize a RevisionOffset + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized RevisionOffset + * @param value the value to serialize + * @return The byte[] containing the serialized RevisionOffset + */ + /* no qualifier*/static byte[] serialize( byte[] buffer, int start, RevisionOffset RevisionOffset ) + { + LongSerializer.serialize( buffer, start, RevisionOffset.getRevision() ); + + byte[] offsets = LongArraySerializer.INSTANCE.serialize( RevisionOffset.getOffsets() ); + + System.arraycopy( offsets, 0, buffer, 8 + start, offsets.length ); + + return buffer; + } + + + /** + * {@inheritDoc} + */ + @Override + public RevisionOffset deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] revisionBytes = bufferHandler.read( 8 ); + long revision = LongSerializer.deserialize( revisionBytes ); + + long[] offsets = LongArraySerializer.INSTANCE.deserialize( bufferHandler ); + + return new RevisionOffset( revision, offsets ); + } + + + /** + * {@inheritDoc} + */ + @Override + public RevisionOffset deserialize( ByteBuffer buffer ) throws IOException + { + // The revision + long revision = buffer.getLong(); + + long[] offsets = LongArraySerializer.INSTANCE.deserialize( buffer ); + + return new RevisionOffset( revision, offsets ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/SplitResult.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/SplitResult.java new file mode 100644 index 000000000..1d8ba7c5e --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/SplitResult.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.directory.mavibot.btree; + + +import java.util.List; + + +/** + * The result of an insert operation, when the page has been split. It contains + * the new pivotal value, plus the reference on the two new pages. + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +/* No qualifier*/class SplitResult extends AbstractResult implements InsertResult +{ + /** The left child */ + protected Page leftPage; + + /** The right child */ + protected Page rightPage; + + /** The key pivot */ + protected K pivot; + + + /** + * The default constructor for SplitResult. + * @param pivot The new key to insert into the parent + * @param leftPage The new left page + * @param rightPage The new right page + */ + public SplitResult( K pivot, Page leftPage, Page rightPage ) + { + super(); + this.pivot = pivot; + this.leftPage = leftPage; + this.rightPage = rightPage; + } + + + /** + * A constructor for SplitResult with copied pages. + * + * @param copiedPages the list of copied pages + * @param pivot The new key to insert into the parent + * @param leftPage The new left page + * @param rightPage The new right page + */ + public SplitResult( List> copiedPages, K pivot, Page leftPage, Page rightPage ) + { + super( copiedPages ); + this.pivot = pivot; + this.leftPage = leftPage; + this.rightPage = rightPage; + } + + + /** + * @return the leftPage + */ + public Page getLeftPage() + { + return leftPage; + } + + + /** + * @return the rightPage + */ + public Page getRightPage() + { + return rightPage; + } + + + /** + * @return the pivot + */ + public K getPivot() + { + return pivot; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "SplitResult, new pivot = " ).append( pivot ); + sb.append( "\n leftPage = " ).append( leftPage ); + sb.append( "\n rightPage = " ).append( rightPage ); + sb.append( super.toString() ); + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TransactionManager.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TransactionManager.java new file mode 100644 index 000000000..bfa9897d2 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TransactionManager.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +/** + * An interface used to manage the transactions mechanism in B-trees. Transactions are cross + * B-trees. + * + * @author Apache Directory Project + */ +public interface TransactionManager +{ + /** + * Starts a transaction + */ + void beginTransaction(); + + + /** + * Commits a transaction + */ + void commit(); + + + /** + * Rollback a transaction + */ + void rollback(); + + + /** + * Gets the current BtreeHeader for a given BTree. + * + * @param btreeName The Btree name we are looking the BtreeHeader for + * @return the current BTreeHeader + */ + BTreeHeader getBTreeHeader( String btreeName ); + + + /** + * Updates the map of new BTreeHeaders + * + * @param btreeHeader The new BtreeHeader + */ + void updateNewBTreeHeaders( BTreeHeader btreeHeader ); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Tuple.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Tuple.java new file mode 100644 index 000000000..7526a46ca --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Tuple.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.util.Comparator; + + +/** + * The Tuple class is used when we browse a btree, it will contain the results + * fetched from the btree. + * + * @author Apache Directory Project + * + * @param The type for the Key + * @param The type for the stored value + */ +public class Tuple implements Comparable> +{ + /** The key */ + protected K key; + + /** The value */ + protected V value; + + /** The key comparator */ + protected Comparator keyComparator; + + + /** + * Creates a Tuple with no content + */ + public Tuple() + { + } + + + /** + * Creates a Tuple containing a key and its associated value. + * @param key The key + * @param value The associated value + */ + public Tuple( K key, V value ) + { + this.key = key; + this.value = value; + } + + + /** + * Creates a Tuple containing a key and its associated value. + * @param key The key + * @param value The associated value + */ + public Tuple( K key, V value, Comparator keyComparator ) + { + this.key = key; + this.value = value; + this.keyComparator = keyComparator; + } + + + /** + * @return the key + */ + public K getKey() + { + return key; + } + + + /** + * @param key the key to set + */ + public void setKey( K key ) + { + this.key = key; + } + + + /** + * @return the value + */ + public V getValue() + { + return value; + } + + + /** + * @param value the value to set + */ + public void setValue( V value ) + { + this.value = value; + } + + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() + { + return key.hashCode(); + } + + + /** + * @see Object#equals() + */ + @SuppressWarnings("unchecked") + @Override + public boolean equals( Object obj ) + { + if ( this == obj ) + { + return true; + } + + if ( !( obj instanceof Tuple ) ) + { + return false; + } + + if ( this.key == null ) + { + return ( ( Tuple ) obj ).key == null; + } + + return this.key.equals( ( ( Tuple ) obj ).key ); + } + + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo( Tuple t ) + { + if ( keyComparator != null ) + { + return keyComparator.compare( key, t.key ); + } + else + { + return 0; + } + } + + + /** + * @return the keyComparator + */ + public Comparator getKeyComparator() + { + return keyComparator; + } + + + /** + * @param keyComparator the keyComparator to set + */ + public void setKeyComparator( Comparator keyComparator ) + { + this.keyComparator = keyComparator; + } + + + /** + * @see Object#toString() + */ + public String toString() + { + return "<" + key + "," + value + ">"; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TupleComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TupleComparator.java new file mode 100644 index 000000000..a6477565c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TupleComparator.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.mavibot.btree; + + +import java.util.Comparator; + + +/** + * An comparator that encapsulate a Comparator to compare two tuples + * using their key. + * + * @author Apache Directory Project + */ +public class TupleComparator implements Comparator> +{ + /** the embedded Comparator to use for the key comparison */ + Comparator keyComparator; + + /** the embedded Comparator to use for the value comparison */ + Comparator valueComparator; + + + /** + * Creates a new instance of TupleComparator. + * + * @param keyComparator The inner key comparator + * @param valueComparator The inner value comparator + */ + public TupleComparator( Comparator keyComparator, Comparator valueComparator ) + { + this.keyComparator = keyComparator; + } + + + /** + * Compare two tuples. We compare the keys only. + * + * @param t1 The first tuple + * @param t2 The second tuple + * @return There are 5 possible results : + *

            + *
          • -1 : the first key is below the second key
          • + *
          • 0 : the two keys are equals, keys and values
          • + *
          • 1 : the first key is above the second key
          • + *
          + */ + @Override + public int compare( Tuple t1, Tuple t2 ) + { + return keyComparator.compare( t1.key, t2.key ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TupleCursor.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TupleCursor.java new file mode 100644 index 000000000..d19397e51 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TupleCursor.java @@ -0,0 +1,1004 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; +import java.util.NoSuchElementException; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; + + +/** + * A Cursor is used to fetch elements in a BTree and is returned by the + * @see BTree#browse method. The cursor must be closed + * when the user is done with it. + *

          + * + * @param The type for the Key + * @param The type for the stored value + * + * @author Apache Directory Project + */ +public class TupleCursor +{ + /** A marker to tell that we are before the first element */ + private static final int BEFORE_FIRST = -1; + + /** A marker to tell that we are after the last element */ + private static final int AFTER_LAST = -2; + + /** The stack of pages from the root down to the leaf */ + protected ParentPos[] stack; + + /** The stack's depth */ + protected int depth = 0; + + /** The transaction used for this cursor */ + protected ReadTransaction transaction; + + + /** + * Creates a new instance of Cursor. + */ + protected TupleCursor() + { + } + + + /** + * Creates a new instance of Cursor, starting on a page at a given position. + * + * @param transaction The transaction this operation is protected by + * @param stack The stack of parent's from root to this page + */ + public TupleCursor( ReadTransaction transaction, ParentPos[] stack, int depth ) + { + this.transaction = transaction; + this.stack = stack; + this.depth = depth; + } + + + /** + * Change the position in the current cursor to set it after the last key + */ + public void afterLast() throws IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + return; + } + + Page child = null; + + for ( int i = 0; i < depth; i++ ) + { + ParentPos parentPos = stack[i]; + + if ( child != null ) + { + parentPos.page = child; + parentPos.pos = child.getNbElems(); + } + else + { + // We have N+1 children if the page is a Node, so we don't decrement the nbElems field + parentPos.pos = parentPos.page.getNbElems(); + } + + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos ); + } + + // and leaf + ParentPos parentPos = stack[depth]; + + if ( child == null ) + { + parentPos.pos = parentPos.page.getNbElems() - 1; + } + else + { + parentPos.page = child; + parentPos.pos = child.getNbElems() - 1; + } + + parentPos.valueCursor = ( ( AbstractPage ) parentPos.page ).getValue( parentPos.pos ).getCursor(); + parentPos.valueCursor.afterLast(); + parentPos.pos = AFTER_LAST; + } + + + /** + * Change the position in the current cursor before the first key + */ + public void beforeFirst() throws IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + return; + } + + Page child = null; + + for ( int i = 0; i < depth; i++ ) + { + ParentPos parentPos = stack[i]; + parentPos.pos = 0; + + if ( child != null ) + { + parentPos.page = child; + } + + child = ( ( AbstractPage ) parentPos.page ).getPage( 0 ); + } + + // and leaf + ParentPos parentPos = stack[depth]; + parentPos.pos = BEFORE_FIRST; + + if ( child != null ) + { + parentPos.page = child; + } + + if ( parentPos.valueCursor != null ) + { + parentPos.valueCursor = ( ( AbstractPage ) parentPos.page ).getValue( 0 ).getCursor(); + parentPos.valueCursor.beforeFirst(); + } + } + + + /** + * Tells if the cursor can return a next element + * + * @return true if there are some more elements + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasNext() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + return false; + } + + // Take the leaf and check if we have no mare values + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // Empty BTree, get out + return false; + } + + if ( parentPos.pos == AFTER_LAST ) + { + // Ok, here, we have reached the last value in the leaf. We have to go up and + // see if we have some remaining values + int currentDepth = depth - 1; + + while ( currentDepth >= 0 ) + { + parentPos = stack[currentDepth]; + + if ( parentPos.pos < parentPos.page.getNbElems() ) + { + // The parent has some remaining values on the right, get out + return true; + } + else + { + currentDepth--; + } + } + + return false; + } + + if ( parentPos.pos < parentPos.page.getNbElems() - 1 ) + { + // Not the last position, we have a next value + return true; + } + else + { + // Check if we have some more value + if ( ( parentPos.valueCursor != null ) && parentPos.valueCursor.hasNext() ) + { + // No problem, we still have some values to read + return true; + } + + // Ok, here, we have reached the last value in the leaf. We have to go up and + // see if we have some remaining values + int currentDepth = depth - 1; + + while ( currentDepth >= 0 ) + { + parentPos = stack[currentDepth]; + + if ( parentPos.pos < parentPos.page.getNbElems() ) + { + // The parent has some remaining values on the right, get out + return true; + } + else + { + currentDepth--; + } + } + + // We are done, there are no more value left + return false; + } + } + + + /** + * Find the next key/value + * + * @return A Tuple containing the found key and value + * @throws IOException + * @throws EndOfFileExceededException + */ + public Tuple next() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + throw new NoSuchElementException( "No tuple present" ); + } + + ParentPos parentPos = stack[depth]; + + if ( ( parentPos.page == null ) || ( parentPos.pos == AFTER_LAST ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + + if ( parentPos.pos == parentPos.page.getNbElems() ) + { + // End of the leaf. We have to go back into the stack up to the + // parent, and down to the leaf + parentPos = findNextParentPos(); + + // we also need to check for the type of page cause + // findNextParentPos will never return a null ParentPos + if ( ( parentPos == null ) || ( parentPos.page == null ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + } + + V value = null; + + if ( parentPos.valueCursor.hasNext() ) + { + // Deal with the BeforeFirst case + if ( parentPos.pos == BEFORE_FIRST ) + { + parentPos.pos++; + } + + value = parentPos.valueCursor.next(); + } + else + { + if ( parentPos.pos == parentPos.page.getNbElems() - 1 ) + { + parentPos = findNextParentPos(); + + if ( ( parentPos == null ) || ( parentPos.page == null ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + } + else + { + parentPos.pos++; + } + + try + { + ValueHolder valueHolder = ( ( AbstractPage ) parentPos.page ).getValue( parentPos.pos ); + + parentPos.valueCursor = valueHolder.getCursor(); + + value = parentPos.valueCursor.next(); + } + catch ( IllegalArgumentException e ) + { + e.printStackTrace(); + } + } + + AbstractPage leaf = ( AbstractPage ) ( parentPos.page ); + Tuple tuple = new Tuple( leaf.getKey( parentPos.pos ), value ); + + return tuple; + } + + + /** + * Get the next non-duplicate key. + * If the BTree contains : + * + *

            + *
          • <1,0>
          • + *
          • <1,1>
          • + *
          • <1,2>
          • + *
          • <2,0>
          • + *
          • <2,1>
          • + *
          + * + * and cursor is present at <1,1> then the returned tuple will be <2,0> (not <1,2>) + * + * @return A Tuple containing the found key and value + * @throws EndOfFileExceededException + * @throws IOException + */ + public Tuple nextKey() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + + if ( parentPos.pos == ( parentPos.page.getNbElems() - 1 ) ) + { + // End of the leaf. We have to go back into the stack up to the + // parent, and down to the next leaf + ParentPos newParentPos = findNextParentPos(); + + // we also need to check the result of the call to + // findNextParentPos as it will return a null ParentPos + if ( ( newParentPos == null ) || ( newParentPos.page == null ) ) + { + // This is the end : no more value + AbstractPage leaf = ( AbstractPage ) ( parentPos.page ); + ValueHolder valueHolder = leaf.getValue( parentPos.pos ); + parentPos.pos = AFTER_LAST; + parentPos.valueCursor = valueHolder.getCursor(); + parentPos.valueCursor.afterLast(); + + return null; + } + else + { + parentPos = newParentPos; + } + } + else + { + // Get the next key + parentPos.pos++; + } + + // The key + AbstractPage leaf = ( AbstractPage ) ( parentPos.page ); + Tuple tuple = new Tuple(); + tuple.setKey( leaf.getKey( parentPos.pos ) ); + + // The value + ValueHolder valueHolder = leaf.getValue( parentPos.pos ); + parentPos.valueCursor = valueHolder.getCursor(); + V value = parentPos.valueCursor.next(); + tuple.setValue( value ); + + return tuple; + } + + + /** + * Tells if the cursor can return a next key + * + * @return true if there are some more keys + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasNextKey() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + // This is the end : no more key + return false; + } + + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // This is the end : no more key + return false; + } + + if ( parentPos.pos == ( parentPos.page.getNbElems() - 1 ) ) + { + // End of the leaf. We have to go back into the stack up to the + // parent, and down to the next leaf + return hasNextParentPos(); + } + else + { + return true; + } + } + + + /** + * Tells if the cursor can return a previous element + * + * @return true if there are some more elements + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasPrev() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + return false; + } + + // Take the leaf and check if we have no mare values + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // Empty BTree, get out + return false; + } + + if ( parentPos.pos > 0 ) + { + // get out, we have values on the left + return true; + } + else + { + // Check that we are not before the first value + if ( parentPos.pos == BEFORE_FIRST ) + { + return false; + } + + // Check if we have some more value + if ( parentPos.valueCursor.hasPrev() ) + { + return true; + } + + // Ok, here, we have reached the first value in the leaf. We have to go up and + // see if we have some remaining values + int currentDepth = depth - 1; + + while ( currentDepth >= 0 ) + { + parentPos = stack[currentDepth]; + + if ( parentPos.pos > 0 ) + { + // The parent has some remaining values on the right, get out + return true; + } + else + { + currentDepth--; + } + } + + return false; + } + } + + + /** + * Find the previous key/value + * + * @return A Tuple containing the found key and value + * @throws IOException + * @throws EndOfFileExceededException + */ + public Tuple prev() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + throw new NoSuchElementException( "No more tuple present" ); + } + + ParentPos parentPos = stack[depth]; + + if ( ( parentPos.page == null ) || ( parentPos.pos == BEFORE_FIRST ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + + if ( ( parentPos.pos == 0 ) && ( !parentPos.valueCursor.hasPrev() ) ) + { + // End of the leaf. We have to go back into the stack up to the + // parent, and down to the leaf + parentPos = findPrevParentPos(); + + // we also need to check for the type of page cause + // findPrevParentPos will never return a null ParentPos + if ( ( parentPos == null ) || ( parentPos.page == null ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + } + + V value = null; + + if ( parentPos.valueCursor.hasPrev() ) + { + // Deal with the AfterLast case + if ( parentPos.pos == AFTER_LAST ) + { + parentPos.pos = parentPos.page.getNbElems() - 1; + } + + value = parentPos.valueCursor.prev(); + } + else + { + if ( parentPos.pos == 0 ) + { + parentPos = findPrevParentPos(); + + if ( ( parentPos == null ) || ( parentPos.page == null ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + } + else + { + parentPos.pos--; + + try + { + ValueHolder valueHolder = ( ( AbstractPage ) parentPos.page ).getValue( parentPos.pos ); + + parentPos.valueCursor = valueHolder.getCursor(); + parentPos.valueCursor.afterLast(); + + value = parentPos.valueCursor.prev(); + } + catch ( IllegalArgumentException e ) + { + e.printStackTrace(); + } + } + } + + AbstractPage leaf = ( AbstractPage ) ( parentPos.page ); + Tuple tuple = new Tuple( leaf.getKey( parentPos.pos ), value ); + + return tuple; + } + + + /** + * Get the previous non-duplicate key. + * If the BTree contains : + * + *
            + *
          • <1,0>
          • + *
          • <1,1>
          • + *
          • <1,2>
          • + *
          • <2,0>
          • + *
          • <2,1>
          • + *
          + * + * and cursor is present at <2,1> then the returned tuple will be <1,0> (not <2,0>) + * + * @return A Tuple containing the found key and value + * @throws EndOfFileExceededException + * @throws IOException + */ + public Tuple prevKey() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + + if ( parentPos.pos == 0 ) + { + // Beginning of the leaf. We have to go back into the stack up to the + // parent, and down to the leaf + parentPos = findPrevParentPos(); + + if ( ( parentPos == null ) || ( parentPos.page == null ) ) + { + // This is the end : no more value + throw new NoSuchElementException( "No more tuples present" ); + } + } + else + { + if ( parentPos.pos == AFTER_LAST ) + { + parentPos.pos = parentPos.page.getNbElems() - 1; + } + else + { + parentPos.pos--; + } + } + + // Update the Tuple + AbstractPage leaf = ( AbstractPage ) ( parentPos.page ); + + // The key + Tuple tuple = new Tuple(); + tuple.setKey( leaf.getKey( parentPos.pos ) ); + + // The value + ValueHolder valueHolder = leaf.getValue( parentPos.pos ); + parentPos.valueCursor = valueHolder.getCursor(); + V value = parentPos.valueCursor.next(); + tuple.setValue( value ); + + return tuple; + } + + + /** + * Tells if the cursor can return a previous key + * + * @return true if there are some more keys + * @throws IOException + * @throws EndOfFileExceededException + */ + public boolean hasPrevKey() throws EndOfFileExceededException, IOException + { + // First check that we have elements in the BTree + if ( ( stack == null ) || ( stack.length == 0 ) ) + { + // This is the end : no more key + return false; + } + + ParentPos parentPos = stack[depth]; + + if ( parentPos.page == null ) + { + // This is the end : no more key + return false; + } + + switch ( parentPos.pos ) + { + case 0: + // Beginning of the leaf. We have to go back into the stack up to the + // parent, and down to the leaf + return hasPrevParentPos(); + + case -1: + // no previous key + return false; + + default: + // we have a previous key + return true; + } + } + + + /** + * Tells if there is a next ParentPos + * + * @return the new ParentPos instance, or null if we have no following leaf + * @throws IOException + * @throws EndOfFileExceededException + */ + private boolean hasNextParentPos() throws EndOfFileExceededException, IOException + { + if ( depth == 0 ) + { + // No need to go any further, there is only one leaf in the btree + return false; + } + + int currentDepth = depth - 1; + Page child = null; + + // First, go up the tree until we find a Node which has some element on the right + while ( currentDepth >= 0 ) + { + // We first go up the tree, until we reach a page whose current position + // is not the last one + ParentPos parentPos = stack[currentDepth]; + + if ( parentPos.pos + 1 > parentPos.page.getNbElems() ) + { + // No more element on the right : go up + currentDepth--; + } + else + { + // We can pick the next element at this level + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos + 1 ); + + // and go down the tree through the nodes + while ( currentDepth < depth - 1 ) + { + currentDepth++; + child = ( ( AbstractPage ) child ).getPage( 0 ); + } + + return true; + } + } + + return false; + } + + + /** + * Find the leaf containing the following elements. + * + * @return the new ParentPos instance, or null if we have no following leaf + * @throws IOException + * @throws EndOfFileExceededException + */ + private ParentPos findNextParentPos() throws EndOfFileExceededException, IOException + { + if ( depth == 0 ) + { + // No need to go any further, there is only one leaf in the btree + return null; + } + + int currentDepth = depth - 1; + Page child = null; + + // First, go up the tree until we find a Node which has some element on the right + while ( currentDepth >= 0 ) + { + // We first go up the tree, until we reach a page whose current position + // is not the last one + ParentPos parentPos = stack[currentDepth]; + + if ( parentPos.pos + 1 > parentPos.page.getNbElems() ) + { + // No more element on the right : go up + currentDepth--; + } + else + { + // We can pick the next element at this level + parentPos.pos++; + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos ); + + // and go down the tree through the nodes + while ( currentDepth < depth - 1 ) + { + currentDepth++; + parentPos = stack[currentDepth]; + parentPos.pos = 0; + parentPos.page = child; + child = ( ( AbstractPage ) child ).getPage( 0 ); + } + + // and the leaf + parentPos = stack[depth]; + parentPos.page = child; + parentPos.pos = 0; + parentPos.valueCursor = ( ( AbstractPage ) child ).getValue( 0 ).getCursor(); + + return parentPos; + } + } + + return null; + } + + + /** + * Find the leaf containing the previous elements. + * + * @return the new ParentPos instance, or null if we have no previous leaf + * @throws IOException + * @throws EndOfFileExceededException + */ + private ParentPos findPrevParentPos() throws EndOfFileExceededException, IOException + { + if ( depth == 0 ) + { + // No need to go any further, there is only one leaf in the btree + return null; + } + + int currentDepth = depth - 1; + Page child = null; + + // First, go up the tree until we find a Node which has some element on the left + while ( currentDepth >= 0 ) + { + // We first go up the tree, until we reach a page whose current position + // is not the last one + ParentPos parentPos = stack[currentDepth]; + + if ( parentPos.pos == 0 ) + { + // No more element on the right : go up + currentDepth--; + } + else + { + // We can pick the next element at this level + parentPos.pos--; + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos ); + + // and go down the tree through the nodes + while ( currentDepth < depth - 1 ) + { + currentDepth++; + parentPos = stack[currentDepth]; + parentPos.pos = child.getNbElems(); + parentPos.page = child; + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.page.getNbElems() ); + } + + // and the leaf + parentPos = stack[depth]; + parentPos.pos = child.getNbElems() - 1; + parentPos.page = child; + ValueHolder valueHolder = ( ( AbstractPage ) parentPos.page ).getValue( parentPos.pos ); + parentPos.valueCursor = valueHolder.getCursor(); + parentPos.valueCursor.afterLast(); + + return parentPos; + } + } + + return null; + } + + + /** + * Tells if there is a prev ParentPos + * + * @return the new ParentPos instance, or null if we have no previous leaf + * @throws IOException + * @throws EndOfFileExceededException + */ + private boolean hasPrevParentPos() throws EndOfFileExceededException, IOException + { + if ( depth == 0 ) + { + // No need to go any further, there is only one leaf in the btree + return false; + } + + int currentDepth = depth - 1; + Page child = null; + + // First, go up the tree until we find a Node which has some element on the right + while ( currentDepth >= 0 ) + { + // We first go up the tree, until we reach a page whose current position + // is not the last one + ParentPos parentPos = stack[currentDepth]; + + if ( parentPos.pos == 0 ) + { + // No more element on the left : go up + currentDepth--; + } + else + { + // We can pick the previous element at this level + child = ( ( AbstractPage ) parentPos.page ).getPage( parentPos.pos - 1 ); + + // and go down the tree through the nodes + while ( currentDepth < depth - 1 ) + { + currentDepth++; + child = ( ( AbstractPage ) child ).getPage( child.getNbElems() ); + } + + return true; + } + } + + return false; + } + + + /** + * {@inheritDoc} + */ + public void close() + { + transaction.close(); + } + + + /** + * Get the creation date + * @return The creation date for this cursor + */ + public long getCreationDate() + { + return transaction.getCreationDate(); + } + + + /** + * Get the current revision + * + * @return The revision this cursor is based on + */ + public long getRevision() + { + return transaction.getRevision(); + } + + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "TupleCursor, depth = " ).append( depth ).append( "\n" ); + + for ( int i = 0; i <= depth; i++ ) + { + sb.append( " " ).append( stack[i] ).append( "\n" ); + } + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueArrayCursor.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueArrayCursor.java new file mode 100644 index 000000000..d7f621dcb --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueArrayCursor.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; + + +/** + * A class that encapsulate the values into an array + * + * @author Apache Directory Project + */ +/* No qualifier */class ValueArrayCursor implements ValueCursor +{ + /** Store the current position in the array or in the BTree */ + private int currentPos; + + /** The array storing values (1 to N) */ + private V[] valueArray; + + + /** + * Create an instance + */ + public ValueArrayCursor( V[] valueArray ) + { + // Start at -1 to be positioned before the first element + currentPos = BEFORE_FIRST; + this.valueArray = valueArray; + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean hasNext() + { + return ( currentPos < valueArray.length - 1 ) && ( currentPos != AFTER_LAST ); + } + + + /** + * {@inheritDoc} + */ + public V next() + { + if ( valueArray == null ) + { + // Deserialize the array + return null; + } + else + { + currentPos++; + + if ( currentPos == valueArray.length ) + { + currentPos = AFTER_LAST; + + // We have reached the end of the array + return null; + } + else + { + return valueArray[currentPos]; + } + } + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean hasPrev() throws EndOfFileExceededException, IOException + { + return currentPos > 0 || currentPos == AFTER_LAST; + } + + + /** + * {@inheritDoc} + */ + @Override + public void close() + { + } + + + /** + * {@inheritDoc} + */ + @Override + public void beforeFirst() throws IOException + { + currentPos = BEFORE_FIRST; + } + + + /** + * {@inheritDoc} + */ + @Override + public void afterLast() throws IOException + { + currentPos = AFTER_LAST; + } + + + /** + * {@inheritDoc} + */ + @Override + public V prev() throws EndOfFileExceededException, IOException + { + if ( valueArray == null ) + { + // Deserialize the array + return null; + } + else + { + if ( currentPos == AFTER_LAST ) + { + currentPos = valueArray.length - 1; + } + else + { + currentPos--; + } + + if ( currentPos == BEFORE_FIRST ) + { + // We have reached the end of the array + return null; + } + else + { + return valueArray[currentPos]; + } + } + } + + + /** + * {@inheritDoc} + */ + @Override + public int size() + { + return valueArray.length; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueBTreeCursor.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueBTreeCursor.java new file mode 100644 index 000000000..10a69ddc4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueBTreeCursor.java @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.BTree; + + +/** + * A class that encapsulate the values into an sub-btree + * + * @author Apache Directory Project + */ +/* No qualifier */class ValueBTreeCursor implements ValueCursor +{ + /** Store the current position in the array or in the BTree */ + private KeyCursor cursor; + + /** The Value sub-btree */ + private BTree valueBtree; + + + /** + * Create an instance + */ + public ValueBTreeCursor( BTree valueBtree ) + { + this.valueBtree = valueBtree; + + // Start at -1 to be positioned before the first element + try + { + if ( valueBtree != null ) + { + cursor = valueBtree.browseKeys(); + } + } + catch ( IOException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch ( KeyNotFoundException knfe ) + { + // TODO Auto-generated catch block + knfe.printStackTrace(); + } + } + + + /** + * {@inheritDoc}} + */ + @Override + public boolean hasNext() + { + if ( cursor == null ) + { + return false; + } + else + { + try + { + return cursor.hasNext(); + } + catch ( EndOfFileExceededException e ) + { + e.printStackTrace(); + return false; + } + catch ( IOException e ) + { + e.printStackTrace(); + return false; + } + } + } + + + /** + * {@inheritDoc}} + */ + public V next() + { + try + { + return cursor.next(); + } + catch ( EndOfFileExceededException e ) + { + e.printStackTrace(); + return null; + } + catch ( IOException e ) + { + e.printStackTrace(); + return null; + } + } + + + /** + * {@inheritDoc}} + */ + @Override + public boolean hasPrev() throws EndOfFileExceededException, IOException + { + if ( cursor == null ) + { + return false; + } + else + { + try + { + return cursor.hasPrev(); + } + catch ( EndOfFileExceededException e ) + { + e.printStackTrace(); + return false; + } + catch ( IOException e ) + { + e.printStackTrace(); + return false; + } + } + } + + + /** + * {@inheritDoc}} + */ + @Override + public void close() + { + if ( cursor != null ) + { + cursor.close(); + } + } + + + /** + * {@inheritDoc}} + */ + @Override + public void beforeFirst() throws IOException + { + if ( cursor != null ) + { + cursor.beforeFirst(); + } + } + + + /** + * {@inheritDoc}} + */ + @Override + public void afterLast() throws IOException + { + if ( cursor != null ) + { + cursor.afterLast(); + } + } + + + /** + * {@inheritDoc}} + */ + @Override + public V prev() throws EndOfFileExceededException, IOException + { + try + { + return cursor.prev(); + } + catch ( EndOfFileExceededException e ) + { + e.printStackTrace(); + return null; + } + catch ( IOException e ) + { + e.printStackTrace(); + return null; + } + } + + + /** + * {@inheritDoc} + */ + @Override + public int size() + { + return ( int ) valueBtree.getNbElems(); + } + + + /** + * @see Object#toString() + */ + public String toString() + { + return "BTreeCursor"; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueCursor.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueCursor.java new file mode 100644 index 000000000..014ffc6de --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueCursor.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; + + +/** + * A Cursor is used to fetch elements in a BTree and is returned by the + * @see BTree#browse method. The cursor must be closed + * when the user is done with it. + *

          + * + * @param The type for the stored value + * + * @author Apache Directory Project + */ +public interface ValueCursor extends Cursor +{ + /** + * Find the next key/value + * + * @return A Tuple containing the found key and value + * @throws IOException + * @throws EndOfFileExceededException + */ + V next() throws EndOfFileExceededException, IOException; + + + /** + * Find the previous key/value + * + * @return A Tuple containing the found key and value + * @throws IOException + * @throws EndOfFileExceededException + */ + V prev() throws EndOfFileExceededException, IOException; + + + /** + * @return The number of elements stored in the cursor + */ + int size(); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueHolder.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueHolder.java new file mode 100644 index 000000000..6215d9903 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueHolder.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +/** + * A holder to store the Values + * + * @author Apache Directory Project + * @param The value type + */ +/* no qualifier */interface ValueHolder extends Cloneable +{ + /** + * Tells if a value is contained in this ValueHolder + * + * @param checkedValue The added to check + */ + boolean contains( V checkedValue ); + + + /** + * @return the number of stored values + */ + int size(); + + + /** + * @return a cursor on top of the values + */ + ValueCursor getCursor(); + + + /** + * @return true if we store the values in a sub btree + */ + boolean isSubBtree(); + + + /** + * Add a new value in the ValueHolder + * + * @param newValue The added value + */ + void add( V newValue ); + + + /** + * Remove a value from the ValueHolder + * + * @param removedValue The value to remove + */ + V remove( V removedValue ); + + + /** + * Replaces the single value present in the array. + * + * This is only applicable for B-Trees that don't + * support duplicate values. + * + * @param newValue the new value + * @return the value that was replaced + */ + V replaceValueArray( V newValue ); + + + /** + * Create a clone of this instance + * + * @return a new instance of a ValueHolder + * @throws CloneNotSupportedException If we can't clone this instance + */ + ValueHolder clone() throws CloneNotSupportedException; +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/WriteTransaction.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/WriteTransaction.java new file mode 100644 index 000000000..b6f9b6868 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/WriteTransaction.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.directory.mavibot.btree.exception.BadTransactionStateException; + +/** + * A data structure used to manage a write transaction + * + * @author Apache Directory Project + */ +/* no qualifier */ class WriteTransaction +{ + /** A lock used to protect the write operation against concurrent access */ + protected ReentrantLock writeLock; + + /* no qualifier */WriteTransaction() + { + //System.out.println( "Creating the transaction oject" ); + writeLock = new ReentrantLock(); + } + + + /* no qualifier */ void start() + { + /* + if ( writeLock.isLocked() ) + { + throw new BadTransactionStateException( "Cannot start a write transaction when it's already started" ); + } + */ + + //System.out.println( "Start a TXN [" + Thread.currentThread().getName() + "]" ); + + writeLock.lock(); + //System.out.println( "WriteTransaction " + Thread.currentThread().getName() ); + } + + + /* no qualifier */ void commit() + { + if ( !writeLock.isLocked() ) + { + throw new BadTransactionStateException( "Cannot commit a write transaction when it's not started" ); + } + + //System.out.println( "Commit a TXN[" + Thread.currentThread().getName() + "]" ); + + writeLock.unlock(); + } + + + /* no qualifier */ void rollback() + { + if ( !writeLock.isLocked() ) + { + throw new BadTransactionStateException( "Cannot commit a write transaction when it's not started" ); + } + + //System.out.println( "Rollback a TXN" ); + writeLock.unlock(); + } + + + /** + * Tells if the transaction has started + * @return true if the transaction has started + */ + /* no qualifier */ boolean isStarted() + { + return writeLock.isLocked(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/BooleanArrayComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/BooleanArrayComparator.java new file mode 100644 index 000000000..7c052a51c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/BooleanArrayComparator.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares boolean arrays. A boolean is considered as below the other one if the first boolean + * is false when the second one is true. + * + * @author Apache Directory Project + */ +public class BooleanArrayComparator implements Comparator +{ + /** A static instance of a BooleanArrayComparator */ + public static final BooleanArrayComparator INSTANCE = new BooleanArrayComparator(); + + /** + * A private constructor of the BooleanArrayComparator class + */ + private BooleanArrayComparator() + { + } + + + /** + * Compare two boolean arrays. + * + * @param booleanArray1 First boolean array + * @param booleanArray2 Second boolean array + * @return 1 if booleanArray1 > booleanArray2, 0 if booleanArray1 == booleanArray2, -1 if booleanArray1 < booleanArray2 + */ + public int compare( boolean[] booleanArray1, boolean[] booleanArray2 ) + { + if ( booleanArray1 == booleanArray2 ) + { + return 0; + } + + if ( booleanArray1 == null ) + { + return -1; + } + + if ( booleanArray2 == null ) + { + return 1; + } + + if ( booleanArray1.length < booleanArray2.length ) + { + return -1; + } + + if ( booleanArray1.length > booleanArray2.length ) + { + return 1; + } + + for ( int pos = 0; pos < booleanArray1.length; pos++ ) + { + int comp = compare( booleanArray1[pos], booleanArray2[pos] ); + + if ( comp != 0 ) + { + return comp; + } + } + + return 0; + } + + + private int compare( boolean boolean1, boolean boolean2 ) + { + if ( boolean1 == boolean2 ) + { + return 0; + } + + if ( boolean1 ) + { + return 1; + } + else + { + return -1; + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/BooleanComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/BooleanComparator.java new file mode 100644 index 000000000..4ffbb88b0 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/BooleanComparator.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.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares booleans + * + * @author Apache Directory Project + */ +public class BooleanComparator implements Comparator +{ + /** A static instance of a BooleanComparator */ + public static final BooleanComparator INSTANCE = new BooleanComparator(); + + /** + * A private constructor of the BooleanComparator class + */ + private BooleanComparator() + { + } + + + /** + * Compare two booleans. + * + * @param boolean1 First boolean + * @param boolean2 Second boolean + * @return 1 if boolean1 > boolean2, 0 if boolean1 == boolean2, -1 if boolean1 < boolean2 + */ + public int compare( Boolean boolean1, Boolean boolean2 ) + { + if ( boolean1 == boolean2 ) + { + return 0; + } + + if ( boolean1 == null ) + { + return -1; + } + + if ( boolean2 == null ) + { + return 1; + } + + return boolean1.compareTo( boolean2 ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ByteArrayComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ByteArrayComparator.java new file mode 100644 index 000000000..bfe54d991 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ByteArrayComparator.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.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares byte arrays. + * + * @author Apache Directory Project + */ +public class ByteArrayComparator implements Comparator +{ + /** A static instance of a ByteArrayComparator */ + public static final ByteArrayComparator INSTANCE = new ByteArrayComparator(); + + /** + * A private constructor of the ByteArrayComparator class + */ + private ByteArrayComparator() + { + } + + + /** + * Compare two byte arrays. + * + * @param byteArray1 First byteArray + * @param byteArray2 Second byteArray + * @return 1 if byteArray1 > byteArray2, 0 if byteArray1 == byteArray2, -1 if byteArray1 < byteArray2 + */ + public int compare( byte[] byteArray1, byte[] byteArray2 ) + { + if ( byteArray1 == byteArray2 ) + { + return 0; + } + + if ( byteArray1 == null ) + { + return -1; + } + else + { + if ( byteArray2 == null ) + { + return 1; + } + else + { + if ( byteArray1.length < byteArray2.length ) + { + int pos = 0; + + for ( byte b1 : byteArray1 ) + { + byte b2 = byteArray2[pos]; + + if ( b1 == b2 ) + { + pos++; + } + else if ( b1 < b2 ) + { + return -1; + } + else + { + return 1; + } + } + + return -1; + } + else + { + int pos = 0; + + for ( byte b2 : byteArray2 ) + { + byte b1 = byteArray1[pos]; + + if ( b1 == b2 ) + { + pos++; + } + else if ( b1 < b2 ) + { + return -1; + } + else + { + return 1; + } + } + + if ( pos < byteArray1.length ) + { + return 1; + } + else + { + return 0; + } + } + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ByteComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ByteComparator.java new file mode 100644 index 000000000..e6f673d0a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ByteComparator.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares bytes + * + * @author Apache Directory Project + */ +public class ByteComparator implements Comparator +{ + /** A static instance of a ByteComparator */ + public static final ByteComparator INSTANCE = new ByteComparator(); + + /** + * A private constructor of the ByteComparator class + */ + private ByteComparator() + { + } + + + /** + * Compare two bytes. + * + * @param byte1 First byte + * @param byte2 Second byte + * @return 1 if byte1 > byte2, 0 if byte1 == byte2, -1 if byte1 < byte2 + */ + public int compare( Byte byte1, Byte byte2 ) + { + if ( byte1 == byte2 ) + { + return 0; + } + + if ( byte1 == null ) + { + return -1; + } + + if ( byte2 == null ) + { + return 1; + } + + if ( byte1 < byte2 ) + { + return -1; + } + else if ( byte1 > byte2 ) + { + return 1; + } + else + { + return 0; + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/CharArrayComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/CharArrayComparator.java new file mode 100644 index 000000000..5f1a847b7 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/CharArrayComparator.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.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares char arrays + * + * @author Apache Directory Project + */ +public class CharArrayComparator implements Comparator +{ + /** A static instance of a CharArrayComparator */ + public static final CharArrayComparator INSTANCE = new CharArrayComparator(); + + /** + * A private constructor of the CharArrayComparator class + */ + private CharArrayComparator() + { + } + + + /** + * Compare two char arrays. + * + * @param charArray1 First char array + * @param charArray2 Second char array + * @return 1 if charArray1 > charArray2, 0 if charArray1 == charArray2, -1 if charArray1 < charArray2 + */ + public int compare( char[] charArray1, char[] charArray2 ) + { + if ( charArray1 == charArray2 ) + { + return 0; + } + + if ( charArray1 == null ) + { + if ( charArray2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( charArray2 == null ) + { + return 1; + } + else + { + if ( charArray1.length < charArray2.length ) + { + int pos = 0; + + for ( char char1 : charArray1 ) + { + char char2 = charArray2[pos]; + + if ( char1 == char2 ) + { + pos++; + } + else if ( char1 < char2 ) + { + return -1; + } + else + { + return 1; + } + } + + return -1; + } + else + { + int pos = 0; + + for ( char char2 : charArray2 ) + { + char char1 = charArray1[pos]; + + if ( char1 == char2 ) + { + pos++; + } + else if ( char1 < char2 ) + { + return -1; + } + else + { + return 1; + } + } + + if ( pos < charArray1.length ) + { + return 1; + } + else + { + return 0; + } + } + } + } + + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/CharComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/CharComparator.java new file mode 100644 index 000000000..9c4302b8e --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/CharComparator.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares chars + * + * @author Apache Directory Project + */ +public class CharComparator implements Comparator +{ + /** A static instance of a CharComparator */ + public static final CharComparator INSTANCE = new CharComparator(); + + /** + * A private constructor of the CharComparator class + */ + private CharComparator() + { + } + + + /** + * Compare two chars. + * + * @param char1 First char + * @param char2 Second char + * @return 1 if char1 > char2, 0 if char1 == char2, -1 if char1 < char2 + */ + public int compare( Character char1, Character char2 ) + { + if ( char1 == char2 ) + { + return 0; + } + + if ( char1 == null ) + { + if ( char2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( char2 == null ) + { + return 1; + } + else + { + if ( char1 < char2 ) + { + return -1; + } + else if ( char1 > char2 ) + { + return 1; + } + else + { + return 0; + } + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/IntArrayComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/IntArrayComparator.java new file mode 100644 index 000000000..7253b9709 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/IntArrayComparator.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares int arrays + * + * @author Apache Directory Project + */ +public class IntArrayComparator implements Comparator +{ + /** A static instance of a IntArrayComparator */ + public static final IntArrayComparator INSTANCE = new IntArrayComparator(); + + /** + * A private constructor of the IntArrayComparator class + */ + private IntArrayComparator() + { + } + + + /** + * Compare two long arrays. + * + * @param intArray1 First int array + * @param intArray2 Second int array + * @return 1 if intArray1 > intArray2, 0 if intArray1 == intArray2, -1 if intArray1 < intArray2 + */ + public int compare( int[] intArray1, int[] intArray2 ) + { + if ( intArray1 == intArray2 ) + { + return 0; + } + + if ( intArray1 == null ) + { + if ( intArray2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( intArray2 == null ) + { + return 1; + } + else + { + if ( intArray1.length < intArray2.length ) + { + int pos = 0; + + for ( int int1 : intArray1 ) + { + int int2 = intArray2[pos]; + + if ( int1 == int2 ) + { + pos++; + } + else if ( int1 < int2 ) + { + return -1; + } + else + { + return 1; + } + } + + return -1; + } + else + { + int pos = 0; + + for ( int int2 : intArray2 ) + { + int int1 = intArray1[pos]; + + if ( int1 == int2 ) + { + pos++; + } + else if ( int1 < int2 ) + { + return -1; + } + else + { + return 1; + } + } + + if ( pos < intArray1.length ) + { + return 1; + } + else + { + return 0; + } + } + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/IntComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/IntComparator.java new file mode 100644 index 000000000..8c6b38a76 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/IntComparator.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares integers + * + * @author Apache Directory Project + */ +public class IntComparator implements Comparator +{ + /** A static instance of a IntComparator */ + public static final IntComparator INSTANCE = new IntComparator(); + + /** + * A private constructor of the IntComparator class + */ + private IntComparator() + { + } + + + /** + * Compare two integers. + * + * @param integer1 First integer + * @param integer2 Second integer + * @return 1 if integer1 > integer2, 0 if integer1 == integer2, -1 if integer1 < integer2 + */ + public int compare( Integer integer1, Integer integer2 ) + { + if ( integer1 == integer2 ) + { + return 0; + } + + if ( integer1 == null ) + { + if ( integer2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( integer2 == null ) + { + return 1; + } + else + { + return integer1.compareTo( integer2 ); + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/LongArrayComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/LongArrayComparator.java new file mode 100644 index 000000000..80f681caa --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/LongArrayComparator.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares long arrays + * + * @author Apache Directory Project + */ +public class LongArrayComparator implements Comparator +{ + /** A static instance of a LongArrayComparator */ + public static final LongArrayComparator INSTANCE = new LongArrayComparator(); + + /** + * A private constructor of the LongArrayComparator class + */ + private LongArrayComparator() + { + } + + + /** + * Compare two long arrays. + * + * @param longArray1 First long array + * @param longArray2 Second long array + * @return 1 if longArray1 > longArray2, 0 if longArray1 == longArray2, -1 if longArray1 < longArray2 + */ + public int compare( long[] longArray1, long[] longArray2 ) + { + if ( longArray1 == longArray2 ) + { + return 0; + } + + if ( longArray1 == null ) + { + if ( longArray2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( longArray2 == null ) + { + return 1; + } + else + { + if ( longArray1.length < longArray2.length ) + { + int pos = 0; + + for ( long long1 : longArray1 ) + { + long long2 = longArray2[pos]; + + if ( long1 == long2 ) + { + pos++; + } + else if ( long1 < long2 ) + { + return -1; + } + else + { + return 1; + } + } + + return -1; + } + else + { + int pos = 0; + + for ( long long2 : longArray2 ) + { + long long1 = longArray1[pos]; + + if ( long1 == long2 ) + { + pos++; + } + else if ( long1 < long2 ) + { + return -1; + } + else + { + return 1; + } + } + + if ( pos < longArray1.length ) + { + return 1; + } + else + { + return 0; + } + } + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/LongComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/LongComparator.java new file mode 100644 index 000000000..53bf82b81 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/LongComparator.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares Longs + * + * @author Apache Directory Project + */ +public class LongComparator implements Comparator +{ + /** A static instance of a LongComparator */ + public static final LongComparator INSTANCE = new LongComparator(); + + /** + * A private constructor of the BooleanArrayComparator class + */ + private LongComparator() + { + } + /** + * Compare two longs. + * + * @param long1 First long + * @param long2 Second long + * @return 1 if long1 > long2, 0 if long1 == long2, -1 if long1 < long2 + */ + public int compare( Long long1, Long long2 ) + { + if ( long1 == long2 ) + { + return 0; + } + + if ( long1 == null ) + { + if ( long2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( long2 == null ) + { + return 1; + } + else + { + if ( long1 < long2 ) + { + return -1; + } + else if ( long1 > long2 ) + { + return 1; + } + else + { + return 0; + } + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ShortArrayComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ShortArrayComparator.java new file mode 100644 index 000000000..dea7430cb --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ShortArrayComparator.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares short arrays + * + * @author Apache Directory Project + */ +public class ShortArrayComparator implements Comparator +{ + /** A static instance of a ShortArrayComparator */ + public static final ShortArrayComparator INSTANCE = new ShortArrayComparator(); + + /** + * A private constructor of the ShortArrayComparator class + */ + private ShortArrayComparator() + { + } + + + /** + * Compare two short arrays. + * + * @param shortArray1 First short array + * @param shortArray2 Second short array + * @return 1 if shortArray1 > shortArray2, 0 if shortArray1 == shortArray2, -1 if shortArray1 < shortArray2 + */ + public int compare( short[] shortArray1, short[] shortArray2 ) + { + if ( shortArray1 == shortArray2 ) + { + return 0; + } + + if ( shortArray1 == null ) + { + if ( shortArray2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( shortArray2 == null ) + { + return 1; + } + else + { + if ( shortArray1.length < shortArray2.length ) + { + int pos = 0; + + for ( short short1 : shortArray1 ) + { + short short2 = shortArray2[pos]; + + if ( short1 == short2 ) + { + pos++; + } + else if ( short1 < short2 ) + { + return -1; + } + else + { + return 1; + } + } + + return -1; + } + else + { + int pos = 0; + + for ( short short2 : shortArray2 ) + { + short short1 = shortArray1[pos]; + + if ( short1 == short2 ) + { + pos++; + } + else if ( short1 < short2 ) + { + return -1; + } + else + { + return 1; + } + } + + if ( pos < shortArray1.length ) + { + return 1; + } + else + { + return 0; + } + } + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ShortComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ShortComparator.java new file mode 100644 index 000000000..117209a3d --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ShortComparator.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares shorts + * + * @author Apache Directory Project + */ +public class ShortComparator implements Comparator +{ + /** A static instance of a ShortComparator */ + public static final ShortComparator INSTANCE = new ShortComparator(); + + /** + * A private constructor of the ShortComparator class + */ + private ShortComparator() + { + } + + + /** + * Compare two shorts. + * + * @param short1 First short + * @param short2 Second short + * @return 1 if short1 > short2, 0 if short1 == short2, -1 if short1 < short2 + */ + public int compare( Short short1, Short short2 ) + { + if ( short1 == short2 ) + { + return 0; + } + + if ( short1 == null ) + { + if ( short2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( short2 == null ) + { + return 1; + } + else + { + if ( short1 < short2 ) + { + return -1; + } + else if ( short1 > short2 ) + { + return 1; + } + else + { + return 0; + } + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/StringComparator.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/StringComparator.java new file mode 100644 index 000000000..0bd5bc96b --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/StringComparator.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import java.util.Comparator; + + +/** + * Compares Strings + * + * @author Apache Directory Project + */ +public class StringComparator implements Comparator +{ + /** A static instance of a StringComparator */ + public static final StringComparator INSTANCE = new StringComparator(); + + /** + * A private constructor of the StringComparator class + */ + private StringComparator() + { + } + + + /** + * Compare two Strings. + * + * @param string1 First String + * @param string2 Second String + * @return 1 if string1 > String2, 0 if string1 == String2, -1 if string1 < String2 + */ + public int compare( String string1, String string2 ) + { + if ( string1 == string2 ) + { + return 0; + } + + if ( string1 == null ) + { + return -1; + } + else if ( string2 == null ) + { + return 1; + } + + int result = string1.compareTo( string2 ); + + if ( result < 0 ) + { + return -1; + } + else if ( result > 0 ) + { + return 1; + } + else + { + return 0; + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeAlreadyCreatedException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeAlreadyCreatedException.java new file mode 100644 index 000000000..675076437 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeAlreadyCreatedException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we try to create a BTree which already exists + * + * @author Apache Directory Project + */ +public class BTreeAlreadyCreatedException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of BTreeAlreadyCreatedException. + */ + public BTreeAlreadyCreatedException() + { + } + + + /** + * Creates a new instance of BTreeAlreadyCreatedException. + * + * @param explanation The message associated with the exception + */ + public BTreeAlreadyCreatedException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of BTreeAlreadyCreatedException. + */ + public BTreeAlreadyCreatedException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of BTreeAlreadyCreatedException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public BTreeAlreadyCreatedException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeAlreadyManagedException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeAlreadyManagedException.java new file mode 100644 index 000000000..dd32a34c9 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeAlreadyManagedException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we try to manage a BTree which name is + * already managed by the RecordManager + * + * @author Apache Directory Project + */ +public class BTreeAlreadyManagedException extends Exception +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of BtreeAlreadyManagedException. + */ + public BTreeAlreadyManagedException() + { + } + + + /** + * Creates a new instance of BtreeAlreadyManagedException. + * + * @param explanation The message associated with the exception + */ + public BTreeAlreadyManagedException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of BtreeAlreadyManagedException. + */ + public BTreeAlreadyManagedException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of KeyNotFoundException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public BTreeAlreadyManagedException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeCreationException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeCreationException.java new file mode 100644 index 000000000..563a18e54 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeCreationException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we try to create a BTree and it fails + * + * @author Apache Directory Project + */ +public class BTreeCreationException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of BTreeCreationException. + */ + public BTreeCreationException() + { + } + + + /** + * Creates a new instance of BTreeCreationException. + * + * @param explanation The message associated with the exception + */ + public BTreeCreationException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of BTreeCreationException. + */ + public BTreeCreationException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of BTreeCreationException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public BTreeCreationException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeOperationException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeOperationException.java new file mode 100644 index 000000000..faef5e63e --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeOperationException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when an operation on a BTree failed + * + * @author Apache Directory Project + */ +public class BTreeOperationException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of BTreeOperationException. + */ + public BTreeOperationException() + { + } + + + /** + * Creates a new instance of BTreeOperationException. + * + * @param explanation The message associated with the exception + */ + public BTreeOperationException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of BTreeOperationException. + */ + public BTreeOperationException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of BTreeOperationException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public BTreeOperationException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BadTransactionStateException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BadTransactionStateException.java new file mode 100644 index 000000000..2419339e9 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BadTransactionStateException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we try to use a transaction in a wrong state + * + * @author Apache Directory Project + */ +public class BadTransactionStateException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of BadTransactionStateException. + */ + public BadTransactionStateException() + { + } + + + /** + * Creates a new instance of BadTransactionStateException. + * + * @param explanation The message associated with the exception + */ + public BadTransactionStateException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of BadTransactionStateException. + */ + public BadTransactionStateException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of BadTransactionStateException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public BadTransactionStateException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/DuplicateValueNotAllowedException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/DuplicateValueNotAllowedException.java new file mode 100644 index 000000000..b4c9d2f3f --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/DuplicateValueNotAllowedException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we try to add a second value for a key into a BTree that + * does not accept duplicate values. + * + * @author Apache Directory Project + */ +public class DuplicateValueNotAllowedException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of DuplicateValueNotAllowedException. + */ + public DuplicateValueNotAllowedException() + { + } + + + /** + * Creates a new instance of DuplicateValueNotAllowedException. + * + * @param explanation The message associated with the exception + */ + public DuplicateValueNotAllowedException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of DuplicateValueNotAllowedException. + */ + public DuplicateValueNotAllowedException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of DuplicateValueNotAllowedException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public DuplicateValueNotAllowedException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/EndOfFileExceededException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/EndOfFileExceededException.java new file mode 100644 index 000000000..3a9c66f92 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/EndOfFileExceededException.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.directory.mavibot.btree.exception; + + +import java.io.IOException; + + +/** + * An exception thrown when we try to access a page beyond the file's size. + * + * @author Apache Directory Project + */ +public class EndOfFileExceededException extends IOException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of EndOfFileExceededException. + */ + public EndOfFileExceededException() + { + } + + + /** + * Creates a new instance of EndOfFileExceededException. + * + * @param explanation The message associated with the exception + */ + public EndOfFileExceededException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of EndOfFileExceededException. + */ + public EndOfFileExceededException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of EndOfFileExceededException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public EndOfFileExceededException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/FileException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/FileException.java new file mode 100644 index 000000000..320a9941d --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/FileException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when there is a problem writing data on disk + * + * @author Apache Directory Project + */ +public class FileException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of FileException. + */ + public FileException() + { + } + + + /** + * Creates a new instance of FileException. + * + * @param explanation The message associated with the exception + */ + public FileException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of FileException. + */ + public FileException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of FileException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public FileException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/FreePageException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/FreePageException.java new file mode 100644 index 000000000..2882931c3 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/FreePageException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we have an error while managing the free pages + * + * @author Apache Directory Project + */ +public class FreePageException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of FreePageException. + */ + public FreePageException() + { + } + + + /** + * Creates a new instance of FreePageException. + * + * @param explanation The message associated with the exception + */ + public FreePageException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of FreePageException. + */ + public FreePageException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of FreePageException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public FreePageException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InitializationException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InitializationException.java new file mode 100644 index 000000000..e3906916a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InitializationException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when the BTree initialization failed + * + * @author Apache Directory Project + */ +public class InitializationException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of InitializationException. + */ + public InitializationException() + { + } + + + /** + * Creates a new instance of InitializationException. + * + * @param explanation The message associated with the exception + */ + public InitializationException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of InitializationException. + */ + public InitializationException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of InitializationException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public InitializationException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InvalidBTreeException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InvalidBTreeException.java new file mode 100644 index 000000000..7ce6d8877 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InvalidBTreeException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when the BTree is detected as invalid during a check + * + * @author Apache Directory Project + */ +public class InvalidBTreeException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of InvalidBTreeException. + */ + public InvalidBTreeException() + { + } + + + /** + * Creates a new instance of InvalidBTreeException. + * + * @param explanation The message associated with the exception + */ + public InvalidBTreeException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of InvalidBTreeException. + */ + public InvalidBTreeException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of InvalidBTreeException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public InvalidBTreeException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InvalidOffsetException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InvalidOffsetException.java new file mode 100644 index 000000000..c5658c67c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InvalidOffsetException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when the offset is incorrect + * + * @author Apache Directory Project + */ +public class InvalidOffsetException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of InvalidOffsetException. + */ + public InvalidOffsetException() + { + } + + + /** + * Creates a new instance of InvalidOffsetException. + * + * @param explanation The message associated with the exception + */ + public InvalidOffsetException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of InvalidOffsetException. + */ + public InvalidOffsetException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of BTreeCreationException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public InvalidOffsetException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/KeyNotFoundException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/KeyNotFoundException.java new file mode 100644 index 000000000..d2fcf03e4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/KeyNotFoundException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we can't find a key in the BTree. + * + * @author Apache Directory Project + */ +public class KeyNotFoundException extends Exception +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + /** A static Exception used to avoid creating a new one every time */ + public static final KeyNotFoundException INSTANCE = new KeyNotFoundException( + "Cannot find an entry associated with this key" ); + + + /** + * Creates a new instance of KeyNotFoundException. + */ + public KeyNotFoundException() + { + } + + + /** + * Creates a new instance of KeyNotFoundException. + * + * @param explanation The message associated with the exception + */ + public KeyNotFoundException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of KeyNotFoundException. + */ + public KeyNotFoundException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of KeyNotFoundException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public KeyNotFoundException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/MissingSerializerException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/MissingSerializerException.java new file mode 100644 index 000000000..fb8059c62 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/MissingSerializerException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we don't have either a key serializer or a value serializer + * + * @author Apache Directory Project + */ +public class MissingSerializerException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of MissingSerializerException. + */ + public MissingSerializerException() + { + } + + + /** + * Creates a new instance of MissingSerializerException. + * + * @param explanation The message associated with the exception + */ + public MissingSerializerException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of MissingSerializerException. + */ + public MissingSerializerException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of MissingSerializerException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public MissingSerializerException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/PageSizeAlreadySetException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/PageSizeAlreadySetException.java new file mode 100644 index 000000000..c0c2e0e24 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/PageSizeAlreadySetException.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we try to change the RecordManager Page Size + * when it's already set. + * already managed by the RecordManager + * + * @author Apache Directory Project + */ +public class PageSizeAlreadySetException extends Exception +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of PageSizeAlreadySetException. + */ + public PageSizeAlreadySetException() + { + } + + + /** + * Creates a new instance of PageSizeAlreadySetException. + * + * @param explanation The message associated with the exception + */ + public PageSizeAlreadySetException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of PageSizeAlreadySetException. + */ + public PageSizeAlreadySetException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of PageSizeAlreadySetException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public PageSizeAlreadySetException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/RecordManagerException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/RecordManagerException.java new file mode 100644 index 000000000..cec6f2124 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/RecordManagerException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we have an exception on a RecordManager operation + * + * @author Apache Directory Project + */ +public class RecordManagerException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of RecordManagerException. + */ + public RecordManagerException() + { + } + + + /** + * Creates a new instance of RecordManagerException. + * + * @param explanation The message associated with the exception + */ + public RecordManagerException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of RecordManagerException. + */ + public RecordManagerException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of RecordManagerException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public RecordManagerException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/SerializerCreationException.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/SerializerCreationException.java new file mode 100644 index 000000000..57cb22db5 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/SerializerCreationException.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.directory.mavibot.btree.exception; + + +/** + * An exception thrown when we can't create a serializer + * + * @author Apache Directory Project + */ +public class SerializerCreationException extends RuntimeException +{ + /** The serial version UUID */ + private static final long serialVersionUID = 1L; + + + /** + * Creates a new instance of SerializerCreationException. + */ + public SerializerCreationException() + { + } + + + /** + * Creates a new instance of SerializerCreationException. + * + * @param explanation The message associated with the exception + */ + public SerializerCreationException( String explanation ) + { + super( explanation ); + } + + + /** + * Creates a new instance of SerializerCreationException. + */ + public SerializerCreationException( Throwable cause ) + { + super( cause ); + } + + + /** + * Creates a new instance of SerializerCreationException. + * + * @param explanation The message associated with the exception + * @param cause The root cause for this exception + */ + public SerializerCreationException( String explanation, Throwable cause ) + { + super( explanation, cause ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/memory/BulkDataSorter.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/memory/BulkDataSorter.java new file mode 100644 index 000000000..077d58592 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/memory/BulkDataSorter.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.memory; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.UUID; + +import org.apache.directory.mavibot.btree.Tuple; +import org.apache.directory.mavibot.btree.util.TupleReaderWriter; + + +/** + * A utility class for sorting a large number of keys before building a BTree using {@link InMemoryBTreeBuilder}. + * + * @author Apache Directory Project + */ +public class BulkDataSorter +{ + private File workDir; + + private int splitAfter = 1000; + + private Comparator> tupleComparator; + + private TupleReaderWriter readerWriter; + + private boolean sorted; + + + public BulkDataSorter( TupleReaderWriter readerWriter, Comparator> tupleComparator, + int splitAfter ) + { + if ( splitAfter <= 0 ) + { + throw new IllegalArgumentException( "Value of splitAfter parameter cannot be null" ); + } + + this.splitAfter = splitAfter; + + this.workDir = new File( System.getProperty( "java.io.tmpdir" ), System.currentTimeMillis() + "-sort" ); + workDir.mkdir(); + + this.readerWriter = readerWriter; + this.tupleComparator = tupleComparator; + } + + + public void sort( File dataFile ) throws IOException + { + int i = 0; + + Tuple[] arr = ( Tuple[] ) Array.newInstance( Tuple.class, splitAfter ); + + Tuple t = null; + + DataInputStream in = new DataInputStream( new FileInputStream( dataFile ) ); + + while ( ( t = readerWriter.readUnsortedTuple( in ) ) != null ) + { + arr[i++] = t; + + if ( ( i % splitAfter ) == 0 ) + { + i = 0; + Arrays.sort( arr, tupleComparator ); + + storeSortedData( arr ); + } + } + + if ( i != 0 ) + { + Tuple[] tmp = ( Tuple[] ) Array.newInstance( Tuple.class, i ); + System.arraycopy( arr, 0, tmp, 0, i ); + Arrays.sort( tmp, tupleComparator ); + + storeSortedData( tmp ); + } + + sorted = true; + } + + + private void storeSortedData( Tuple[] arr ) throws IOException + { + File tempFile = File.createTempFile( UUID.randomUUID().toString(), ".batch", workDir ); + DataOutputStream out = new DataOutputStream( new FileOutputStream( tempFile ) ); + + for ( Tuple t : arr ) + { + readerWriter.storeSortedTuple( t, out ); + } + + out.flush(); + out.close(); + } + + + public File getWorkDir() + { + return workDir; + } + + + public Iterator> getMergeSortedTuples() throws IOException + { + if ( !sorted ) + { + throw new IllegalStateException( "Data is not sorted" ); + } + + File[] batches = workDir.listFiles(); + + if ( batches.length == 0 ) + { + return Collections.EMPTY_LIST.iterator(); + } + + final DataInputStream[] streams = new DataInputStream[batches.length]; + + for ( int i = 0; i < batches.length; i++ ) + { + streams[i] = new DataInputStream( new FileInputStream( batches[i] ) ); + } + + Iterator> itr = new Iterator>() + { + private Tuple[] heads = ( Tuple[] ) Array.newInstance( Tuple.class, streams.length ); + + private Tuple candidate = null; + + private boolean closed; + + private int candidatePos = -1; + + + @Override + public boolean hasNext() + { + + if ( closed ) + { + throw new IllegalStateException( "No elements to read" ); + } + + Tuple available = null; + + for ( int i = 0; i < streams.length; i++ ) + { + if ( heads[i] == null ) + { + heads[i] = readerWriter.readUnsortedTuple( streams[i] ); + } + + if ( available == null ) + { + available = heads[i]; + candidatePos = i; + } + else + { + if ( ( available != null ) && ( heads[i] != null ) ) + { + int comp = tupleComparator.compare( heads[i], available ); + if ( comp <= 0 ) + { + available = heads[i]; + candidatePos = i; + } + } + } + } + + heads[candidatePos] = null; + + if ( available == null ) + { + for ( int i = 0; i < streams.length; i++ ) + { + if ( heads[i] != null ) + { + available = heads[i]; + heads[i] = readerWriter.readUnsortedTuple( streams[i] ); + break; + } + } + } + + if ( available != null ) + { + candidate = available; + return true; + } + + // finally close the streams + for ( DataInputStream in : streams ) + { + try + { + in.close(); + } + catch ( Exception e ) + { + e.printStackTrace(); + } + } + + closed = true; + + return false; + } + + + @Override + public Tuple next() + { + if ( candidate == null ) + { + if ( !closed ) + { + hasNext(); + } + } + + if ( candidate == null ) + { + throw new NoSuchElementException( "No tuples found" ); + } + + return candidate; + } + + + @Override + public void remove() + { + throw new UnsupportedOperationException( "Not supported" ); + } + + }; + + return itr; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/persisted/BulkDataSorter.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/persisted/BulkDataSorter.java new file mode 100644 index 000000000..b67f1a55a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/persisted/BulkDataSorter.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.persisted; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.UUID; + +import org.apache.directory.mavibot.btree.Tuple; +import org.apache.directory.mavibot.btree.util.TupleReaderWriter; + + +/** + * A utility class for sorting a large number of keys before building a BTree using {@link PersistedBTreeBuilder}. + * + * @author Apache Directory Project + */ +public class BulkDataSorter +{ + private File workDir; + + private int splitAfter = 1000; + + private Comparator> tupleComparator; + + private TupleReaderWriter readerWriter; + + private boolean sorted; + + + public BulkDataSorter( TupleReaderWriter readerWriter, Comparator> tupleComparator, + int splitAfter ) + { + if ( splitAfter <= 0 ) + { + throw new IllegalArgumentException( "Value of splitAfter parameter cannot be null" ); + } + + this.splitAfter = splitAfter; + + this.workDir = new File( System.getProperty( "java.io.tmpdir" ), System.currentTimeMillis() + "-sort" ); + workDir.mkdir(); + + this.readerWriter = readerWriter; + this.tupleComparator = tupleComparator; + } + + + public void sort( File dataFile ) throws IOException + { + int i = 0; + + Tuple[] arr = ( Tuple[] ) Array.newInstance( Tuple.class, splitAfter ); + + Tuple t = null; + + DataInputStream in = new DataInputStream( new FileInputStream( dataFile ) ); + + while ( ( t = readerWriter.readUnsortedTuple( in ) ) != null ) + { + arr[i++] = t; + + if ( ( i % splitAfter ) == 0 ) + { + i = 0; + Arrays.sort( arr, tupleComparator ); + + storeSortedData( arr ); + } + } + + if ( i != 0 ) + { + Tuple[] tmp = ( Tuple[] ) Array.newInstance( Tuple.class, i ); + System.arraycopy( arr, 0, tmp, 0, i ); + Arrays.sort( tmp, tupleComparator ); + + storeSortedData( tmp ); + } + + sorted = true; + } + + + private void storeSortedData( Tuple[] arr ) throws IOException + { + File tempFile = File.createTempFile( UUID.randomUUID().toString(), ".batch", workDir ); + DataOutputStream out = new DataOutputStream( new FileOutputStream( tempFile ) ); + + for ( Tuple t : arr ) + { + readerWriter.storeSortedTuple( t, out ); + } + + out.flush(); + out.close(); + } + + + public File getWorkDir() + { + return workDir; + } + + + public Iterator> getMergeSortedTuples() throws IOException + { + if ( !sorted ) + { + throw new IllegalStateException( "Data is not sorted" ); + } + + File[] batches = workDir.listFiles(); + + if ( batches.length == 0 ) + { + return Collections.EMPTY_LIST.iterator(); + } + + final DataInputStream[] streams = new DataInputStream[batches.length]; + + for ( int i = 0; i < batches.length; i++ ) + { + streams[i] = new DataInputStream( new FileInputStream( batches[i] ) ); + } + + Iterator> itr = new Iterator>() + { + private Tuple[] heads = ( Tuple[] ) Array.newInstance( Tuple.class, streams.length ); + + private Tuple candidate = null; + + private boolean closed; + + private int candidatePos = -1; + + + @Override + public boolean hasNext() + { + + if ( closed ) + { + throw new IllegalStateException( "No elements to read" ); + } + + Tuple available = null; + + for ( int i = 0; i < streams.length; i++ ) + { + if ( heads[i] == null ) + { + heads[i] = readerWriter.readSortedTuple( streams[i] ); + } + + if ( available == null ) + { + available = heads[i]; + candidatePos = i; + } + else + { + if ( ( available != null ) && ( heads[i] != null ) ) + { + int comp = tupleComparator.compare( heads[i], available ); + if ( comp <= 0 ) + { + available = heads[i]; + candidatePos = i; + } + } + } + } + + heads[candidatePos] = null; + + if ( available == null ) + { + for ( int i = 0; i < streams.length; i++ ) + { + if ( heads[i] != null ) + { + available = heads[i]; + heads[i] = readerWriter.readUnsortedTuple( streams[i] ); + break; + } + } + } + + if ( available != null ) + { + candidate = available; + return true; + } + + // finally close the streams + for ( DataInputStream in : streams ) + { + try + { + in.close(); + } + catch ( Exception e ) + { + e.printStackTrace(); + } + } + + closed = true; + + return false; + } + + + @Override + public Tuple next() + { + if ( candidate == null ) + { + if ( !closed ) + { + hasNext(); + } + } + + if ( candidate == null ) + { + throw new NoSuchElementException( "No tuples found" ); + } + + return candidate; + } + + + @Override + public void remove() + { + throw new UnsupportedOperationException( "Not supported" ); + } + + }; + + return itr; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/AbstractElementSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/AbstractElementSerializer.java new file mode 100644 index 000000000..a7b2090ba --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/AbstractElementSerializer.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Comparator; + + +/** + * An abstract ElementSerializer that implements comon methods + * + * @param The type for the element to serialize and compare + * + * @author Apache Directory Project + */ +public abstract class AbstractElementSerializer implements ElementSerializer +{ + /** The associated comparator */ + private final Comparator comparator; + + /** The type which is being serialized */ + private Class type; + + + /** + * Create a new instance of Serializer + */ + public AbstractElementSerializer( Comparator comparator ) + { + this.comparator = comparator; + + // We will extract the Type to use for values, using the comparator for that + Class comparatorClass = comparator.getClass(); + Type[] types = comparatorClass.getGenericInterfaces(); + + if ( types[0] instanceof Class ) + { + type = ( Class ) types[0]; + } + else + { + Type[] argumentTypes = ( ( ParameterizedType ) types[0] ).getActualTypeArguments(); + + if ( ( argumentTypes != null ) && ( argumentTypes.length > 0 ) ) + { + if ( argumentTypes[0] instanceof Class ) + { + type = ( Class ) argumentTypes[0]; + } + else if ( argumentTypes[0] instanceof GenericArrayType ) + { + Class clazz = ( Class ) ( ( GenericArrayType ) argumentTypes[0] ).getGenericComponentType(); + + type = Array.newInstance( clazz, 0 ).getClass(); + } + } + } + } + + + /** + * {@inheritDoc} + */ + public int compare( T type1, T type2 ) + { + return comparator.compare( type1, type2 ); + } + + + /** + * {@inheritDoc} + */ + @Override + public Comparator getComparator() + { + return comparator; + } + + + /** + * {@inheritDoc} + */ + @Override + public Class getType() + { + return type; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/BooleanSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/BooleanSerializer.java new file mode 100644 index 000000000..6abdc3331 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/BooleanSerializer.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.comparator.BooleanComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; + + +/** + * The Boolean serializer. + * + * @author Apache Directory Project + */ +public class BooleanSerializer extends AbstractElementSerializer +{ + /** A static instance of a BooleanSerializer */ + public static final BooleanSerializer INSTANCE = new BooleanSerializer(); + + /** + * Create a new instance of BooleanSerializer + */ + private BooleanSerializer() + { + super( BooleanComparator.INSTANCE ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( Boolean element ) + { + byte[] bytes = new byte[1]; + + return serialize( bytes, 0, element ); + } + + + /** + * Serialize a boolean + * + * @param value the value to serialize + * @return The byte[] containing the serialized boolean + */ + public static byte[] serialize( boolean element ) + { + byte[] bytes = new byte[1]; + + return serialize( bytes, 0, element ); + } + + + /** + * Serialize a boolean + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized boolean + * @param value the value to serialize + * @return The byte[] containing the serialized boolean + */ + public static byte[] serialize( byte[] buffer, int start, boolean element ) + { + buffer[start] = element ? ( byte ) 0x01 : ( byte ) 0x00; + + return buffer; + } + + + /** + * A static method used to deserialize a Boolean from a byte array. + * + * @param in The byte array containing the boolean + * @return A boolean + */ + public static Boolean deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a Boolean from a byte array. + * + * @param in The byte array containing the boolean + * @param start the position in the byte[] we will deserialize the boolean from + * @return A boolean + */ + public static Boolean deserialize( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 1 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Boolean from a buffer with not enough bytes" ); + } + + return in[start] == 0x01; + } + + + /** + * A method used to deserialize a Boolean from a byte array. + * + * @param in The byte array containing the boolean + * @return A boolean + */ + public Boolean fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A method used to deserialize a Boolean from a byte array. + * + * @param in The byte array containing the boolean + * @param start the position in the byte[] we will deserialize the boolean from + * @return A boolean + */ + public Boolean fromBytes( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 1 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Boolean from a buffer with not enough bytes" ); + } + + return in[start] == 0x01; + } + + + /** + * {@inheritDoc} + */ + public Boolean deserialize( ByteBuffer buffer ) throws IOException + { + return buffer.get() != 0x00; + } + + + /** + * {@inheritDoc} + */ + @Override + public Boolean deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 1 ); + + return deserialize( in ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/BufferHandler.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/BufferHandler.java new file mode 100644 index 000000000..154200cba --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/BufferHandler.java @@ -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. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + + +/** + * A class used to hide the buffer read from the underlying file. + * + * @author Apache Directory Project + */ +public class BufferHandler +{ + /** The channel we read bytes from */ + private FileChannel channel; + + /** The buffer containing the bytes we read from the channel */ + private ByteBuffer buffer; + + + /** + * Create a new BufferHandler + * @param buffer The buffer used to transfer data + */ + public BufferHandler( byte[] buffer ) + { + this.buffer = ByteBuffer.allocate( buffer.length ); + this.buffer.put( buffer ); + this.buffer.flip(); + } + + + /** + * Create a new BufferHandler + * @param channel The channel to read + * @param buffer The buffer used to transfer data + */ + public BufferHandler( FileChannel channel, ByteBuffer buffer ) + { + this.channel = channel; + this.buffer = buffer; + + try + { + // Initial read + channel.read( buffer ); + buffer.flip(); + } + catch ( IOException ioe ) + { + + } + } + + + public byte[] getBuffer() + { + byte[] bytes = new byte[buffer.capacity()]; + + buffer.get( bytes ); + + return bytes; + } + + + /** + * Read a buffer containing the given number of bytes + * @param len The number of bytes to read + * @return + */ + public byte[] read( int len ) throws IOException + { + byte[] result = new byte[len]; + + if ( len <= buffer.remaining() ) + { + buffer.get( result ); + + return result; + } + + int requested = len; + int position = 0; + + while ( requested != 0 ) + { + int nbRemainingRead = buffer.limit() - buffer.position(); + + if ( nbRemainingRead > requested ) + { + buffer.get( result, position, requested ); + break; + } + else + { + System.arraycopy( buffer.array(), buffer.position(), result, position, nbRemainingRead ); + position += nbRemainingRead; + } + + buffer.clear(); + + if ( channel != null ) + { + int nbReads = channel.read( buffer ); + buffer.flip(); + + if ( nbReads <= 0 ) + { + throw new EOFException(); + } + } + else + { + throw new IOException( "Not enough bytes in the buffer" ); + } + + requested -= nbRemainingRead; + } + + return result; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ByteArraySerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ByteArraySerializer.java new file mode 100644 index 000000000..cd78e7f4d --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ByteArraySerializer.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Comparator; + +import org.apache.directory.mavibot.btree.comparator.ByteArrayComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; + + +/** + * A serializer for a byte[]. + * + * @author Apache Directory Project + */ +public class ByteArraySerializer extends AbstractElementSerializer +{ + /** A static instance of a BytearraySerializer */ + public static final ByteArraySerializer INSTANCE = new ByteArraySerializer(); + + /** + * Create a new instance of ByteArraySerializer + */ + private ByteArraySerializer() + { + super( ByteArrayComparator.INSTANCE ); + } + + + /** + * Create a new instance of ByteArraySerializer with custom comparator + */ + public ByteArraySerializer( Comparator comparator ) + { + super( comparator ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( byte[] element ) + { + int len = -1; + + if ( element != null ) + { + len = element.length; + } + + byte[] bytes = null; + + switch ( len ) + { + case 0: + bytes = new byte[4]; + + bytes[0] = 0x00; + bytes[1] = 0x00; + bytes[2] = 0x00; + bytes[3] = 0x00; + + break; + + case -1: + bytes = new byte[4]; + + bytes[0] = ( byte ) 0xFF; + bytes[1] = ( byte ) 0xFF; + bytes[2] = ( byte ) 0xFF; + bytes[3] = ( byte ) 0xFF; + + break; + + default: + bytes = new byte[len + 4]; + + System.arraycopy( element, 0, bytes, 4, len ); + + bytes[0] = ( byte ) ( len >>> 24 ); + bytes[1] = ( byte ) ( len >>> 16 ); + bytes[2] = ( byte ) ( len >>> 8 ); + bytes[3] = ( byte ) ( len ); + } + + return bytes; + } + + + /** + * Serialize a byte[] + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized byte[] + * @param value the value to serialize + * @return The byte[] containing the serialized byte[] + */ + public static byte[] serialize( byte[] buffer, int start, byte[] element ) + { + int len = -1; + + if ( element != null ) + { + len = element.length; + } + + switch ( len ) + { + case 0: + buffer[start] = 0x00; + buffer[start + 1] = 0x00; + buffer[start + 2] = 0x00; + buffer[start + 3] = 0x00; + + break; + + case -1: + buffer[start] = ( byte ) 0xFF; + buffer[start + 1] = ( byte ) 0xFF; + buffer[start + 2] = ( byte ) 0xFF; + buffer[start + 3] = ( byte ) 0xFF; + + break; + + default: + + buffer[start] = ( byte ) ( len >>> 24 ); + buffer[start + 1] = ( byte ) ( len >>> 16 ); + buffer[start + 2] = ( byte ) ( len >>> 8 ); + buffer[start + 3] = ( byte ) ( len ); + + System.arraycopy( element, 0, buffer, 4 + start, len ); + } + + return buffer; + + } + + + /** + * A static method used to deserialize a byte array from a byte array. + * + * @param in The byte array containing the byte array + * @return A byte[] + */ + public static byte[] deserialize( byte[] in ) + { + if ( ( in == null ) || ( in.length < 4 ) ) + { + throw new SerializerCreationException( "Cannot extract a byte[] from a buffer with not enough bytes" ); + } + + int len = IntSerializer.deserialize( in ); + + switch ( len ) + { + case 0: + return new byte[] + {}; + + case -1: + return null; + + default: + byte[] result = new byte[len]; + System.arraycopy( in, 4, result, 0, len ); + + return result; + } + } + + + /** + * A static method used to deserialize a byte array from a byte array. + * + * @param in The byte array containing the byte array + * @param start the position in the byte[] we will deserialize the byte[] from + * @return A byte[] + */ + public static byte[] deserialize( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 4 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a byte[] from a buffer with not enough bytes" ); + } + + int len = IntSerializer.deserialize( in, start ); + + switch ( len ) + { + case 0: + return new byte[] + {}; + + case -1: + return null; + + default: + byte[] result = new byte[len]; + System.arraycopy( in, 4 + start, result, 0, len ); + + return result; + } + } + + + /** + * A method used to deserialize a byte array from a byte array. + * + * @param in The byte array containing the byte array + * @return A byte[] + */ + public byte[] fromBytes( byte[] in ) + { + if ( ( in == null ) || ( in.length < 4 ) ) + { + throw new SerializerCreationException( "Cannot extract a byte[] from a buffer with not enough bytes" ); + } + + int len = IntSerializer.deserialize( in ); + + switch ( len ) + { + case 0: + return new byte[] + {}; + + case -1: + return null; + + default: + byte[] result = new byte[len]; + System.arraycopy( in, 4, result, 0, len ); + + return result; + } + } + + + /** + * A method used to deserialize a byte array from a byte array. + * + * @param in The byte array containing the byte array + * @param start the position in the byte[] we will deserialize the byte[] from + * @return A byte[] + */ + public byte[] fromBytes( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 4 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a byte[] from a buffer with not enough bytes" ); + } + + int len = IntSerializer.deserialize( in, start ); + + switch ( len ) + { + case 0: + return new byte[] + {}; + + case -1: + return null; + + default: + byte[] result = new byte[len]; + System.arraycopy( in, 4 + start, result, 0, len ); + + return result; + } + } + + + /** + * {@inheritDoc} + */ + public byte[] deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 4 ); + + int len = IntSerializer.deserialize( in ); + + switch ( len ) + { + case 0: + return new byte[] + {}; + + case -1: + return null; + + default: + in = bufferHandler.read( len ); + + return in; + } + } + + + /** + * {@inheritDoc} + */ + public byte[] deserialize( ByteBuffer buffer ) throws IOException + { + int len = buffer.getInt(); + + switch ( len ) + { + case 0: + return new byte[] + {}; + + case -1: + return null; + + default: + byte[] bytes = new byte[len]; + + buffer.get( bytes ); + + return bytes; + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ByteSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ByteSerializer.java new file mode 100644 index 000000000..425dc38f5 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ByteSerializer.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.comparator.ByteComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; + + +/** + * The Byte serializer. + * + * @author Apache Directory Project + */ +public class ByteSerializer extends AbstractElementSerializer +{ + /** A static instance of a ByteSerializer */ + public static final ByteSerializer INSTANCE = new ByteSerializer(); + + /** + * Create a new instance of ByteSerializer + */ + private ByteSerializer() + { + super( ByteComparator.INSTANCE ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( Byte element ) + { + byte[] bytes = new byte[1]; + + return serialize( bytes, 0, element ); + } + + + /** + * Serialize a byte + * + * @param value the value to serialize + * @return The byte[] containing the serialized byte + */ + public static byte[] serialize( byte value ) + { + byte[] bytes = new byte[1]; + + return serialize( bytes, 0, value ); + } + + + /** + * Serialize a byte + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized byte + * @param value the value to serialize + * @return The byte[] containing the serialized byte + */ + public static byte[] serialize( byte[] buffer, int start, byte value ) + { + buffer[start] = value; + + return buffer; + } + + + /** + * A static method used to deserialize a Byte from a byte array. + * @param in The byte array containing the Byte + * @return A Byte + */ + public static Byte deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a Byte from a byte array. + * @param in The byte array containing the Byte + * @param start the position in the byte[] we will deserialize the byte from + * @return A Byte + */ + public static Byte deserialize( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 1 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Byte from a buffer with not enough bytes" ); + } + + return in[start]; + } + + + /** + * A method used to deserialize a Byte from a byte array. + * @param in The byte array containing the Byte + * @return A Byte + */ + public Byte fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A method used to deserialize a Byte from a byte array. + * @param in The byte array containing the Byte + * @param start the position in the byte[] we will deserialize the byte from + * @return A Byte + */ + public Byte fromBytes( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 1 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Byte from a buffer with not enough bytes" ); + } + + return in[start]; + } + + + /** + * {@inheritDoc} + */ + public Byte deserialize( ByteBuffer buffer ) throws IOException + { + return buffer.get(); + } + + + /** + * {@inheritDoc} + */ + public Byte deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 1 ); + + return deserialize( in ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/CharArraySerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/CharArraySerializer.java new file mode 100644 index 000000000..0b5456918 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/CharArraySerializer.java @@ -0,0 +1,279 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.comparator.CharArrayComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; + + +/** + * A serializer for a char[]. + * + * @author Apache Directory Project + */ +public class CharArraySerializer extends AbstractElementSerializer +{ + /** A static instance of a CharArraySerializer */ + public static final CharArraySerializer INSTANCE = new CharArraySerializer(); + + /** + * Create a new instance of CharArraySerializer + */ + private CharArraySerializer() + { + super( CharArrayComparator.INSTANCE ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( char[] element ) + { + int len = -1; + + if ( element != null ) + { + len = element.length; + } + + byte[] bytes = null; + + switch ( len ) + { + case 0: + bytes = new byte[4]; + + bytes[0] = 0x00; + bytes[1] = 0x00; + bytes[2] = 0x00; + bytes[3] = 0x00; + + break; + + case -1: + bytes = new byte[4]; + + bytes[0] = ( byte ) 0xFF; + bytes[1] = ( byte ) 0xFF; + bytes[2] = ( byte ) 0xFF; + bytes[3] = ( byte ) 0xFF; + + break; + + default: + bytes = new byte[len * 2 + 4]; + int pos = 4; + + bytes[0] = ( byte ) ( len >>> 24 ); + bytes[1] = ( byte ) ( len >>> 16 ); + bytes[2] = ( byte ) ( len >>> 8 ); + bytes[3] = ( byte ) ( len ); + + for ( char c : element ) + { + bytes[pos++] = ( byte ) ( c >>> 8 ); + bytes[pos++] = ( byte ) ( c ); + } + } + + return bytes; + } + + + /** + * A static method used to deserialize a char array from a byte array. + * + * @param in The byte array containing the char array + * @return A char[] + */ + public static char[] deserialize( byte[] in ) + { + if ( ( in == null ) || ( in.length < 4 ) ) + { + throw new SerializerCreationException( "Cannot extract a byte[] from a buffer with not enough bytes" ); + } + + int len = IntSerializer.deserialize( in ); + + switch ( len ) + { + case 0: + return new char[] + {}; + + case -1: + return null; + + default: + char[] result = new char[len]; + + for ( int i = 4; i < len * 2 + 4; i += 2 ) + { + result[i] = Character.valueOf( ( char ) ( ( in[i] << 8 ) + + ( in[i + 1] & 0xFF ) ) ); + } + + return result; + } + } + + + /** + * A method used to deserialize a char array from a byte array. + * + * @param in The byte array containing the char array + * @return A char[] + */ + public char[] fromBytes( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length - start < 4 ) ) + { + throw new SerializerCreationException( "Cannot extract a byte[] from a buffer with not enough bytes" ); + } + + int len = IntSerializer.deserialize( in, start ); + + switch ( len ) + { + case 0: + return new char[] + {}; + + case -1: + return null; + + default: + char[] result = new char[len]; + + for ( int i = 4; i < len * 2 + 4; i += 2 ) + { + result[i] = Character.valueOf( ( char ) ( ( in[i] << 8 ) + + ( in[i + 1] & 0xFF ) ) ); + } + + return result; + } + } + + + /** + * A method used to deserialize a char array from a byte array. + * + * @param in The byte array containing the char array + * @return A char[] + */ + public char[] fromBytes( byte[] in ) + { + if ( ( in == null ) || ( in.length < 4 ) ) + { + throw new SerializerCreationException( "Cannot extract a byte[] from a buffer with not enough bytes" ); + } + + int len = IntSerializer.deserialize( in ); + + switch ( len ) + { + case 0: + return new char[] + {}; + + case -1: + return null; + + default: + char[] result = new char[len]; + + for ( int i = 4; i < len * 2 + 4; i += 2 ) + { + result[i] = Character.valueOf( ( char ) ( ( in[i] << 8 ) + + ( in[i + 1] & 0xFF ) ) ); + } + + return result; + } + } + + + /** + * {@inheritDoc} + */ + public char[] deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 4 ); + + int len = IntSerializer.deserialize( in ); + + switch ( len ) + { + case 0: + return new char[] + {}; + + case -1: + return null; + + default: + char[] result = new char[len]; + byte[] buffer = bufferHandler.read( len * 2 ); + + for ( int i = 0; i < len * 2; i += 2 ) + { + result[i] = Character.valueOf( ( char ) ( ( buffer[i] << 8 ) + + ( buffer[i + 1] & 0xFF ) ) ); + } + + return result; + } + } + + + /** + * {@inheritDoc} + */ + public char[] deserialize( ByteBuffer buffer ) throws IOException + { + int len = buffer.getInt(); + + switch ( len ) + { + case 0: + return new char[] + {}; + + case -1: + return null; + + default: + char[] result = new char[len]; + + for ( int i = 0; i < len; i++ ) + { + result[i] = buffer.getChar(); + } + + return result; + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/CharSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/CharSerializer.java new file mode 100644 index 000000000..6e443a9f5 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/CharSerializer.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.comparator.CharComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; + + +/** + * The Character serializer. + * + * @author Apache Directory Project + */ +public class CharSerializer extends AbstractElementSerializer +{ + /** A static instance of a CharSerializer */ + public static final CharSerializer INSTANCE = new CharSerializer(); + + /** + * Create a new instance of CharSerializer + */ + private CharSerializer() + { + super( CharComparator.INSTANCE ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( Character element ) + { + byte[] bytes = new byte[2]; + + return serialize( bytes, 0, element ); + } + + + /** + * Serialize a char + * + * @param value the value to serialize + * @return The byte[] containing the serialized char + */ + public static byte[] serialize( char value ) + { + byte[] bytes = new byte[2]; + + return serialize( bytes, 0, value ); + } + + + /** + * Serialize a char + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized char + * @param value the value to serialize + * @return The byte[] containing the serialized char + */ + public static byte[] serialize( byte[] buffer, int start, char value ) + { + buffer[start] = ( byte ) ( value >>> 8 ); + buffer[start + 1] = ( byte ) ( value ); + + return buffer; + } + + + /** + * A static method used to deserialize a Character from a byte array. + * @param in The byte array containing the Character + * @return A Character + */ + public static Character deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a Character from a byte array. + * @param in The byte array containing the Character + * @param start the position in the byte[] we will deserialize the char from + * @return A Character + */ + public static Character deserialize( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 2 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Character from a buffer with not enough bytes" ); + } + + return Character.valueOf( ( char ) ( ( in[start] << 8 ) + + ( in[start + 1] & 0xFF ) ) ); + } + + + /** + * A method used to deserialize a Character from a byte array. + * @param in The byte array containing the Character + * @return A Character + */ + public Character fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a Character from a byte array. + * @param in The byte array containing the Character + * @param start the position in the byte[] we will deserialize the char from + * @return A Character + */ + public Character fromBytes( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 2 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Character from a buffer with not enough bytes" ); + } + + return Character.valueOf( ( char ) ( ( in[start] << 8 ) + + ( in[start + 1] & 0xFF ) ) ); + } + + + /** + * {@inheritDoc} + */ + public Character deserialize( ByteBuffer buffer ) throws IOException + { + return buffer.getChar(); + } + + + /** + * {@inheritDoc} + */ + public Character deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 2 ); + + return deserialize( in ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ElementSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ElementSerializer.java new file mode 100644 index 000000000..2f73e840c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ElementSerializer.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Comparator; + + +/** + * This interface is used by implementations of serializer, deserializer and comparator. + * + * @param The type for the element to serialize and compare + * + * @author Apache Directory Project + */ +public interface ElementSerializer +{ + /** + * Produce the byte[] representation of the element + * + * @param key The element to serialize + * @return The byte[] containing the serialized element + */ + byte[] serialize( T key ); + + + /** + * Deserialize an element from a BufferHandler + * + * @param bufferHandler The incoming bufferHandler + * @return The deserialized element + * @throws IOException If the deserialization failed + */ + T deserialize( BufferHandler bufferHandler ) throws IOException; + + + /** + * Deserialize an element from a ByteBuffer + * + * @param buffer The incoming ByteBuffer + * @return The deserialized element + * @throws IOException If the deserialization failed + */ + T deserialize( ByteBuffer buffer ) throws IOException; + + + /** + * Deserialize an element from a byte[] + * + * @param buffer The incoming byte[] + * @return The deserialized element + * @throws IOException If the deserialization failed + */ + T fromBytes( byte[] buffer ) throws IOException; + + + /** + * Deserialize an element from a byte[] + * + * @param buffer The incoming byte[] + * @return The deserialized element + * @throws IOException If the deserialization failed + */ + T fromBytes( byte[] buffer, int pos ) throws IOException; + + + /** + * Returns the comparison of two types.
          + *

            + *
          • If type1 < type2, return -1
          • + *
          • If type1 > type2, return 1
          • + *
          • If type1 == type2, return 0
          • + *
          + * + * @param type1 The first type to compare + * @param type2 The second type to compare + * @return The comparison result + */ + int compare( T type1, T type2 ); + + + /** + * @return the comparator for the used type + */ + Comparator getComparator(); + + + /** + * @return the type being serialized + */ + Class getType(); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/IntSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/IntSerializer.java new file mode 100644 index 000000000..b2a6ea224 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/IntSerializer.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.comparator.IntComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; + + +/** + * The Integer serializer. + * + * @author Apache Directory Project + */ +public class IntSerializer extends AbstractElementSerializer +{ + /** A static instance of a IntSerializer */ + public static final IntSerializer INSTANCE = new IntSerializer(); + + /** + * Create a new instance of IntSerializer + */ + private IntSerializer() + { + super( IntComparator.INSTANCE ); + } + + + /** + * A static method used to deserialize an Integer from a byte array. + * @param in The byte array containing the Integer + * @return An Integer + */ + public static Integer deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize an Integer from a byte array. + * @param in The byte array containing the Integer + * @param start the position in the byte[] we will deserialize the int from + * @return An Integer + */ + public static Integer deserialize( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 4 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Integer from a buffer with not enough bytes" ); + } + + return ( in[start] << 24 ) + + ( ( in[start + 1] & 0xFF ) << 16 ) + + ( ( in[start + 2] & 0xFF ) << 8 ) + + ( in[start + 3] & 0xFF ); + } + + + /** + * A method used to deserialize an Integer from a byte array. + * @param in The byte array containing the Integer + * @return An Integer + */ + public Integer fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A method used to deserialize an Integer from a byte array. + * @param in The byte array containing the Integer + * @param start the position in the byte[] we will deserialize the int from + * @return An Integer + */ + public Integer fromBytes( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 4 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Integer from a buffer with not enough bytes" ); + } + + return ( in[start] << 24 ) + + ( ( in[start + 1] & 0xFF ) << 16 ) + + ( ( in[start + 2] & 0xFF ) << 8 ) + + ( in[start + 3] & 0xFF ); + } + + + /** + * {@inheritDoc} + */ + public Integer deserialize( ByteBuffer buffer ) throws IOException + { + return buffer.getInt(); + } + + + /** + * {@inheritDoc} + */ + public Integer deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 4 ); + + return deserialize( in ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( Integer element ) + { + return serialize( element.intValue() ); + } + + + /** + * Serialize an int + * + * @param value the value to serialize + * @return The byte[] containing the serialized int + */ + public static byte[] serialize( int value ) + { + byte[] bytes = new byte[4]; + + return serialize( bytes, 0, value ); + } + + + /** + * Serialize an int + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized int + * @param value the value to serialize + * @return The byte[] containing the serialized int + */ + public static byte[] serialize( byte[] buffer, int start, int value ) + { + buffer[start] = ( byte ) ( value >>> 24 ); + buffer[start + 1] = ( byte ) ( value >>> 16 ); + buffer[start + 2] = ( byte ) ( value >>> 8 ); + buffer[start + 3] = ( byte ) ( value ); + + return buffer; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/LongArraySerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/LongArraySerializer.java new file mode 100644 index 000000000..0e696cfd4 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/LongArraySerializer.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.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.comparator.LongArrayComparator; + + +/** + * A serializer for a Long[]. + * + * @author Apache Directory Project + */ +public class LongArraySerializer extends AbstractElementSerializer +{ + /** A static instance of a LongArraySerializer */ + public static final LongArraySerializer INSTANCE = new LongArraySerializer(); + + /** + * Create a new instance of LongSerializer + */ + private LongArraySerializer() + { + super( LongArrayComparator.INSTANCE ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( long[] element ) + { + int len = -1; + + if ( element != null ) + { + len = element.length; + } + + byte[] bytes = null; + int pos = 0; + + switch ( len ) + { + case 0: + bytes = new byte[4]; + + // The number of Long. Here, 0 + bytes[pos++] = 0x00; + bytes[pos++] = 0x00; + bytes[pos++] = 0x00; + bytes[pos++] = 0x00; + + break; + + case -1: + bytes = new byte[4]; + + // The number of Long. Here, null + bytes[pos++] = ( byte ) 0xFF; + bytes[pos++] = ( byte ) 0xFF; + bytes[pos++] = ( byte ) 0xFF; + bytes[pos++] = ( byte ) 0xFF; + + break; + + default: + int dataLen = len * 8 + 4; + bytes = new byte[dataLen]; + + // The number of longs + bytes[pos++] = ( byte ) ( len >>> 24 ); + bytes[pos++] = ( byte ) ( len >>> 16 ); + bytes[pos++] = ( byte ) ( len >>> 8 ); + bytes[pos++] = ( byte ) ( len ); + + // Serialize the longs now + for ( long value : element ) + { + bytes[pos++] = ( byte ) ( value >>> 56 ); + bytes[pos++] = ( byte ) ( value >>> 48 ); + bytes[pos++] = ( byte ) ( value >>> 40 ); + bytes[pos++] = ( byte ) ( value >>> 32 ); + bytes[pos++] = ( byte ) ( value >>> 24 ); + bytes[pos++] = ( byte ) ( value >>> 16 ); + bytes[pos++] = ( byte ) ( value >>> 8 ); + bytes[pos++] = ( byte ) ( value ); + } + } + + return bytes; + } + + + /** + * {@inheritDoc} + */ + public long[] deserialize( BufferHandler bufferHandler ) throws IOException + { + // Read the DataLength first. Note that we don't use it here. + byte[] in = bufferHandler.read( 4 ); + + IntSerializer.deserialize( in ); + + // Now, read the number of Longs + in = bufferHandler.read( 4 ); + + int nbLongs = IntSerializer.deserialize( in ); + + switch ( nbLongs ) + { + case 0: + return new long[] + {}; + + case -1: + return null; + + default: + long[] longs = new long[nbLongs]; + in = bufferHandler.read( nbLongs * 8 ); + + int pos = 0; + + for ( int i = 0; i < nbLongs; i++ ) + { + longs[i] = ( ( long ) in[pos++] << 56 ) + + ( ( in[pos++] & 0xFFL ) << 48 ) + + ( ( in[pos++] & 0xFFL ) << 40 ) + + ( ( in[pos++] & 0xFFL ) << 32 ) + + ( ( in[pos++] & 0xFFL ) << 24 ) + + ( ( in[pos++] & 0xFFL ) << 16 ) + + ( ( in[pos++] & 0xFFL ) << 8 ) + + ( in[pos++] & 0xFFL ); + } + + return longs; + } + } + + + /** + * {@inheritDoc} + */ + public long[] deserialize( ByteBuffer buffer ) throws IOException + { + // Read the dataLength. Note that we don't use it here. + buffer.getInt(); + + // The number of longs + int nbLongs = buffer.getInt(); + + switch ( nbLongs ) + { + case 0: + return new long[] + {}; + + case -1: + return null; + + default: + long[] longs = new long[nbLongs]; + + for ( int i = 0; i < nbLongs; i++ ) + { + longs[i] = buffer.getLong(); + } + + return longs; + } + } + + + /** + * {@inheritDoc} + */ + @Override + public int compare( long[] type1, long[] type2 ) + { + if ( type1 == type2 ) + { + return 0; + } + + if ( type1 == null ) + { + if ( type2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( type2 == null ) + { + return 1; + } + else + { + if ( type1.length < type2.length ) + { + int pos = 0; + + for ( long b1 : type1 ) + { + long b2 = type2[pos]; + + if ( b1 == b2 ) + { + pos++; + } + else if ( b1 < b2 ) + { + return -1; + } + else + { + return 1; + } + } + + return 1; + } + else + { + int pos = 0; + + for ( long b2 : type2 ) + { + long b1 = type1[pos]; + + if ( b1 == b2 ) + { + pos++; + } + else if ( b1 < b2 ) + { + return -1; + } + else + { + return 1; + } + } + + return -11; + } + } + } + } + + + @Override + public long[] fromBytes( byte[] buffer ) throws IOException + { + int len = IntSerializer.deserialize( buffer ); + int pos = 4; + + switch ( len ) + { + case 0: + return new long[] + {}; + + case -1: + return null; + + default: + long[] longs = new long[len]; + + for ( int i = 0; i < len; i++ ) + { + longs[i] = LongSerializer.deserialize( buffer, pos ); + pos += 8; + } + + return longs; + } + } + + + @Override + public long[] fromBytes( byte[] buffer, int pos ) throws IOException + { + int len = IntSerializer.deserialize( buffer, pos ); + int newPos = pos + 4; + + switch ( len ) + { + case 0: + return new long[] + {}; + + case -1: + return null; + + default: + long[] longs = new long[len]; + + for ( int i = 0; i < len; i++ ) + { + longs[i] = LongSerializer.deserialize( buffer, newPos ); + newPos += 8; + } + + return longs; + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/LongSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/LongSerializer.java new file mode 100644 index 000000000..0eaac4434 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/LongSerializer.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.comparator.LongComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; + + +/** + * The Long serializer. + * + * @author Apache Directory Project + */ +public class LongSerializer extends AbstractElementSerializer +{ + /** A static instance of a LongSerializer */ + public final static LongSerializer INSTANCE = new LongSerializer(); + + /** + * Create a new instance of LongSerializer + */ + private LongSerializer() + { + super( LongComparator.INSTANCE ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( Long element ) + { + return serialize( element.longValue() ); + } + + + /** + * Serialize an long + * + * @param value the value to serialize + * @return The byte[] containing the serialized long + */ + public static byte[] serialize( long value ) + { + byte[] bytes = new byte[8]; + + return serialize( bytes, 0, value ); + } + + + /** + * Serialize an long + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized long + * @param value the value to serialize + * @return The byte[] containing the serialized long + */ + public static byte[] serialize( byte[] buffer, int start, long value ) + { + buffer[start] = ( byte ) ( value >>> 56 ); + buffer[start + 1] = ( byte ) ( value >>> 48 ); + buffer[start + 2] = ( byte ) ( value >>> 40 ); + buffer[start + 3] = ( byte ) ( value >>> 32 ); + buffer[start + 4] = ( byte ) ( value >>> 24 ); + buffer[start + 5] = ( byte ) ( value >>> 16 ); + buffer[start + 6] = ( byte ) ( value >>> 8 ); + buffer[start + 7] = ( byte ) ( value ); + + return buffer; + } + + + /** + * A static method used to deserialize a Long from a byte array. + * @param in The byte array containing the Long + * @param start the position in the byte[] we will deserialize the long from + * @return A Long + */ + public static Long deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize an Integer from a byte array. + * @param in The byte array containing the Integer + * @param start the position in the byte[] we will deserialize the long from + * @return An Integer + */ + public static Long deserialize( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 8 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Long from a buffer with not enough bytes" ); + } + + long result = ( ( long ) in[start] << 56 ) + + ( ( in[start + 1] & 0x00FFL ) << 48 ) + + ( ( in[start + 2] & 0x00FFL ) << 40 ) + + ( ( in[start + 3] & 0x00FFL ) << 32 ) + + ( ( in[start + 4] & 0x00FFL ) << 24 ) + + ( ( in[start + 5] & 0x00FFL ) << 16 ) + + ( ( in[start + 6] & 0x00FFL ) << 8 ) + + ( in[start + 7] & 0x00FFL ); + + return result; + } + + + /** + * A method used to deserialize a Long from a byte array. + * @param in The byte array containing the Long + * @param start the position in the byte[] we will deserialize the long from + * @return A Long + */ + public Long fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A method used to deserialize an Integer from a byte array. + * @param in The byte array containing the Integer + * @param start the position in the byte[] we will deserialize the long from + * @return An Integer + */ + public Long fromBytes( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 8 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Long from a buffer with not enough bytes" ); + } + + long result = ( ( long ) in[start] << 56 ) + + ( ( in[start + 1] & 0xFFL ) << 48 ) + + ( ( in[start + 2] & 0xFFL ) << 40 ) + + ( ( in[start + 3] & 0xFFL ) << 32 ) + + ( ( in[start + 4] & 0xFFL ) << 24 ) + + ( ( in[start + 5] & 0xFFL ) << 16 ) + + ( ( in[start + 6] & 0xFFL ) << 8 ) + + ( in[start + 7] & 0xFFL ); + + return result; + } + + + /** + * {@inheritDoc} + */ + public Long deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 8 ); + + return deserialize( in ); + } + + + /** + * {@inheritDoc} + */ + public Long deserialize( ByteBuffer buffer ) throws IOException + { + return buffer.getLong(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/Serializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/Serializer.java new file mode 100644 index 000000000..85d3a4be3 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/Serializer.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; + + +/** + * This interface is used by implementations of serializer, deserializr and comparator. + * + * @param The type for the element to serialize + * + * @author Apache Directory Project + */ +public interface Serializer +{ + /** + * Produce the byte[] representation of the type + * + * @param type The type to serialize + * @return The byte[] containing the serialized type + */ + byte[] serialize( T type ); + + + /** + * Deserialize a type from a byte[] + * + * @param bufferHandler The incoming BufferHandler + * @return The deserialized type + * @throws IOException If the deserialization failed + */ + T deserialize( BufferHandler bufferHandler ) throws IOException; + + + /** + * Returns the comparison of two types.
          + *
            + *
          • If type1 < type2, return -1
          • + *
          • If type1 > type2, return 1
          • + *
          • If type1 == type2, return 0
          • + *
          + * + * @param type1 The first type to compare + * @param type2 The second type to compare + * @return The comparison result + */ + int compare( T type1, T type2 ); +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ShortSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ShortSerializer.java new file mode 100644 index 000000000..2f34103dd --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ShortSerializer.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.comparator.ShortComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; + + +/** + * The Short serializer. + * + * @author Apache Directory Project + */ +public class ShortSerializer extends AbstractElementSerializer +{ + /** A static instance of a ShortSerializer */ + public final static ShortSerializer INSTANCE = new ShortSerializer(); + + /** + * Create a new instance of ShortSerializer + */ + private ShortSerializer() + { + super( ShortComparator.INSTANCE ); + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( Short element ) + { + byte[] bytes = new byte[2]; + + return serialize( bytes, 0, element ); + } + + + /** + * Serialize a short + * + * @param value the value to serialize + * @return The byte[] containing the serialized short + */ + public static byte[] serialize( short value ) + { + byte[] bytes = new byte[2]; + + return serialize( bytes, 0, value ); + } + + + /** + * Serialize a short + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized short + * @param value the value to serialize + * @return The byte[] containing the serialized short + */ + public static byte[] serialize( byte[] buffer, int start, short value ) + { + buffer[start] = ( byte ) ( value >>> 8 ); + buffer[start + 1] = ( byte ) ( value ); + + return buffer; + } + + + /** + * A static method used to deserialize a Short from a byte array. + * @param in The byte array containing the Short + * @return A Short + */ + public static Short deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a Short from a byte array. + * @param in The byte array containing the Short + * @param start the position in the byte[] we will deserialize the short from + * @return A Short + */ + public static Short deserialize( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 2 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Short from a buffer with not enough bytes" ); + } + + return ( short ) ( ( in[start] << 8 ) + ( in[start + 1] & 0xFF ) ); + } + + + /** + * A method used to deserialize a Short from a byte array. + * @param in The byte array containing the Short + * @return A Short + */ + public Short fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A method used to deserialize a Short from a byte array. + * @param in The byte array containing the Short + * @param start the position in the byte[] we will deserialize the short from + * @return A Short + */ + public Short fromBytes( byte[] in, int start ) + { + if ( ( in == null ) || ( in.length < 2 + start ) ) + { + throw new SerializerCreationException( "Cannot extract a Short from a buffer with not enough bytes" ); + } + + return ( short ) ( ( in[start] << 8 ) + ( in[start + 1] & 0xFF ) ); + } + + + /** + * {@inheritDoc} + */ + public Short deserialize( ByteBuffer buffer ) throws IOException + { + return buffer.getShort(); + } + + + /** + * {@inheritDoc} + */ + public Short deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 2 ); + + return deserialize( in ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/StringSerializer.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/StringSerializer.java new file mode 100644 index 000000000..1e20f4b58 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/StringSerializer.java @@ -0,0 +1,381 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.Comparator; + +import org.apache.directory.mavibot.btree.comparator.StringComparator; +import org.apache.directory.mavibot.btree.exception.SerializerCreationException; +import org.apache.directory.mavibot.btree.util.Strings; + + +/** + * The String serializer. + * + * @author Apache Directory Project + */ +public class StringSerializer extends AbstractElementSerializer +{ + /** A static instance of a StringSerializer */ + public static final StringSerializer INSTANCE = new StringSerializer(); + + /** + * Create a new instance of StringSerializer + */ + private StringSerializer() + { + super( StringComparator.INSTANCE ); + } + + + /** + * Create a new instance of StringSerializer with custom comparator + */ + public StringSerializer( Comparator comparator ) + { + super( comparator ); + } + + + /** + * A static method used to deserialize a String from a byte array. + * @param in The byte array containing the String + * @return A String + */ + public static String deserialize( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A static method used to deserialize a String from a byte array. + * @param in The byte array containing the String + * @return A String + */ + public static String deserialize( byte[] in, int start ) + { + int length = IntSerializer.deserialize( in, start ); + + if ( length == 0xFFFFFFFF ) + { + return null; + } + + if ( in.length < length + 4 + start ) + { + throw new SerializerCreationException( "Cannot extract a String from a buffer with not enough bytes" ); + } + + return Strings.utf8ToString( in, start + 4, length ); + } + + + /** + * A method used to deserialize a String from a byte array. + * @param in The byte array containing the String + * @return A String + */ + public String fromBytes( byte[] in ) + { + return deserialize( in, 0 ); + } + + + /** + * A method used to deserialize a String from a byte array. + * @param in The byte array containing the String + * @return A String + */ + public String fromBytes( byte[] in, int start ) + { + int length = IntSerializer.deserialize( in, start ); + + if ( length == 0xFFFFFFFF ) + { + return null; + } + + if ( in.length < length + start ) + { + throw new SerializerCreationException( "Cannot extract a String from a buffer with not enough bytes" ); + } + + return Strings.utf8ToString( in, start + 4, length ); + } + + + /** + * Serialize a String. We store the length on 4 bytes, then the String + * + * @param buffer the Buffer that will contain the serialized value + * @param start the position in the buffer we will store the serialized String + * @param value the value to serialize + * @return The byte[] containing the serialized String + */ + public static byte[] serialize( byte[] buffer, int start, String element ) + { + int len = -1; + + if ( element != null ) + { + len = element.length(); + } + + switch ( len ) + { + case 0: + buffer[start] = 0x00; + buffer[start + 1] = 0x00; + buffer[start + 2] = 0x00; + buffer[start + 3] = 0x00; + + break; + + case -1: + buffer[start] = ( byte ) 0xFF; + buffer[start + 1] = ( byte ) 0xFF; + buffer[start + 2] = ( byte ) 0xFF; + buffer[start + 3] = ( byte ) 0xFF; + + break; + + default: + try + { + byte[] strBytes = element.getBytes( "UTF-8" ); + + buffer = new byte[strBytes.length + 4]; + + System.arraycopy( strBytes, 0, buffer, 4, strBytes.length ); + + buffer[start] = ( byte ) ( strBytes.length >>> 24 ); + buffer[start + 1] = ( byte ) ( strBytes.length >>> 16 ); + buffer[start + 2] = ( byte ) ( strBytes.length >>> 8 ); + buffer[start + 3] = ( byte ) ( strBytes.length ); + } + catch ( UnsupportedEncodingException uee ) + { + // if this happens something is really strange + throw new SerializerCreationException( uee ); + } + } + + return buffer; + } + + + /** + * {@inheritDoc} + */ + public byte[] serialize( String element ) + { + int len = -1; + + if ( element != null ) + { + len = element.length(); + } + + byte[] bytes = null; + + switch ( len ) + { + case 0: + bytes = new byte[4]; + + bytes[0] = 0x00; + bytes[1] = 0x00; + bytes[2] = 0x00; + bytes[3] = 0x00; + + break; + + case -1: + bytes = new byte[4]; + + bytes[0] = ( byte ) 0xFF; + bytes[1] = ( byte ) 0xFF; + bytes[2] = ( byte ) 0xFF; + bytes[3] = ( byte ) 0xFF; + + break; + + default: + char[] chars = element.toCharArray(); + byte[] tmpBytes = new byte[chars.length * 2]; + + int pos = 0; + len = 0; + + for ( char c : chars ) + { + if ( ( c & 0xFF80 ) == 0 ) + { + tmpBytes[pos++] = ( byte ) c; + } + else if ( ( c & 0xF800 ) == 0 ) + { + tmpBytes[pos++] = ( byte ) ( ( byte ) 0x00C0 | ( byte ) ( ( c & 0x07C0 ) >> 6 ) ); + tmpBytes[pos++] = ( byte ) ( ( byte ) 0x80 | ( byte ) ( c & 0x003F ) ); + } + else + { + tmpBytes[pos++] = ( byte ) ( ( byte ) 0x80 | ( byte ) ( c & 0x001F ) ); + tmpBytes[pos++] = ( byte ) ( ( byte ) 0x80 | ( byte ) ( c & 0x07C0 ) ); + tmpBytes[pos++] = ( byte ) ( ( byte ) 0xE0 | ( byte ) ( c & 0x7800 ) ); + } + } + + bytes = new byte[pos + 4]; + + bytes[0] = ( byte ) ( pos >>> 24 ); + bytes[1] = ( byte ) ( pos >>> 16 ); + bytes[2] = ( byte ) ( pos >>> 8 ); + bytes[3] = ( byte ) ( pos ); + + System.arraycopy( tmpBytes, 0, bytes, 4, pos ); + } + + return bytes; + } + + + /** + * {@inheritDoc} + * @throws IOException + */ + public String deserialize( BufferHandler bufferHandler ) throws IOException + { + byte[] in = bufferHandler.read( 4 ); + + int len = IntSerializer.deserialize( in ); + + switch ( len ) + { + case 0: + return ""; + + case -1: + return null; + + default: + in = bufferHandler.read( len ); + + return Strings.utf8ToString( in ); + } + } + + + /** + * {@inheritDoc} + */ + public String deserialize( ByteBuffer buffer ) throws IOException + { + int len = buffer.getInt(); + + switch ( len ) + { + case 0: + return ""; + + case -1: + return null; + + default: + byte[] bytes = new byte[len]; + + buffer.get( bytes ); + char[] chars = new char[len]; + int clen = 0; + + for ( int i = 0; i < len; i++ ) + { + byte b = bytes[i]; + + if ( b >= 0 ) + { + chars[clen++] = ( char ) b; + } + else + { + if ( ( b & 0xE0 ) == 0 ) + { + // 3 bytes long char + i++; + byte b2 = bytes[i]; + i++; + byte b3 = bytes[i]; + chars[clen++] = ( char ) ( ( ( b & 0x000F ) << 12 ) | ( ( b2 & 0x003F ) << 6 ) | ( ( b3 & 0x003F ) ) ); + } + else + { + // 2 bytes long char + i++; + byte b2 = bytes[i]; + chars[clen++] = ( char ) ( ( ( b & 0x001F ) << 6 ) | ( b2 & 0x003F ) ); + } + } + } + + return new String( chars, 0, clen ); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public int compare( String type1, String type2 ) + { + if ( type1 == type2 ) + { + return 0; + } + + if ( type1 == null ) + { + if ( type2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else + { + if ( type2 == null ) + { + return 1; + } + else + { + return type1.compareTo( type2 ); + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/IntTupleReaderWriter.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/IntTupleReaderWriter.java new file mode 100644 index 000000000..167346066 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/IntTupleReaderWriter.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.directory.mavibot.btree.util; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.apache.directory.mavibot.btree.Tuple; + + +/** + * TODO IntTupleReaderWriter. + * + * @author Apache Directory Project + */ +public class IntTupleReaderWriter implements TupleReaderWriter +{ + + @Override + public void storeSortedTuple( Tuple t, DataOutputStream out ) throws IOException + { + out.writeInt( t.getKey() ); + out.writeInt( t.getValue() ); + } + + + @Override + public Tuple readSortedTuple( DataInputStream in ) + { + return readUnsortedTuple( in ); + } + + + @Override + public Tuple readUnsortedTuple( DataInputStream in ) + { + + try + { + if ( in.available() <= 0 ) + { + return null; + } + + Tuple t = new Tuple( in.readInt(), in.readInt() ); + + return t; + } + catch ( IOException e ) + { + e.printStackTrace(); + } + + return null; + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/Strings.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/Strings.java new file mode 100644 index 000000000..63d96fc01 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/Strings.java @@ -0,0 +1,571 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.util; + + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * Various string manipulation methods that are more efficient then chaining + * string operations: all is done in the same buffer without creating a bunch of + * string objects. + * + * @author Apache Directory Project + */ +public final class Strings +{ + /** Hex chars */ + private static final byte[] HEX_CHAR = new byte[] + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + /** A empty byte array */ + public static final byte[] EMPTY_BYTES = new byte[0]; + + + /** + * Helper function that dump an array of bytes in hex form + * + * @param buffer The bytes array to dump + * @return A string representation of the array of bytes + */ + public static String dumpBytes( byte[] buffer ) + { + if ( buffer == null ) + { + return ""; + } + + StringBuffer sb = new StringBuffer(); + + for ( byte b : buffer ) + { + sb.append( "0x" ).append( ( char ) ( HEX_CHAR[( b & 0x00F0 ) >> 4] ) ).append( + ( char ) ( HEX_CHAR[b & 0x000F] ) ).append( " " ); + } + + return sb.toString(); + } + + + /** + * Helper function that dump a byte in hex form + * + * @param octet The byte to dump + * @return A string representation of the byte + */ + public static String dumpByte( byte octet ) + { + return new String( new byte[] + { '0', 'x', HEX_CHAR[( octet & 0x00F0 ) >> 4], HEX_CHAR[octet & 0x000F] } ); + } + + + /** + * Helper function that returns a char from an hex + * + * @param hex The hex to dump + * @return A char representation of the hex + */ + public static char dumpHex( byte hex ) + { + return ( char ) HEX_CHAR[hex & 0x000F]; + } + + + /** + * Helper function that dump an array of bytes in hex pair form, + * without '0x' and space chars + * + * @param buffer The bytes array to dump + * @return A string representation of the array of bytes + */ + public static String dumpHexPairs( byte[] buffer ) + { + if ( buffer == null ) + { + return ""; + } + + char[] str = new char[buffer.length << 1]; + + int pos = 0; + + for ( byte b : buffer ) + { + str[pos++] = ( char ) ( HEX_CHAR[( b & 0x00F0 ) >> 4] ); + str[pos++] = ( char ) ( HEX_CHAR[b & 0x000F] ); + } + + return new String( str ); + } + + + /** + * Gets a hex string from byte array. + * + * @param res the byte array + * @return the hex string representing the binary values in the array + */ + public static String toHexString( byte[] res ) + { + StringBuffer buf = new StringBuffer( res.length << 1 ); + + for ( byte b : res ) + { + String digit = Integer.toHexString( 0xFF & b ); + + if ( digit.length() == 1 ) + { + digit = '0' + digit; + } + + buf.append( digit ); + } + + return buf.toString().toUpperCase(); + } + + + /** + * Get byte array from hex string + * + * @param hexString the hex string to convert to a byte array + * @return the byte form of the hex string. + */ + public static byte[] toByteArray( String hexString ) + { + int arrLength = hexString.length() >> 1; + byte[] buf = new byte[arrLength]; + + for ( int ii = 0; ii < arrLength; ii++ ) + { + int index = ii << 1; + + String digit = hexString.substring( index, index + 2 ); + buf[ii] = ( byte ) Integer.parseInt( digit, 16 ); + } + + return buf; + } + + private static final byte[] UTF8 = new byte[] + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, + 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, + 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F }; + + + /** + * Return an UTF-8 encoded String + * + * @param bytes The byte array to be transformed to a String + * @return A String. + */ + public static String utf8ToString( byte[] bytes ) + { + if ( bytes == null ) + { + return ""; + } + + char[] chars = new char[bytes.length]; + int pos = 0; + + try + { + for ( byte b : bytes ) + { + chars[pos++] = ( char ) UTF8[b]; + } + } + catch ( ArrayIndexOutOfBoundsException aioobe ) + { + try + { + return new String( bytes, "UTF-8" ); + } + catch ( UnsupportedEncodingException uee ) + { + // if this happens something is really strange + throw new RuntimeException( uee ); + } + } + + return new String( chars ); + } + + + /** + * Return an UTF-8 encoded String + * + * @param bytes The byte array to be transformed to a String + * @return A String. + */ + public static String utf8ToString( ByteBuffer bytes ) + { + if ( bytes == null ) + { + return ""; + } + + char[] chars = new char[bytes.limit()]; + int pos = 0; + int currentPos = bytes.position(); + + do + { + chars[pos++] = ( char ) UTF8[bytes.get()]; + } + while ( bytes.position() < bytes.limit() ); + + // restore the buffer + bytes.position( currentPos ); + + return new String( chars ); + } + + + /** + * Return an UTF-8 encoded String + * + * @param bytes The byte array to be transformed to a String + * @param length The length of the byte array to be converted + * @return A String. + */ + public static String utf8ToString( byte[] bytes, int length ) + { + if ( bytes == null ) + { + return ""; + } + + try + { + return new String( bytes, 0, length, "UTF-8" ); + } + catch ( UnsupportedEncodingException uee ) + { + // if this happens something is really strange + throw new RuntimeException( uee ); + } + } + + + /** + * Return an UTF-8 encoded String + * + * @param bytes The byte array to be transformed to a String + * @param start the starting position in the byte array + * @param length The length of the byte array to be converted + * @return A String. + */ + public static String utf8ToString( byte[] bytes, int start, int length ) + { + if ( bytes == null ) + { + return ""; + } + + try + { + return new String( bytes, start, length, "UTF-8" ); + } + catch ( UnsupportedEncodingException uee ) + { + // if this happens something is really strange + throw new RuntimeException( uee ); + } + } + + + /** + *

          + * Checks if a String 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 + * String. That functionality is available in isBlank(). + *

          + * + * @param str the String to check, may be null + * @return true if the String is empty or null + */ + public static boolean isEmpty( String str ) + { + return ( str == null ) || ( str.length() == 0 ); + } + + + /** + * Checks if a bytes array is empty or null. + * + * @param bytes The bytes array to check, may be null + * @return true if the bytes array is empty or null + */ + public static boolean isEmpty( byte[] bytes ) + { + return ( bytes == null ) || ( bytes.length == 0 ); + } + + + /** + * Return UTF-8 encoded byte[] representation of a String + * + * @param string The string to be transformed to a byte array + * @return The transformed byte array + */ + public static byte[] getBytesUtf8( String string ) + { + if ( string == null ) + { + return EMPTY_BYTES; + } + + try + { + return string.getBytes( "UTF-8" ); + } + catch ( UnsupportedEncodingException uee ) + { + // if this happens something is really strange + throw new RuntimeException( uee ); + } + } + + + /** + * When the string to convert to bytes is pure ascii, this is a faster + * method than the getBytesUtf8. Otherwise, it's slower. + * + * @param string The string to convert to byte[] + * @return The bytes + */ + public static byte[] getBytesUtf8Ascii( String string ) + { + if ( string == null ) + { + return new byte[0]; + } + + try + { + try + { + char[] chars = string.toCharArray(); + byte[] bytes = new byte[chars.length]; + int pos = 0; + + for ( char c : chars ) + { + bytes[pos++] = UTF8[c]; + } + + return bytes; + } + catch ( ArrayIndexOutOfBoundsException aioobe ) + { + return string.getBytes( "UTF-8" ); + } + } + catch ( UnsupportedEncodingException uee ) + { + // if this happens something is really strange + throw new RuntimeException( uee ); + } + } + + + /** + * Utility method that return a String representation of a list + * + * @param list The list to transform to a string + * @return A csv string + */ + public static String listToString( List list ) + { + if ( ( list == null ) || ( list.size() == 0 ) ) + { + return ""; + } + + StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + + for ( Object elem : list ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( elem ); + } + + return sb.toString(); + } + + + /** + * Utility method that return a String representation of a set + * + * @param set The set to transform to a string + * @return A csv string + */ + public static String setToString( Set set ) + { + if ( ( set == null ) || ( set.size() == 0 ) ) + { + return ""; + } + + StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + + for ( Object elem : set ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( elem ); + } + + return sb.toString(); + } + + + /** + * Utility method that return a String representation of a list + * + * @param list The list to transform to a string + * @param tabs The tabs to add in ffront of the elements + * @return A csv string + */ + public static String listToString( List list, String tabs ) + { + if ( ( list == null ) || ( list.size() == 0 ) ) + { + return ""; + } + + StringBuffer sb = new StringBuffer(); + + for ( Object elem : list ) + { + sb.append( tabs ); + sb.append( elem ); + sb.append( '\n' ); + } + + return sb.toString(); + } + + + /** + * Utility method that return a String representation of a map. The elements + * will be represented as "key = value" + * + * @param map The map to transform to a string + * @return A csv string + */ + public static String mapToString( Map map ) + { + if ( ( map == null ) || ( map.size() == 0 ) ) + { + return ""; + } + + StringBuffer sb = new StringBuffer(); + boolean isFirst = true; + + for ( Map.Entry entry : map.entrySet() ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ", " ); + } + + sb.append( entry.getKey() ); + sb.append( " = '" ).append( entry.getValue() ).append( "'" ); + } + + return sb.toString(); + } + + + /** + * Utility method that return a String representation of a map. The elements + * will be represented as "key = value" + * + * @param map The map to transform to a string + * @param tabs The tabs to add in ffront of the elements + * @return A csv string + */ + public static String mapToString( Map map, String tabs ) + { + if ( ( map == null ) || ( map.size() == 0 ) ) + { + return ""; + } + + StringBuffer sb = new StringBuffer(); + + for ( Map.Entry entry : map.entrySet() ) + { + sb.append( tabs ); + sb.append( entry.getKey() ); + + sb.append( " = '" ).append( entry.getValue().toString() ).append( "'\n" ); + } + + return sb.toString(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/TupleReaderWriter.java b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/TupleReaderWriter.java new file mode 100644 index 000000000..6b4acd087 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/TupleReaderWriter.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.util; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.apache.directory.mavibot.btree.Tuple; + + +/** + * TODO TupleReaderWriter. + * + * @author Apache Directory Project + */ +public interface TupleReaderWriter +{ + Tuple readUnsortedTuple( DataInputStream in ); + + + Tuple readSortedTuple( DataInputStream in ); + + + void storeSortedTuple( Tuple t, DataOutputStream out ) throws IOException; +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/BulkLoaderTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/BulkLoaderTest.java new file mode 100644 index 000000000..937016a37 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/BulkLoaderTest.java @@ -0,0 +1,1189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.directory.mavibot.btree.BulkLoader.LevelEnum; +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.Ignore; +import org.junit.Test; + + +/** + * Test the BulkLoader class. + * + * @author Apache Directory Project + */ +public class BulkLoaderTest +{ + private void checkBtree( BTree oldBtree, BTree newBtree ) + throws EndOfFileExceededException, IOException, KeyNotFoundException + { + assertEquals( oldBtree.getNbElems(), newBtree.getNbElems() ); + + TupleCursor cursorOld = oldBtree.browse(); + TupleCursor cursorNew = newBtree.browse(); + + while ( cursorOld.hasNext() && cursorNew.hasNext() ) + { + Tuple tupleOld = cursorOld.next(); + Tuple tupleNew = cursorNew.next(); + + assertEquals( tupleOld.getKey(), tupleNew.getKey() ); + assertEquals( tupleOld.getValue(), tupleNew.getValue() ); + } + + assertEquals( cursorOld.hasNext(), cursorNew.hasNext() ); + } + + + /** + * Test that we can compact a btree which has no element + */ + @Test + public void testInMemoryBulkLoadNoElement() throws IOException, KeyNotFoundException + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + BTree newBtree = ( BTree ) BulkLoader.compact( btree ); + + checkBtree( btree, newBtree ); + TupleCursor cursorOld = btree.browse(); + TupleCursor cursorNew = btree.browse(); + + assertFalse( cursorOld.hasNext() ); + assertFalse( cursorNew.hasNext() ); + } + + + /** + * Test that we can compact a btree which has a partially full leaf only + */ + @Ignore + @Test + public void testInMemoryBulkLoad3Elements() throws IOException, KeyNotFoundException + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( Long i = 0L; i < 3L; i++ ) + { + String value = "V" + i; + btree.insert( i, value ); + } + + BTree newBtree = ( BTree ) BulkLoader.compact( btree ); + + checkBtree( btree, newBtree ); + } + + + /** + * Test that we can compact a btree which has a 2 full leaves + */ + @Ignore + @Test + public void testInMemoryBulkLoad8Elements() throws IOException, KeyNotFoundException + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( Long i = 0L; i < 8L; i++ ) + { + String value = "V" + i; + btree.insert( i, value ); + } + + BTree newBtree = ( BTree ) BulkLoader.compact( btree ); + + checkBtree( btree, newBtree ); + } + + + /** + * Test that we can load 100 BTrees with 0 to 1000 elements + * @throws BTreeAlreadyManagedException + */ + @Test + public void testPersistedBulkLoad1000Elements() throws IOException, KeyNotFoundException, + BTreeAlreadyManagedException + { + for ( int i = 1000000; i < 1000001; i++ ) + { + Random random = new Random( System.currentTimeMillis() ); + File file = File.createTempFile( "managedbtreebuilder", ".data" ); + file.deleteOnExit(); + + try + { + RecordManager rm = new RecordManager( file.getAbsolutePath() ); + PersistedBTree btree = ( PersistedBTree ) rm.addBTree( "test", + LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + btree.setPageSize( 64 ); + + int nbElems = i; + int addedElems = 0; + + final Tuple[] elems = new Tuple[nbElems]; + Map>> expected = new HashMap>>(); + + long t00 = System.currentTimeMillis(); + + while ( addedElems < nbElems ) + { + long key = random.nextLong() % 3333333L; + + if ( expected.containsKey( key ) ) + { + continue; + } + + long w = random.nextLong() % 3333333L; + String value = "V" + w; + elems[addedElems] = new Tuple( key, value ); + + Tuple> expectedTuple = expected.get( key ); + + if ( expectedTuple == null ) + { + expectedTuple = new Tuple>( key, new TreeSet() ); + } + + expectedTuple.value.add( value ); + expected.put( key, expectedTuple ); + addedElems++; + } + + long t01 = System.currentTimeMillis(); + + // System.out.println( "Time to create the " + nbElems + " elements " + ( ( t01 - t00 ) / 1 ) ); + + Iterator> tupleIterator = new Iterator>() + { + private int pos = 0; + + + @Override + public Tuple next() + { + return elems[pos++]; + } + + + @Override + public boolean hasNext() + { + return pos < elems.length; + } + + + @Override + public void remove() + { + } + }; + + long t0 = System.currentTimeMillis(); + BTree result = BulkLoader.load( btree, tupleIterator, 1024000 ); + long t1 = System.currentTimeMillis(); + + if ( ( i % 100 ) == 0 ) + { + System.out.println( "== Btree #" + i + ", Time to bulkoad the " + nbElems + " elements " + + ( t1 - t0 ) + "ms" ); + } + + TupleCursor cursor = result.browse(); + int nbFetched = 0; + + long t2 = System.currentTimeMillis(); + + while ( cursor.hasNext() ) + { + Tuple elem = cursor.next(); + + assertTrue( expected.containsKey( elem.key ) ); + Tuple> tuple = expected.get( elem.key ); + assertNotNull( tuple ); + nbFetched++; + } + + long t3 = System.currentTimeMillis(); + + //System.out.println( "Time to read the " + nbElems + " elements " + ( t3 - t2 ) ); + assertEquals( nbElems, nbFetched ); + + checkBtree( btree, result ); + } + finally + { + file.delete(); + } + } + } + + + /** + * Test that we can compact a btree which has a full parent node, with all the leaves full. + */ + @Test + public void testInMemoryBulkLoad20Elements() throws IOException, KeyNotFoundException + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( Long i = 0L; i < 20L; i++ ) + { + String value = "V" + i; + btree.insert( i, value ); + } + + BTree newBtree = ( BTree ) BulkLoader.compact( btree ); + + checkBtree( btree, newBtree ); + } + + + /** + * Test that we can compact a btree which has two full parent nodes, with all the leaves full. + * That means we have an upper node with one element. + */ + @Ignore + @Test + public void testInMemoryBulkLoad40Elements() throws IOException, KeyNotFoundException + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( Long i = 0L; i < 40L; i++ ) + { + String value = "V" + i; + btree.insert( i, value ); + } + + BTree newBtree = ( BTree ) BulkLoader.compact( btree ); + + checkBtree( btree, newBtree ); + } + + + /** + * Test that we can compact a btree which has two full parent nodes, with all the leaves full. + * That means we have an upper node with one element. + */ + @Test + public void testInMemoryBulkLoad100Elements() throws IOException, KeyNotFoundException + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( Long i = 0L; i < 100L; i++ ) + { + String value = "V" + i; + btree.insert( i, value ); + } + + BTree newBtree = ( BTree ) BulkLoader.compact( btree ); + + checkBtree( btree, newBtree ); + } + + + @Ignore + @Test + public void testInMemoryBulkLoadN() throws IOException, KeyNotFoundException + { + Random random = new Random( System.nanoTime() ); + long t0 = System.currentTimeMillis(); + + for ( long n = 0L; n < 2500L; n++ ) + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( Long i = 0L; i < n; i++ ) + { + String value = "V" + i; + btree.insert( i, value ); + } + + //long t1 = System.currentTimeMillis(); + + //System.out.println( "Delta initial load = " + ( t1 - t0 ) ); + + //long t2 = System.currentTimeMillis(); + + BTree newBtree = ( BTree ) BulkLoader.compact( btree ); + + //long t3 = System.currentTimeMillis(); + + //System.out.println( "Delta initial load = " + ( t3 - t2 ) ); + + //System.out.println( "Checking for N = " + n ); + checkBtree( btree, newBtree ); + } + } + + + @Ignore + @Test + public void testInMemoryBulkLoad21() throws IOException, KeyNotFoundException + { + Random random = new Random( System.nanoTime() ); + long t0 = System.currentTimeMillis(); + + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( Long i = 0L; i < 21; i++ ) + { + String value = "V" + i; + btree.insert( i, value ); + } + + //long t1 = System.currentTimeMillis(); + + //System.out.println( "Delta initial load = " + ( t1 - t0 ) ); + + //long t2 = System.currentTimeMillis(); + + BTree newBtree = ( BTree ) BulkLoader.compact( btree ); + + //long t3 = System.currentTimeMillis(); + + //System.out.println( "Delta initial load = " + ( t3 - t2 ) ); + + //System.out.println( "Checking for N = " + 21 ); + checkBtree( btree, newBtree ); + } + + + /** + * test the computeLeafLevel method + */ + @Test + public void testPersistedBulkLoadComputeLeafLevel() throws IOException, KeyNotFoundException, + BTreeAlreadyManagedException + { + Random random = new Random( System.currentTimeMillis() ); + File file = File.createTempFile( "managedbtreebuilder", ".data" ); + file.deleteOnExit(); + + try + { + RecordManager rm = new RecordManager( file.getAbsolutePath() ); + PersistedBTree btree = ( PersistedBTree ) rm.addBTree( "test", + LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + + int[] expectedNbPages = new int[] + { + 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 + }; + + int[] expectedLimit = new int[] + { + 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 32, + 16, 16, 16, 16, 16, 16, 16, 32, 32, 32, 32, 32, 32, 32, 32, 48 + }; + + int[] expectedKeys = new int[] + { + 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 + }; + + for ( int i = 0; i < 49; i++ ) + { + LevelInfo leafInfo = BulkLoader.computeLevel( btree, i, LevelEnum.LEAF ); + + assertEquals( expectedNbPages[i], leafInfo.getNbPages() ); + assertEquals( expectedLimit[i], leafInfo.getNbElemsLimit() ); + assertEquals( expectedKeys[i], leafInfo.getCurrentPage().getNbElems() ); + } + } + finally + { + file.delete(); + } + } + + + /** + * test the computeNodeLevel method + */ + @Test + public void testPersistedBulkLoadComputeNodeLevel() throws IOException, KeyNotFoundException, + BTreeAlreadyManagedException + { + Random random = new Random( System.currentTimeMillis() ); + File file = File.createTempFile( "managedbtreebuilder", ".data" ); + file.deleteOnExit(); + + try + { + RecordManager rm = new RecordManager( file.getAbsolutePath() ); + PersistedBTree btree = ( PersistedBTree ) rm.addBTree( "test", + LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + + int[] expectedNbPages = new int[] + { + -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 + }; + + int[] expectedLimit = new int[] + { + -1, + -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 34, + 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34, 34, 34, 51 + }; + + int[] expectedKeys = new int[] + { + -1, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 + }; + + for ( int i = 2; i < 52; i++ ) + { + LevelInfo nodeInfo = BulkLoader.computeLevel( btree, i, LevelEnum.NODE ); + + assertEquals( expectedNbPages[i], nodeInfo.getNbPages() ); + assertEquals( expectedLimit[i], nodeInfo.getNbElemsLimit() ); + assertEquals( expectedKeys[i], nodeInfo.getCurrentPage().getNbElems() ); + } + } + finally + { + file.delete(); + } + } + + + /** + * test the computeNodeLevel method + */ + @Test + public void testPersistedBulkLoadComputeLevels() throws IOException, KeyNotFoundException, + BTreeAlreadyManagedException + { + Random random = new Random( System.currentTimeMillis() ); + File file = File.createTempFile( "managedbtreebuilder", ".data" ); + file.deleteOnExit(); + + try + { + RecordManager rm = new RecordManager( file.getAbsolutePath() ); + PersistedBTree btree = ( PersistedBTree ) rm.addBTree( "test", + LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + + int[] expectedNbPages = new int[] + { + -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 + }; + + int[] expectedLimit = new int[] + { + -1, + -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 34, + 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34, 34, 34, 51 + }; + + int[] expectedKeys = new int[] + { + -1, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 + }; + + for ( int i = 2599; i <= 2599; i++ ) + { + List> levels = BulkLoader.computeLevels( btree, i ); + } + } + finally + { + file.delete(); + } + } + + + /** + * Test that we can load 100 BTrees with 0 to 1000 elements, each one of them having multiple values + * @throws BTreeAlreadyManagedException + */ + //@Ignore("The test is failing atm due to the sub-btree construction which is not working correctly when we have too many elements") + @Test + public void testPersistedBulkLoad1000ElementsMultipleValues() throws IOException, KeyNotFoundException, + BTreeAlreadyManagedException + { + for ( int i = 1; i < 1001; i++ ) + { + Random random = new Random( System.currentTimeMillis() ); + File file = File.createTempFile( "managedbtreebuilder", ".data" ); + file.deleteOnExit(); + + try + { + RecordManager rm = new RecordManager( file.getAbsolutePath() ); + PersistedBTree btree = ( PersistedBTree ) rm.addBTree( "test", + LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + + int nbElems = i; + int addedElems = 0; + + final Tuple[] elems = new Tuple[nbElems]; + Map>> expected = new HashMap>>(); + long valueNumber = 0; + + long t00 = System.currentTimeMillis(); + + while ( addedElems < nbElems ) + { + long key = random.nextLong() % 33L; + String value = "V" + valueNumber++; + + elems[addedElems] = new Tuple( key, value ); + + Tuple> expectedTuple = expected.get( key ); + + if ( expectedTuple == null ) + { + expectedTuple = new Tuple>( key, new TreeSet() ); + } + + expectedTuple.value.add( value ); + expected.put( key, expectedTuple ); + addedElems++; + + if ( addedElems % 100 == 0 ) + { + //System.out.println( "Nb added elements = " + addedElems ); + } + } + + long t01 = System.currentTimeMillis(); + + // System.out.println( "Time to create the " + nbElems + " elements " + ( ( t01 - t00 ) / 1 ) ); + + Iterator> tupleIterator = new Iterator>() + { + private int pos = 0; + + + @Override + public Tuple next() + { + return elems[pos++]; + } + + + @Override + public boolean hasNext() + { + return pos < elems.length; + } + + + @Override + public void remove() + { + } + }; + + long t0 = System.currentTimeMillis(); + BTree result = BulkLoader.load( btree, tupleIterator, 128 ); + long t1 = System.currentTimeMillis(); + + //System.out.println( "== Btree #" + i + ", Time to bulkoad the " + nbElems + " elements " + // + ( t1 - t0 ) + "ms" ); + + TupleCursor cursor = result.browse(); + int nbFetched = 0; + + long t2 = System.currentTimeMillis(); + + try + { + while ( cursor.hasNext() ) + { + Tuple elem = cursor.next(); + + assertTrue( expected.containsKey( elem.key ) ); + Tuple> tuple = expected.get( elem.key ); + assertNotNull( tuple ); + nbFetched++; + } + } + catch ( Exception e ) + { + for ( Tuple tuple : elems ) + { + System.out + .println( "listTuples.add( new Tuple( " + tuple.getKey() + "L, \"" + + tuple.getValue() + "\" ) );" ); + } + + e.printStackTrace(); + break; + } + + long t3 = System.currentTimeMillis(); + + //System.out.println( "Time to read the " + nbElems + " elements " + ( t3 - t2 ) ); + assertEquals( nbElems, nbFetched ); + + checkBtree( btree, result ); + } + finally + { + file.delete(); + } + } + } + + + /** + * Test that we can load 100 BTrees with 0 to 1000 elements, each one of them having multiple values + * @throws BTreeAlreadyManagedException + */ + @Test + public void testPersistedBulkLoad1000ElementsMultipleValuesDebug() throws IOException, KeyNotFoundException, + BTreeAlreadyManagedException + { + Random random = new Random( System.currentTimeMillis() ); + File file = File.createTempFile( "managedbtreebuilder", ".data" ); + file.deleteOnExit(); + + try + { + RecordManager rm = new RecordManager( file.getAbsolutePath() ); + PersistedBTree btree = ( PersistedBTree ) rm.addBTree( "test", + LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + + int nbElems = 4; + int addedElems = 0; + + final Tuple[] elems = new Tuple[nbElems]; + Map>> expected = new HashMap>>(); + long valueNumber = 0; + + elems[0] = new Tuple( 26L, "V0" ); + elems[1] = new Tuple( 26L, "V1" ); + elems[2] = new Tuple( -22L, "V2" ); + elems[3] = new Tuple( 5L, "V3" ); + + Iterator> tupleIterator = new Iterator>() + { + private int pos = 0; + + + @Override + public Tuple next() + { + return elems[pos++]; + } + + + @Override + public boolean hasNext() + { + return pos < elems.length; + } + + + @Override + public void remove() + { + } + }; + + long t0 = System.currentTimeMillis(); + BTree result = null; + + result = BulkLoader.load( btree, tupleIterator, 128 ); + long t1 = System.currentTimeMillis(); + + TupleCursor cursor = result.browse(); + int nbFetched = 0; + + long t2 = System.currentTimeMillis(); + + while ( cursor.hasNext() ) + { + Tuple elem = cursor.next(); + nbFetched++; + } + + long t3 = System.currentTimeMillis(); + + //System.out.println( "Time to read the " + nbElems + " elements " + ( t3 - t2 ) ); + assertEquals( nbElems, nbFetched ); + + checkBtree( btree, result ); + } + finally + { + file.delete(); + } + } + + + @Test + public void testDebug() throws IOException + { + final List> listTuples = new ArrayList>(); + + listTuples.add( new Tuple( 0L, "V0" ) ); + listTuples.add( new Tuple( -14L, "V1" ) ); + listTuples.add( new Tuple( 7L, "V2" ) ); + listTuples.add( new Tuple( 6L, "V3" ) ); + listTuples.add( new Tuple( -12L, "V4" ) ); + listTuples.add( new Tuple( 17L, "V5" ) ); + listTuples.add( new Tuple( -18L, "V6" ) ); + listTuples.add( new Tuple( 7L, "V7" ) ); + listTuples.add( new Tuple( 32L, "V8" ) ); + listTuples.add( new Tuple( -21L, "V9" ) ); + listTuples.add( new Tuple( 9L, "V10" ) ); + listTuples.add( new Tuple( 0L, "V11" ) ); + listTuples.add( new Tuple( -7L, "V12" ) ); + listTuples.add( new Tuple( -13L, "V13" ) ); + listTuples.add( new Tuple( 23L, "V14" ) ); + listTuples.add( new Tuple( -1L, "V15" ) ); + listTuples.add( new Tuple( 0L, "V16" ) ); + listTuples.add( new Tuple( -13L, "V17" ) ); + listTuples.add( new Tuple( 9L, "V18" ) ); + listTuples.add( new Tuple( 26L, "V19" ) ); + listTuples.add( new Tuple( 0L, "V20" ) ); + listTuples.add( new Tuple( 7L, "V21" ) ); + listTuples.add( new Tuple( 28L, "V22" ) ); + listTuples.add( new Tuple( 21L, "V23" ) ); + listTuples.add( new Tuple( 3L, "V24" ) ); + listTuples.add( new Tuple( -31L, "V25" ) ); + listTuples.add( new Tuple( -14L, "V26" ) ); + listTuples.add( new Tuple( -1L, "V27" ) ); + listTuples.add( new Tuple( 5L, "V28" ) ); + listTuples.add( new Tuple( 29L, "V29" ) ); + listTuples.add( new Tuple( -24L, "V30" ) ); + listTuples.add( new Tuple( 8L, "V31" ) ); + listTuples.add( new Tuple( -1L, "V32" ) ); + listTuples.add( new Tuple( -19L, "V33" ) ); + listTuples.add( new Tuple( -24L, "V34" ) ); + listTuples.add( new Tuple( -7L, "V35" ) ); + listTuples.add( new Tuple( -3L, "V36" ) ); + listTuples.add( new Tuple( -7L, "V37" ) ); + listTuples.add( new Tuple( -9L, "V38" ) ); + listTuples.add( new Tuple( -19L, "V39" ) ); + listTuples.add( new Tuple( -27L, "V40" ) ); + listTuples.add( new Tuple( 19L, "V41" ) ); + listTuples.add( new Tuple( 26L, "V42" ) ); + listTuples.add( new Tuple( -14L, "V43" ) ); + listTuples.add( new Tuple( -4L, "V44" ) ); + listTuples.add( new Tuple( -2L, "V45" ) ); + listTuples.add( new Tuple( -19L, "V46" ) ); + listTuples.add( new Tuple( -21L, "V47" ) ); + listTuples.add( new Tuple( 17L, "V48" ) ); + listTuples.add( new Tuple( 21L, "V49" ) ); + listTuples.add( new Tuple( -11L, "V50" ) ); + listTuples.add( new Tuple( -23L, "V51" ) ); + listTuples.add( new Tuple( 3L, "V52" ) ); + listTuples.add( new Tuple( 4L, "V53" ) ); + listTuples.add( new Tuple( -28L, "V54" ) ); + listTuples.add( new Tuple( 24L, "V55" ) ); + listTuples.add( new Tuple( 12L, "V56" ) ); + listTuples.add( new Tuple( 0L, "V57" ) ); + listTuples.add( new Tuple( -2L, "V58" ) ); + listTuples.add( new Tuple( -3L, "V59" ) ); + listTuples.add( new Tuple( 14L, "V60" ) ); + listTuples.add( new Tuple( -6L, "V61" ) ); + listTuples.add( new Tuple( -9L, "V62" ) ); + listTuples.add( new Tuple( 16L, "V63" ) ); + listTuples.add( new Tuple( -15L, "V64" ) ); + listTuples.add( new Tuple( -25L, "V65" ) ); + listTuples.add( new Tuple( 17L, "V66" ) ); + listTuples.add( new Tuple( -12L, "V67" ) ); + listTuples.add( new Tuple( -13L, "V68" ) ); + listTuples.add( new Tuple( -21L, "V69" ) ); + listTuples.add( new Tuple( -27L, "V70" ) ); + listTuples.add( new Tuple( -8L, "V71" ) ); + listTuples.add( new Tuple( -14L, "V72" ) ); + listTuples.add( new Tuple( -24L, "V73" ) ); + listTuples.add( new Tuple( 12L, "V74" ) ); + listTuples.add( new Tuple( 1L, "V75" ) ); + listTuples.add( new Tuple( -6L, "V76" ) ); + listTuples.add( new Tuple( 2L, "V77" ) ); + listTuples.add( new Tuple( -10L, "V78" ) ); + listTuples.add( new Tuple( 26L, "V79" ) ); + listTuples.add( new Tuple( 12L, "V80" ) ); + listTuples.add( new Tuple( 21L, "V81" ) ); + listTuples.add( new Tuple( 10L, "V82" ) ); + listTuples.add( new Tuple( 28L, "V83" ) ); + listTuples.add( new Tuple( 23L, "V84" ) ); + listTuples.add( new Tuple( -20L, "V85" ) ); + listTuples.add( new Tuple( 22L, "V86" ) ); + listTuples.add( new Tuple( -2L, "V87" ) ); + listTuples.add( new Tuple( 21L, "V88" ) ); + listTuples.add( new Tuple( 0L, "V89" ) ); + listTuples.add( new Tuple( -7L, "V90" ) ); + listTuples.add( new Tuple( 20L, "V91" ) ); + listTuples.add( new Tuple( 21L, "V92" ) ); + listTuples.add( new Tuple( 12L, "V93" ) ); + listTuples.add( new Tuple( 24L, "V94" ) ); + listTuples.add( new Tuple( 5L, "V95" ) ); + listTuples.add( new Tuple( 1L, "V96" ) ); + listTuples.add( new Tuple( 11L, "V97" ) ); + listTuples.add( new Tuple( 3L, "V98" ) ); + listTuples.add( new Tuple( -4L, "V99" ) ); + listTuples.add( new Tuple( 6L, "V100" ) ); + listTuples.add( new Tuple( 27L, "V101" ) ); + listTuples.add( new Tuple( -23L, "V102" ) ); + listTuples.add( new Tuple( 18L, "V103" ) ); + listTuples.add( new Tuple( 30L, "V104" ) ); + listTuples.add( new Tuple( -29L, "V105" ) ); + listTuples.add( new Tuple( 13L, "V106" ) ); + listTuples.add( new Tuple( -19L, "V107" ) ); + listTuples.add( new Tuple( 2L, "V108" ) ); + listTuples.add( new Tuple( 1L, "V109" ) ); + listTuples.add( new Tuple( 10L, "V110" ) ); + listTuples.add( new Tuple( -11L, "V111" ) ); + listTuples.add( new Tuple( 29L, "V112" ) ); + listTuples.add( new Tuple( -21L, "V113" ) ); + listTuples.add( new Tuple( -30L, "V114" ) ); + listTuples.add( new Tuple( 2L, "V115" ) ); + listTuples.add( new Tuple( 9L, "V116" ) ); + listTuples.add( new Tuple( 5L, "V117" ) ); + listTuples.add( new Tuple( 12L, "V118" ) ); + listTuples.add( new Tuple( -32L, "V119" ) ); + listTuples.add( new Tuple( -1L, "V120" ) ); + listTuples.add( new Tuple( -10L, "V121" ) ); + listTuples.add( new Tuple( -22L, "V122" ) ); + listTuples.add( new Tuple( -32L, "V123" ) ); + listTuples.add( new Tuple( -23L, "V124" ) ); + listTuples.add( new Tuple( -25L, "V125" ) ); + listTuples.add( new Tuple( -24L, "V126" ) ); + listTuples.add( new Tuple( 9L, "V127" ) ); + listTuples.add( new Tuple( -27L, "V128" ) ); + listTuples.add( new Tuple( 0L, "V129" ) ); + listTuples.add( new Tuple( 12L, "V130" ) ); + listTuples.add( new Tuple( -17L, "V131" ) ); + listTuples.add( new Tuple( -6L, "V132" ) ); + listTuples.add( new Tuple( 14L, "V133" ) ); + listTuples.add( new Tuple( -16L, "V134" ) ); + listTuples.add( new Tuple( 2L, "V135" ) ); + listTuples.add( new Tuple( -19L, "V136" ) ); + listTuples.add( new Tuple( 20L, "V137" ) ); + listTuples.add( new Tuple( -2L, "V138" ) ); + listTuples.add( new Tuple( 14L, "V139" ) ); + listTuples.add( new Tuple( 26L, "V140" ) ); + listTuples.add( new Tuple( 13L, "V141" ) ); + listTuples.add( new Tuple( 26L, "V142" ) ); + listTuples.add( new Tuple( -29L, "V143" ) ); + listTuples.add( new Tuple( -19L, "V144" ) ); + listTuples.add( new Tuple( 6L, "V145" ) ); + listTuples.add( new Tuple( -22L, "V146" ) ); + listTuples.add( new Tuple( 0L, "V147" ) ); + listTuples.add( new Tuple( -4L, "V148" ) ); + listTuples.add( new Tuple( 27L, "V149" ) ); + listTuples.add( new Tuple( 31L, "V150" ) ); + listTuples.add( new Tuple( 0L, "V151" ) ); + listTuples.add( new Tuple( 30L, "V152" ) ); + listTuples.add( new Tuple( -31L, "V153" ) ); + listTuples.add( new Tuple( -6L, "V154" ) ); + listTuples.add( new Tuple( 26L, "V155" ) ); + listTuples.add( new Tuple( -22L, "V156" ) ); + listTuples.add( new Tuple( 15L, "V157" ) ); + listTuples.add( new Tuple( 25L, "V158" ) ); + listTuples.add( new Tuple( -26L, "V159" ) ); + listTuples.add( new Tuple( 22L, "V160" ) ); + listTuples.add( new Tuple( 32L, "V161" ) ); + listTuples.add( new Tuple( 16L, "V162" ) ); + listTuples.add( new Tuple( -27L, "V163" ) ); + listTuples.add( new Tuple( 11L, "V164" ) ); + listTuples.add( new Tuple( -9L, "V165" ) ); + listTuples.add( new Tuple( -11L, "V166" ) ); + listTuples.add( new Tuple( -14L, "V167" ) ); + listTuples.add( new Tuple( 19L, "V168" ) ); + listTuples.add( new Tuple( -21L, "V169" ) ); + listTuples.add( new Tuple( -21L, "V170" ) ); + listTuples.add( new Tuple( 10L, "V171" ) ); + listTuples.add( new Tuple( 17L, "V172" ) ); + listTuples.add( new Tuple( 30L, "V173" ) ); + listTuples.add( new Tuple( -12L, "V174" ) ); + listTuples.add( new Tuple( 21L, "V175" ) ); + listTuples.add( new Tuple( 14L, "V176" ) ); + listTuples.add( new Tuple( 9L, "V177" ) ); + listTuples.add( new Tuple( -14L, "V178" ) ); + listTuples.add( new Tuple( 5L, "V179" ) ); + listTuples.add( new Tuple( 8L, "V180" ) ); + listTuples.add( new Tuple( -32L, "V181" ) ); + listTuples.add( new Tuple( 0L, "V182" ) ); + listTuples.add( new Tuple( -17L, "V183" ) ); + listTuples.add( new Tuple( -26L, "V184" ) ); + listTuples.add( new Tuple( -26L, "V185" ) ); + listTuples.add( new Tuple( 0L, "V186" ) ); + listTuples.add( new Tuple( -12L, "V187" ) ); + listTuples.add( new Tuple( 7L, "V188" ) ); + listTuples.add( new Tuple( 21L, "V189" ) ); + listTuples.add( new Tuple( 16L, "V190" ) ); + listTuples.add( new Tuple( -26L, "V191" ) ); + listTuples.add( new Tuple( -26L, "V192" ) ); + listTuples.add( new Tuple( 26L, "V193" ) ); + listTuples.add( new Tuple( 0L, "V194" ) ); + listTuples.add( new Tuple( -24L, "V195" ) ); + listTuples.add( new Tuple( 32L, "V196" ) ); + listTuples.add( new Tuple( 9L, "V197" ) ); + listTuples.add( new Tuple( 13L, "V198" ) ); + listTuples.add( new Tuple( 26L, "V199" ) ); + listTuples.add( new Tuple( 32L, "V200" ) ); + listTuples.add( new Tuple( -29L, "V201" ) ); + listTuples.add( new Tuple( -16L, "V202" ) ); + listTuples.add( new Tuple( 9L, "V203" ) ); + listTuples.add( new Tuple( 25L, "V204" ) ); + listTuples.add( new Tuple( 18L, "V205" ) ); + listTuples.add( new Tuple( 4L, "V206" ) ); + listTuples.add( new Tuple( -4L, "V207" ) ); + listTuples.add( new Tuple( 4L, "V208" ) ); + listTuples.add( new Tuple( 23L, "V209" ) ); + listTuples.add( new Tuple( 31L, "V210" ) ); + listTuples.add( new Tuple( 17L, "V211" ) ); + listTuples.add( new Tuple( -10L, "V212" ) ); + listTuples.add( new Tuple( -19L, "V213" ) ); + listTuples.add( new Tuple( 18L, "V214" ) ); + listTuples.add( new Tuple( 8L, "V215" ) ); + listTuples.add( new Tuple( -5L, "V216" ) ); + listTuples.add( new Tuple( 13L, "V217" ) ); + listTuples.add( new Tuple( -10L, "V218" ) ); + listTuples.add( new Tuple( -19L, "V219" ) ); + listTuples.add( new Tuple( 22L, "V220" ) ); + listTuples.add( new Tuple( -2L, "V221" ) ); + listTuples.add( new Tuple( -3L, "V222" ) ); + listTuples.add( new Tuple( -9L, "V223" ) ); + listTuples.add( new Tuple( -4L, "V224" ) ); + listTuples.add( new Tuple( -10L, "V225" ) ); + listTuples.add( new Tuple( 18L, "V226" ) ); + listTuples.add( new Tuple( -8L, "V227" ) ); + listTuples.add( new Tuple( 1L, "V228" ) ); + listTuples.add( new Tuple( 0L, "V229" ) ); + listTuples.add( new Tuple( 25L, "V230" ) ); + listTuples.add( new Tuple( 22L, "V231" ) ); + listTuples.add( new Tuple( 26L, "V232" ) ); + listTuples.add( new Tuple( -27L, "V233" ) ); + listTuples.add( new Tuple( -19L, "V234" ) ); + listTuples.add( new Tuple( -27L, "V235" ) ); + listTuples.add( new Tuple( 17L, "V236" ) ); + listTuples.add( new Tuple( -15L, "V237" ) ); + listTuples.add( new Tuple( 3L, "V238" ) ); + listTuples.add( new Tuple( -1L, "V239" ) ); + listTuples.add( new Tuple( -10L, "V240" ) ); + listTuples.add( new Tuple( -17L, "V241" ) ); + listTuples.add( new Tuple( -18L, "V242" ) ); + listTuples.add( new Tuple( 0L, "V243" ) ); + listTuples.add( new Tuple( 7L, "V244" ) ); + listTuples.add( new Tuple( 18L, "V245" ) ); + listTuples.add( new Tuple( 2L, "V246" ) ); + listTuples.add( new Tuple( -31L, "V247" ) ); + listTuples.add( new Tuple( 18L, "V248" ) ); + listTuples.add( new Tuple( -28L, "V249" ) ); + listTuples.add( new Tuple( 7L, "V250" ) ); + listTuples.add( new Tuple( -10L, "V251" ) ); + listTuples.add( new Tuple( 0L, "V252" ) ); + listTuples.add( new Tuple( -15L, "V253" ) ); + listTuples.add( new Tuple( -4L, "V254" ) ); + listTuples.add( new Tuple( 11L, "V255" ) ); + listTuples.add( new Tuple( 30L, "V256" ) ); + listTuples.add( new Tuple( -27L, "V257" ) ); + listTuples.add( new Tuple( 30L, "V258" ) ); + listTuples.add( new Tuple( -6L, "V259" ) ); + listTuples.add( new Tuple( -4L, "V260" ) ); + listTuples.add( new Tuple( 2L, "V261" ) ); + listTuples.add( new Tuple( 7L, "V262" ) ); + listTuples.add( new Tuple( -6L, "V263" ) ); + listTuples.add( new Tuple( -4L, "V264" ) ); + listTuples.add( new Tuple( 29L, "V265" ) ); + listTuples.add( new Tuple( 26L, "V266" ) ); + listTuples.add( new Tuple( -7L, "V267" ) ); + listTuples.add( new Tuple( -24L, "V268" ) ); + listTuples.add( new Tuple( 4L, "V269" ) ); + listTuples.add( new Tuple( -9L, "V270" ) ); + listTuples.add( new Tuple( -18L, "V271" ) ); + listTuples.add( new Tuple( 2L, "V272" ) ); + listTuples.add( new Tuple( -10L, "V273" ) ); + listTuples.add( new Tuple( 24L, "V274" ) ); + listTuples.add( new Tuple( -13L, "V275" ) ); + listTuples.add( new Tuple( 31L, "V276" ) ); + listTuples.add( new Tuple( -21L, "V277" ) ); + listTuples.add( new Tuple( -10L, "V278" ) ); + listTuples.add( new Tuple( -5L, "V279" ) ); + listTuples.add( new Tuple( -6L, "V280" ) ); + listTuples.add( new Tuple( -17L, "V281" ) ); + listTuples.add( new Tuple( -1L, "V282" ) ); + listTuples.add( new Tuple( -1L, "V283" ) ); + listTuples.add( new Tuple( 2L, "V284" ) ); + listTuples.add( new Tuple( -29L, "V285" ) ); + listTuples.add( new Tuple( 1L, "V286" ) ); + listTuples.add( new Tuple( -15L, "V287" ) ); + listTuples.add( new Tuple( 14L, "V288" ) ); + listTuples.add( new Tuple( -15L, "V289" ) ); + listTuples.add( new Tuple( -6L, "V290" ) ); + listTuples.add( new Tuple( -26L, "V291" ) ); + listTuples.add( new Tuple( 24L, "V292" ) ); + listTuples.add( new Tuple( -22L, "V293" ) ); + listTuples.add( new Tuple( 2L, "V294" ) ); + listTuples.add( new Tuple( 21L, "V295" ) ); + listTuples.add( new Tuple( -10L, "V296" ) ); + listTuples.add( new Tuple( 11L, "V297" ) ); + listTuples.add( new Tuple( 28L, "V298" ) ); + listTuples.add( new Tuple( 15L, "V299" ) ); + listTuples.add( new Tuple( 17L, "V300" ) ); + listTuples.add( new Tuple( -25L, "V301" ) ); + listTuples.add( new Tuple( 0L, "V302" ) ); + listTuples.add( new Tuple( -20L, "V303" ) ); + listTuples.add( new Tuple( -12L, "V304" ) ); + listTuples.add( new Tuple( -10L, "V305" ) ); + listTuples.add( new Tuple( -9L, "V306" ) ); + listTuples.add( new Tuple( 16L, "V307" ) ); + listTuples.add( new Tuple( -25L, "V308" ) ); + listTuples.add( new Tuple( 6L, "V309" ) ); + listTuples.add( new Tuple( 20L, "V310" ) ); + listTuples.add( new Tuple( -31L, "V311" ) ); + listTuples.add( new Tuple( -17L, "V312" ) ); + listTuples.add( new Tuple( -19L, "V313" ) ); + listTuples.add( new Tuple( 0L, "V314" ) ); + listTuples.add( new Tuple( -32L, "V315" ) ); + listTuples.add( new Tuple( 21L, "V316" ) ); + listTuples.add( new Tuple( 19L, "V317" ) ); + listTuples.add( new Tuple( -31L, "V318" ) ); + + File file = File.createTempFile( "managedbtreebuilder", ".data" ); + file.deleteOnExit(); + + try + { + RecordManager rm = new RecordManager( file.getAbsolutePath() ); + PersistedBTree btree = ( PersistedBTree ) rm.addBTree( "test", + LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + + // btree.valueThresholdUp = 8; + + Iterator> tupleIterator = new Iterator>() + { + private int pos = 0; + + + @Override + public Tuple next() + { + Tuple tuple = listTuples.get( pos++ ); + + return tuple; + } + + + @Override + public boolean hasNext() + { + return pos < listTuples.size(); + } + + + @Override + public void remove() + { + } + }; + + long t0 = System.currentTimeMillis(); + BTree result = null; + + result = BulkLoader.load( btree, tupleIterator, 128 ); + + TupleCursor cursor = result.browse(); + int nbFetched = 0; + Tuple prev = null; + Tuple elem = null; + + long t2 = System.currentTimeMillis(); + + try + { + while ( cursor.hasNext() ) + { + prev = elem; + elem = cursor.next(); + nbFetched++; + } + } + catch ( Exception e ) + { + System.out.println( "--->" + prev ); + e.printStackTrace(); + } + + long t3 = System.currentTimeMillis(); + } + catch ( Exception e ) + { + e.printStackTrace(); + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeBuilderTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeBuilderTest.java new file mode 100644 index 000000000..3459971b0 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeBuilderTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.junit.Ignore; +import org.junit.Test; + + +/** + * Test cases for BTreeBuilder. + * + * @author Apache Directory Project + */ +public class InMemoryBTreeBuilderTest +{ + @Test + @Ignore + public void testIntegerTree() throws IOException, KeyNotFoundException + { + List> sortedTuple = new ArrayList>(); + + for ( int i = 1; i < 8; i++ ) + { + Tuple t = new Tuple( i, i ); + sortedTuple.add( t ); + } + + IntSerializer ser = IntSerializer.INSTANCE; + InMemoryBTreeBuilder bb = new InMemoryBTreeBuilder( "master", 4, ser, ser ); + + // contains 1, 2, 3, 4, 5, 6, 7 + BTree btree = bb.build( sortedTuple.iterator() ); + + assertEquals( 1, btree.getRootPage().getNbElems() ); + + assertEquals( 7, btree.getRootPage().findRightMost().getKey().intValue() ); + + assertEquals( 1, btree.getRootPage().findLeftMost().getKey().intValue() ); + + TupleCursor cursor = btree.browse(); + int i = 0; + + while ( cursor.hasNext() ) + { + Tuple expected = sortedTuple.get( i++ ); + Tuple actual = cursor.next(); + assertEquals( expected.getKey(), actual.getKey() ); + assertEquals( expected.getValue(), actual.getValue() ); + } + + cursor.close(); + btree.close(); + } + +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeConfigurationTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeConfigurationTest.java new file mode 100644 index 000000000..2d48b382e --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeConfigurationTest.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; + +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * Test the creation of a BTree with a configuration. + * + * @author Apache Directory Project + */ +public class InMemoryBTreeConfigurationTest +{ + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + // Some values to inject in a btree + private static int[] sortedValues = new int[] + { + 0, 1, 2, 4, 5, 6, 8, 9, 11, 12, + 13, 14, 16, 19, 21, 22, 23, 25, 26, 28, + 30, 31, 32, 34, 36, 37, 38, 39, 41, 42, + 44, 45, 47, 50, 52, 53, 54, 55, 56, 58, + 59, 60, 63, 64, 67, 68, 70, 72, 73, 74, + 76, 77, 79, 80, 81, 82, 85, 88, 89, 90, + 92, 93, 95, 97, 98, 100, 101, 102, 103, 104, + 105, 106, 107, 109, 110, 111, 112, 117, 118, 120, + 121, 128, 129, 130, 131, 132, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 146, 147, 148, 149, 150, + 152, 154, 156, 160, 161, 162, 163, 165, 167, 168, + 169, 171, 173, 174, 175, 176, 177, 178, 179, 180, + 181, 182, 183, 189, 190, 193, 194, 195, 199, 200, + 202, 203, 205, 206, 207, 208, 209, 210, 212, 215, + 216, 217, 219, 220, 222, 223, 224, 225, 226, 227, + 228, 230, 231, 235, 236, 238, 239, 241, 242, 243, + 245, 246, 247, 249, 250, 251, 252, 254, 256, 257, + 258, 259, 261, 262, 263, 264, 266, 268, 272, 273, + 274, 276, 277, 278, 279, 282, 283, 286, 289, 290, + 292, 293, 294, 296, 298, 299, 300, 301, 303, 305, + 308, 310, 316, 317, 318, 319, 322, 323, 324, 326, + 327, 329, 331, 333, 334, 335, 336, 337, 338, 339, + 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, + 355, 356, 357, 358, 359, 361, 365, 366, 373, 374, + 375, 379, 380, 381, 382, 384, 385, 387, 388, 389, + 390, 392, 393, 395, 396, 397, 398, 399, 400, 401, + 404, 405, 406, 407, 410, 411, 412, 416, 417, 418, + 420, 421, 422, 424, 426, 427, 428, 430, 431, 432, + 433, 436, 439, 441, 443, 444, 445, 446, 447, 448, + 449, 450, 451, 452, 453, 454, 455, 456, 458, 459, + 464, 466, 469, 470, 471, 472, 475, 477, 478, 482, + 483, 484, 485, 486, 488, 490, 491, 492, 493, 495, + 496, 497, 500, 502, 503, 504, 505, 506, 507, 509, + 510, 514, 516, 518, 520, 521, 523, 524, 526, 527, + 528, 529, 530, 532, 533, 535, 538, 539, 540, 542, + 543, 544, 546, 547, 549, 550, 551, 553, 554, 558, + 559, 561, 563, 564, 566, 567, 568, 569, 570, 571, + 572, 576, 577, 578, 580, 582, 583, 586, 588, 589, + 590, 592, 593, 596, 597, 598, 599, 600, 601, 604, + 605, 606, 607, 609, 610, 613, 615, 617, 618, 619, + 620, 621, 626, 627, 628, 631, 632, 633, 635, 636, + 637, 638, 639, 640, 641, 643, 645, 647, 648, 649, + 650, 651, 652, 653, 655, 656, 658, 659, 660, 662, + 666, 669, 673, 674, 675, 676, 677, 678, 680, 681, + 682, 683, 685, 686, 687, 688, 689, 690, 691, 692, + 693, 694, 696, 698, 699, 700, 701, 705, 708, 709, + 711, 713, 714, 715, 719, 720, 723, 725, 726, 727, + 728, 731, 732, 733, 734, 735, 736, 739, 740, 743, + 744, 745, 746, 747, 749, 750, 752, 753, 762, 763, + 765, 766, 768, 770, 772, 773, 774, 776, 777, 779, + 782, 784, 785, 788, 790, 791, 793, 794, 795, 798, + 799, 800, 801, 803, 804, 805, 808, 810, 812, 813, + 814, 816, 818, 821, 822, 823, 824, 827, 828, 829, + 831, 832, 833, 834, 835, 837, 838, 839, 840, 843, + 846, 847, 849, 852, 853, 854, 856, 857, 859, 860, + 863, 864, 865, 866, 867, 868, 869, 872, 873, 877, + 880, 881, 882, 883, 887, 888, 889, 890, 891, 894, + 895, 897, 898, 899, 902, 904, 905, 907, 908, 910, + 911, 912, 915, 916, 917, 918, 919, 923, 925, 926, + 927, 928, 929, 930, 932, 935, 936, 937, 938, 939, + 944, 945, 947, 952, 953, 954, 955, 956, 957, 958, + 960, 967, 970, 971, 972, 974, 975, 976, 978, 979, + 980, 981, 983, 984, 985, 987, 988, 989, 991, 995 + }; + + + /** + * Test the creation of a in-memory BTree using the BTreeConfiguration. + */ + @Test + public void testConfigurationBasic() throws IOException, KeyNotFoundException + { + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setName( "basic" ); + config.setPageSize( 32 ); + config.setSerializers( IntSerializer.INSTANCE, StringSerializer.INSTANCE ); + + try + { + // Create the BTree + BTree btree = new InMemoryBTree( config ); + + // Inject the values + for ( int value : sortedValues ) + { + String strValue = "V" + value; + + btree.insert( value, strValue ); + } + + // Check that the tree contains all the values + for ( int key : sortedValues ) + { + String value = btree.get( key ); + + assertNotNull( value ); + } + + btree.close(); + } + finally + { + // Erase the mavibot file now + File mavibotFile = new File( "", "mavibot" ); + + if ( mavibotFile.exists() ) + { + mavibotFile.delete(); + } + + // Erase the journal too + File mavibotJournal = new File( "", "mavibot.log" ); + + if ( mavibotJournal.exists() ) + { + mavibotJournal.delete(); + } + } + } + + + /** + * Test the creation of a BTree using the BTreeConfiguration, flushing the + * tree on disk, then reloading it in another BTree. + */ + @Test + public void testConfigurationFlushReload() throws IOException, KeyNotFoundException + { + // Create a temporary file + File file = tempFolder.newFile( "testFlush.data" ); + String parent = file.getParent(); + + try + { + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setPageSize( 32 ); + config.setSerializers( IntSerializer.INSTANCE, StringSerializer.INSTANCE ); + + config.setFilePath( parent ); + config.setName( "mavibot" ); + + // Create the BTree + BTree btree = new InMemoryBTree( config ); + + // Inject the values + for ( int value : sortedValues ) + { + String strValue = "V" + value; + + btree.insert( value, strValue ); + } + + // Check that the tree contains all the values + for ( int key : sortedValues ) + { + String value = btree.get( key ); + + assertNotNull( value ); + } + + // Flush the data + btree.close(); + + // Now, create a new BTree using the same configuration + BTree btreeCopy = new InMemoryBTree( config ); + + // Check that the tree contains all the values + for ( int key : sortedValues ) + { + String value = btreeCopy.get( key ); + + assertNotNull( value ); + } + + btreeCopy.close(); + } + finally + { + // Erase the mavibot file now + File mavibotFile = new File( parent, "mavibot.db" ); + + if ( mavibotFile.exists() ) + { + mavibotFile.delete(); + } + + // Erase the journal too + File mavibotJournal = new File( parent, "mavibot.db.log" ); + + if ( mavibotJournal.exists() ) + { + mavibotJournal.delete(); + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeDuplicateKeyTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeDuplicateKeyTest.java new file mode 100644 index 000000000..17219927a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeDuplicateKeyTest.java @@ -0,0 +1,768 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.UUID; + +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.DuplicateValueNotAllowedException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.Ignore; +import org.junit.Test; + + +/** + * TODO BTreeDuplicateKeyTest. + * + * @author Apache Directory Project + */ +public class InMemoryBTreeDuplicateKeyTest +{ + @Test + public void testInsertNullValue() throws IOException, KeyNotFoundException + { + IntSerializer serializer = IntSerializer.INSTANCE; + + BTree btree = BTreeFactory.createInMemoryBTree( "master", serializer, serializer ); + + btree.insert( 1, null ); + + TupleCursor cursor = btree.browse(); + assertTrue( cursor.hasNext() ); + + Tuple t = cursor.next(); + + assertEquals( Integer.valueOf( 1 ), t.getKey() ); + assertEquals( null, t.getValue() ); + + cursor.close(); + btree.close(); + } + + + @Test + public void testBrowseEmptyTree() throws IOException, KeyNotFoundException + { + IntSerializer serializer = IntSerializer.INSTANCE; + + BTree btree = BTreeFactory.createInMemoryBTree( "master", serializer, serializer ); + + TupleCursor cursor = btree.browse(); + assertFalse( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + try + { + cursor.next(); + fail( "Should not reach here" ); + } + catch ( NoSuchElementException e ) + { + assertTrue( true ); + } + + try + { + cursor.prev(); + fail( "Should not reach here" ); + } + catch ( NoSuchElementException e ) + { + assertTrue( true ); + } + + cursor.close(); + btree.close(); + } + + + @Test + public void testDuplicateKey() throws IOException, KeyNotFoundException + { + IntSerializer serializer = IntSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + btree.insert( 1, 1 ); + btree.insert( 1, 2 ); + + TupleCursor cursor = btree.browse(); + assertTrue( cursor.hasNext() ); + + Tuple t = cursor.next(); + + assertEquals( Integer.valueOf( 1 ), t.getKey() ); + assertEquals( Integer.valueOf( 1 ), t.getValue() ); + + assertTrue( cursor.hasNext() ); + + t = cursor.next(); + + assertEquals( Integer.valueOf( 1 ), t.getKey() ); + assertEquals( Integer.valueOf( 2 ), t.getValue() ); + + assertFalse( cursor.hasNext() ); + + // test backward move + assertTrue( cursor.hasPrev() ); + + t = cursor.prev(); + + assertEquals( Integer.valueOf( 1 ), t.getKey() ); + assertEquals( Integer.valueOf( 1 ), t.getValue() ); + + assertFalse( cursor.hasPrev() ); + + // again forward + assertTrue( cursor.hasNext() ); + + t = cursor.next(); + + assertEquals( Integer.valueOf( 1 ), t.getKey() ); + assertEquals( Integer.valueOf( 2 ), t.getValue() ); + + assertFalse( cursor.hasNext() ); + + cursor.close(); + btree.close(); + } + + + @Test + public void testGetDuplicateKey() throws Exception + { + IntSerializer serializer = IntSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + Integer retVal = btree.insert( 1, 1 ); + assertNull( retVal ); + + retVal = btree.insert( 1, 2 ); + assertNull( retVal ); + + // check the return value when an existing value is added again + retVal = btree.insert( 1, 2 ); + assertEquals( Integer.valueOf( 2 ), retVal ); + + assertEquals( Integer.valueOf( 1 ), btree.get( 1 ) ); + assertTrue( btree.contains( 1, 1 ) ); + assertTrue( btree.contains( 1, 2 ) ); + + assertFalse( btree.contains( 1, 0 ) ); + assertFalse( btree.contains( 0, 1 ) ); + assertFalse( btree.contains( 0, 0 ) ); + assertFalse( btree.contains( null, 0 ) ); + assertFalse( btree.contains( 0, null ) ); + assertFalse( btree.contains( null, null ) ); + + btree.close(); + } + + + @Test + public void testRemoveDuplicateKey() throws Exception + { + IntSerializer serializer = IntSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + btree.insert( 1, 1 ); + btree.insert( 1, 2 ); + + // We should have only one element in the tree (even if it has 2 values) + assertEquals( 2l, btree.getNbElems() ); + + Tuple t = btree.delete( 1, 1 ); + assertEquals( Integer.valueOf( 1 ), t.getKey() ); + assertEquals( Integer.valueOf( 1 ), t.getValue() ); + + assertEquals( 1l, btree.getNbElems() ); + + t = btree.delete( 1, 2 ); + assertEquals( Integer.valueOf( 1 ), t.getKey() ); + assertNull( t.getValue() ); + + assertEquals( 0l, btree.getNbElems() ); + + t = btree.delete( 1, 2 ); + assertNull( t ); + + btree.close(); + } + + + @Test + public void testFullPage() throws Exception + { + StringSerializer serializer = StringSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + int i = 7; + + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + for ( int k = 0; k < i; k++ ) + { + String val = ch + Integer.toString( k ); + btree.insert( String.valueOf( ch ), val ); + } + } + + TupleCursor cursor = btree.browse(); + + char ch = 'a'; + int k = 0; + + while ( cursor.hasNext() ) + { + Tuple t = cursor.next(); + assertEquals( String.valueOf( ch ), t.getKey() ); + k++; + + if ( ( k % i ) == 0 ) + { + ch++; + } + } + + assertEquals( ( 'z' + 1 ), ch ); + + ch = 'z'; + cursor.afterLast(); + + while ( cursor.hasPrev() ) + { + Tuple t = cursor.prev(); + assertEquals( String.valueOf( ch ), t.getKey() ); + k--; + + if ( ( k % i ) == 0 ) + { + ch--; + } + } + + assertEquals( ( 'a' - 1 ), ch ); + + cursor.close(); + btree.close(); + } + + + @Test + public void testMoveFirst() throws Exception + { + StringSerializer serializer = StringSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + btree.insert( String.valueOf( ch ), UUID.randomUUID().toString() ); + } + + assertEquals( 26, btree.getNbElems() ); + + // add one more value for 'a' + btree.insert( String.valueOf( 'a' ), UUID.randomUUID().toString() ); + + assertEquals( 27, btree.getNbElems() ); + + TupleCursor cursor = btree.browseFrom( "c" ); + + int i = 0; + + while ( cursor.hasNext() ) + { + assertNotNull( cursor.next() ); + i++; + } + + assertEquals( 24, i ); + + // now move the cursor first + cursor.beforeFirst(); + assertTrue( cursor.hasNext() ); + Tuple tuple = cursor.next(); + + assertEquals( "a", tuple.getKey() ); + + i = 0; + + while ( cursor.hasNext() ) + { + tuple = cursor.next(); + assertNotNull( tuple ); + i++; + } + + assertEquals( 26, i ); + + cursor.close(); + + // Rebrowse + cursor = btree.browse(); + + i = 0; + + while ( cursor.hasNext() ) + { + assertNotNull( cursor.next() ); + i++; + } + + assertEquals( 27, i ); + + // now move the cursor first + cursor.beforeFirst(); + assertTrue( cursor.hasNext() ); + assertEquals( "a", cursor.nextKey().getKey() ); + + i = 0; + + while ( cursor.hasNext() ) + { + tuple = cursor.nextKey(); + String key = tuple.getKey(); + assertNotNull( key ); + i++; + } + + assertEquals( 25, i ); + + btree.close(); + } + + + @Test(expected = NoSuchElementException.class) + public void testMoveLast() throws Exception + { + StringSerializer serializer = StringSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + btree.insert( String.valueOf( ch ), UUID.randomUUID().toString() ); + } + + btree.insert( String.valueOf( 'z' ), UUID.randomUUID().toString() ); + + TupleCursor cursor = btree.browseFrom( "c" ); + cursor.afterLast(); + + assertFalse( cursor.hasNext() ); + assertTrue( cursor.hasPrev() ); + assertEquals( "z", cursor.prev().getKey() ); + // the key, 'z', has two values + assertEquals( "z", cursor.prev().getKey() ); + assertEquals( "y", cursor.prev().getKey() ); + + cursor.beforeFirst(); + assertEquals( "a", cursor.next().getKey() ); + + cursor.afterLast(); + assertFalse( cursor.hasNext() ); + + // make sure it throws NoSuchElementException + try + { + cursor.next(); + } + finally + { + btree.close(); + } + } + + + @Test(expected = NoSuchElementException.class) + public void testNextPrevKey() throws Exception + { + StringSerializer serializer = StringSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + int i = 7; + + // Insert keys from a to z with 7 values for each key + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + for ( int k = 0; k < i; k++ ) + { + btree.insert( String.valueOf( ch ), String.valueOf( k ) ); + } + } + + TupleCursor cursor = btree.browse(); + + assertTrue( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + for ( int k = 0; k < 2; k++ ) + { + assertEquals( "a", cursor.next().getKey() ); + } + + assertEquals( "a", cursor.next().getKey() ); + + Tuple tuple = cursor.nextKey(); + + assertEquals( "b", tuple.getKey() ); + + for ( char ch = 'b'; ch < 'z'; ch++ ) + { + assertEquals( String.valueOf( ch ), cursor.next().getKey() ); + tuple = cursor.nextKey(); + char t = ch; + assertEquals( String.valueOf( ++t ), tuple.getKey() ); + } + + for ( int k = 0; k < i; k++ ) + { + assertEquals( "z", cursor.next().getKey() ); + } + + assertFalse( cursor.hasNextKey() ); + assertTrue( cursor.hasPrevKey() ); + tuple = cursor.prev(); + assertEquals( "z", tuple.getKey() ); + assertEquals( "6", tuple.getValue() ); + + for ( char ch = 'z'; ch > 'a'; ch-- ) + { + char t = ch; + t--; + + assertEquals( String.valueOf( ch ), cursor.prev().getKey() ); + + tuple = cursor.prevKey(); + + assertEquals( String.valueOf( t ), tuple.getKey() ); + } + + for ( int k = 5; k >= 0; k-- ) + { + tuple = cursor.prev(); + assertEquals( "a", tuple.getKey() ); + assertEquals( String.valueOf( k ), tuple.getValue() ); + } + + assertTrue( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + tuple = cursor.next(); + assertEquals( "a", tuple.getKey() ); + assertEquals( "0", tuple.getValue() ); + + cursor.close(); + + cursor = btree.browseFrom( "y" ); + tuple = cursor.prevKey(); + assertNotNull( tuple ); + assertEquals( "y", tuple.getKey() ); + assertEquals( "6", tuple.getValue() ); + cursor.close(); + + cursor = btree.browse(); + cursor.beforeFirst(); + assertFalse( cursor.hasPrev() ); + + // make sure it throws NoSuchElementException + try + { + cursor.prev(); + } + finally + { + btree.close(); + } + } + + + /** + * Test for moving between two leaves. When moveToNextNonDuplicateKey is called + * and cursor is on the last element of the current leaf. + * + * @throws Exception + */ + @Test + public void testMoveToNextAndPrevWithPageBoundaries() throws Exception + { + IntSerializer serializer = IntSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setPageSize( 4 ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + int i = 7; + for ( int k = 0; k < i; k++ ) + { + btree.insert( k, k ); + } + + // 3 is the last element of the first leaf + TupleCursor cursor = btree.browseFrom( 3 ); + Tuple tuple = cursor.nextKey(); + + assertNotNull( tuple ); + assertEquals( Integer.valueOf( 4 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 4 ), tuple.getValue() ); + cursor.close(); + + cursor = btree.browseFrom( 3 ); + tuple = cursor.prevKey(); + + assertNotNull( tuple ); + assertEquals( Integer.valueOf( 2 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 2 ), tuple.getValue() ); + cursor.close(); + + // 4 is the first element of the second leaf + cursor = btree.browseFrom( 4 ); + tuple = cursor.prevKey(); + + assertNotNull( tuple ); + assertEquals( Integer.valueOf( 3 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 3 ), tuple.getValue() ); + + assertTrue( cursor.hasNext() ); + tuple = cursor.next(); + assertEquals( Integer.valueOf( 4 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 4 ), tuple.getValue() ); + + cursor.close(); + + // test the extremes of the BTree instead of that of leaves + cursor = btree.browseFrom( 5 ); + tuple = cursor.nextKey(); + assertFalse( cursor.hasNext() ); + assertTrue( cursor.hasPrev() ); + + assertEquals( Integer.valueOf( 6 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 6 ), tuple.getValue() ); + cursor.close(); + + cursor = btree.browse(); + cursor.beforeFirst(); + assertTrue( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + tuple = cursor.next(); + + assertEquals( Integer.valueOf( 0 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 0 ), tuple.getValue() ); + + cursor.close(); + btree.close(); + } + + + @Test + public void testNextAfterPrev() throws Exception + { + IntSerializer serializer = IntSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setPageSize( 4 ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + int i = 7; + for ( int k = 0; k < i; k++ ) + { + btree.insert( k, k ); + } + + // 3 is the last element of the first leaf + TupleCursor cursor = btree.browseFrom( 4 ); + + assertTrue( cursor.hasNext() ); + Tuple tuple = cursor.next(); + assertEquals( Integer.valueOf( 4 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 4 ), tuple.getValue() ); + + assertTrue( cursor.hasPrev() ); + tuple = cursor.prev(); + assertEquals( Integer.valueOf( 3 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 3 ), tuple.getValue() ); + + assertTrue( cursor.hasNext() ); + tuple = cursor.next(); + assertEquals( Integer.valueOf( 4 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 4 ), tuple.getValue() ); + + cursor.close(); + btree.close(); + } + + + /** + * Test for moving after a key and traversing backwards. + * + * @throws Exception + */ + @Test + public void testMoveToNextAndTraverseBackward() throws Exception + { + IntSerializer serializer = IntSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setPageSize( 8 ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + int i = 5; + for ( int k = 0; k < i; k++ ) + { + btree.insert( k, k ); + } + + // 4 is the last element in the tree + TupleCursor cursor = btree.browseFrom( 4 ); + cursor.nextKey(); + + int currentKey = 4; + while ( cursor.hasPrev() ) + { + assertEquals( Integer.valueOf( currentKey ), cursor.prev().getKey() ); + currentKey--; + } + + cursor.close(); + btree.close(); + } + + + /** + * Test for moving after a key and traversing backwards. + * + * @throws Exception + */ + @Test + public void testMoveToPrevAndTraverseForward() throws Exception + { + IntSerializer serializer = IntSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setAllowDuplicates( true ); + config.setName( "master" ); + config.setPageSize( 8 ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + int i = 5; + + for ( int k = 0; k < i; k++ ) + { + btree.insert( k, k ); + } + + // 4 is the last element in the tree + TupleCursor cursor = btree.browseFrom( 0 ); + + int currentKey = 0; + + while ( cursor.hasNext() ) + { + assertEquals( Integer.valueOf( currentKey ), cursor.next().getKey() ); + currentKey++; + } + + cursor.close(); + btree.close(); + } + + + /** + * Test that a BTree which forbid duplicate values does not accept them + */ + @Test(expected = DuplicateValueNotAllowedException.class) + @Ignore("this condition is removed") + public void testBTreeForbidDups() throws IOException, BTreeAlreadyManagedException + { + BTree singleValueBtree = BTreeFactory.createInMemoryBTree( "test2", LongSerializer.INSTANCE, + StringSerializer.INSTANCE, BTree.FORBID_DUPLICATES ); + + for ( long i = 0; i < 64; i++ ) + { + singleValueBtree.insert( i, Long.toString( i ) ); + } + + try + { + singleValueBtree.insert( 18L, "Duplicate" ); + fail(); + } + finally + { + singleValueBtree.close(); + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeFlushTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeFlushTest.java new file mode 100644 index 000000000..7946378cf --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeFlushTest.java @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Random; +import java.util.Set; + +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * A unit test class for BTree flush() operation + * + * @author Apache Directory Project + */ +public class InMemoryBTreeFlushTest +{ + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + // Some values to inject in a btree + private static int[] sortedValues = new int[] + { + 0, 1, 2, 4, 5, 6, 8, 9, 11, 12, + 13, 14, 16, 19, 21, 22, 23, 25, 26, 28, + 30, 31, 32, 34, 36, 37, 38, 39, 41, 42, + 44, 45, 47, 50, 52, 53, 54, 55, 56, 58, + 59, 60, 63, 64, 67, 68, 70, 72, 73, 74, + 76, 77, 79, 80, 81, 82, 85, 88, 89, 90, + 92, 93, 95, 97, 98, 100, 101, 102, 103, 104, + 105, 106, 107, 109, 110, 111, 112, 117, 118, 120, + 121, 128, 129, 130, 131, 132, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 146, 147, 148, 149, 150, + 152, 154, 156, 160, 161, 162, 163, 165, 167, 168, + 169, 171, 173, 174, 175, 176, 177, 178, 179, 180, + 181, 182, 183, 189, 190, 193, 194, 195, 199, 200, + 202, 203, 205, 206, 207, 208, 209, 210, 212, 215, + 216, 217, 219, 220, 222, 223, 224, 225, 226, 227, + 228, 230, 231, 235, 236, 238, 239, 241, 242, 243, + 245, 246, 247, 249, 250, 251, 252, 254, 256, 257, + 258, 259, 261, 262, 263, 264, 266, 268, 272, 273, + 274, 276, 277, 278, 279, 282, 283, 286, 289, 290, + 292, 293, 294, 296, 298, 299, 300, 301, 303, 305, + 308, 310, 316, 317, 318, 319, 322, 323, 324, 326, + 327, 329, 331, 333, 334, 335, 336, 337, 338, 339, + 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, + 355, 356, 357, 358, 359, 361, 365, 366, 373, 374, + 375, 379, 380, 381, 382, 384, 385, 387, 388, 389, + 390, 392, 393, 395, 396, 397, 398, 399, 400, 401, + 404, 405, 406, 407, 410, 411, 412, 416, 417, 418, + 420, 421, 422, 424, 426, 427, 428, 430, 431, 432, + 433, 436, 439, 441, 443, 444, 445, 446, 447, 448, + 449, 450, 451, 452, 453, 454, 455, 456, 458, 459, + 464, 466, 469, 470, 471, 472, 475, 477, 478, 482, + 483, 484, 485, 486, 488, 490, 491, 492, 493, 495, + 496, 497, 500, 502, 503, 504, 505, 506, 507, 509, + 510, 514, 516, 518, 520, 521, 523, 524, 526, 527, + 528, 529, 530, 532, 533, 535, 538, 539, 540, 542, + 543, 544, 546, 547, 549, 550, 551, 553, 554, 558, + 559, 561, 563, 564, 566, 567, 568, 569, 570, 571, + 572, 576, 577, 578, 580, 582, 583, 586, 588, 589, + 590, 592, 593, 596, 597, 598, 599, 600, 601, 604, + 605, 606, 607, 609, 610, 613, 615, 617, 618, 619, + 620, 621, 626, 627, 628, 631, 632, 633, 635, 636, + 637, 638, 639, 640, 641, 643, 645, 647, 648, 649, + 650, 651, 652, 653, 655, 656, 658, 659, 660, 662, + 666, 669, 673, 674, 675, 676, 677, 678, 680, 681, + 682, 683, 685, 686, 687, 688, 689, 690, 691, 692, + 693, 694, 696, 698, 699, 700, 701, 705, 708, 709, + 711, 713, 714, 715, 719, 720, 723, 725, 726, 727, + 728, 731, 732, 733, 734, 735, 736, 739, 740, 743, + 744, 745, 746, 747, 749, 750, 752, 753, 762, 763, + 765, 766, 768, 770, 772, 773, 774, 776, 777, 779, + 782, 784, 785, 788, 790, 791, 793, 794, 795, 798, + 799, 800, 801, 803, 804, 805, 808, 810, 812, 813, + 814, 816, 818, 821, 822, 823, 824, 827, 828, 829, + 831, 832, 833, 834, 835, 837, 838, 839, 840, 843, + 846, 847, 849, 852, 853, 854, 856, 857, 859, 860, + 863, 864, 865, 866, 867, 868, 869, 872, 873, 877, + 880, 881, 882, 883, 887, 888, 889, 890, 891, 894, + 895, 897, 898, 899, 902, 904, 905, 907, 908, 910, + 911, 912, 915, 916, 917, 918, 919, 923, 925, 926, + 927, 928, 929, 930, 932, 935, 936, 937, 938, 939, + 944, 945, 947, 952, 953, 954, 955, 956, 957, 958, + 960, 967, 970, 971, 972, 974, 975, 976, 978, 979, + 980, 981, 983, 984, 985, 987, 988, 989, 991, 995 + }; + + + private String create100KElementsFile() throws IOException + { + Random random = new Random( System.nanoTime() ); + + int nbError = 0; + + long l1 = System.currentTimeMillis(); + int n = 0; + long delta = l1; + int nbElems = 100000; + + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 32 ); + + for ( int i = 0; i < nbElems; i++ ) + { + Long key = ( long ) random.nextLong(); + String value = Long.toString( key ); + + try + { + btree.insert( key, value ); + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while adding " + value ); + nbError++; + btree.close(); + + return null; + } + + if ( i % 10000 == 0 ) + { + if ( n > 0 ) + { + long t0 = System.currentTimeMillis(); + System.out.println( "Written " + i + " elements in : " + ( t0 - delta ) + "ms" ); + delta = t0; + } + + n++; + } + } + + long l2 = System.currentTimeMillis(); + + System.out.println( "Delta : " + ( l2 - l1 ) + ", nbError = " + nbError + + ", Nb insertion per second : " + ( ( nbElems ) / ( l2 - l1 ) ) * 1000 ); + + // Now, flush the btree + + File tempFile = tempFolder.newFile( "mavibot.tmp" ); + + long t0 = System.currentTimeMillis(); + + ( ( InMemoryBTree ) btree ).flush( tempFile ); + + long t1 = System.currentTimeMillis(); + + System.out.println( "Time to flush 100 000 elements : " + ( t1 - t0 ) + "ms" ); + btree.close(); + + return tempFile.getCanonicalPath(); + } + + + /** + * Checks the created BTree contains the expected values + */ + private boolean checkTreeLong( Set expected, BTree btree ) throws IOException + { + // We loop on all the expected value to see if they have correctly been inserted + // into the btree + for ( Long key : expected ) + { + try + { + btree.get( key ); + } + catch ( KeyNotFoundException knfe ) + { + return false; + } + } + + return true; + } + + + /** + * Test the browse method going backward + * @throws Exception + */ + @Test + public void testFlushBTree() throws Exception + { + // Create a BTree with pages containing 8 elements + String path = tempFolder.getRoot().getCanonicalPath(); + + BTree btree = BTreeFactory.createInMemoryBTree( "test", path, IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + + File journal = ( ( InMemoryBTree ) btree ).getJournal(); + File data = ( ( InMemoryBTree ) btree ).getFile(); + + try + { + // Inject the values + for ( int value : sortedValues ) + { + String strValue = "V" + value; + + btree.insert( value, strValue ); + } + + // The journal must be full + assertTrue( journal.length() > 0 ); + + // Now, flush the btree + btree.flush(); + + // The journal must be empty + assertEquals( 0, journal.length() ); + + // Load the data into a new tree + BTree btreeLoaded = BTreeFactory.createInMemoryBTree( "test", path, IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + + TupleCursor cursor1 = btree.browse(); + TupleCursor cursor2 = btree.browse(); + + while ( cursor1.hasNext() ) + { + assertTrue( cursor2.hasNext() ); + + Tuple tuple1 = cursor1.next(); + Tuple tuple2 = cursor2.next(); + + assertEquals( tuple1.getKey(), tuple2.getKey() ); + assertEquals( tuple1.getValue(), tuple2.getValue() ); + } + + assertFalse( cursor2.hasNext() ); + + btree.close(); + btreeLoaded.close(); + } + finally + { + data.delete(); + journal.delete(); + } + } + + + /** + * Test the insertion of 5 million elements in a BTree + * @throws Exception + */ + @Test + public void testLoadBTreeFromFile() throws Exception + { + String data100K = create100KElementsFile(); + File dataFile = new File( data100K ); + BTree btree = BTreeFactory.createInMemoryBTree( + "test", + dataFile.getParent(), + LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 32 ); + btree.close(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeTest.java new file mode 100644 index 000000000..49bb731d1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeTest.java @@ -0,0 +1,2011 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.Ignore; +import org.junit.Test; + + +/** + * A unit test class for in-memory BTree + * + * @author Apache Directory Project + */ +public class InMemoryBTreeTest +{ + // Some values to inject in a btree + private static int[] sortedValues = new int[] + { + 0, 1, 2, 4, 5, 6, 8, 9, 11, 12, + 13, 14, 16, 19, 21, 22, 23, 25, 26, 28, + 30, 31, 32, 34, 36, 37, 38, 39, 41, 42, + 44, 45, 47, 50, 52, 53, 54, 55, 56, 58, + 59, 60, 63, 64, 67, 68, 70, 72, 73, 74, + 76, 77, 79, 80, 81, 82, 85, 88, 89, 90, + 92, 93, 95, 97, 98, 100, 101, 102, 103, 104, + 105, 106, 107, 109, 110, 111, 112, 117, 118, 120, + 121, 128, 129, 130, 131, 132, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 146, 147, 148, 149, 150, + 152, 154, 156, 160, 161, 162, 163, 165, 167, 168, + 169, 171, 173, 174, 175, 176, 177, 178, 179, 180, + 181, 182, 183, 189, 190, 193, 194, 195, 199, 200, + 202, 203, 205, 206, 207, 208, 209, 210, 212, 215, + 216, 217, 219, 220, 222, 223, 224, 225, 226, 227, + 228, 230, 231, 235, 236, 238, 239, 241, 242, 243, + 245, 246, 247, 249, 250, 251, 252, 254, 256, 257, + 258, 259, 261, 262, 263, 264, 266, 268, 272, 273, + 274, 276, 277, 278, 279, 282, 283, 286, 289, 290, + 292, 293, 294, 296, 298, 299, 300, 301, 303, 305, + 308, 310, 316, 317, 318, 319, 322, 323, 324, 326, + 327, 329, 331, 333, 334, 335, 336, 337, 338, 339, + 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, + 355, 356, 357, 358, 359, 361, 365, 366, 373, 374, + 375, 379, 380, 381, 382, 384, 385, 387, 388, 389, + 390, 392, 393, 395, 396, 397, 398, 399, 400, 401, + 404, 405, 406, 407, 410, 411, 412, 416, 417, 418, + 420, 421, 422, 424, 426, 427, 428, 430, 431, 432, + 433, 436, 439, 441, 443, 444, 445, 446, 447, 448, + 449, 450, 451, 452, 453, 454, 455, 456, 458, 459, + 464, 466, 469, 470, 471, 472, 475, 477, 478, 482, + 483, 484, 485, 486, 488, 490, 491, 492, 493, 495, + 496, 497, 500, 502, 503, 504, 505, 506, 507, 509, + 510, 514, 516, 518, 520, 521, 523, 524, 526, 527, + 528, 529, 530, 532, 533, 535, 538, 539, 540, 542, + 543, 544, 546, 547, 549, 550, 551, 553, 554, 558, + 559, 561, 563, 564, 566, 567, 568, 569, 570, 571, + 572, 576, 577, 578, 580, 582, 583, 586, 588, 589, + 590, 592, 593, 596, 597, 598, 599, 600, 601, 604, + 605, 606, 607, 609, 610, 613, 615, 617, 618, 619, + 620, 621, 626, 627, 628, 631, 632, 633, 635, 636, + 637, 638, 639, 640, 641, 643, 645, 647, 648, 649, + 650, 651, 652, 653, 655, 656, 658, 659, 660, 662, + 666, 669, 673, 674, 675, 676, 677, 678, 680, 681, + 682, 683, 685, 686, 687, 688, 689, 690, 691, 692, + 693, 694, 696, 698, 699, 700, 701, 705, 708, 709, + 711, 713, 714, 715, 719, 720, 723, 725, 726, 727, + 728, 731, 732, 733, 734, 735, 736, 739, 740, 743, + 744, 745, 746, 747, 749, 750, 752, 753, 762, 763, + 765, 766, 768, 770, 772, 773, 774, 776, 777, 779, + 782, 784, 785, 788, 790, 791, 793, 794, 795, 798, + 799, 800, 801, 803, 804, 805, 808, 810, 812, 813, + 814, 816, 818, 821, 822, 823, 824, 827, 828, 829, + 831, 832, 833, 834, 835, 837, 838, 839, 840, 843, + 846, 847, 849, 852, 853, 854, 856, 857, 859, 860, + 863, 864, 865, 866, 867, 868, 869, 872, 873, 877, + 880, 881, 882, 883, 887, 888, 889, 890, 891, 894, + 895, 897, 898, 899, 902, 904, 905, 907, 908, 910, + 911, 912, 915, 916, 917, 918, 919, 923, 925, 926, + 927, 928, 929, 930, 932, 935, 936, 937, 938, 939, + 944, 945, 947, 952, 953, 954, 955, 956, 957, 958, + 960, 967, 970, 971, 972, 974, 975, 976, 978, 979, + 980, 981, 983, 984, 985, 987, 988, 989, 991, 995 + }; + + + /** + * Checks the created BTree contains the expected values + */ + private boolean checkTreeLong( Set expected, BTree btree ) throws IOException + { + // We loop on all the expected value to see if they have correctly been inserted + // into the btree + for ( Long key : expected ) + { + try + { + btree.get( key ); + } + catch ( KeyNotFoundException knfe ) + { + return false; + } + } + + return true; + } + + + /** + * Test the insertion of elements in a BTree. We will try 1000 times to insert 1000 + * random elements in [0..1024], and check every tree to see if all the added elements + * are present. This pretty much validate the the insertion, assuming that due to the + * randomization of the injected values, we will statically meet all the use cases. + * @throws Exception + */ + @Test + public void testPageInsert() throws Exception + { + Set expected = new HashSet(); + List added = new ArrayList(); + + Random random = new Random( System.nanoTime() ); + + int nbError = 0; + + long l1 = System.currentTimeMillis(); + int n = 0; + long delta = l1; + int nbTrees = 1000; + int nbElems = 1000; + + for ( int j = 0; j < nbTrees; j++ ) + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 32 ); + + for ( int i = 0; i < nbElems; i++ ) + { + Long key = ( long ) random.nextInt( 1024 ); + String value = "V" + key; + expected.add( key ); + added.add( key ); + + //System.out.println( "Adding " + i + "th : " + key ); + + try + { + btree.insert( key, value ); + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while adding " + value ); + nbError++; + btree.close(); + + return; + } + } + + assertTrue( checkTreeLong( expected, btree ) ); + + /* For debug only + if ( !checkTree( expected, btree ) ) + { + boolean isFirst = true; + + for ( Long key : added ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + System.out.print( ", " ); + } + + System.out.print( key ); + } + } + */ + + if ( j % 10000 == 0 ) + { + if ( n > 0 ) + { + long t0 = System.currentTimeMillis(); + System.out.println( "Delta" + n + ": " + ( t0 - delta ) ); + delta = t0; + } + + n++; + } + + expected.clear(); + added.clear(); + + btree.close(); + } + + long l2 = System.currentTimeMillis(); + + System.out.println( "Delta : " + ( l2 - l1 ) + ", nbError = " + nbError + + ", Nb insertion per second : " + ( nbTrees * nbElems * 1000 ) / ( l2 - l1 ) ); + } + + + /** + * Test the deletion of elements in a BTree. We will try 1000 times to delete 1000 + * random elements in [0..1024], and check every tree to see if all the removed elements + * are absent. This pretty much validate the the deletion operation is valid, assuming + * that due to the randomization of the deleted values, we will statically meet all the + * use cases. + * @throws Exception + */ + @Test + public void testPageDeleteRandom() throws IOException + { + Set expected = new HashSet(); + List added = new ArrayList(); + + Random random = new Random( System.nanoTime() ); + + int nbError = 0; + + long l1 = System.currentTimeMillis(); + int n = 0; + long delta = l1; + int nbTrees = 1000; + int nbElems = 1000; + + for ( int j = 0; j < nbTrees; j++ ) + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + + for ( int i = 0; i < nbElems; i++ ) + { + Long key = ( long ) random.nextInt( 1024 ); + String value = "V" + key; + expected.add( key ); + added.add( key ); + + //System.out.println( "Adding " + i + "th : " + key ); + + try + { + btree.insert( key, value ); + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while adding " + value ); + nbError++; + btree.close(); + + return; + } + } + + assertTrue( checkTreeLong( expected, btree ) ); + + // Now, delete the elements + /* + boolean isFirst = true; + + for ( long element : added ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + System.out.print( ", " ); + } + + System.out.print( element ); + } + + //System.out.println( "\n--------------------" ); + */ + + //int i = 0; + + for ( long element : expected ) + { + //System.out.println( "Deleting #" + i + " : " + element ); + //i++; + //System.out.println( btree ); + Tuple tuple = btree.delete( element ); + + if ( tuple == null ) + { + System.out.println( btree ); + } + + assertEquals( Long.valueOf( element ), tuple.getKey() ); + + checkNull( btree, element ); + + //System.out.println( "" ); + } + + if ( j % 10000 == 0 ) + { + if ( n > 0 ) + { + long t0 = System.currentTimeMillis(); + System.out.println( "Delta" + n + ": " + ( t0 - delta ) ); + delta = t0; + } + + n++; + + } + + expected.clear(); + added.clear(); + + btree.close(); + } + + long l2 = System.currentTimeMillis(); + + System.out.println( "Delta : " + ( l2 - l1 ) + ", nbError = " + nbError + + ", Nb deletion per second : " + ( nbTrees * nbElems * 1000 ) / ( l2 - l1 ) ); + } + + + @Test + public void testDeleteDebug() throws IOException + { + long[] values = new long[] + { + 148, 746, 525, 327, 1, 705, 171, 1023, 769, 1021, + 128, 772, 744, 771, 925, 884, 346, 519, 989, 350, + 649, 895, 464, 164, 190, 298, 203, 69, 483, 38, + 266, 83, 88, 285, 879, 342, 231, 432, 722, 432, + 258, 307, 237, 151, 43, 36, 135, 166, 325, 886, + 878, 307, 925, 835, 800, 895, 519, 947, 703, 27, + 324, 668, 40, 943, 804, 230, 223, 584, 828, 575, + 69, 955, 344, 325, 896, 423, 855, 783, 225, 447, + 28, 23, 262, 679, 782, 517, 412, 878, 641, 940, + 368, 245, 1005, 226, 939, 320, 396, 437, 373, 61 + }; + + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + + for ( long value : values ) + { + String strValue = "V" + value; + + try + { + btree.insert( value, strValue ); + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while adding " + value ); + btree.close(); + + return; + } + } + + long[] deletes = new long[] + { + 1, + 828, + 285, + 804, + 258, + 262, + }; + + for ( long value : deletes ) + { + Tuple tuple = btree.delete( value ); + + if ( tuple != null ) + { + assertEquals( Long.valueOf( value ), tuple.getKey() ); + } + + checkNull( btree, value ); + } + + btree.close(); + } + + + /** + * Test the deletion of elements from a BTree. + */ + @Test + public void testPageDelete() throws Exception + { + Set expected = new HashSet(); + List added = new ArrayList(); + + Random random = new Random( System.nanoTime() ); + + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + + // Insert some values + for ( int i = 0; i < 8; i++ ) + { + Long key = ( long ) random.nextInt( 1024 ); + String value = "V" + key; + added.add( key ); + + try + { + btree.insert( key, value ); + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while adding " + value ); + btree.close(); + + return; + } + } + + assertTrue( checkTreeLong( expected, btree ) ); + + // Now, delete entries + for ( long key : added ) + { + //System.out.println( "Removing " + key + " from " + btree ); + try + { + btree.delete( key ); + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while deleting " + key ); + btree.close(); + + return; + } + + assertTrue( checkTreeLong( expected, btree ) ); + } + + btree.close(); + } + + + /** + * This test is used to debug some corner cases. + * We don't run it except to check a special condition + */ + @Test + @Ignore("This is a debug test") + public void testPageInsertDebug() throws Exception + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + Long[] elems = new Long[] + { + 235L, 135L, 247L, 181L, 12L, 112L, 117L, 253L, + 37L, 158L, 56L, 118L, 184L, 101L, 173L, 126L, + 61L, 81L, 140L, 173L, 32L, 163L, 224L, 114L, + 133L, 18L, 14L, 82L, 107L, 219L, 244L, 255L, + 6L, 103L, 170L, 151L, 134L, 196L, 155L, 97L, + 80L, 122L, 89L, 253L, 33L, 101L, 56L, 168L, + 253L, 187L, 99L, 58L, 151L, 206L, 34L, 96L, + 20L, 188L, 143L, 150L, 76L, 111L, 234L, 66L, + 12L, 194L, 164L, 190L, 19L, 192L, 161L, 147L, + 92L, 89L, 237L, 187L, 250L, 13L, 233L, 34L, + 187L, 232L, 248L, 237L, 129L, 1L, 233L, 252L, + 18L, 98L, 56L, 121L, 162L, 233L, 29L, 48L, + 176L, 48L, 182L, 130L + }; + + int size = 0; + for ( Long elem : elems ) + { + size++; + String value = "V" + elem; + btree.insert( elem, value ); + + System.out.println( "Adding " + elem + " :\n" + btree ); + + for ( int i = 0; i < size; i++ ) + { + try + { + btree.get( elems[i] ); + } + catch ( KeyNotFoundException knfe ) + { + System.out.println( "Bad tree, missing " + elems[i] + ", " + btree ); + } + } + + if ( size == 27 ) + { + System.out.println( btree ); + } + //System.out.println( "added " + elem + ":\n" + btree ); + } + + //System.out.println( btree ); + + btree.close(); + } + + + /* + @Test + public void testPageRemove() throws Exception + { + Long[] keys = new Long[]{ 101L, 113L, 20L, 72L, 215L, 239L, 108L, 21L }; + + BTree btree = BTreeFactory.createInMemoryBTree( new LongComparator(), 8 ); + System.out.println( btree ); + + for ( Long key : keys ) + { + btree.insert( key, "V" + key ); + } + + System.out.println( btree ); + + // Remove from the left + btree.remove( 20L ); + System.out.println( btree ); + + // Remove from the right + btree.remove( 239L ); + System.out.println( btree ); + + // Remove from the middle + btree.remove( 72L ); + System.out.println( btree ); + + // Remove all the remaining elements + btree.remove( 101L ); + System.out.println( btree ); + btree.remove( 108L ); + System.out.println( btree ); + btree.remove( 215L ); + System.out.println( btree ); + btree.remove( 113L ); + System.out.println( btree ); + btree.remove( 21L ); + System.out.println( btree ); + + btree.close(); + } + */ + + /** + * Test the browse method going forward + * @throws Exception + */ + @Test + public void testBrowseForward() throws Exception + { + // Create a BTree with pages containing 8 elements + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + + // Inject the values + for ( int value : sortedValues ) + { + String strValue = "V" + value; + + btree.insert( value, strValue ); + } + + // Check that the tree contains all the values + for ( int key : sortedValues ) + { + String value = btree.get( key ); + + assertNotNull( value ); + } + + // Browse starting at position 10 + int pos = 10; + TupleCursor cursor = btree.browseFrom( sortedValues[pos] ); + + while ( cursor.hasNext() ) + { + Tuple tuple = cursor.next(); + + assertNotNull( tuple ); + Integer val = sortedValues[pos]; + Integer res = tuple.getKey(); + assertEquals( val, res ); + pos++; + } + + cursor.close(); + + // Now, start on a non existing key (7) + cursor = btree.browseFrom( 7 ); + + // We should start reading values superior to 7, so value 8 at position 6 in the array + pos = 6; + + while ( cursor.hasNext() ) + { + Tuple tuple = cursor.next(); + + assertNotNull( tuple ); + Integer val = sortedValues[pos]; + Integer res = tuple.getKey(); + assertEquals( val, res ); + pos++; + } + + cursor.close(); + + // Last, let's browse with no key, we should get all the values + cursor = btree.browse(); + + pos = 0; + + while ( cursor.hasNext() ) + { + Tuple tuple = cursor.next(); + + assertNotNull( tuple ); + Integer val = sortedValues[pos]; + Integer res = tuple.getKey(); + assertEquals( val, res ); + pos++; + } + + cursor.close(); + btree.close(); + } + + + /** + * Test the browse method going backward + * @throws Exception + */ + @Test + public void testBrowseBackward() throws Exception + { + // Create a BTree with pages containing 8 elements + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + + // Inject the values + for ( int value : sortedValues ) + { + String strValue = "V" + value; + + btree.insert( value, strValue ); + } + + // Check that the tree contains all the values + for ( int key : sortedValues ) + { + String value = btree.get( key ); + + assertNotNull( value ); + } + + // Browse starting at position 10 + int pos = 10; + TupleCursor cursor = btree.browseFrom( sortedValues[pos] ); + + while ( cursor.hasPrev() ) + { + Tuple tuple = cursor.prev(); + + pos--; + + assertNotNull( tuple ); + Integer val = sortedValues[pos]; + Integer res = tuple.getKey(); + assertEquals( val, res ); + } + + cursor.close(); + + // Now, start on a non existing key (7) + cursor = btree.browseFrom( 7 ); + + // We should start reading values superior to 7, so value 8 at position 6 in the array + pos = 6; + + while ( cursor.hasPrev() ) + { + Tuple tuple = cursor.prev(); + + pos--; + assertNotNull( tuple ); + Integer val = sortedValues[pos]; + Integer res = tuple.getKey(); + assertEquals( val, res ); + } + + cursor.close(); + + // Last, let's browse with no key, we should get no values + cursor = btree.browse(); + + pos = 0; + + assertFalse( cursor.hasPrev() ); + + cursor.close(); + btree.close(); + } + + + /** + * Test a browse over an empty tree + */ + @Test + public void testBrowseEmptyTree() throws Exception + { + // Create a BTree with pages containing 8 elements + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + + TupleCursor cursor = btree.browse(); + + assertFalse( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + cursor.close(); + btree.close(); + } + + + /** + * Test a browse forward and backward + */ + @Test + public void testBrowseForwardBackward() throws Exception + { + // Create a BTree with pages containing 4 elements + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( int i = 0; i < 16; i++ ) + { + String strValue = "V" + i; + btree.insert( i, strValue ); + } + + // Start to browse in the middle + TupleCursor cursor = btree.browseFrom( 8 ); + + assertTrue( cursor.hasNext() ); + + // Get 8 + assertEquals( 8, cursor.next().getKey().intValue() ); + + // get 9 + assertEquals( 9, cursor.next().getKey().intValue() ); + + // get 10 + assertEquals( 10, cursor.next().getKey().intValue() ); + + // get 11 + assertEquals( 11, cursor.next().getKey().intValue() ); + + // get 12 (now, we must have gone through at least 2 pages) + assertEquals( 12, cursor.next().getKey().intValue() ); + + assertTrue( cursor.hasPrev() ); + + // Lets go backward. + assertEquals( 11, cursor.prev().getKey().intValue() ); + + // Get 10 + assertEquals( 10, cursor.prev().getKey().intValue() ); + + // Get 9 + assertEquals( 9, cursor.prev().getKey().intValue() ); + + // Get 8 + assertEquals( 8, cursor.prev().getKey().intValue() ); + + // Get 7 + assertEquals( 7, cursor.prev().getKey().intValue() ); + + cursor.close(); + btree.close(); + } + + + /** + * Test various deletions in a tree, when we have full leaves + */ + @Test + public void testDeleteFromFullLeaves() throws Exception + { + // Create a BTree with pages containing 4 elements + BTree btree = createTwoLevelBTreeFullLeaves(); + + // Test removals leadings to various RemoveResult. + // The tree remains the same after the deletion + // First, no borrow nor merge + btree.delete( 1 ); + + checkNull( btree, 1 ); + + btree.insert( 1, "V1" ); + + btree.delete( 3 ); + + checkNull( btree, 3 ); + + btree.insert( 3, "V3" ); + + btree.delete( 4 ); + + checkNull( btree, 4 ); + + btree.insert( 4, "V4" ); + + btree.delete( 11 ); + + checkNull( btree, 11 ); + + btree.insert( 11, "V11" ); + + btree.delete( 20 ); + + checkNull( btree, 20 ); + + btree.insert( 20, "V20" ); + + btree.delete( 0 ); + + checkNull( btree, 0 ); + + btree.delete( 5 ); + + checkNull( btree, 5 ); + + btree.delete( 9 ); + + checkNull( btree, 9 ); + + btree.close(); + } + + + /** + * Test the exist() method + */ + @Test + public void testExist() throws IOException, KeyNotFoundException + { + // Create a BTree with pages containing 4 elements + BTree btree = createTwoLevelBTreeFullLeaves(); + + for ( int i = 1; i < 21; i++ ) + { + assertTrue( btree.hasKey( 5 ) ); + } + + assertFalse( btree.hasKey( 0 ) ); + assertFalse( btree.hasKey( 21 ) ); + + btree.close(); + } + + + /** + * Test various deletions in a tree, leadings to borrowFromSibling + */ + @Test + public void testDeleteBorrowFromSibling() throws Exception + { + // Create a BTree with pages containing 4 elements + BTree btree = createTwoLevelBTreeFullLeaves(); + + // Delete some useless elements to simulate the tree we want to test + // Make the left leaf to contain N/2 elements + btree.delete( 3 ); + btree.delete( 4 ); + + // Make the right leaf to contain N/2 elements + btree.delete( 19 ); + btree.delete( 20 ); + + // Make the middle leaf to contain N/2 elements + btree.delete( 11 ); + btree.delete( 12 ); + + // Delete the leftmost key + btree.delete( 1 ); + + checkNull( btree, 1 ); + + // Delete the rightmost key + btree.delete( 18 ); + + checkNull( btree, 18 ); + + // Delete one element in the left page, but not the first one + btree.delete( 5 ); + + checkNull( btree, 5 ); + + // Delete the one element in the right page, but the first one + btree.delete( 16 ); + + checkNull( btree, 16 ); + + btree.close(); + + // Now do that with a deeper btree + btree = createMultiLevelBTreeLeavesHalfFull(); + + // Add some more elements on the second leaf before deleting some elements in the first leaf + btree.insert( 8, "V8" ); + btree.insert( 9, "V9" ); + + // and delete some + btree.delete( 2 ); + + checkNull( btree, 2 ); + + btree.delete( 6 ); + + checkNull( btree, 6 ); + + // Add some more elements on the pre-last leaf before deleting some elements in the last leaf + btree.insert( 96, "V96" ); + btree.insert( 97, "V97" ); + + // and delete some + btree.delete( 98 ); + + checkNull( btree, 98 ); + + btree.delete( 99 ); + + checkNull( btree, 99 ); + + // Now try to delete elements in the middle + btree.insert( 48, "V48" ); + + btree.delete( 42 ); + + checkNull( btree, 42 ); + + btree.insert( 72, "V72" ); + + btree.delete( 67 ); + + checkNull( btree, 67 ); + + btree.close(); + } + + + /** + * Test the browse method with a non existing key + * @throws Exception + */ + @Test + public void testBrowseNonExistingKey() throws Exception + { + // Create a BTree with pages containing 8 elements + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + for ( int i = 0; i < 11; i++ ) + { + btree.insert( i, String.valueOf( i ) ); + } + + for ( int i = 0; i < 11; i++ ) + { + assertNotNull( btree.get( i ) ); + } + + assertTrue( btree.hasKey( 8 ) ); + assertFalse( btree.hasKey( 11 ) ); + + TupleCursor cursor = btree.browseFrom( 11 ); + assertFalse( cursor.hasNext() ); + + btree.close(); + } + + + private Page createLeaf( BTree btree, long revision, + Tuple... tuples ) + { + InMemoryLeaf leaf = new InMemoryLeaf( btree ); + int pos = 0; + leaf.setRevision( revision ); + leaf.setNbElems( tuples.length ); + leaf.setKeys( new KeyHolder[leaf.getNbElems()] ); + leaf.values = ( InMemoryValueHolder[] ) Array + .newInstance( InMemoryValueHolder.class, leaf.getNbElems() ); + + for ( Tuple tuple : tuples ) + { + leaf.setKey( pos, new KeyHolder( tuple.getKey() ) ); + leaf.values[pos] = new InMemoryValueHolder( btree, tuple.getValue() ); + pos++; + } + + return leaf; + } + + + private void addPage( BTree btree, InMemoryNode node, Page page, + int pos ) + throws EndOfFileExceededException, IOException + { + Tuple leftmost = page.findLeftMost(); + + if ( pos > 0 ) + { + node.setKey( pos - 1, new KeyHolder( leftmost.getKey() ) ); + } + + node.setPageHolder( pos, new PageHolder( btree, page ) ); + } + + + /** + * Creates a 2 level depth tree of full pages + */ + private BTree createTwoLevelBTreeFullLeaves() throws IOException + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + // Create a tree with 5 children containing 4 elements each. The tree is full. + int[] keys = new int[] + { 1, 2, 5, 6, 3, 4, 9, 10, 7, 8, 13, 14, 11, 12, 17, 18, 15, 16, 19, 20 }; + + for ( int key : keys ) + { + String value = "V" + key; + btree.insert( key, value ); + } + + return btree; + } + + + /** + * Creates a 2 level depth tree of half full pages + */ + private BTree createTwoLevelBTreeHalfFullLeaves() throws IOException + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + // Create a tree with 5 children containing 4 elements each. The tree is full. + int[] keys = new int[] + { 1, 2, 17, 18, 13, 14, 9, 10, 5, 6, 3 }; + + for ( int key : keys ) + { + String value = "V" + key; + btree.insert( key, value ); + } + + // Regulate the tree by removing the last value added, so that all the leaves have only 2 elements + btree.delete( 3 ); + + return btree; + } + + /** A set used to check that the tree contains the contained elements */ + private Set EXPECTED1 = new HashSet(); + + + /** + * Creates a 3 level depth tree, with each page containing only N/2 elements + */ + private BTree createMultiLevelBTreeLeavesHalfFull() throws IOException + { + // Create a BTree with pages containing 4 elements + int pageSize = 4; + + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE, + pageSize ); + + InMemoryNode root = new InMemoryNode( btree, 1L, pageSize ); + + // Create the tree with 3 levels, all the leaves containing only N/2 elements + int counter = 1; + for ( int i = 0; i < pageSize + 1; i++ ) + { + InMemoryNode node = new InMemoryNode( btree, 1L, pageSize ); + + for ( int j = 0; j < pageSize + 1; j++ ) + { + int even = counter * 2; + + @SuppressWarnings("unchecked") + Page leaf = createLeaf( + btree, + 1L, + new Tuple( even, "v" + even ), + new Tuple( even + 1, "v" + ( even + 1 ) ) + ); + + counter += 2; + + addPage( btree, node, leaf, j ); + + EXPECTED1.add( even ); + EXPECTED1.add( even + 1 ); + } + + addPage( btree, root, node, i ); + } + + ( ( AbstractBTree ) btree ).setRootPage( root ); + + return btree; + } + + + /** + * Remove an element from the tree, checking that the removal was successful + * @param btree The btree on which we remove an element + * @param element The removed element + * @param expected The expected set of elements + */ + private void checkRemoval( BTree btree, int element, Set expected ) throws IOException, + KeyNotFoundException + { + Tuple removed = btree.delete( element ); + assertEquals( element, removed.getKey().intValue() ); + assertEquals( "v" + element, removed.getValue() ); + + checkNull( btree, element ); + + expected.remove( element ); + checkTree( btree, expected ); + } + + + /** + * Check that the tree contains all the elements in the expected set, and that + * all the elements in the tree are also present in the set + * + * @param btree The tree to check + * @param expected The set with the expected elements + */ + private void checkTree( BTree btree, Set expected ) throws KeyNotFoundException + { + try + { + TupleCursor cursor = btree.browse(); + Integer value = null; + + while ( cursor.hasNext() ) + { + Tuple tuple = cursor.next(); + + if ( value == null ) + { + value = tuple.getKey(); + } + else + { + assertTrue( value < tuple.getKey() ); + value = tuple.getKey(); + } + + assertTrue( expected.contains( value ) ); + expected.remove( value ); + } + + assertEquals( 0, expected.size() ); + } + catch ( IOException ioe ) + { + fail(); + } + } + + + /** + * Remove a set of values from a btree + * + * @param btree The modified btree + * @param expected The set of expected values to update + * @param values The values to remove + */ + private void delete( BTree btree, Set expected, int... values ) throws IOException + { + for ( int value : values ) + { + btree.delete( value ); + expected.remove( value ); + } + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will generate a merge in the leaves. + */ + @Test + public void testDeleteMultiLevelsLeadingToLeafMerge() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // Case 1 : delete the leftmost element in the btree in the leftmost leaf + Tuple removed = btree.delete( 2 ); + assertEquals( 2, removed.getKey().intValue() ); + assertEquals( "v2", removed.getValue() ); + checkNull( btree, 2 ); + + // delete the third element in the first leaf + removed = btree.delete( 7 ); + assertEquals( 7, removed.getKey().intValue() ); + assertEquals( "v7", removed.getValue() ); + checkNull( btree, 7 ); + + // Case 2 : Delete the second element in the leftmost leaf + removed = btree.delete( 6 ); + assertEquals( 6, removed.getKey().intValue() ); + assertEquals( "v6", removed.getValue() ); + checkNull( btree, 6 ); + + // delete the third element in the first leaf + removed = btree.delete( 11 ); + assertEquals( 11, removed.getKey().intValue() ); + assertEquals( "v11", removed.getValue() ); + checkNull( btree, 11 ); + + // Case 3 : delete the rightmost element in the btree in the rightmost leaf + removed = btree.delete( 99 ); + assertEquals( 99, removed.getKey().intValue() ); + assertEquals( "v99", removed.getValue() ); + checkNull( btree, 99 ); + + // delete the third element in the last leaf + removed = btree.delete( 98 ); + assertEquals( 98, removed.getKey().intValue() ); + assertEquals( "v98", removed.getValue() ); + checkNull( btree, 98 ); + + // Case 2 : Delete the first element in the rightmost leaf + removed = btree.delete( 94 ); + assertEquals( 94, removed.getKey().intValue() ); + assertEquals( "v94", removed.getValue() ); + checkNull( btree, 94 ); + + // delete the third element in the last leaf + removed = btree.delete( 95 ); + assertEquals( 95, removed.getKey().intValue() ); + assertEquals( "v95", removed.getValue() ); + checkNull( btree, 95 ); + + // Case 5 : delete the leftmost element which is referred in the root node + removed = btree.delete( 22 ); + assertEquals( 22, removed.getKey().intValue() ); + assertEquals( "v22", removed.getValue() ); + checkNull( btree, 22 ); + + // delete the third element in the last leaf + removed = btree.delete( 27 ); + assertEquals( 27, removed.getKey().intValue() ); + assertEquals( "v27", removed.getValue() ); + checkNull( btree, 27 ); + + // Case 6 : delete the leftmost element in a leaf in the middle of the tree + removed = btree.delete( 70 ); + assertEquals( 70, removed.getKey().intValue() ); + assertEquals( "v70", removed.getValue() ); + checkNull( btree, 70 ); + + // delete the third element in the leaf + removed = btree.delete( 71 ); + assertEquals( 71, removed.getKey().intValue() ); + assertEquals( "v71", removed.getValue() ); + checkNull( btree, 71 ); + + // Case 7 : delete the rightmost element in a leaf in the middle of the tree + removed = btree.delete( 51 ); + assertEquals( 51, removed.getKey().intValue() ); + assertEquals( "v51", removed.getValue() ); + checkNull( btree, 51 ); + + // delete the third element in the leaf + removed = btree.delete( 50 ); + assertEquals( 50, removed.getKey().intValue() ); + assertEquals( "v50", removed.getValue() ); + checkNull( btree, 50 ); + + btree.close(); + } + + + /** + * Test various deletions in a two level high tree, when we have leaves + * containing N/2 elements (thus each deletion leads to a merge) + */ + @Test + public void testDelete2LevelsTreeWithHalfFullLeaves() throws Exception + { + // Create a BTree with pages containing 4 elements + BTree btree = createTwoLevelBTreeHalfFullLeaves(); + + // Test removals leadings to various merges. + // Delete from the middle, not the leftmost value of the leaf + btree.delete( 10 ); + checkNull( btree, 10 ); + + // Delete the extraneous value + btree.delete( 9 ); + checkNull( btree, 9 ); + + // Delete the leftmost element in the middle + btree.delete( 13 ); + checkNull( btree, 13 ); + + // Delete the extraneous value + btree.delete( 14 ); + checkNull( btree, 14 ); + + // Delete the rightmost value + btree.delete( 18 ); + checkNull( btree, 18 ); + + // Delete the extraneous value + btree.delete( 5 ); + checkNull( btree, 5 ); + + // Delete the leftmost value of the right leaf + btree.delete( 6 ); + checkNull( btree, 6 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 1: remove the leftmost element + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowRight1() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 2, 3, 6, 7 ); + + // delete the element + checkRemoval( btree, 10, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 2: remove an element on the leftmost page but not the first one + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowRight2() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 2, 3, 6, 7 ); + + // delete the element + checkRemoval( btree, 11, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 3: remove an element on the rightmost page on the leftmost node on the upper level + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowRight3() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 2, 3, 6, 7 ); + + // delete the element + checkRemoval( btree, 19, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 4: remove the first element in a page in the middle of the first node + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowRight4() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 2, 3, 6, 7 ); + + // delete the element + checkRemoval( btree, 14, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 5: remove the second element in a page in the middle of the first node + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowRight5() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 2, 3, 6, 7 ); + + // delete the element + checkRemoval( btree, 15, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 1: remove the rightmost element + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft1() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 94, 95, 98, 99 ); + + // delete the element + checkRemoval( btree, 91, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 1: remove the element before the rightmost element + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft2() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 94, 95, 98, 99 ); + + // delete the element + checkRemoval( btree, 90, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 1: remove the leftmost element of the rightmost leaf + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft3() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 94, 95, 98, 99 ); + + // delete the element + checkRemoval( btree, 82, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 1: remove the second elemnt of the leftmost page on the rightmost second level node + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft4() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 94, 95, 98, 99 ); + + // delete the element + checkRemoval( btree, 83, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 6: remove the first element of a leaf in the middle of the tree + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft6() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 42, 43, 46, 47 ); + + // delete + checkRemoval( btree, 50, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 7: remove the second element of a leaf in the middle of the tree + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft7() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 42, 43, 46, 47 ); + + // delete + checkRemoval( btree, 51, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 8: remove the last element of a leaf in the middle of the tree + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft8() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 42, 43, 46, 47 ); + + // delete + checkRemoval( btree, 59, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 9: remove the element before the last one of a leaf in the middle of the tree + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft9() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 42, 43, 46, 47 ); + + // delete + checkRemoval( btree, 58, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 10: remove the mid element of a leaf in the middle of the tree + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft10() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 42, 43, 46, 47 ); + + // delete + checkRemoval( btree, 54, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test deletions in a tree with more than one level. We are specifically testing + * the deletions that will make a node borrowing some element from a sibling. + * + * 11: remove the mid+1 element of a leaf in the middle of the tree + */ + @Test + public void testDeleteMultiLevelsLeadingToNodeBorrowLeft11() throws Exception + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // deleting as many elements as necessary to get the node ready for a merge + delete( btree, EXPECTED1, 42, 43, 46, 47 ); + + // delete + checkRemoval( btree, 55, EXPECTED1 ); + + btree.close(); + } + + + /** + * Test the addition of elements with null values + */ + @Test + public void testAdditionNullValues() throws IOException, KeyNotFoundException + { + BTree btree = createMultiLevelBTreeLeavesHalfFull(); + + // Adding an element with a null value + btree.insert( 100, null ); + + assertTrue( btree.hasKey( 100 ) ); + + try + { + assertNull( btree.get( 100 ) ); + } + catch ( KeyNotFoundException knfe ) + { + fail(); + } + + Tuple deleted = btree.delete( 100 ); + + assertNotNull( deleted ); + assertNull( deleted.getValue() ); + + btree.close(); + } + + + /** + * Test the insertion of 5 million elements in a BTree + * @throws Exception + */ + @Test + public void testBrowse500K() throws Exception + { + Random random = new Random( System.nanoTime() ); + + int nbError = 0; + + int n = 0; + int nbElems = 500000; + long delta = System.currentTimeMillis(); + + // Create a BTree with 5 million entries + BTree btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 32 ); + + for ( int i = 0; i < nbElems; i++ ) + { + Long key = ( long ) random.nextLong(); + String value = Long.toString( key ); + + try + { + btree.insert( key, value ); + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while adding " + value ); + nbError++; + btree.close(); + + return; + } + + if ( i % 100000 == 0 ) + { + if ( n > 0 ) + { + long t0 = System.currentTimeMillis(); + System.out.println( "Delta" + n + ": " + ( t0 - delta ) ); + delta = t0; + } + + n++; + } + } + + // Now browse them + long l1 = System.currentTimeMillis(); + + TupleCursor cursor = btree.browse(); + + int nb = 0; + long elem = Long.MIN_VALUE; + + while ( cursor.hasNext() ) + { + Tuple res = cursor.next(); + + if ( res.getKey() > elem ) + { + elem = res.getKey(); + nb++; + } + } + + System.out.println( "Nb elements read : " + nb ); + + cursor.close(); + btree.close(); + + long l2 = System.currentTimeMillis(); + + System.out.println( "Delta : " + ( l2 - l1 ) + ", nbError = " + nbError + + ", Nb searches per second : " + ( ( nbElems * 1000 ) / ( l2 - l1 ) ) ); + } + + + private void checkNull( BTree btree, long key ) throws IOException + { + try + { + btree.get( key ); + fail(); + } + catch ( KeyNotFoundException knfe ) + { + // expected + } + } + + + private void checkNull( BTree btree, int key ) throws IOException + { + try + { + btree.get( key ); + fail(); + } + catch ( KeyNotFoundException knfe ) + { + // expected + } + } + + + /** + * Test a browse forward and backward + */ + @Test + public void testBrowseForwardBackwardExtremes() throws Exception + { + // Create a BTree with pages containing 4 elements + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + StringSerializer.INSTANCE ); + btree.setPageSize( 4 ); + + for ( int i = 8; i < 13; i++ ) + { + String strValue = "V" + i; + btree.insert( i, strValue ); + } + + // Start to browse in the middle + TupleCursor cursor = btree.browseFrom( 8 ); + + assertTrue( cursor.hasNext() ); + + // Get 8 + assertEquals( 8, cursor.next().getKey().intValue() ); + + // get 9 + assertEquals( 9, cursor.next().getKey().intValue() ); + + // get 10 + assertEquals( 10, cursor.next().getKey().intValue() ); + + // get 11 + assertEquals( 11, cursor.next().getKey().intValue() ); + + // get 12 (now, we must have gone through at least 2 pages) + assertEquals( 12, cursor.next().getKey().intValue() ); + + assertFalse( cursor.hasNext() ); + assertTrue( cursor.hasPrev() ); + + // Lets go backward. + assertEquals( 11, cursor.prev().getKey().intValue() ); + + // Get 10 + assertEquals( 10, cursor.prev().getKey().intValue() ); + + // Get 9 + assertEquals( 9, cursor.prev().getKey().intValue() ); + + // Get 8 + assertEquals( 8, cursor.prev().getKey().intValue() ); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + cursor.close(); + btree.close(); + } + + + @Test + public void testNextAfterPrev() throws Exception + { + IntSerializer serializer = IntSerializer.INSTANCE; + + InMemoryBTreeConfiguration config = new InMemoryBTreeConfiguration(); + config.setName( "master" ); + config.setPageSize( 4 ); + config.setSerializers( serializer, serializer ); + BTree btree = new InMemoryBTree( config ); + + int i = 7; + for ( int k = 0; k < i; k++ ) + { + btree.insert( k, k ); + } + + // 3 is the last element of the first leaf + TupleCursor cursor = btree.browseFrom( 4 ); + + assertTrue( cursor.hasNext() ); + Tuple tuple = cursor.next(); + assertEquals( Integer.valueOf( 4 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 4 ), tuple.getValue() ); + + assertTrue( cursor.hasPrev() ); + tuple = cursor.prev(); + assertEquals( Integer.valueOf( 3 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 3 ), tuple.getValue() ); + + assertTrue( cursor.hasNext() ); + tuple = cursor.next(); + assertEquals( Integer.valueOf( 4 ), tuple.getKey() ); + assertEquals( Integer.valueOf( 4 ), tuple.getValue() ); + + cursor.close(); + btree.close(); + } + + + @Test + public void testCheckRootPageContents() throws Exception + { + IntSerializer ser = IntSerializer.INSTANCE; + BTree btree = BTreeFactory.createInMemoryBTree( "master1", ser, ser, 4 ); + + for ( int i = 1; i < 8; i++ ) + { + btree.insert( i, i ); + } + + System.out.println( btree.getRootPage() ); + assertEquals( 2, btree.getRootPage().getNbElems() ); + + assertEquals( 7, btree.getRootPage().findRightMost().getKey().intValue() ); + + assertEquals( 1, btree.getRootPage().findLeftMost().getKey().intValue() ); + + btree.close(); + } + + + /** + * Test the overwriting of elements + */ + @Test + public void testOverwrite() throws Exception + { + BTree btree = BTreeFactory.createInMemoryBTree( "test", IntSerializer.INSTANCE, + IntSerializer.INSTANCE ); + + // Adding an element with a null value + btree.insert( 1, 1 ); + + assertTrue( btree.hasKey( 1 ) ); + + assertEquals( Integer.valueOf( 1 ), btree.get( 1 ) ); + + btree.insert( 1, 10 ); + + assertTrue( btree.hasKey( 1 ) ); + assertEquals( Integer.valueOf( 10 ), btree.get( 1 ) ); + + btree.close(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeTestOps.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeTestOps.java new file mode 100644 index 000000000..c887c7118 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeTestOps.java @@ -0,0 +1,115 @@ +package org.apache.directory.mavibot.btree; + + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +import java.io.IOException; +import java.util.Random; + +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + + +/** + * A class to test multi-threaded operations on the btree + * + * @author Apache Directory Project + */ +public class InMemoryBTreeTestOps +{ + /** The btree we use */ + private static BTree btree; + + + /** + * Create the btree once + * @throws IOException If the creation failed + */ + @BeforeClass + public static void setup() throws IOException + { + btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE ); + } + + + /** + * Close the btree + */ + @AfterClass + public static void shutdown() throws IOException + { + btree.close(); + } + + + /** + * Create a btree with 500 000 elements in it + * @throws IOException If the creation failed + */ + private void createTree() throws IOException + { + Random random = new Random( System.nanoTime() ); + + int nbElems = 50000; + + // Create a BTree with 500 000 entries + btree.setPageSize( 32 ); + for ( int i = 0; i < nbElems; i++ ) + { + Long key = ( long ) random.nextLong(); + String value = Long.toString( key ); + + try + { + btree.insert( key, value ); + + if ( i % 100000 == 0 ) + { + System.out.println( "Written " + i + " elements" ); + } + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while adding " + value ); + return; + } + } + + } + + + @Test + public void testCreateTree() throws InterruptedException, IOException + { + + long t0 = System.currentTimeMillis(); + + // Start the writer + createTree(); + long t1 = System.currentTimeMillis(); + System.out.println( "Time to create a tree with 500 000 elements in memory:" + ( ( t1 - t0 ) ) + + " milliseconds" ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBulkDataSorterTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBulkDataSorterTest.java new file mode 100644 index 000000000..d337b228a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBulkDataSorterTest.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Random; + +import org.apache.directory.mavibot.btree.Tuple; +import org.apache.directory.mavibot.btree.memory.BulkDataSorter; +import org.apache.directory.mavibot.btree.util.IntTupleReaderWriter; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * Test cases for BulkDataSorter. + * + * @author Apache Directory Project + */ +public class InMemoryBulkDataSorterTest +{ + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + /* A counter of tuple files */ + private int counter = 0; + + private Comparator> tupleComp = new Comparator>() + { + + @Override + public int compare( Tuple o1, Tuple o2 ) + { + return o1.getKey().compareTo( o2.getKey() ); + } + }; + + + @Test + public void testSortedFileCount() throws IOException + { + int count = 7; + IntTupleReaderWriter itrw = new IntTupleReaderWriter(); + Random random = new Random(); + + File dataFile = tempFolder.newFile( "tuple.data" ); + dataFile.deleteOnExit(); + DataOutputStream out = new DataOutputStream( new FileOutputStream( dataFile ) ); + + Tuple[] arr = ( Tuple[] ) Array.newInstance( Tuple.class, count ); + + for ( int i = 0; i < count; i++ ) + { + int x = random.nextInt( 100 ); + + Tuple t = new Tuple( x, x ); + + arr[i] = t; + + itrw.storeSortedTuple( t, out ); + } + + out.close(); + + BulkDataSorter bds = new BulkDataSorter( itrw, tupleComp, 4 ); + bds.sort( dataFile ); + + assertEquals( 2, bds.getWorkDir().list().length ); + + deleteDir( bds.getWorkDir() ); + } + + + @Test + public void testSortedFileMerge() throws IOException + { + testSortedFileMerge( 10, 2 ); + testSortedFileMerge( 100, 7 ); + testSortedFileMerge( 1000, 25 ); + testSortedFileMerge( 10000, 100 ); + testSortedFileMerge( 10000, 101 ); + testSortedFileMerge( 100000, 501 ); + } + + + private void testSortedFileMerge( int count, int splitAfter ) throws IOException + { + IntTupleReaderWriter itrw = new IntTupleReaderWriter(); + Random random = new Random(); + + File dataFile = tempFolder.newFile( "tuple.data" + counter ); + counter++; + dataFile.deleteOnExit(); + + DataOutputStream out = new DataOutputStream( new FileOutputStream( dataFile ) ); + + Tuple[] arr = ( Tuple[] ) Array.newInstance( Tuple.class, count ); + + int randUpper = count; + if ( count < 100 ) + { + randUpper = 100; + } + + for ( int i = 0; i < count; i++ ) + { + int x = random.nextInt( randUpper ); + + Tuple t = new Tuple( x, x ); + + arr[i] = t; + + itrw.storeSortedTuple( t, out ); + } + + out.close(); + + BulkDataSorter bds = new BulkDataSorter( itrw, tupleComp, splitAfter ); + bds.sort( dataFile ); + + Iterator> itr = bds.getMergeSortedTuples(); + + Integer prev = null; + + while ( itr.hasNext() ) + { + Tuple t = itr.next(); + + if ( prev == null ) + { + prev = t.getKey(); + } + else + { + assertTrue( prev <= t.getKey() ); + } + } + + deleteDir( bds.getWorkDir() ); + } + + + private void deleteDir( File dir ) + { + if ( dir.isFile() ) + { + dir.delete(); + } + + File[] files = dir.listFiles(); + + for ( File f : files ) + { + f.delete(); + } + + dir.delete(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryLeafTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryLeafTest.java new file mode 100644 index 000000000..2f6786e7c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryLeafTest.java @@ -0,0 +1,446 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + +/** + * A unit test class for Leaf + * + * @author Apache Directory Project + */ +public class InMemoryLeafTest +{ + private BTree btree = null; + + + /** + * Create a btree + */ + @Before + public void setup() throws IOException + { + btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE ); + btree.setPageSize( 8 ); + } + + + @After + public void shutdown() throws IOException + { + btree.close(); + } + + + /** + * A helper method to insert elements in a Leaf + * @throws IOException + */ + private InMemoryLeaf insert( InMemoryLeaf leaf, long key, String value ) + throws IOException + { + InsertResult result = leaf.insert( key, value, 1L ); + + return ( InMemoryLeaf ) ( ( ModifyResult ) result ).getModifiedPage(); + } + + + /** + * Test that deleting an entry from an empty page returns a NOT_PRESENT result + * @throws IOException + */ + @Test + public void testDeleteFromEmptyLeaf() throws IOException + { + InMemoryLeaf leaf = new InMemoryLeaf( btree ); + + DeleteResult result = leaf.delete( 1L, null, 1L, null, -1 ); + + assertEquals( NotPresentResult.NOT_PRESENT, result ); + } + + + /** + * Test that deleting an entry which is not present in the leaf works + * @throws IOException + */ + @Test + public void testDeleteNotPresentElementFromRootLeaf() throws IOException + { + InMemoryLeaf leaf = new InMemoryLeaf( btree ); + leaf = insert( leaf, 1L, "v1" ); + leaf = insert( leaf, 2L, "v2" ); + leaf = insert( leaf, 3L, "v3" ); + leaf = insert( leaf, 4L, "v4" ); + + DeleteResult result = leaf.delete( 5L, null, 2L, null, -1 ); + + assertEquals( NotPresentResult.NOT_PRESENT, result ); + } + + + /** + * Test that deleting an entry which is present in the leaf works + * @throws IOException + */ + @Test + public void testDeletePresentElementFromRootLeaf() throws IOException + { + InMemoryLeaf leaf = new InMemoryLeaf( btree ); + leaf = insert( leaf, 1L, "v1" ); + leaf = insert( leaf, 2L, "v2" ); + leaf = insert( leaf, 3L, "v3" ); + leaf = insert( leaf, 4L, "v4" ); + + DeleteResult result = leaf.delete( 3L, null, 4L, null, -1 ); + + assertTrue( result instanceof RemoveResult ); + + Tuple removedElement = ( ( RemoveResult ) result ).getRemovedElement(); + Page newLeaf = ( ( RemoveResult ) result ).getModifiedPage(); + + assertEquals( Long.valueOf( 3L ), removedElement.getKey() ); + assertEquals( "v3", removedElement.getValue() ); + assertEquals( 3, newLeaf.getNbElems() ); + + try + { + assertEquals( "v1", newLeaf.get( 1L ) ); + assertEquals( "v2", newLeaf.get( 2L ) ); + assertEquals( "v4", newLeaf.get( 4L ) ); + } + catch ( KeyNotFoundException knfe ) + { + fail(); + } + + try + { + newLeaf.get( 3L ); + fail(); + } + catch ( KeyNotFoundException knfe ) + { + // Expected + } + } + + + /** + * Test that deleting the first element return the correct result + * @throws IOException + */ + @Test + public void testDeleteFirstElementFromRootLeaf() throws IOException + { + InMemoryLeaf leaf = new InMemoryLeaf( btree ); + leaf = insert( leaf, 1L, "v1" ); + leaf = insert( leaf, 2L, "v2" ); + leaf = insert( leaf, 3L, "v3" ); + leaf = insert( leaf, 4L, "v4" ); + + DeleteResult result = leaf.delete( 1L, null, 4L, null, -1 ); + + assertTrue( result instanceof RemoveResult ); + + RemoveResult removeResult = ( RemoveResult ) result; + + Tuple removedElement = removeResult.getRemovedElement(); + Page newLeaf = removeResult.getModifiedPage(); + + assertEquals( Long.valueOf( 1L ), removedElement.getKey() ); + assertEquals( "v1", removedElement.getValue() ); + assertEquals( 3, newLeaf.getNbElems() ); + + try + { + newLeaf.get( 1L ); + fail(); + } + catch ( KeyNotFoundException knfe ) + { + // expected + } + + try + { + assertEquals( "v2", newLeaf.get( 2L ) ); + assertEquals( "v3", newLeaf.get( 3L ) ); + assertEquals( "v4", newLeaf.get( 4L ) ); + } + catch ( KeyNotFoundException knfe ) + { + fail(); + } + } + + + /** + * Check that deleting an element from a leaf with N/2 element works when we borrow + * an element in a left page with more than N/2 elements. + * The BTree contains : + * +--[1, 2, 3, 4, 5] + * [6, 10]-+--[6, 7, 8, 9] + * +--[10, 11, 12, 13] + * @throws IOException + */ + @Test + public void testDeleteBorrowingFromLeftSibling() throws IOException + { + InMemoryNode parent = new InMemoryNode( btree, 1L, 2 ); + InMemoryLeaf left = new InMemoryLeaf( btree ); + InMemoryLeaf target = new InMemoryLeaf( btree ); + InMemoryLeaf right = new InMemoryLeaf( btree ); + + // Fill the left page + left = insert( left, 1L, "v1" ); + left = insert( left, 2L, "v2" ); + left = insert( left, 3L, "v3" ); + left = insert( left, 4L, "v4" ); + left = insert( left, 5L, "v5" ); + + // Fill the target page + target = insert( target, 6L, "v6" ); + target = insert( target, 7L, "v7" ); + target = insert( target, 8L, "v8" ); + target = insert( target, 9L, "v9" ); + + // Fill the right page + right = insert( right, 10L, "v10" ); + right = insert( right, 11L, "v11" ); + right = insert( right, 12L, "v12" ); + right = insert( right, 13L, "v13" ); + + parent.setPageHolder( 0, new PageHolder( btree, left ) ); + parent.setPageHolder( 1, new PageHolder( btree, target ) ); + parent.setPageHolder( 2, new PageHolder( btree, right ) ); + + // Update the parent + parent.setKey( 0, new KeyHolder( 6L ) ); + parent.setKey( 1, new KeyHolder( 10L ) ); + + // Now, delete the element from the target page + DeleteResult result = target.delete( 7L, null, 2L, parent, 1 ); + + assertTrue( result instanceof BorrowedFromLeftResult ); + + BorrowedFromLeftResult borrowed = ( BorrowedFromLeftResult ) result; + Tuple removedKey = borrowed.getRemovedElement(); + + assertEquals( Long.valueOf( 7L ), removedKey.getKey() ); + + // Check the modified leaf + InMemoryLeaf newLeaf = ( InMemoryLeaf ) borrowed.getModifiedPage(); + + assertEquals( 4, newLeaf.getNbElems() ); + assertEquals( Long.valueOf( 5L ), newLeaf.getKey( 0 ) ); + assertEquals( Long.valueOf( 6L ), newLeaf.getKey( 1 ) ); + assertEquals( Long.valueOf( 8L ), newLeaf.getKey( 2 ) ); + assertEquals( Long.valueOf( 9L ), newLeaf.getKey( 3 ) ); + + // Check the sibling + InMemoryLeaf leftSibling = ( InMemoryLeaf ) borrowed.getModifiedSibling(); + + assertEquals( 4, leftSibling.getNbElems() ); + assertEquals( Long.valueOf( 1L ), leftSibling.getKey( 0 ) ); + assertEquals( Long.valueOf( 2L ), leftSibling.getKey( 1 ) ); + assertEquals( Long.valueOf( 3L ), leftSibling.getKey( 2 ) ); + assertEquals( Long.valueOf( 4L ), leftSibling.getKey( 3 ) ); + } + + + /** + * Check that deleting an element from a leaf with N/2 element works when we borrow + * an element in a right page with more than N/2 elements + * @throws IOException + */ + @Test + public void testDeleteBorrowingFromRightSibling() throws IOException + { + InMemoryNode parent = new InMemoryNode( btree, 1L, 2 ); + InMemoryLeaf left = new InMemoryLeaf( btree ); + InMemoryLeaf target = new InMemoryLeaf( btree ); + InMemoryLeaf right = new InMemoryLeaf( btree ); + + // Fill the left page + left = insert( left, 1L, "v1" ); + left = insert( left, 2L, "v2" ); + left = insert( left, 3L, "v3" ); + left = insert( left, 4L, "v4" ); + + // Fill the target page + target = insert( target, 6L, "v6" ); + target = insert( target, 7L, "v7" ); + target = insert( target, 8L, "v8" ); + target = insert( target, 9L, "v9" ); + + // Fill the right page + right = insert( right, 10L, "v10" ); + right = insert( right, 11L, "v11" ); + right = insert( right, 12L, "v12" ); + right = insert( right, 13L, "v13" ); + right = insert( right, 14L, "v14" ); + + parent.setPageHolder( 0, new PageHolder( btree, left ) ); + parent.setPageHolder( 1, new PageHolder( btree, target ) ); + parent.setPageHolder( 2, new PageHolder( btree, right ) ); + + // Update the parent + parent.setKey( 0, new KeyHolder( 6L ) ); + parent.setKey( 1, new KeyHolder( 10L ) ); + + // Now, delete the element from the target page + DeleteResult result = target.delete( 7L, null, 2L, parent, 1 ); + + assertTrue( result instanceof BorrowedFromRightResult ); + + BorrowedFromRightResult borrowed = ( BorrowedFromRightResult ) result; + assertEquals( Long.valueOf( 11L ), borrowed.getModifiedSibling().getKey( 0 ) ); + Tuple removedKey = borrowed.getRemovedElement(); + + assertEquals( Long.valueOf( 7L ), removedKey.getKey() ); + + // Check the modified leaf + InMemoryLeaf newLeaf = ( InMemoryLeaf ) borrowed.getModifiedPage(); + + assertEquals( 4, newLeaf.getNbElems() ); + assertEquals( Long.valueOf( 6L ), newLeaf.getKey( 0 ) ); + assertEquals( Long.valueOf( 8L ), newLeaf.getKey( 1 ) ); + assertEquals( Long.valueOf( 9L ), newLeaf.getKey( 2 ) ); + assertEquals( Long.valueOf( 10L ), newLeaf.getKey( 3 ) ); + + // Check the sibling + InMemoryLeaf rightSibling = ( InMemoryLeaf ) borrowed.getModifiedSibling(); + + assertEquals( 4, rightSibling.getNbElems() ); + assertEquals( Long.valueOf( 11L ), rightSibling.getKey( 0 ) ); + assertEquals( Long.valueOf( 12L ), rightSibling.getKey( 1 ) ); + assertEquals( Long.valueOf( 13L ), rightSibling.getKey( 2 ) ); + assertEquals( Long.valueOf( 14L ), rightSibling.getKey( 3 ) ); + } + + + /** + * Check that deleting an element from a leaf with N/2 element works when we merge + * it with one of its sibling, if both has N/2 elements + * @throws IOException + */ + @Test + public void testDeleteMergeWithSibling() throws IOException + { + InMemoryNode parent = new InMemoryNode( btree, 1L, 2 ); + InMemoryLeaf left = new InMemoryLeaf( btree ); + InMemoryLeaf target = new InMemoryLeaf( btree ); + InMemoryLeaf right = new InMemoryLeaf( btree ); + + // Fill the left page + left = insert( left, 1L, "v1" ); + left = insert( left, 2L, "v2" ); + left = insert( left, 3L, "v3" ); + left = insert( left, 4L, "v4" ); + + // Fill the target page + target = insert( target, 5L, "v5" ); + target = insert( target, 6L, "v6" ); + target = insert( target, 7L, "v7" ); + target = insert( target, 8L, "v8" ); + + // Fill the right page + right = insert( right, 9L, "v9" ); + right = insert( right, 10L, "v10" ); + right = insert( right, 11L, "v11" ); + right = insert( right, 12L, "v12" ); + + parent.setPageHolder( 0, new PageHolder( btree, left ) ); + parent.setPageHolder( 1, new PageHolder( btree, target ) ); + parent.setPageHolder( 2, new PageHolder( btree, right ) ); + + // Update the parent + parent.setKey( 0, new KeyHolder( 5L ) ); + parent.setKey( 1, new KeyHolder( 9L ) ); + + // Now, delete the element from the target page + DeleteResult result = target.delete( 7L, null, 2L, parent, 1 ); + + assertTrue( result instanceof MergedWithSiblingResult ); + + MergedWithSiblingResult merged = ( MergedWithSiblingResult ) result; + Tuple removedKey = merged.getRemovedElement(); + + assertEquals( Long.valueOf( 7L ), removedKey.getKey() ); + + // Check the modified leaf + InMemoryLeaf newLeaf = ( InMemoryLeaf ) merged.getModifiedPage(); + + assertEquals( 7, newLeaf.getNbElems() ); + assertEquals( Long.valueOf( 1L ), newLeaf.getKey( 0 ) ); + assertEquals( Long.valueOf( 2L ), newLeaf.getKey( 1 ) ); + assertEquals( Long.valueOf( 3L ), newLeaf.getKey( 2 ) ); + assertEquals( Long.valueOf( 4L ), newLeaf.getKey( 3 ) ); + assertEquals( Long.valueOf( 5L ), newLeaf.getKey( 4 ) ); + assertEquals( Long.valueOf( 6L ), newLeaf.getKey( 5 ) ); + assertEquals( Long.valueOf( 8L ), newLeaf.getKey( 6 ) ); + } + + + /** + * Test the findPos() method + * @throws Exception + */ + @Test + public void testFindPos() throws Exception + { + InMemoryLeaf leaf = new InMemoryLeaf( btree ); + + // Inject the values + for ( long i = 0; i < 8; i++ ) + { + long value = i + i + 1; + leaf = ( InMemoryLeaf ) ( ( ModifyResult ) leaf.insert( value, "V" + value, 0L ) ) + .getModifiedPage(); + } + + // Check the findPos() method now + for ( long i = 0; i < 17; i++ ) + { + if ( i % 2 == 1 ) + { + assertEquals( -( i / 2 + 1 ), leaf.findPos( i ) ); + } + else + { + assertEquals( i / 2, leaf.findPos( i ) ); + } + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/MultiThreadedInMemoryBtreeTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/MultiThreadedInMemoryBtreeTest.java new file mode 100644 index 000000000..26e70b6af --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/MultiThreadedInMemoryBtreeTest.java @@ -0,0 +1,343 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.Ignore; + + +/** + * A class to test multi-threaded operations on the btree + * + * @author Apache Directory Project + */ +public class MultiThreadedInMemoryBtreeTest +{ + /** The btree we use */ + private static BTree btree; + + + /** + * Create the btree once + * @throws IOException If the creation failed + */ + @BeforeClass + public static void setup() throws IOException + { + btree = BTreeFactory.createInMemoryBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE ); + } + + + /** + * Close the btree + */ + @AfterClass + public static void shutdown() throws IOException + { + btree.close(); + } + + + /** + * Create a btree with 50 000 elements in it + * @throws IOException If the creation failed + */ + private void create50KBTree() throws IOException + { + Random random = new Random( System.nanoTime() ); + + int nbElems = 50000; + + // Create a BTree with 50 000 entries + btree.setPageSize( 32 ); + + for ( int i = 0; i < nbElems; i++ ) + { + Long key = ( long ) random.nextLong(); + String value = Long.toString( key ); + + try + { + btree.insert( key, value ); + + if ( i % 10000 == 0 ) + { + System.out.println( "Written " + i + " elements" ); + } + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( btree ); + System.out.println( "Error while adding " + value ); + return; + } + } + } + + + /** + * Browse the btree in its current revision, reading all of its elements + * @return The number of read elements + * @throws IOException If the browse failed + */ + private int testBrowse() throws IOException, KeyNotFoundException + { + TupleCursor cursor = btree.browse(); + + int nb = 0; + long elem = Long.MIN_VALUE; + + while ( cursor.hasNext() ) + { + Tuple res = cursor.next(); + + if ( res.getKey() > elem ) + { + elem = res.getKey(); + nb++; + } + } + + cursor.close(); + + return nb; + } + + + /** + * Check that we can read the btree while it is being modified. We will start + * 100 readers for one writer. + * + * @throws InterruptedException If the btree access failed. + */ + @Test + public void testBrowseMultiThreads() throws InterruptedException + { + int nbThreads = 100; + final CountDownLatch latch = new CountDownLatch( nbThreads ); + + Thread writer = new Thread() + { + public void run() + { + try + { + create50KBTree(); + } + catch ( Exception e ) + { + } + } + }; + + long t0 = System.currentTimeMillis(); + + // Start the writer + writer.start(); + + for ( int i = 0; i < nbThreads; i++ ) + { + Thread test = new Thread() + { + public void run() + { + try + { + int res = 0; + int previous = -1; + + while ( previous < res ) + { + previous = res; + res = testBrowse(); + Thread.sleep( 500 ); + } + + latch.countDown(); + } + catch ( Exception e ) + { + } + } + }; + + // Start each reader + test.start(); + } + + // Wait for all the readers to be done + latch.await(); + + long t1 = System.currentTimeMillis(); + + System.out.println( " Time to create 50K entries and to have " + nbThreads + " threads reading them : " + + ( ( t1 - t0 ) / 1000 ) + " seconds" ); + } + + + /** + * Test that we can use many threads inserting data in a BTree + * @throws InterruptedException + */ + @Test + public void testInsertMultiThreads() throws InterruptedException, IOException + { + int nbThreads = 100; + final CountDownLatch latch = new CountDownLatch( nbThreads ); + final AtomicBoolean error = new AtomicBoolean(false); + + //Thread.sleep( 60000L ); + + long t0 = System.currentTimeMillis(); + + class MyThread extends Thread + { + private int prefix = 0; + + public void run() + { + try + { + // Inject 1000 elements + for ( int j = 0; j < 1000; j++ ) + { + long value = prefix * 1000 + j; + String valStr = Long.toString( value ); + //System.out.println( "---------------------------Inserting " + valStr + " for Thread " + Thread.currentThread().getName() ); + btree.insert( value, valStr ); + + if ( j % 100 == 0 ) + { + //System.out.println( "---------------------------Inserting " + valStr + " for Thread " + Thread.currentThread().getName() ); +// long res = checkBtree( prefix, 1000, j ); +// +// if ( res != -1L ) +// { +// //retry +// System.out.println( "Failure to retrieve " + j ); +// latch.countDown(); +// error.set( true ); +// return; +// } + } + } + + latch.countDown(); + } + catch ( Exception e ) + { + e.printStackTrace(); + System.out.println( e.getMessage() ); + } + } + + public MyThread( int prefix ) + { + this.prefix = prefix; + } + } + + for ( int i = 0; i < nbThreads; i++ ) + { + MyThread test = new MyThread( i ); + + // Start each reader + test.start(); + } + + // Wait for all the readers to be done + latch.await(); + + if ( error.get() ) + { + System.out.println( "ERROR -----------------" ); + return; + } + + long t1 = System.currentTimeMillis(); + + // Check that the tree contains all the values + assertEquals( -1L, checkBtree( 1000, nbThreads ) ); + + System.out.println( " Time to create 1M entries : " + + ( ( t1 - t0 ) ) + " milliseconds" ); + } + + + private long checkBtree( int prefix, int nbElems, int currentElem ) throws IOException + { + long i = 0L; + + try + { + for ( i = 0L; i < currentElem; i++ ) + { + long key = prefix * nbElems + i; + assertEquals( Long.toString( key ), btree.get( key ) ); + } + + return -1L; + } + catch ( KeyNotFoundException knfe ) + { + System.out.println( "cannot find " + ( prefix * nbElems + i ) ); + return i; + } + } + + + private long checkBtree( int nbElems, int nbThreads ) throws IOException + { + long i = 0L; + + try + { + for ( long j = 0; j < nbThreads; j++ ) + { + for ( i = 0L; i < nbElems; i++ ) + { + long key = j * nbElems + i; + assertEquals( Long.toString( key ), btree.get( key ) ); + } + } + + return -1L; + } + catch ( KeyNotFoundException knfe ) + { + System.out.println( "cannot find " + i ); + return i; + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PageReclaimerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PageReclaimerTest.java new file mode 100644 index 000000000..8f5625e90 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PageReclaimerTest.java @@ -0,0 +1,335 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; + +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.apache.directory.mavibot.btree.util.Strings; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +/** + * Tests for free page reclaimer. + * + * @author Apache Directory Project + */ +public class PageReclaimerTest +{ + private static final String TREE_NAME = "uid-tree"; + + private RecordManager rm; + + private PersistedBTree uidTree; + + @Rule + public TemporaryFolder tmpDir; + + private File dbFile; + + + @Before + public void setup() throws Exception + { + tmpDir = new TemporaryFolder(); + tmpDir.create(); + + dbFile = tmpDir.newFile( "spacereclaimer.db" ); + + //System.out.println(dbFile.getAbsolutePath()); + rm = new RecordManager( dbFile.getAbsolutePath() ); + rm.setPageReclaimerThreshold( 10 ); + + uidTree = ( PersistedBTree ) rm.addBTree( TREE_NAME, IntSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + } + + + @After + public void cleanup() throws Exception + { + rm.close(); + dbFile.delete(); + tmpDir.delete(); + } + + + private void closeAndReopenRM() throws Exception + { + uidTree.close(); + rm.close(); + rm = new RecordManager( dbFile.getAbsolutePath() ); + uidTree = ( PersistedBTree ) rm.getManagedTree( TREE_NAME ); + } + + + @Test + public void testReclaimer() throws Exception + { + int total = 11; + for ( int i=0; i < total; i++ ) + { + uidTree.insert( i, String.valueOf( i ) ); + } + + //System.out.println( "Total size before closing " + dbFile.length() ); + //System.out.println( dbFile.length() ); + closeAndReopenRM(); + //System.out.println( "Total size AFTER closing " + dbFile.length() ); + + int count = 0; + TupleCursor cursor = uidTree.browse(); + while ( cursor.hasNext() ) + { + Tuple t = cursor.next(); + assertEquals( t.key, Integer.valueOf( count ) ); + count++; + } + + assertEquals( count, total ); + } + + + /** + * with the reclaimer threshold 10 and total entries of 1120 + * there was a condition that resulted in OOM while reopening the RM + * + * This issue was fixed after PageReclaimer was updated to run in + * a transaction. + * + * This test is present to verify the fix + * + * @throws Exception + */ + @Test + public void testReclaimerWithMagicNum() throws Exception + { + rm.setPageReclaimerThreshold( 10 ); + + int total = 1120; + for ( int i=0; i < total; i++ ) + { + uidTree.insert( i, String.valueOf( i ) ); + } + + closeAndReopenRM(); + + int count = 0; + TupleCursor cursor = uidTree.browse(); + while ( cursor.hasNext() ) + { + Tuple t = cursor.next(); + assertEquals( t.key, Integer.valueOf( count ) ); + count++; + } + + assertEquals( count, total ); + } + + + /** + * Test reclaimer functionality while multiple threads writing to the same BTree + * + * @throws Exception + */ + @Test + public void testReclaimerWithMultiThreads() throws Exception + { + final int numEntriesPerThread = 11; + final int numThreads = 5; + + final int total = numThreads * numEntriesPerThread; + + final Map keyMap = new ConcurrentHashMap(); + + final Random rnd = new Random(); + + final CountDownLatch latch = new CountDownLatch( numThreads ); + + Runnable r = new Runnable() + { + @Override + public void run() + { + for ( int i=0; i < numEntriesPerThread; i++ ) + { + try + { + int key = rnd.nextInt( total ); + while( true ) + { + if( !keyMap.containsKey( key ) ) + { + keyMap.put( key, key ); + break; + } + + //System.out.println( "duplicate " + key ); + key = rnd.nextInt( total ); + } + + uidTree.insert( key, String.valueOf( key ) ); + } + catch( Exception e ) + { + throw new RuntimeException(e); + } + } + + latch.countDown(); + } + }; + + for ( int i=0; i cursor = uidTree.browse(); + while ( cursor.hasNext() ) + { + Tuple t = cursor.next(); + assertEquals( t.key, Integer.valueOf( count ) ); + count++; + } + + cursor.close(); + + assertEquals( count, total ); + } + + @Test + @SuppressWarnings("all") + public void testInspectTreeState() throws Exception + { + File file = File.createTempFile( "freepagedump", ".db" ); + + if ( file.exists() ) + { + boolean deleted = file.delete(); + if ( !deleted ) + { + throw new IllegalStateException( "Could not delete the data file " + file.getAbsolutePath() ); + } + } + + RecordManager manager = new RecordManager( file.getAbsolutePath() ); + manager.setPageReclaimerThreshold(17); + //manager._disableReclaimer( true ); + + PersistedBTreeConfiguration config = new PersistedBTreeConfiguration(); + + config.setName( "dump-tree" ); + config.setKeySerializer( IntSerializer.INSTANCE ); + config.setValueSerializer( StringSerializer.INSTANCE ); + config.setAllowDuplicates( false ); + config.setPageSize( 4 ); + + BTree btree = new PersistedBTree( config ); + manager.manage( btree ); + + // insert 5 so that we get 1 root and 2 child nodes + for( int i=0; i<5; i++ ) + { + btree.insert( i, String.valueOf( i ) ); + } + + /* + System.out.println( "Total number of pages created " + manager.nbCreatedPages ); + System.out.println( "Total number of pages reused " + manager.nbReusedPages ); + System.out.println( "Total number of pages freed " + manager.nbFreedPages ); + System.out.println( "Total file size (bytes) " + file.length() ); + */ + + long totalPages = file.length() / RecordManager.DEFAULT_PAGE_SIZE; + + // in RM the header page gets skipped before incrementing nbCreatedPages + assertEquals( manager.nbCreatedPages.get() + 1, totalPages ); + + //System.out.println(btree.getRootPage()); + //System.out.println( file.getAbsolutePath() ); + + check( manager, btree ); + + manager.close(); + + file.delete(); + } + + + private void check(RecordManager manager, BTree btree) throws Exception + { + MavibotInspector.check(manager); + + List allOffsets = MavibotInspector.getGlobalPages(); + //System.out.println( "Global: " + allOffsets); + //System.out.println("Total global offsets " + allOffsets.size() ); + + int pagesize = RecordManager.DEFAULT_PAGE_SIZE; + long total = manager.fileChannel.size(); + + List unaccounted = new ArrayList(); + + for(long i = pagesize; i<= total-pagesize; i+=pagesize) + { + if( !allOffsets.contains( Long.valueOf( i ) ) ) + { + unaccounted.add( i ); + } + } + + TupleCursor cursor = manager.btreeOfBtrees.browse(); + while(cursor.hasNext()) + { + Tuple t = cursor.next(); + System.out.println( t.getKey() + " offset " + t.getValue() ); + } + + cursor.close(); + + //System.out.println("Unaccounted offsets " + unaccounted); + assertEquals( 0, unaccounted.size() ); + } + +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeBrowseTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeBrowseTest.java new file mode 100644 index 000000000..81e8a485b --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeBrowseTest.java @@ -0,0 +1,1292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.UUID; + +import org.apache.commons.io.FileUtils; +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * Tests the browse methods on a managed BTree + * + * @author Apache Directory Project + */ +public class PersistedBTreeBrowseTest +{ + private BTree btree = null; + + private RecordManager recordManager1 = null; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File dataDir = null; + + + /** + * Create a BTree for this test + */ + @Before + public void createBTree() throws IOException + { + dataDir = tempFolder.newFolder( UUID.randomUUID().toString() ); + + openRecordManagerAndBtree(); + + try + { + // Create a new BTree which allows duplicate values + btree = recordManager1.addBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE, true ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @After + public void cleanup() throws IOException + { + dataDir = new File( System.getProperty( "java.io.tmpdir" ) + "/recordman" ); + + btree.close(); + + if ( dataDir.exists() ) + { + FileUtils.deleteDirectory( dataDir ); + } + + recordManager1.close(); + assertTrue( recordManager1.isContextOk() ); + } + + + /** + * Reload the BTree into a new record manager + */ + private void openRecordManagerAndBtree() + { + try + { + if ( recordManager1 != null ) + { + recordManager1.close(); + } + + // Now, try to reload the file back + recordManager1 = new RecordManager( dataDir.getAbsolutePath() ); + + // load the last created btree + if ( btree != null ) + { + btree = recordManager1.getManagedTree( btree.getName() ); + } + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + /** + * Check a tuple + */ + private void checkTuple( Tuple tuple, long key, String value ) throws EndOfFileExceededException, + IOException + { + assertNotNull( tuple ); + assertEquals( key, ( long ) tuple.getKey() ); + assertEquals( value, tuple.getValue() ); + } + + + /** + * Check a next() call + */ + private void checkNext( TupleCursor cursor, long key, String value, boolean next, boolean prev ) + throws EndOfFileExceededException, IOException + { + Tuple tuple = cursor.next(); + + checkTuple( tuple, key, value ); + assertEquals( next, cursor.hasNext() ); + assertEquals( prev, cursor.hasPrev() ); + } + + + /** + * Check a prev() call + */ + private void checkPrev( TupleCursor cursor, long key, String value, boolean next, boolean prev ) + throws EndOfFileExceededException, IOException + { + Tuple tuple = cursor.prev(); + assertNotNull( tuple ); + assertEquals( key, ( long ) tuple.getKey() ); + assertEquals( value, tuple.getValue() ); + assertEquals( next, cursor.hasNext() ); + assertEquals( prev, cursor.hasPrev() ); + } + + + /** + * Construct a String representation of a number padded with 0 on the left + */ + private String toString( long value, int size ) + { + String valueStr = Long.toString( value ); + + StringBuilder sb = new StringBuilder(); + + if ( size > valueStr.length() ) + { + for ( int i = valueStr.length(); i < size; i++ ) + { + sb.append( "0" ); + } + } + + sb.append( valueStr ); + + return sb.toString(); + } + + + //---------------------------------------------------------------------------------------- + // The Browse tests + //---------------------------------------------------------------------------------------- + /** + * Test the browse methods on an empty btree + * @throws KeyNotFoundException + */ + @Test + public void testBrowseEmptyBTree() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + TupleCursor cursor = btree.browse(); + + assertFalse( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + try + { + cursor.next(); + fail(); + } + catch ( NoSuchElementException nsee ) + { + // Expected + } + + try + { + cursor.prev(); + fail(); + } + catch ( NoSuchElementException nsee ) + { + // Expected + } + + assertEquals( 0L, cursor.getRevision() ); + } + + + /** + * Test the browse methods on a btree containing just a leaf + */ + @Test + public void testBrowseBTreeLeafNext() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + btree.insert( 1L, "1" ); + btree.insert( 4L, "4" ); + btree.insert( 2L, "2" ); + btree.insert( 3L, "3" ); + btree.insert( 5L, "5" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move forward + cursor.beforeFirst(); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 1L, "1", true, false ); + checkNext( cursor, 2L, "2", true, true ); + checkNext( cursor, 3L, "3", true, true ); + checkNext( cursor, 4L, "4", true, true ); + checkNext( cursor, 5L, "5", false, true ); + } + + + /** + * Test the browse methods on a btree containing just a leaf + */ + @Test + public void testBrowseBTreeLeafPrev() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + btree.insert( 1L, "1" ); + btree.insert( 4L, "4" ); + btree.insert( 2L, "2" ); + btree.insert( 3L, "3" ); + btree.insert( 5L, "5" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move backward + cursor.afterLast(); + + checkPrev( cursor, 5L, "5", false, true ); + checkPrev( cursor, 4L, "4", true, true ); + checkPrev( cursor, 3L, "3", true, true ); + checkPrev( cursor, 2L, "2", true, true ); + checkPrev( cursor, 1L, "1", true, false ); + } + + + /** + * Test the browse methods on a btree containing just a leaf and see if we can + * move at the end or at the beginning + */ + @Test + public void testBrowseBTreeLeafFirstLast() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + btree.insert( 1L, "1" ); + btree.insert( 4L, "4" ); + btree.insert( 2L, "2" ); + btree.insert( 3L, "3" ); + btree.insert( 5L, "5" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // We should not be able to move backward + try + { + cursor.prev(); + fail(); + } + catch ( NoSuchElementException nsee ) + { + // Expected + } + + // Start browsing three elements + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + Tuple tuple = cursor.next(); + tuple = cursor.next(); + tuple = cursor.next(); + + // We should be at 3 now + assertTrue( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + assertEquals( 3L, ( long ) tuple.getKey() ); + assertEquals( "3", tuple.getValue() ); + + // Move to the end + cursor.afterLast(); + + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + + // We should not be able to move forward + try + { + cursor.next(); + fail(); + } + catch ( NoSuchElementException nsee ) + { + // Expected + } + + // We should be at 5 + tuple = cursor.prev(); + assertEquals( 5L, ( long ) tuple.getKey() ); + assertEquals( "5", tuple.getValue() ); + + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + + // Move back to the origin + cursor.beforeFirst(); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + // We should be at 1 + tuple = cursor.next(); + assertEquals( 1L, ( long ) tuple.getKey() ); + assertEquals( "1", tuple.getValue() ); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + } + + + /** + * Test the browse methods on a btree containing just a leaf and see if we can + * move back and forth + */ + @Test + public void testBrowseBTreeLeafNextPrev() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + btree.insert( 1L, "1" ); + btree.insert( 4L, "4" ); + btree.insert( 2L, "2" ); + btree.insert( 3L, "3" ); + btree.insert( 5L, "5" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // We should not be able to move backward + try + { + cursor.prev(); + fail(); + } + catch ( NoSuchElementException nsee ) + { + // Expected + } + + // Start browsing three elements + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + Tuple tuple = cursor.next(); + tuple = cursor.next(); + tuple = cursor.next(); + + // We should be at 3 now + assertTrue( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + assertEquals( 3L, ( long ) tuple.getKey() ); + assertEquals( "3", tuple.getValue() ); + + // Now, move to the prev value + tuple = cursor.prev(); + assertEquals( 2L, ( long ) tuple.getKey() ); + assertEquals( "2", tuple.getValue() ); + + // And to the next value + tuple = cursor.next(); + assertEquals( 3L, ( long ) tuple.getKey() ); + assertEquals( "3", tuple.getValue() ); + } + + + /** + * Test the browse methods on a btree containing many nodes + */ + @Test + public void testBrowseBTreeNodesNext() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + for ( long i = 1; i < 1000L; i++ ) + { + btree.insert( i, Long.toString( i ) ); + } + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move forward + cursor.beforeFirst(); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 1L, "1", true, false ); + + for ( long i = 2L; i < 999L; i++ ) + { + checkNext( cursor, i, Long.toString( i ), true, true ); + } + + checkNext( cursor, 999L, "999", false, true ); + } + + + /** + * Test the browse methods on a btree containing many nodes + */ + @Test + public void testBrowseBTreeNodesPrev() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + for ( long i = 1; i < 1000L; i++ ) + { + btree.insert( i, Long.toString( i ) ); + } + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move backward + cursor.afterLast(); + + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + + checkPrev( cursor, 999L, "999", false, true ); + + for ( long i = 998L; i > 1L; i-- ) + { + checkPrev( cursor, i, Long.toString( i ), true, true ); + } + + checkPrev( cursor, 1L, "1", true, false ); + } + + + /** + * Test the browse methods on a btree containing just a leaf with duplicate values + */ + @Test + public void testBrowseBTreeLeafNextDups1() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some duplicate data + btree.insert( 1L, "1" ); + btree.insert( 1L, "4" ); + btree.insert( 1L, "2" ); + btree.insert( 1L, "3" ); + btree.insert( 1L, "5" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move forward + cursor.beforeFirst(); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 1L, "1", true, false ); + checkNext( cursor, 1L, "2", true, true ); + checkNext( cursor, 1L, "3", true, true ); + checkNext( cursor, 1L, "4", true, true ); + checkNext( cursor, 1L, "5", false, true ); + } + + + /** + * Test the browse methods on a btree containing just a leaf with duplicate values + */ + @Test + public void testBrowseBTreeLeafNextDupsN() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some duplicate data + btree.insert( 1L, "1" ); + btree.insert( 1L, "4" ); + btree.insert( 1L, "2" ); + btree.insert( 2L, "3" ); + btree.insert( 3L, "5" ); + btree.insert( 3L, "7" ); + btree.insert( 3L, "6" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move forward + cursor.beforeFirst(); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 1L, "1", true, false ); + checkNext( cursor, 1L, "2", true, true ); + checkNext( cursor, 1L, "4", true, true ); + checkNext( cursor, 2L, "3", true, true ); + checkNext( cursor, 3L, "5", true, true ); + checkNext( cursor, 3L, "6", true, true ); + checkNext( cursor, 3L, "7", false, true ); + } + + + /** + * Test the browse methods on a btree containing just a leaf with duplicate values + */ + @Test + public void testBrowseBTreeLeafPrevDups1() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some duplicate data + btree.insert( 1L, "1" ); + btree.insert( 1L, "4" ); + btree.insert( 1L, "2" ); + btree.insert( 1L, "3" ); + btree.insert( 1L, "5" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move backward + cursor.afterLast(); + + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + + checkPrev( cursor, 1L, "5", false, true ); + checkPrev( cursor, 1L, "4", true, true ); + checkPrev( cursor, 1L, "3", true, true ); + checkPrev( cursor, 1L, "2", true, true ); + checkPrev( cursor, 1L, "1", true, false ); + } + + + /** + * Test the browse methods on a btree containing just a leaf with duplicate values + */ + @Test + public void testBrowseBTreeLeafPrevDupsN() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some duplicate data + btree.insert( 1L, "1" ); + btree.insert( 1L, "4" ); + btree.insert( 1L, "2" ); + btree.insert( 2L, "3" ); + btree.insert( 3L, "5" ); + btree.insert( 3L, "7" ); + btree.insert( 3L, "6" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move backward + cursor.afterLast(); + + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + + checkPrev( cursor, 3L, "7", false, true ); + checkPrev( cursor, 3L, "6", true, true ); + checkPrev( cursor, 3L, "5", true, true ); + checkPrev( cursor, 2L, "3", true, true ); + checkPrev( cursor, 1L, "4", true, true ); + checkPrev( cursor, 1L, "2", true, true ); + checkPrev( cursor, 1L, "1", true, false ); + } + + + /** + * Test the browse methods on a btree containing nodes with duplicate values + */ + @Test + public void testBrowseBTreeNodesNextDupsN() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + for ( long i = 1; i < 1000L; i++ ) + { + for ( long j = 1; j < 10; j++ ) + { + btree.insert( i, Long.toString( j ) ); + } + } + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move backward + cursor.beforeFirst(); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + boolean next = true; + boolean prev = false; + + for ( long i = 1L; i < 1000L; i++ ) + { + for ( long j = 1L; j < 10L; j++ ) + { + checkNext( cursor, i, Long.toString( j ), next, prev ); + + if ( ( i == 1L ) && ( j == 1L ) ) + { + prev = true; + } + + if ( ( i == 999L ) && ( j == 8L ) ) + { + next = false; + } + } + } + } + + + /** + * Test the browse methods on a btree containing nodes with duplicate values + */ + @Test + public void testBrowseBTreeNodesPrevDupsN() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + for ( long i = 1; i < 1000L; i++ ) + { + for ( int j = 1; j < 10; j++ ) + { + btree.insert( i, Long.toString( j ) ); + } + } + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move backward + cursor.afterLast(); + + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + boolean next = false; + boolean prev = true; + + for ( long i = 999L; i > 0L; i-- ) + { + for ( long j = 9L; j > 0L; j-- ) + { + checkPrev( cursor, i, Long.toString( j ), next, prev ); + + if ( ( i == 1L ) && ( j == 2L ) ) + { + prev = false; + } + + if ( ( i == 999L ) && ( j == 9L ) ) + { + next = true; + } + } + } + } + + + /** + * Test the browse methods on a btree containing just a leaf with duplicate values + * stored into a sub btree + */ + @Test + public void testBrowseBTreeLeafNextDupsSubBTree1() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + // Inject some duplicate data which will be stored into a sub btree + for ( long i = 1L; i < 32L; i++ ) + { + btree.insert( 1L, toString( i, 2 ) ); + } + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move forward + cursor.beforeFirst(); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 1L, "01", true, false ); + + for ( long i = 2L; i < 31L; i++ ) + { + checkNext( cursor, 1L, toString( i, 2 ), true, true ); + } + + checkNext( cursor, 1L, "31", false, true ); + } + + + /** + * Test the browse methods on a btree containing just a leaf with duplicate values + */ + @Test + public void testBrowseBTreeLeafPrevDupsSubBTree1() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + // Inject some duplicate data which will be stored into a sub btree + for ( long i = 1L; i < 32L; i++ ) + { + btree.insert( 1L, toString( i, 2 ) ); + } + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move backward + cursor.afterLast(); + + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + + checkPrev( cursor, 1L, "31", false, true ); + + for ( long i = 30L; i > 1L; i-- ) + { + checkPrev( cursor, 1L, toString( i, 2 ), true, true ); + } + + checkPrev( cursor, 1L, "01", true, false ); + } + + + //---------------------------------------------------------------------------------------- + // The BrowseFrom tests + //---------------------------------------------------------------------------------------- + /** + * Test the browseFrom method on an empty tree + */ + @Test + public void testBrowseFromEmptyBTree() throws IOException, BTreeAlreadyManagedException + { + TupleCursor cursor = btree.browseFrom( 1L ); + + assertFalse( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + try + { + cursor.next(); + fail(); + } + catch ( NoSuchElementException nsee ) + { + // Expected + } + + try + { + cursor.prev(); + fail(); + } + catch ( NoSuchElementException nsee ) + { + // Expected + } + + assertEquals( -1L, cursor.getRevision() ); + } + + + /** + * Test the browseFrom methods on a btree containing just a leaf + */ + @Test + public void testBrowseFromBTreeLeaf() throws IOException, BTreeAlreadyManagedException + { + // Inject some data + btree.insert( 1L, "1" ); + btree.insert( 7L, "7" ); + btree.insert( 3L, "3" ); + btree.insert( 5L, "5" ); + btree.insert( 9L, "9" ); + + // Create the cursor, starting at 5 + TupleCursor cursor = btree.browseFrom( 5L ); + + assertTrue( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + // Move forward + checkNext( cursor, 5L, "5", true, true ); + checkNext( cursor, 7L, "7", true, true ); + checkNext( cursor, 9L, "9", false, true ); + + cursor.close(); + + // now, start at 5 and move backward + cursor = btree.browseFrom( 5L ); + + assertTrue( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + // Move backward + checkPrev( cursor, 3L, "3", true, true ); + checkPrev( cursor, 1L, "1", true, false ); + cursor.close(); + + // Start at the first key + cursor = btree.browseFrom( 1L ); + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 1L, "1", true, false ); + checkNext( cursor, 3L, "3", true, true ); + + // Start before the first key + cursor = btree.browseFrom( 0L ); + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 1L, "1", true, false ); + checkNext( cursor, 3L, "3", true, true ); + + // Start at the last key + cursor = btree.browseFrom( 9L ); + assertTrue( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 9L, "9", false, true ); + checkPrev( cursor, 7L, "7", true, true ); + + // Start after the last key + cursor = btree.browseFrom( 10L ); + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + + checkPrev( cursor, 9L, "9", false, true ); + checkPrev( cursor, 7L, "7", true, true ); + + // Start in the middle with a non existent key + cursor = btree.browseFrom( 4L ); + assertTrue( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + + checkNext( cursor, 5L, "5", true, true ); + + // Start in the middle with a non existent key + cursor = btree.browseFrom( 4L ); + + checkPrev( cursor, 3L, "3", true, true ); + } + + + /** + * Test the browseFrom method on a btree with a non existing key + */ + @Test + public void testBrowseFromBTreeNodesNotExistingKey() throws IOException, BTreeAlreadyManagedException + { + // Inject some data + for ( long i = 0; i <= 1000L; i += 2 ) + { + btree.insert( i, Long.toString( i ) ); + } + + // Create the cursor + TupleCursor cursor = btree.browseFrom( 1500L ); + + assertFalse( cursor.hasNext() ); + assertTrue( cursor.hasPrev() ); + assertEquals( 1000L, cursor.prev().getKey().longValue() ); + } + + + /** + * Test the browseFrom method on a btree containing nodes with duplicate values + */ + @Test + public void testBrowseFromBTreeNodesPrevDupsN() throws IOException, BTreeAlreadyManagedException + { + // Inject some data + for ( long i = 1; i < 1000L; i += 2 ) + { + for ( int j = 1; j < 10; j++ ) + { + btree.insert( i, Long.toString( j ) ); + } + } + + // Create the cursor + TupleCursor cursor = btree.browseFrom( 500L ); + + // Move forward + + assertTrue( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + boolean next = true; + boolean prev = true; + + for ( long i = 501L; i < 1000L; i += 2 ) + { + for ( long j = 1L; j < 10L; j++ ) + { + if ( ( i == 999L ) && ( j == 9L ) ) + { + next = false; + } + + checkNext( cursor, i, Long.toString( j ), next, prev ); + } + } + } + + + //---------------------------------------------------------------------------------------- + // The TupleCursor.moveToNext/PrevNonDuplicateKey method tests + //---------------------------------------------------------------------------------------- + /** + * Test the TupleCursor.nextKey method on a btree containing nodes + * with duplicate values. + */ + @Test + public void testNextKey() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + for ( long i = 1; i < 1000L; i++ ) + { + for ( long j = 1; j < 10; j++ ) + { + btree.insert( i, Long.toString( j ) ); + } + } + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move forward + cursor.beforeFirst(); + + assertFalse( cursor.hasPrev() ); + assertTrue( cursor.hasNext() ); + boolean next = true; + boolean prev = false; + + for ( long i = 1L; i < 999L; i++ ) + { + Tuple tuple = cursor.nextKey(); + + checkTuple( tuple, i, "1" ); + + if ( i == 999L ) + { + next = false; + } + + assertEquals( next, cursor.hasNext() ); + assertEquals( prev, cursor.hasPrev() ); + + if ( i == 1L ) + { + prev = true; + } + } + } + + + /** + * Test the TupleCursor.nextKey method on a btree containing nodes + * with duplicate values. + */ + @Test + @Ignore + public void testNextKeyDups() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + //for ( long i = 1; i < 3; i++ ) + { + for ( long j = 1; j < 9; j++ ) + { + btree.insert( 1L, Long.toString( j ) ); + } + } + + btree.insert( 1L, "10" ); + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move forward + cursor.beforeFirst(); + + assertFalse( cursor.hasPrevKey() ); + assertTrue( cursor.hasNextKey() ); + + Tuple tuple = cursor.nextKey(); + + checkTuple( tuple, 1L, "1" ); + + cursor.beforeFirst(); + long val = 1L; + + while ( cursor.hasNext() ) + { + tuple = cursor.next(); + + assertEquals( Long.valueOf( 1L ), tuple.getKey() ); + assertEquals( Long.toString( val ), tuple.getValue() ); + + val++; + } + + assertFalse( cursor.hasNextKey() ); + assertFalse( cursor.hasPrevKey() ); + } + + + /** + * Test the TupleCursor.moveToPrevNonDuplicateKey method on a btree containing nodes + * with duplicate values. + */ + @Test + public void testPrevKey() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Inject some data + for ( long i = 1; i < 1000L; i++ ) + { + for ( long j = 1; j < 10; j++ ) + { + btree.insert( i, Long.toString( j ) ); + } + } + + // Create the cursor + TupleCursor cursor = btree.browse(); + + // Move backward + cursor.afterLast(); + + assertTrue( cursor.hasPrev() ); + assertFalse( cursor.hasNext() ); + boolean next = true; + boolean prev = true; + + for ( long i = 999L; i > 0L; i-- ) + { + Tuple tuple = cursor.prevKey(); + + if ( i == 1L ) + { + prev = false; + } + + checkTuple( tuple, i, "1" ); + assertEquals( next, cursor.hasNext() ); + assertEquals( prev, cursor.hasPrev() ); + + if ( i == 999L ) + { + next = true; + } + } + } + + + /** + * Test the overwriting of elements + */ + @Test + public void testOverwrite() throws Exception + { + btree.setAllowDuplicates( false ); + + // Adding an element with a null value + btree.insert( 1L, "1" ); + + assertTrue( btree.hasKey( 1L ) ); + + assertEquals( "1", btree.get( 1L ) ); + + btree.insert( 1L, "10" ); + + assertTrue( btree.hasKey( 1L ) ); + assertEquals( "10", btree.get( 1L ) ); + + btree.close(); + } + + + @Ignore("test used for debugging") + @Test + public void testAdd20Random() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + long[] values = new long[] + { + 14, 7, 43, 37, 49, 3, 20, 26, 17, 29, + 40, 33, 21, 18, 9, 30, 45, 36, 12, 8 + }; + + btree.setPageSize( 4 ); + // Inject some data + for ( long value : values ) + { + btree.insert( value, Long.toString( value ) ); + System.out.println( btree ); + } + + TupleCursor cursor = btree.browse(); + + while ( cursor.hasNext() ) + { + System.out.println( cursor.nextKey() ); + } + } + + + /** + * Test the browse methods on a btree containing 500 random entries, with multiple values, and + * try to browse it. + */ + @Test + public void testBrowseBTreeMultipleValues() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + BTree btreeLong = null; + + try + { + btreeLong = recordManager1.addBTree( "testLong", LongSerializer.INSTANCE, LongSerializer.INSTANCE, true ); + + // Create a set of 500 values from 0 to 499, in a random order + // (all the values are there, they are just shuffled) + int nbKeys = 500; + List values = new ArrayList( nbKeys ); + long[] randomVals = new long[nbKeys]; + Random r = new Random( System.currentTimeMillis() ); + + // Create the data to inject into the btree + for ( long i = 0L; i < nbKeys; i++ ) + { + values.add( i ); + } + + for ( int i = 0; i < nbKeys; i++ ) + { + int index = r.nextInt( nbKeys - i ); + randomVals[i] = values.get( index ); + values.remove( index ); + } + + long sum = 0L; + + for ( int i = 0; i < nbKeys; i++ ) + { + sum += randomVals[i]; + } + + assertEquals( ( nbKeys * ( nbKeys - 1 ) ) / 2, sum ); + + int nbValues = 9; + + // Inject the 500 keys, each of them with 10 values + for ( int i = 0; i < nbKeys; i++ ) + { + Long value = randomVals[i]; + + for ( Long j = 0L; j < nbValues; j++ ) + { + btreeLong.insert( randomVals[i], value + j ); + } + } + + long t0 = System.currentTimeMillis(); + + // Now, browse the BTree fully, as many time as we have keys. + // We always browse from a different position, we should cover all + // the possible situations. + for ( Long i = 0L; i < nbKeys; i++ ) + { + // Create the cursor, positionning it before the key + TupleCursor cursor = btreeLong.browseFrom( i ); + + assertTrue( cursor.hasNext() ); + Long expected = i; + + while ( cursor.hasNext() ) + { + for ( Long j = 0L; j < nbValues; j++ ) + { + Tuple tuple1 = cursor.next(); + + assertEquals( expected, tuple1.getKey() ); + assertEquals( ( Long ) ( expected + j ), tuple1.getValue() ); + } + + expected++; + } + + cursor.close(); + } + long t1 = System.currentTimeMillis(); + + System.out.println( "Browse Forward for " + nbValues + " = " + ( t1 - t0 ) ); + + long t00 = System.currentTimeMillis(); + + // Now, browse the BTree backward + for ( Long i = nbKeys - 1L; i >= 0; i-- ) + { + // Create the cursor + TupleCursor cursor = btreeLong.browseFrom( i ); + + if ( i > 0 ) + { + assertTrue( cursor.hasPrev() ); + } + + Long expected = i; + + while ( cursor.hasPrev() ) + { + for ( Long j = Long.valueOf( nbValues - 1 ); j >= 0L; j-- ) + { + Tuple tuple1 = cursor.prev(); + + assertEquals( Long.valueOf( expected - 1L ), tuple1.getKey() ); + assertEquals( ( Long ) ( expected - 1L + j ), tuple1.getValue() ); + } + + expected--; + } + + cursor.close(); + } + long t11 = System.currentTimeMillis(); + + System.out.println( "Browe backward for " + nbValues + " = " + ( t11 - t00 ) ); + } + finally + { + btreeLong.close(); + } + } +} \ No newline at end of file diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeBuilderTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeBuilderTest.java new file mode 100644 index 000000000..44f4eb1f7 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeBuilderTest.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.junit.Ignore; +import org.junit.Test; + + +/** + * Test cases for ManagedBTreeBuilder. + * + * @author Apache Directory Project + */ +@Ignore("until ApacheDS works with mavibot") +public class PersistedBTreeBuilderTest +{ + + @Test + public void testManagedBTreeBuilding() throws Exception + { + List> sortedTuple = new ArrayList>(); + + for ( int i = 1; i < 8; i++ ) + { + Tuple t = new Tuple( i, i ); + sortedTuple.add( t ); + } + + File file = File.createTempFile( "managedbtreebuilder", ".data" ); + file.deleteOnExit(); + + try + { + RecordManager rm = new RecordManager( file.getAbsolutePath() ); + + IntSerializer ser = IntSerializer.INSTANCE; + PersistedBTreeBuilder bb = new PersistedBTreeBuilder( rm, "master", 4, + ser, + ser ); + + // contains 1, 2, 3, 4, 5, 6, 7 + BTree btree = bb.build( sortedTuple.iterator() ); + + rm.close(); + + rm = new RecordManager( file.getAbsolutePath() ); + btree = rm.getManagedTree( "master" ); + + assertEquals( 1, btree.getRootPage().getNbElems() ); + + assertEquals( 7, btree.getRootPage().findRightMost().getKey().intValue() ); + + assertEquals( 1, btree.getRootPage().findLeftMost().getKey().intValue() ); + + TupleCursor cursor = btree.browse(); + int i = 0; + + while ( cursor.hasNext() ) + { + Tuple expected = sortedTuple.get( i++ ); + Tuple actual = cursor.next(); + assertEquals( expected.getKey(), actual.getKey() ); + assertEquals( expected.getValue(), actual.getValue() ); + } + + cursor.close(); + btree.close(); + } + finally + { + file.delete(); + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeDuplicateKeyTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeDuplicateKeyTest.java new file mode 100644 index 000000000..8dfd94cbd --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeDuplicateKeyTest.java @@ -0,0 +1,850 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.UUID; + +import org.apache.commons.io.FileUtils; +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.DuplicateValueNotAllowedException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * TODO BTreeDuplicateKeyTest. + * + * @author Apache Directory Project + */ +public class PersistedBTreeDuplicateKeyTest +{ + private BTree btree = null; + + private RecordManager recordManager1 = null; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File dataDir = null; + + + @Before + public void createBTree() throws IOException + { + dataDir = tempFolder.newFolder( UUID.randomUUID().toString() ); + + openRecordManagerAndBtree(); + + try + { + // Create a new BTree + btree = recordManager1.addBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE, + BTree.ALLOW_DUPLICATES ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @After + public void cleanup() throws IOException + { + dataDir = new File( System.getProperty( "java.io.tmpdir" ) + "/recordman" ); + + btree.close(); + + if ( dataDir.exists() ) + { + FileUtils.deleteDirectory( dataDir ); + } + + recordManager1.close(); + assertTrue( recordManager1.isContextOk() ); + } + + + private void openRecordManagerAndBtree() + { + try + { + if ( recordManager1 != null ) + { + recordManager1.close(); + } + + // Now, try to reload the file back + recordManager1 = new RecordManager( dataDir.getAbsolutePath() ); + + // load the last created btree + if ( btree != null ) + { + btree = recordManager1.getManagedTree( btree.getName() ); + } + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @Test + public void testInsertNullValue() throws IOException, KeyNotFoundException + { + btree.insert( 1L, null ); + + TupleCursor cursor = btree.browse(); + assertTrue( cursor.hasNext() ); + + Tuple t = cursor.next(); + + assertEquals( Long.valueOf( 1 ), t.getKey() ); + assertEquals( null, t.getValue() ); + + cursor.close(); + + btree.close(); + } + + + @Test + public void testBrowseEmptyTree() throws IOException, KeyNotFoundException, BTreeAlreadyManagedException + { + IntSerializer serializer = IntSerializer.INSTANCE; + + BTree btree = BTreeFactory.createPersistedBTree( "master", serializer, serializer ); + + // Inject the newly created BTree into teh recordManager + recordManager1.manage( btree ); + + TupleCursor cursor = btree.browse(); + assertFalse( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + try + { + cursor.next(); + fail( "Should not reach here" ); + } + catch ( NoSuchElementException e ) + { + assertTrue( true ); + } + + try + { + cursor.prev(); + fail( "Should not reach here" ); + } + catch ( NoSuchElementException e ) + { + assertTrue( true ); + } + + cursor.close(); + btree.close(); + } + + + @Test + public void testDuplicateKey() throws IOException, KeyNotFoundException + { + btree.insert( 1L, "1" ); + btree.insert( 1L, "2" ); + + TupleCursor cursor = btree.browse(); + assertTrue( cursor.hasNext() ); + + Tuple t = cursor.next(); + + assertEquals( Long.valueOf( 1 ), t.getKey() ); + assertEquals( "1", t.getValue() ); + + assertTrue( cursor.hasNext() ); + + t = cursor.next(); + + assertEquals( Long.valueOf( 1 ), t.getKey() ); + assertEquals( "2", t.getValue() ); + + assertFalse( cursor.hasNext() ); + + // test backward move + assertTrue( cursor.hasPrev() ); + + t = cursor.prev(); + + assertEquals( Long.valueOf( 1 ), t.getKey() ); + assertEquals( "1", t.getValue() ); + + assertFalse( cursor.hasPrev() ); + + // again forward + assertTrue( cursor.hasNext() ); + + t = cursor.next(); + + assertEquals( Long.valueOf( 1 ), t.getKey() ); + assertEquals( "2", t.getValue() ); + + assertFalse( cursor.hasNext() ); + + cursor.close(); + btree.close(); + } + + + @Test + public void testGetDuplicateKey() throws Exception + { + String retVal = btree.insert( 1L, "1" ); + assertNull( retVal ); + + retVal = btree.insert( 1L, "2" ); + assertNull( retVal ); + + // check the return value when an existing value is added again + retVal = btree.insert( 1L, "2" ); + assertEquals( "2", retVal ); + + assertEquals( "1", btree.get( 1L ) ); + assertTrue( btree.contains( 1L, "1" ) ); + assertTrue( btree.contains( 1L, "2" ) ); + + assertFalse( btree.contains( 1L, "0" ) ); + assertFalse( btree.contains( 0L, "1" ) ); + assertFalse( btree.contains( 0L, "0" ) ); + assertFalse( btree.contains( null, "0" ) ); + assertFalse( btree.contains( 0L, null ) ); + assertFalse( btree.contains( null, null ) ); + btree.close(); + } + + + @Test + public void testRemoveDuplicateKey() throws Exception + { + btree.insert( 1L, "1" ); + btree.insert( 1L, "2" ); + + assertEquals( 2, btree.getNbElems() ); + + Tuple t = btree.delete( 1L, "1" ); + assertEquals( Long.valueOf( 1 ), t.getKey() ); + assertEquals( "1", t.getValue() ); + + assertEquals( 1l, btree.getNbElems() ); + + t = btree.delete( 1L, "2" ); + assertEquals( Long.valueOf( 1 ), t.getKey() ); + assertEquals( "2", t.getValue() ); + + assertEquals( 0l, btree.getNbElems() ); + + t = btree.delete( 1L, "2" ); + assertNull( t ); + btree.close(); + } + + + @Test + public void testFullPage() throws Exception + { + int i = 7; + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + for ( int k = 0; k < i; k++ ) + { + String val = ch + Integer.toString( k ); + btree.insert( Long.valueOf( ch ), val ); + } + } + + TupleCursor cursor = btree.browse(); + + char ch = 'a'; + int k = 0; + + while ( cursor.hasNext() ) + { + Tuple t = cursor.next(); + assertEquals( Long.valueOf( ch ), t.getKey() ); + k++; + + if ( ( k % i ) == 0 ) + { + ch++; + } + } + + assertEquals( ( 'z' + 1 ), ch ); + + ch = 'z'; + cursor.afterLast(); + + while ( cursor.hasPrev() ) + { + Tuple t = cursor.prev(); + assertEquals( Long.valueOf( ch ), t.getKey() ); + k--; + + if ( ( k % i ) == 0 ) + { + ch--; + } + } + + assertEquals( ( 'a' - 1 ), ch ); + cursor.close(); + } + + + @Test + public void testMoveFirst() throws Exception + { + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + String val = Character.toString( ch ); + btree.insert( Long.valueOf( ch ), val ); + } + + assertEquals( 26, btree.getNbElems() ); + + // add one more value for 'a' + btree.insert( Long.valueOf( 'a' ), "val" ); + + assertEquals( 27, btree.getNbElems() ); + + // Start from c : we should have only 24 values + TupleCursor cursor = btree.browseFrom( Long.valueOf( 'c' ) ); + + int i = 0; + + while ( cursor.hasNext() ) + { + Tuple tuple = cursor.next(); + assertNotNull( tuple ); + i++; + } + + assertEquals( 24, i ); + + // now move the cursor first + cursor.beforeFirst(); + assertTrue( cursor.hasNext() ); + Tuple tuple = cursor.next(); + + // We should be on the first position + assertEquals( Long.valueOf( 'a' ), tuple.getKey() ); + + // Count the number of element after the first one, we should have 26 only + i = 0; + + while ( cursor.hasNext() ) + { + tuple = cursor.next(); + assertNotNull( tuple ); + i++; + } + + assertEquals( 26, i ); + + cursor.close(); + + // Rebrowse + cursor = btree.browse(); + + i = 0; + + while ( cursor.hasNext() ) + { + assertNotNull( cursor.next() ); + i++; + } + + // again, we should see 27 elements + assertEquals( 27, i ); + + // now move the cursor first, but move forward the keys + cursor.beforeFirst(); + assertTrue( cursor.hasNextKey() ); + assertEquals( Long.valueOf( 'a' ), cursor.nextKey().getKey() ); + + i = 0; + + while ( cursor.hasNextKey() ) + { + tuple = cursor.nextKey(); + long key = tuple.getKey(); + assertNotNull( key ); + i++; + } + + // We should have 25 keys only, as we just moved forward the first one + assertEquals( 25, i ); + } + + + @Test + public void testMoveLast() throws Exception + { + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + String val = Character.toString( ch ); + btree.insert( Long.valueOf( ch ), val ); + } + + assertEquals( 26, btree.getNbElems() ); + + // add one more value for 'z' + btree.insert( Long.valueOf( 'z' ), "val" ); + + assertEquals( 27, btree.getNbElems() ); + + // Start from x : we should have only 23 values + TupleCursor cursor = btree.browseFrom( Long.valueOf( 'x' ) ); + + int i = 0; + + while ( cursor.hasPrev() ) + { + Tuple tuple = cursor.prev(); + assertNotNull( tuple ); + i++; + } + + assertEquals( 23, i ); + + // now move the cursor to the last element + cursor.afterLast(); + assertTrue( cursor.hasPrev() ); + Tuple tuple = cursor.prev(); + + // We should be on the last position + assertEquals( Long.valueOf( 'z' ), tuple.getKey() ); + + // Count the number of element before the last one, we should have 26 + i = 0; + + while ( cursor.hasPrev() ) + { + tuple = cursor.prev(); + assertNotNull( tuple ); + i++; + } + + assertEquals( 26, i ); + + cursor.close(); + + // Rebrowse + cursor = btree.browse(); + cursor.afterLast(); + + i = 0; + + while ( cursor.hasPrev() ) + { + assertNotNull( cursor.prev() ); + i++; + } + + // again, we should see 27 elements + assertEquals( 27, i ); + + // now move the cursor first, but move backward the keys + cursor.afterLast(); + assertTrue( cursor.hasPrevKey() ); + assertEquals( Long.valueOf( 'z' ), cursor.prevKey().getKey() ); + + i = 0; + + while ( cursor.hasPrevKey() ) + { + tuple = cursor.prevKey(); + long key = tuple.getKey(); + assertNotNull( key ); + i++; + } + + // We should have 25 keys only, as we just moved forward the first one + assertEquals( 25, i ); + } + + + @Test(expected = NoSuchElementException.class) + public void testMoveLast2() throws Exception + { + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + btree.insert( Long.valueOf( ch ), UUID.randomUUID().toString() ); + } + + btree.insert( Long.valueOf( 'z' ), UUID.randomUUID().toString() ); + + TupleCursor cursor = btree.browseFrom( Long.valueOf( 'c' ) ); + cursor.afterLast(); + + assertFalse( cursor.hasNext() ); + assertTrue( cursor.hasPrev() ); + assertEquals( Long.valueOf( 'z' ), cursor.prev().getKey() ); + // the key, 'z', has two values + assertEquals( Long.valueOf( 'z' ), cursor.prev().getKey() ); + assertEquals( Long.valueOf( 'y' ), cursor.prev().getKey() ); + + cursor.beforeFirst(); + assertEquals( Long.valueOf( 'a' ), cursor.next().getKey() ); + + cursor.afterLast(); + assertFalse( cursor.hasNext() ); + // make sure it throws NoSuchElementException + cursor.next(); + } + + + @Test(expected = NoSuchElementException.class) + public void testNextPrevKey() throws Exception + { + int i = 7; + + // Insert keys from a to z with 7 values for each key + for ( char ch = 'a'; ch <= 'z'; ch++ ) + { + for ( int k = 0; k < i; k++ ) + { + btree.insert( Long.valueOf( ch ), String.valueOf( k ) ); + } + } + + TupleCursor cursor = btree.browse(); + + assertTrue( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + for ( int k = 0; k < 2; k++ ) + { + assertEquals( Long.valueOf( 'a' ), cursor.next().getKey() ); + } + + assertEquals( Long.valueOf( 'a' ), cursor.next().getKey() ); + + Tuple tuple = cursor.nextKey(); + + assertEquals( Long.valueOf( 'b' ), tuple.getKey() ); + + for ( char ch = 'b'; ch < 'z'; ch++ ) + { + assertEquals( Long.valueOf( ch ), cursor.next().getKey() ); + tuple = cursor.nextKey(); + char t = ch; + assertEquals( Long.valueOf( ++t ), tuple.getKey() ); + } + + for ( int k = 0; k < i; k++ ) + { + assertEquals( Long.valueOf( 'z' ), cursor.next().getKey() ); + } + + assertFalse( cursor.hasNextKey() ); + assertTrue( cursor.hasPrevKey() ); + tuple = cursor.prev(); + assertEquals( Long.valueOf( 'z' ), tuple.getKey() ); + assertEquals( "6", tuple.getValue() ); + + for ( char ch = 'z'; ch > 'a'; ch-- ) + { + char t = ch; + t--; + + assertEquals( Long.valueOf( ch ), cursor.prev().getKey() ); + + tuple = cursor.prevKey(); + + assertEquals( Long.valueOf( t ), tuple.getKey() ); + } + + for ( int k = 5; k >= 0; k-- ) + { + tuple = cursor.prev(); + assertEquals( Long.valueOf( 'a' ), tuple.getKey() ); + assertEquals( String.valueOf( k ), tuple.getValue() ); + } + + assertTrue( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + tuple = cursor.next(); + assertEquals( Long.valueOf( 'a' ), tuple.getKey() ); + assertEquals( "0", tuple.getValue() ); + + cursor.close(); + + cursor = btree.browseFrom( Long.valueOf( 'y' ) ); + tuple = cursor.prevKey(); + assertNotNull( tuple ); + assertEquals( Long.valueOf( 'y' ), tuple.getKey() ); + assertEquals( "6", tuple.getValue() ); + cursor.close(); + + cursor = btree.browse(); + cursor.beforeFirst(); + assertFalse( cursor.hasPrev() ); + // make sure it throws NoSuchElementException + cursor.prev(); + } + + + /** + * Test for moving between two leaves. When moveToNextNonDuplicateKey is called + * and cursor is on the last element of the current leaf. + * + * @throws Exception + */ + @Test + public void testMoveToNextAndPrevWithPageBoundaries() throws Exception + { + int i = 32; + for ( int k = 0; k < i; k++ ) + { + btree.insert( ( long ) k, Long.toString( k ) ); + } + + // 15 is the last element of the first leaf + // Check that we correctly jump to the next page + TupleCursor cursor = btree.browseFrom( 15L ); + Tuple tuple = cursor.nextKey(); + + assertNotNull( tuple ); + assertEquals( Long.valueOf( 16 ), tuple.getKey() ); + assertEquals( "16", tuple.getValue() ); + cursor.close(); + + // Do the same check, on the revert side : moving backward + cursor = btree.browseFrom( 16L ); + tuple = cursor.prevKey(); + + assertNotNull( tuple ); + assertEquals( Long.valueOf( 15 ), tuple.getKey() ); + assertEquals( "15", tuple.getValue() ); + cursor.close(); + + // Now do a next followed by a prev on the boundary of 2 pages + cursor = btree.browseFrom( 16L ); + tuple = cursor.prevKey(); + + assertNotNull( tuple ); + assertEquals( Long.valueOf( 15 ), tuple.getKey() ); + assertEquals( "15", tuple.getValue() ); + + // Move next, we should be back to the initial value + assertTrue( cursor.hasNext() ); + tuple = cursor.next(); + assertEquals( Long.valueOf( 16 ), tuple.getKey() ); + assertEquals( "16", tuple.getValue() ); + cursor.close(); + + // test the extremes of the BTree instead of that of leaves + cursor = btree.browseFrom( 30L ); + tuple = cursor.nextKey(); + assertFalse( cursor.hasNext() ); + assertTrue( cursor.hasPrev() ); + + assertEquals( Long.valueOf( 31 ), tuple.getKey() ); + assertEquals( "31", tuple.getValue() ); + cursor.close(); + + cursor = btree.browse(); + assertTrue( cursor.hasNext() ); + assertFalse( cursor.hasPrev() ); + + tuple = cursor.nextKey(); + assertEquals( Long.valueOf( 0 ), tuple.getKey() ); + assertEquals( "0", tuple.getValue() ); + cursor.close(); + } + + + @Test + public void testNextAfterPrev() throws Exception + { + int i = 32; + + for ( int k = 0; k < i; k++ ) + { + btree.insert( ( long ) k, String.valueOf( k ) ); + } + + // 15 is the last element of the first leaf + TupleCursor cursor = btree.browseFrom( 16L ); + + assertTrue( cursor.hasNext() ); + Tuple tuple = cursor.next(); + assertEquals( Long.valueOf( 16 ), tuple.getKey() ); + assertEquals( "16", tuple.getValue() ); + + assertTrue( cursor.hasPrev() ); + tuple = cursor.prev(); + assertEquals( Long.valueOf( 15 ), tuple.getKey() ); + assertEquals( "15", tuple.getValue() ); + + assertTrue( cursor.hasNext() ); + tuple = cursor.next(); + assertEquals( Long.valueOf( 16 ), tuple.getKey() ); + assertEquals( "16", tuple.getValue() ); + cursor.close(); + + } + + + /** + * Test for moving after a key and traversing backwards. + * + * @throws Exception + */ + @Test + public void testMoveToNextAndTraverseBackward() throws Exception + { + int i = 5; + + for ( int k = 0; k < i; k++ ) + { + btree.insert( ( long ) k, Long.toString( k ) ); + } + + // 4 is the last element in the tree + TupleCursor cursor = btree.browseFrom( 4L ); + cursor.nextKey(); + + long currentKey = 4L; + + while ( cursor.hasPrev() ) + { + assertEquals( Long.valueOf( currentKey ), cursor.prev().getKey() ); + currentKey--; + } + + cursor.close(); + } + + + /** + * Test for moving after a key and traversing backwards. + * + * @throws Exception + */ + @Test + public void testMoveToPrevAndTraverseForward() throws Exception + { + int i = 5; + + for ( int k = 0; k < i; k++ ) + { + btree.insert( ( long ) k, Long.toString( k ) ); + } + + // 4 is the last element in the tree + TupleCursor cursor = btree.browseFrom( 0L ); + + long currentKey = 0L; + + while ( cursor.hasNext() ) + { + assertEquals( Long.valueOf( currentKey ), cursor.next().getKey() ); + currentKey++; + } + + cursor.close(); + } + + + @Test + public void testFindLeftAndRightMosetInSubBTree() throws Exception + { + PersistedBTreeConfiguration config = new PersistedBTreeConfiguration(); + + config.setName( "test" ); + config.setKeySerializer( IntSerializer.INSTANCE ); + config.setValueSerializer( IntSerializer.INSTANCE ); + config.setAllowDuplicates( false ); + config.setBtreeType( BTreeTypeEnum.PERSISTED_SUB ); + + PersistedBTree subBtree = new PersistedBTree( config ); + + subBtree.setRecordManager( recordManager1 ); + + subBtree.insert( 1, 1 ); // the values will be discarded in this BTree type + subBtree.insert( 2, 2 ); + subBtree.insert( 3, 3 ); + subBtree.insert( 4, 4 ); + subBtree.insert( 5, 5 ); + + Tuple t = subBtree.getRootPage().findLeftMost(); + assertEquals( Integer.valueOf( 1 ), t.getKey() ); + + t = subBtree.getRootPage().findRightMost(); + assertEquals( Integer.valueOf( 5 ), t.getKey() ); + } + + /** + * Test that a BTree which forbid duplicate values does not accept them + */ + @Test(expected = DuplicateValueNotAllowedException.class) + @Ignore("this condition is removed") + public void testBTreeForbidDups() throws IOException, BTreeAlreadyManagedException + { + BTree singleValueBtree = recordManager1.addBTree( "test2", LongSerializer.INSTANCE, + StringSerializer.INSTANCE, BTree.FORBID_DUPLICATES ); + + for ( long i = 0; i < 64; i++ ) + { + singleValueBtree.insert( i, Long.toString( i ) ); + } + + try + { + singleValueBtree.insert( 18L, "Duplicate" ); + fail(); + } + finally + { + singleValueBtree.close(); + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeTransactionTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeTransactionTest.java new file mode 100644 index 000000000..b8abe9697 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeTransactionTest.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +import org.apache.commons.io.FileUtils; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +/** + * Test the PersistedBTree with transaction + * + * @author Apache Directory Project + */ +public class PersistedBTreeTransactionTest +{ + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + private File dataDirWithTxn = null; + private File dataDirNoTxn = null; + private BTree btreeWithTransactions = null; + private BTree btreeNoTransactions = null; + private RecordManager recordManagerTxn = null; + private RecordManager recordManagerNoTxn = null; + + + @Before + public void createBTree() throws IOException + { + dataDirWithTxn = tempFolder.newFolder( UUID.randomUUID().toString() ); + dataDirNoTxn = tempFolder.newFolder( UUID.randomUUID().toString() ); + + openRecordManagerAndBtrees(); + + try + { + // Create a new BTree with transaction and another one without + btreeWithTransactions = recordManagerTxn.addBTree( "testWithTxn", LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + btreeNoTransactions = recordManagerNoTxn.addBTree( "testNoTxn", LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @After + public void cleanup() throws IOException + { + btreeNoTransactions.close(); + btreeWithTransactions.close(); + + recordManagerNoTxn.close(); + recordManagerTxn.close(); + + assertTrue( recordManagerNoTxn.isContextOk() ); + assertTrue( recordManagerTxn.isContextOk() ); + + if ( dataDirNoTxn.exists() ) + { + FileUtils.deleteDirectory( dataDirNoTxn ); + } + + if ( dataDirWithTxn.exists() ) + { + FileUtils.deleteDirectory( dataDirWithTxn ); + } + } + + + private void openRecordManagerAndBtrees() + { + try + { + if ( recordManagerTxn != null ) + { + recordManagerTxn.close(); + } + + if ( recordManagerNoTxn != null ) + { + recordManagerNoTxn.close(); + } + + // Now, try to reload the file back + recordManagerTxn = new RecordManager( dataDirWithTxn.getAbsolutePath() ); + recordManagerNoTxn = new RecordManager( dataDirNoTxn.getAbsolutePath() ); + + // load the last created btree + if ( btreeWithTransactions != null ) + { + btreeWithTransactions = recordManagerTxn.getManagedTree( btreeWithTransactions.getName() ); + } + + if ( btreeNoTransactions != null ) + { + btreeNoTransactions = recordManagerNoTxn.getManagedTree( btreeNoTransactions.getName() ); + } + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @Test + public void testWithoutTransaction() throws IOException + { + long t0 = System.currentTimeMillis(); + + for ( long i = 0L; i < 1000L; i++ ) + { + btreeNoTransactions.insert( i, Long.toString( i ) ); + } + + long t1 = System.currentTimeMillis(); + + System.out.println( "Delta without transaction for 100K elements = " + ( t1 - t0 ) ); + } + + + @Test + @Ignore("Fails atm") + public void testWithTransaction() throws IOException + { + long t0 = System.currentTimeMillis(); + + for ( long i = 0L; i < 1000L; i++ ) + { + System.out.println( i ); + //btreeWithTransactions.beginTransaction(); + btreeWithTransactions.insert( i, Long.toString( i ) ); + //btreeWithTransactions.commit(); + } + + long t1 = System.currentTimeMillis(); + + System.out.println( "Delta with transaction for 100K elements = " + ( t1 - t0 ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedReadTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedReadTest.java new file mode 100644 index 000000000..517c25826 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedReadTest.java @@ -0,0 +1,360 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; + +import org.apache.directory.mavibot.btree.PageIO; +import org.apache.directory.mavibot.btree.RecordManager; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * Test the RecordManager.readXXX() methods using reflection + * + * @author Apache Directory Project + */ +public class PersistedReadTest +{ + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + + /** + * Test the readInt method + */ + @Test + public void testReadInt() throws Exception + { + File tempFile = tempFolder.newFile( "mavibot.db" ); + String tempFileName = tempFile.getAbsolutePath(); + + // Create page size of 32 only + RecordManager recordManager = new RecordManager( tempFileName, 32 ); + Method storeMethod = RecordManager.class.getDeclaredMethod( "store", long.class, int.class, PageIO[].class ); + Method readIntMethod = RecordManager.class.getDeclaredMethod( "readInt", PageIO[].class, long.class ); + storeMethod.setAccessible( true ); + readIntMethod.setAccessible( true ); + + // Allocate some Pages + PageIO[] pageIos = new PageIO[2]; + pageIos[0] = new PageIO(); + pageIos[0].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[1] = new PageIO(); + pageIos[1].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + + // Set the int at the beginning + storeMethod.invoke( recordManager, 0, 0x12345678, pageIos ); + + // Read it back + int readValue = ( Integer ) readIntMethod.invoke( recordManager, pageIos, 0 ); + + assertEquals( 0x12345678, readValue ); + + // Set the int at the end of the first page + storeMethod.invoke( recordManager, 16, 0x12345678, pageIos ); + + // Read it back + readValue = ( Integer ) readIntMethod.invoke( recordManager, pageIos, 16 ); + + assertEquals( 0x12345678, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 1 byte overlapping + storeMethod.invoke( recordManager, 17, 0x12345678, pageIos ); + + // Read it back + readValue = ( Integer ) readIntMethod.invoke( recordManager, pageIos, 17 ); + + assertEquals( 0x12345678, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 2 bytes overlapping + storeMethod.invoke( recordManager, 18, 0x12345678, pageIos ); + + // Read it back + readValue = ( Integer ) readIntMethod.invoke( recordManager, pageIos, 18 ); + + assertEquals( 0x12345678, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 3 bytes overlapping + storeMethod.invoke( recordManager, 19, 0x12345678, pageIos ); + + // Read it back + readValue = ( Integer ) readIntMethod.invoke( recordManager, pageIos, 19 ); + + assertEquals( 0x12345678, readValue ); + + // Set the int at the beginning of the second page + storeMethod.invoke( recordManager, 20, 0x12345678, pageIos ); + + // Read it back + readValue = ( Integer ) readIntMethod.invoke( recordManager, pageIos, 20 ); + + recordManager.close(); + } + + + /** + * Test the readLong method + */ + @Test + public void testReadLong() throws Exception + { + File tempFile = tempFolder.newFile( "mavibot.db" ); + String tempFileName = tempFile.getAbsolutePath(); + + // Create page size of 32 only + RecordManager recordManager = new RecordManager( tempFileName, 32 ); + Method storeMethod = RecordManager.class.getDeclaredMethod( "store", long.class, long.class, PageIO[].class ); + Method readLongMethod = RecordManager.class.getDeclaredMethod( "readLong", PageIO[].class, long.class ); + storeMethod.setAccessible( true ); + readLongMethod.setAccessible( true ); + + // Allocate some Pages + PageIO[] pageIos = new PageIO[2]; + pageIos[0] = new PageIO(); + pageIos[0].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[1] = new PageIO(); + pageIos[1].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + + // Set the int at the beginning + storeMethod.invoke( recordManager, 0, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + long readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 0 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the end of the first page + storeMethod.invoke( recordManager, 12, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 12 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 1 byte overlapping + storeMethod.invoke( recordManager, 13, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 13 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 2 bytes overlapping + storeMethod.invoke( recordManager, 14, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 14 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 3 bytes overlapping + storeMethod.invoke( recordManager, 15, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 15 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 4 bytes overlapping + storeMethod.invoke( recordManager, 16, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 16 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 5 bytes overlapping + storeMethod.invoke( recordManager, 17, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 17 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 6 bytes overlapping + storeMethod.invoke( recordManager, 18, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 18 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the end of the first page and overlapping on the second page + // 7 bytes overlapping + storeMethod.invoke( recordManager, 19, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 19 ); + + assertEquals( 0x0123456789ABCDEFL, readValue ); + + // Set the int at the beginning of the second page + storeMethod.invoke( recordManager, 20, 0x0123456789ABCDEFL, pageIos ); + + // Read it back + readValue = ( Long ) readLongMethod.invoke( recordManager, pageIos, 20 ); + + recordManager.close(); + } + + + /** + * Test the readBytes() method + */ + @Test + public void testReadBytes() throws Exception + { + File tempFile = tempFolder.newFile( "mavibot.db" ); + String tempFileName = tempFile.getAbsolutePath(); + + // We use smaller pages + RecordManager recordManager = new RecordManager( tempFileName, 32 ); + Method storeMethod = RecordManager.class.getDeclaredMethod( "store", long.class, byte[].class, PageIO[].class ); + Method readBytesMethod = RecordManager.class.getDeclaredMethod( "readBytes", PageIO[].class, long.class ); + storeMethod.setAccessible( true ); + readBytesMethod.setAccessible( true ); + + // Allocate some Pages + PageIO[] pageIos = new PageIO[4]; + pageIos[0] = new PageIO(); + pageIos[0].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[1] = new PageIO(); + pageIos[1].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[2] = new PageIO(); + pageIos[2].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[3] = new PageIO(); + pageIos[3].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + + // We start with 4 bytes + byte[] bytes = new byte[] + { 0x01, 0x23, 0x45, 0x67 }; + + // Set the bytes at the beginning + storeMethod.invoke( recordManager, 0L, bytes, pageIos ); + + // Read the bytes back + ByteBuffer readBytes = ( ByteBuffer ) readBytesMethod.invoke( recordManager, pageIos, 0L ); + + // The byte length + assertNotNull( readBytes ); + assertEquals( 4, readBytes.limit() ); + // The data + assertEquals( 0x01, readBytes.get() ); + assertEquals( 0x23, readBytes.get() ); + assertEquals( 0x45, readBytes.get() ); + assertEquals( 0x67, readBytes.get() ); + + // Set the bytes at the end of the first page + storeMethod.invoke( recordManager, 12L, bytes, pageIos ); + + // Read the bytes back + readBytes = ( ByteBuffer ) readBytesMethod.invoke( recordManager, pageIos, 12L ); + + // The byte length + assertNotNull( readBytes ); + assertEquals( 4, readBytes.limit() ); + // The data + assertEquals( 0x01, readBytes.get() ); + assertEquals( 0x23, readBytes.get() ); + assertEquals( 0x45, readBytes.get() ); + assertEquals( 0x67, readBytes.get() ); + + // Set A full page of bytes in the first page + bytes = new byte[16]; + + for ( int i = 0; i < 16; i++ ) + { + bytes[i] = ( byte ) ( i + 1 ); + } + + storeMethod.invoke( recordManager, 0L, bytes, pageIos ); + + // Read the bytes back + readBytes = ( ByteBuffer ) readBytesMethod.invoke( recordManager, pageIos, 0L ); + + // The byte length + assertNotNull( readBytes ); + assertEquals( 16, readBytes.limit() ); + + // The data + for ( int i = 0; i < 16; i++ ) + { + assertEquals( i + 1, readBytes.get() ); + } + + // Write the bytes over 2 pages + storeMethod.invoke( recordManager, 15L, bytes, pageIos ); + + // Read the bytes back + readBytes = ( ByteBuffer ) readBytesMethod.invoke( recordManager, pageIos, 15L ); + + // The byte length + assertNotNull( readBytes ); + assertEquals( 16, readBytes.limit() ); + // The data + for ( int i = 0; i < 16; i++ ) + { + assertEquals( i + 1, readBytes.get() ); + } + + // Write the bytes over 4 pages + bytes = new byte[80]; + + for ( int i = 0; i < 80; i++ ) + { + bytes[i] = ( byte ) ( i + 1 ); + } + + storeMethod.invoke( recordManager, 2L, bytes, pageIos ); + + // Read the bytes back + readBytes = ( ByteBuffer ) readBytesMethod.invoke( recordManager, pageIos, 2L ); + + // The byte length + assertNotNull( readBytes ); + assertEquals( 80, readBytes.limit() ); + + // The data + for ( int i = 0; i < 80; i++ ) + { + assertEquals( i + 1, readBytes.get() ); + } + + recordManager.close(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedStoreTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedStoreTest.java new file mode 100644 index 000000000..47f8057e5 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedStoreTest.java @@ -0,0 +1,462 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * Test the RecordManager.store() method using reflection + * + * @author Apache Directory Project + */ +public class PersistedStoreTest +{ + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + + /** + * Test the store( int ) method + */ + @Test + public void testInjectInt() throws Exception + { + File tempFile = tempFolder.newFile( "mavibot.db" ); + String tempFileName = tempFile.getAbsolutePath(); + + RecordManager recordManager = new RecordManager( tempFileName, 4 * 1024 ); + Method method = RecordManager.class.getDeclaredMethod( "store", long.class, int.class, PageIO[].class ); + method.setAccessible( true ); + + // Allocate some Pages + PageIO[] pageIos = new PageIO[2]; + pageIos[0] = new PageIO(); + pageIos[0].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[1] = new PageIO(); + pageIos[1].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + + // Set the int at the beginning + long position = ( Long ) method.invoke( recordManager, 0, 0x12345678, pageIos ); + + assertEquals( 4, position ); + int pos = 12; + assertEquals( 0x12, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x34, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x56, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x78, pageIos[0].getData().get( pos++ ) ); + + // Set the int at the end of the first page + position = ( Long ) method.invoke( recordManager, 4080, 0x12345678, pageIos ); + + assertEquals( 4084, position ); + pos = 4092; + assertEquals( 0x12, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x34, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x56, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x78, pageIos[0].getData().get( pos++ ) ); + + // Set the int at the end of the first page and overlapping on the second page + // 1 byte overlapping + position = ( Long ) method.invoke( recordManager, 4081, 0x12345678, pageIos ); + + assertEquals( 4085, position ); + pos = 4093; + assertEquals( 0x12, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x34, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x56, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( 0x78, pageIos[1].getData().get( pos++ ) ); + + // Set the int at the end of the first page and overlapping on the second page + // 2 bytes overlapping + position = ( Long ) method.invoke( recordManager, 4082, 0x12345678, pageIos ); + + assertEquals( 4086, position ); + pos = 4094; + assertEquals( 0x12, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x34, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( 0x56, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x78, pageIos[1].getData().get( pos++ ) ); + + // Set the int at the end of the first page and overlapping on the second page + // 3 bytes overlapping + position = ( Long ) method.invoke( recordManager, 4083, 0x12345678, pageIos ); + + assertEquals( 4087, position ); + pos = 4095; + assertEquals( 0x12, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( 0x34, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x56, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x78, pageIos[1].getData().get( pos++ ) ); + + // Set the int at the beginning of the second page + position = ( Long ) method.invoke( recordManager, 4084, 0x12345678, pageIos ); + + assertEquals( 4088, position ); + pos = 8; + assertEquals( 0x12, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x34, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x56, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x78, pageIos[1].getData().get( pos++ ) ); + + recordManager.close(); + } + + + /** + * Test the store( long ) method + */ + @Test + public void testInjectLong() throws Exception + { + File tempFile = tempFolder.newFile( "mavibot.db" ); + String tempFileName = tempFile.getAbsolutePath(); + + RecordManager recordManager = new RecordManager( tempFileName, 4 * 1024 ); + Method method = RecordManager.class.getDeclaredMethod( "store", long.class, long.class, PageIO[].class ); + method.setAccessible( true ); + + // Allocate some Pages + PageIO[] pageIos = new PageIO[2]; + pageIos[0] = new PageIO(); + pageIos[0].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[1] = new PageIO(); + pageIos[1].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + + // Set the long at the beginning + long position = ( Long ) method.invoke( recordManager, 0, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 8, position ); + int pos = 12; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[0].getData().get( pos++ ) ); + + // Set the long at the end of the first page + position = ( Long ) method.invoke( recordManager, 4076, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4084, position ); + pos = 4088; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[0].getData().get( pos++ ) ); + + // Set the long at the end of the first page and overlapping on the second page + // 1 byte overlapping + position = ( Long ) method.invoke( recordManager, 4077, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4085, position ); + pos = 4089; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( ( byte ) 0xEF, pageIos[1].getData().get( pos++ ) ); + + // Set the long at the end of the first page and overlapping on the second page + // 2 bytes overlapping + position = ( Long ) method.invoke( recordManager, 4078, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4086, position ); + pos = 4090; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( ( byte ) 0xCD, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[1].getData().get( pos++ ) ); + + // Set the long at the end of the first page and overlapping on the second page + // 3 bytes overlapping + position = ( Long ) method.invoke( recordManager, 4079, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4087, position ); + pos = 4091; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[0].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( ( byte ) 0xAB, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[1].getData().get( pos++ ) ); + + // Set the long at the end of the first page and overlapping on the second page + // 4 byte overlapping + position = ( Long ) method.invoke( recordManager, 4080, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4088, position ); + pos = 4092; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( ( byte ) 0x89, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[1].getData().get( pos++ ) ); + + // Set the long at the end of the first page and overlapping on the second page + // 5 bytes overlapping + position = ( Long ) method.invoke( recordManager, 4081, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4089, position ); + pos = 4093; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( 0x67, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[1].getData().get( pos++ ) ); + + // Set the long at the end of the first page and overlapping on the second page + // 6 bytes overlapping + position = ( Long ) method.invoke( recordManager, 4082, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4090, position ); + pos = 4094; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( 0x45, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[1].getData().get( pos++ ) ); + + // Set the long at the end of the first page and overlapping on the second page + // 7 bytes overlapping + position = ( Long ) method.invoke( recordManager, 4083, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4091, position ); + pos = 4095; + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + pos = 8; + assertEquals( 0x23, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[1].getData().get( pos++ ) ); + + // Set the long at the beginning of the second page + position = ( Long ) method.invoke( recordManager, 4084, 0x0123456789ABCDEFL, pageIos ); + + assertEquals( 4092, position ); + pos = 8; + assertEquals( 0x01, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[1].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0x89, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xAB, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xCD, pageIos[1].getData().get( pos++ ) ); + assertEquals( ( byte ) 0xEF, pageIos[1].getData().get( pos++ ) ); + + recordManager.close(); + } + + + /** + * Test the store( bytes ) method + */ + @Test + public void testInjectBytes() throws Exception + { + File tempFile = tempFolder.newFile( "mavibot.db" ); + String tempFileName = tempFile.getAbsolutePath(); + + // We use smaller pages + RecordManager recordManager = new RecordManager( tempFileName, 32 ); + Method storeMethod = RecordManager.class.getDeclaredMethod( "store", long.class, byte[].class, PageIO[].class ); + storeMethod.setAccessible( true ); + + // Allocate some Pages + PageIO[] pageIos = new PageIO[3]; + pageIos[0] = new PageIO(); + pageIos[0].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[1] = new PageIO(); + pageIos[1].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + pageIos[2] = new PageIO(); + pageIos[2].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); +// pageIos[3] = new PageIO(); +// pageIos[3].setData( ByteBuffer.allocate( recordManager.getPageSize() ) ); + + // We start with 4 bytes + byte[] bytes = new byte[] + { 0x01, 0x23, 0x45, 0x67 }; + + // Set the bytes at the beginning + long position = ( Long ) storeMethod.invoke( recordManager, 0L, bytes, pageIos ); + + assertEquals( 8, position ); + int pos = 12; + // The byte length + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x04, pageIos[0].getData().get( pos++ ) ); + // The data + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[0].getData().get( pos++ ) ); + + // Set the bytes at the end of the first page + position = ( Long ) storeMethod.invoke( recordManager, 12L, bytes, pageIos ); + + assertEquals( 20, position ); + pos = 24; + // The byte length + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x04, pageIos[0].getData().get( pos++ ) ); + // The data + assertEquals( 0x01, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x23, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x45, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x67, pageIos[0].getData().get( pos++ ) ); + + // Set A full page of bytes in the first page + bytes = new byte[16]; + + for ( int i = 0; i < 16; i++ ) + { + bytes[i] = ( byte ) ( i + 1 ); + } + + position = ( Long ) storeMethod.invoke( recordManager, 0L, bytes, pageIos ); + + assertEquals( 20, position ); + pos = 12; + // The byte length + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x10, pageIos[0].getData().get( pos++ ) ); + + // The data + for ( int i = 0; i < 16; i++ ) + { + assertEquals( ( byte ) ( i + 1 ), pageIos[0].getData().get( pos++ ) ); + } + + // Write the bytes over 2 pages + position = ( Long ) storeMethod.invoke( recordManager, 47L, bytes, pageIos ); + + assertEquals( 67, position ); + pos = 59; + // The byte length + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x10, pageIos[0].getData().get( pos++ ) ); + + // The data in the first page + assertEquals( 1, pageIos[0].getData().get( pos++ ) ); + + // and in the second page + pos = 8; + + for ( int i = 0; i < 15; i++ ) + { + assertEquals( ( byte ) ( i + 2 ), pageIos[1].getData().get( pos++ ) ); + } + + // Write the bytes over 4 pages + bytes = new byte[112]; + + for ( int i = 0; i < 112; i++ ) + { + bytes[i] = ( byte ) ( i + 1 ); + } + + position = ( Long ) storeMethod.invoke( recordManager, 2L, bytes, pageIos ); + + assertEquals( 118, position ); + pos = 14; + // The byte length + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x00, pageIos[0].getData().get( pos++ ) ); + assertEquals( 0x70, pageIos[0].getData().get( pos++ ) ); + + // The data in the first page + for ( int i = 0; i < 46; i++ ) + { + assertEquals( ( byte ) ( i + 1 ), pageIos[0].getData().get( pos++ ) ); + } + + // The data in the second page + pos = 8; + + for ( int i = 46; i < 102; i++ ) + { + assertEquals( ( byte ) ( i + 1 ), pageIos[1].getData().get( pos++ ) ); + } + + // The data in the third page + pos = 8; + + for ( int i = 102; i < 112; i++ ) + { + assertEquals( ( byte ) ( i + 1 ), pageIos[2].getData().get( pos++ ) ); + } + + recordManager.close(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedSubBtreeKeyCursorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedSubBtreeKeyCursorTest.java new file mode 100644 index 000000000..0d5d81ff1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedSubBtreeKeyCursorTest.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.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +import org.apache.commons.io.FileUtils; +import org.apache.directory.mavibot.btree.serializer.IntSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * Tests for KeyCursor of a persisted sub-Btree. + * + * @author Apache Directory Project + */ +public class PersistedSubBtreeKeyCursorTest +{ + private BTree btree = null; + + private RecordManager recordManager = null; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File dataDir = null; + + + @Before + public void createBTree() throws IOException + { + dataDir = tempFolder.newFolder( UUID.randomUUID().toString() ); + + // Now, try to reload the file back + recordManager = new RecordManager( dataDir.getAbsolutePath() ); + + try + { + PersistedBTreeConfiguration configuration = new PersistedBTreeConfiguration(); + configuration.setAllowDuplicates( false ); + configuration.setKeySerializer( IntSerializer.INSTANCE ); + configuration.setValueSerializer( IntSerializer.INSTANCE ); + configuration.setName( "sub-btree" ); + configuration.setBtreeType( BTreeTypeEnum.PERSISTED_SUB ); + + btree = BTreeFactory.createPersistedBTree( configuration ); + + recordManager.manage( btree ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @After + public void cleanup() throws IOException + { + dataDir = new File( System.getProperty( "java.io.tmpdir" ) + "/recordman" ); + + btree.close(); + + if ( dataDir.exists() ) + { + FileUtils.deleteDirectory( dataDir ); + } + + recordManager.close(); + assertTrue( recordManager.isContextOk() ); + } + + + @Test + public void testBrowseKeys() throws Exception + { + for ( int i = 0; i < 10; i++ ) + { + // only the keys are stored, values are ignored + btree.insert( i, i ); + } + + KeyCursor cursor = btree.browseKeys(); + + for ( int i = 0; i < 10; i++ ) + { + assertTrue( cursor.hasNext() ); + assertEquals( String.valueOf( i ), String.valueOf( cursor.next() ) ); + } + + assertFalse( cursor.hasNext() ); + + cursor.afterLast(); + + for ( int i = 9; i >= 0; i-- ) + { + assertTrue( cursor.hasPrev() ); + assertEquals( String.valueOf( i ), String.valueOf( cursor.prev() ) ); + } + + assertFalse( cursor.hasPrev() ); + cursor.close(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerFreePageTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerFreePageTest.java new file mode 100644 index 000000000..314b76831 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerFreePageTest.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Set; + +import org.apache.commons.io.FileUtils; +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + +/** + * test the RecordManager's free page management + * + * @author Apache Directory Project + */ +public class RecordManagerFreePageTest +{ + private BTree btree = null; + + private RecordManager recordManager1 = null; + + private File dataDir = null; + + + @Before + public void createBTree() throws IOException + { + dataDir = new File( System.getProperty( "java.io.tmpdir" ) + "/recordman" ); + + if ( dataDir.exists() ) + { + FileUtils.deleteDirectory( dataDir ); + } + + dataDir.mkdirs(); + + openRecordManagerAndBtree(); + + try + { + // Create a new BTree + btree = recordManager1.addBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @After + public void cleanup() throws IOException + { + dataDir = new File( System.getProperty( "java.io.tmpdir" ) + "/recordman" ); + + btree.close(); + + recordManager1.close(); + assertTrue( recordManager1.isContextOk() ); + + if ( dataDir.exists() ) + { + FileUtils.deleteDirectory( dataDir ); + } + } + + + private void openRecordManagerAndBtree() + { + try + { + if ( recordManager1 != null ) + { + recordManager1.close(); + } + + // Now, try to reload the file back + recordManager1 = new RecordManager( dataDir.getAbsolutePath() ); + + // load the last created btree + if ( btree != null ) + { + btree = recordManager1.getManagedTree( btree.getName() ); + } + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + private int nbElems = 10000; + + + /** + * Test the creation of a RecordManager, and that we can read it back. + */ + @Test + public void testRecordManager() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + assertEquals( 1, recordManager1.getNbManagedTrees() ); + + Set managedBTrees = recordManager1.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + int nbError = 0; + + long l1 = System.currentTimeMillis(); + int n = 0; + long delta = l1; + + for ( int i = 0; i < nbElems; i++ ) + { + // System.out.println( i ); + Long key = ( long ) i; + String value = Long.toString( key ); + + btree.insert( key, value ); + + if ( i % 10000 == 0 ) + { + if ( n > 0 ) + { + long t0 = System.currentTimeMillis(); + System.out.println( "Written " + i + " elements in : " + ( t0 - delta ) + "ms" ); + delta = t0; + } + + n++; + } + } + + long l2 = System.currentTimeMillis(); + + System.out.println( "Delta : " + ( l2 - l1 ) + ", nbError = " + nbError + + ", Nb insertion per second : " + ( ( nbElems ) / ( l2 - l1 ) ) * 1000 ); + + long length = new File( dataDir, "mavibot.db" ).length(); + String units = "MB"; + + long size = length / ( 1024 * 1024 ); + + if ( size == 0 ) + { + size = length / 1024; + units = "KB"; + } + + // System.out.println( size + units ); + + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager1.getNbManagedTrees() ); + + assertTrue( nbElems == btree.getNbElems() ); + + TupleCursor cursor = btree.browse(); + + long i = 0; + + while ( cursor.hasNext() ) + { + Tuple t = cursor.next(); + assertEquals( ( Long ) i, t.getKey() ); + assertEquals( String.valueOf( i ), t.getValue() ); + i++; + } + + cursor.close(); + + assertEquals( nbElems, i ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerPrivateMethodTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerPrivateMethodTest.java new file mode 100644 index 000000000..da9f579f7 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerPrivateMethodTest.java @@ -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. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.UUID; + +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * Test some of the RecordManager prvate methods + * + * @author Apache Directory Project + */ +public class RecordManagerPrivateMethodTest +{ + private BTree btree = null; + + private RecordManager recordManager = null; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File dataDir = null; + + + @Before + public void createRecordManager() throws Exception + { + dataDir = tempFolder.newFolder( UUID.randomUUID().toString() ); + + System.out.println( dataDir + "/mavibot.db" ); + + // Now, try to reload the file back + recordManager = new RecordManager( dataDir.getAbsolutePath(), 32 ); + + // Create a new BTree + btree = recordManager.addBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + } + + + @After + public void closeBTree() throws IOException + { + recordManager.close(); + } + + + /** + * Test the getFreePageIOs method + */ + @Test + public void testGetFreePageIos() throws IOException, NoSuchMethodException, InvocationTargetException, + IllegalAccessException + { + Method getFreePageIOsMethod = RecordManager.class.getDeclaredMethod( "getFreePageIOs", int.class ); + getFreePageIOsMethod.setAccessible( true ); + + PageIO[] pages = ( org.apache.directory.mavibot.btree.PageIO[] ) getFreePageIOsMethod.invoke( recordManager, 0 ); + + assertEquals( 0, pages.length ); + + for ( int i = 1; i <= 52; i++ ) + { + pages = ( org.apache.directory.mavibot.btree.PageIO[] ) getFreePageIOsMethod.invoke( recordManager, i ); + assertEquals( 1, pages.length ); + } + + for ( int i = 53; i <= 108; i++ ) + { + pages = ( org.apache.directory.mavibot.btree.PageIO[] ) getFreePageIOsMethod.invoke( recordManager, i ); + assertEquals( 2, pages.length ); + } + + for ( int i = 109; i <= 164; i++ ) + { + pages = ( org.apache.directory.mavibot.btree.PageIO[] ) getFreePageIOsMethod.invoke( recordManager, i ); + assertEquals( 3, pages.length ); + } + + btree.close(); + } + + + /** + * Test the ComputeNbPages method + */ + @Test + public void testComputeNbPages() throws IOException, SecurityException, NoSuchMethodException, + IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + Method computeNbPagesMethod = RecordManager.class.getDeclaredMethod( "computeNbPages", int.class ); + computeNbPagesMethod.setAccessible( true ); + + assertEquals( 0, ( ( Integer ) computeNbPagesMethod.invoke( recordManager, 0 ) ).intValue() ); + + for ( int i = 1; i < 53; i++ ) + { + assertEquals( 1, ( ( Integer ) computeNbPagesMethod.invoke( recordManager, i ) ).intValue() ); + } + + for ( int i = 53; i < 109; i++ ) + { + assertEquals( 2, ( ( Integer ) computeNbPagesMethod.invoke( recordManager, i ) ).intValue() ); + } + + for ( int i = 109; i < 164; i++ ) + { + assertEquals( 3, ( ( Integer ) computeNbPagesMethod.invoke( recordManager, i ) ).intValue() ); + } + + btree.close(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerTest.java new file mode 100644 index 000000000..d2dd0859e --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerTest.java @@ -0,0 +1,962 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.apache.commons.io.FileUtils; +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * test the RecordManager + * + * @author Apache Directory Project + */ +public class RecordManagerTest +{ + private BTree btree = null; + + private RecordManager recordManager = null; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File dataDir = null; + + + @Before + public void createBTree() throws IOException + { + dataDir = tempFolder.newFolder( UUID.randomUUID().toString() ); + + openRecordManagerAndBtree(); + + try + { + // Create a new BTree + btree = recordManager.addBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE, false ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @After + public void cleanup() throws IOException + { + btree.close(); + + recordManager.close(); + + if ( dataDir.exists() ) + { + FileUtils.deleteDirectory( dataDir ); + } + } + + + private void openRecordManagerAndBtree() + { + try + { + if ( recordManager != null ) + { + recordManager.close(); + } + + // Now, try to reload the file back + recordManager = new RecordManager( dataDir.getAbsolutePath() ); + + // load the last created btree + if ( btree != null ) + { + btree = recordManager.getManagedTree( btree.getName() ); + } + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + /** + * Test the creation of a RecordManager, and that we can read it back. + */ + @Test + @Ignore + public void testRecordManager() throws IOException, BTreeAlreadyManagedException + { + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + } + + + /** + * Test the creation of a RecordManager with a BTree containing data. + */ + @Test + public void testRecordManagerWithBTree() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + // Now, add some elements in the BTree + btree.insert( 3L, "V3" ); + btree.insert( 1L, "V1" ); + btree.insert( 5L, "V5" ); + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + + // Check the stored element + assertTrue( btree1.hasKey( 1L ) ); + assertTrue( btree1.hasKey( 3L ) ); + assertTrue( btree1.hasKey( 5L ) ); + assertEquals( "V1", btree1.get( 1L ) ); + assertEquals( "V3", btree1.get( 3L ) ); + assertEquals( "V5", btree1.get( 5L ) ); + } + + + /** + * Test the creation of a RecordManager with a BTree containing data, enough for some Node to be created. + */ + @Test + public void testRecordManagerWithBTreeLeafNode() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + // Now, add some elements in the BTree + for ( long i = 1L; i < 32L; i++ ) + { + btree.insert( i, "V" + i ); + } + + for ( long i = 1L; i < 32L; i++ ) + { + if ( !btree.hasKey( i ) ) + { + System.out.println( "Not found !!! " + i ); + } + assertTrue( btree.hasKey( i ) ); + assertEquals( "V" + i, btree.get( i ) ); + } + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + + // Check the stored element + for ( long i = 1L; i < 32L; i++ ) + { + if ( !btree1.hasKey( i ) ) + { + System.out.println( "Not found " + i ); + } + assertTrue( btree1.hasKey( i ) ); + assertEquals( "V" + i, btree1.get( i ) ); + } + } + + + /** + * Test the creation of a RecordManager with a BTree containing 100 000 elements + */ + @Test + @Ignore("This is a performance test") + public void testRecordManagerWithBTreeLeafNode100K() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + // Don't keep any revision + recordManager.setKeepRevisions( false ); + + String fileName = dataDir.getAbsolutePath() + "/mavibot.db"; + File file = new File( fileName ); + long fileSize = file.length(); + long nbElems = 100000L; + System.out.println( "----- Size before = " + fileSize ); + + // Now, add some elements in the BTree + long t0 = System.currentTimeMillis(); + + for ( Long i = 0L; i < nbElems; i++ ) + { + String value = "V" + i; + btree.insert( i, value ); + + /* + if ( !recordManager1.check() ) + { + System.out.println( "Failure while adding element " + i ); + fail(); + } + */ + + if ( i % 10000 == 0 ) + { + fileSize = file.length(); + System.out.println( "----- Size after insertion of " + i + " = " + fileSize ); + System.out.println( recordManager ); + //System.out.println( btree ); + } + } + long t1 = System.currentTimeMillis(); + + fileSize = file.length(); + System.out.println( "Size after insertion of 100 000 elements : " + fileSize ); + System.out.println( "Time taken to write 100 000 elements : " + ( t1 - t0 ) ); + System.out.println( " Nb elem/s : " + ( ( nbElems * 1000 ) / ( t1 - t0 ) ) ); + System.out.println( "Nb created page " + recordManager.nbCreatedPages.get() ); + System.out.println( "Nb allocated page " + recordManager.nbReusedPages.get() ); + System.out.println( "Nb page we have freed " + recordManager.nbFreedPages.get() ); + System.out.println( recordManager ); + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + + // Check the stored element + long t2 = System.currentTimeMillis(); + for ( long i = 0L; i < nbElems; i++ ) + { + //assertTrue( btree1.exist( i ) ); + assertEquals( "V" + i, btree1.get( i ) ); + } + long t3 = System.currentTimeMillis(); + System.out.println( "Time taken to verify 100 000 elements : " + ( t3 - t2 ) ); + + // Check the stored element a second time + long t4 = System.currentTimeMillis(); + for ( long i = 0L; i < nbElems; i++ ) + { + //assertTrue( btree1.exist( i ) ); + assertEquals( "V" + i, btree1.get( i ) ); + } + long t5 = System.currentTimeMillis(); + System.out.println( "Time taken to verify 100 000 elements : " + ( t5 - t4 ) ); + } + + + private void checkBTreeRevisionBrowse( BTree btree, long revision, long... values ) + throws IOException, + KeyNotFoundException + { + TupleCursor cursor = btree.browse( revision ); + List expected = new ArrayList( values.length ); + Set found = new HashSet( values.length ); + + for ( long value : values ) + { + expected.add( value ); + } + + int nb = 0; + + while ( cursor.hasNext() ) + { + Tuple res = cursor.next(); + + long key = res.getKey(); + assertEquals( expected.get( nb ), ( Long ) key ); + assertFalse( found.contains( key ) ); + found.add( key ); + assertEquals( "V" + key, res.getValue() ); + nb++; + } + + assertEquals( values.length, nb ); + cursor.close(); + } + + + private void checkBTreeRevisionBrowseFrom( BTree btree, long revision, long from, long... values ) + throws IOException, + KeyNotFoundException + { + TupleCursor cursor = btree.browseFrom( revision, from ); + List expected = new ArrayList( values.length ); + Set found = new HashSet( values.length ); + + for ( long value : values ) + { + expected.add( value ); + } + + int nb = 0; + + while ( cursor.hasNext() ) + { + Tuple res = cursor.next(); + + long key = res.getKey(); + assertEquals( expected.get( nb ), ( Long ) key ); + assertFalse( found.contains( key ) ); + found.add( key ); + assertEquals( "V" + key, res.getValue() ); + nb++; + } + + assertEquals( values.length, nb ); + cursor.close(); + + } + + + /** + * Test the creation of a RecordManager with a BTree containing data, where we keep the revisions, + * and browse the BTree. + */ + @Test + public void testRecordManagerBrowseWithKeepRevisions() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + recordManager.setKeepRevisions( true ); + + // Now, add some elements in the BTree + btree.insert( 3L, "V3" ); + long rev1 = btree.getRevision(); + + btree.insert( 1L, "V1" ); + long rev2 = btree.getRevision(); + + btree.insert( 5L, "V5" ); + long rev3 = btree.getRevision(); + + // Check that we can browse each revision + // revision 1 + checkBTreeRevisionBrowse( btree, rev1, 3L ); + + // Revision 2 + checkBTreeRevisionBrowse( btree, rev2, 1L, 3L ); + + // Revision 3 + checkBTreeRevisionBrowse( btree, rev3, 1L, 3L, 5L ); + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + + // Check the stored element + assertTrue( btree1.hasKey( 1L ) ); + assertTrue( btree1.hasKey( 3L ) ); + assertTrue( btree1.hasKey( 5L ) ); + assertEquals( "V1", btree1.get( 1L ) ); + assertEquals( "V3", btree1.get( 3L ) ); + assertEquals( "V5", btree1.get( 5L ) ); + + // Check that we can read the revision again + // revision 1 + checkBTreeRevisionBrowse( btree, rev1 ); + + // Revision 2 + checkBTreeRevisionBrowse( btree, rev2 ); + + // Revision 3 + checkBTreeRevisionBrowse( btree, rev3, 1L, 3L, 5L ); + } + + + /** + * Test the creation of a RecordManager with a BTree containing data, where we keep the revision, and + * we browse from a key + */ + @Test + public void testRecordManagerBrowseFromWithRevision() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + recordManager.setKeepRevisions( true ); + + // Now, add some elements in the BTree + btree.insert( 3L, "V3" ); + long rev1 = btree.getRevision(); + + btree.insert( 1L, "V1" ); + long rev2 = btree.getRevision(); + + btree.insert( 5L, "V5" ); + long rev3 = btree.getRevision(); + + // Check that we can browse each revision + // revision 1 + checkBTreeRevisionBrowseFrom( btree, rev1, 3L, 3L ); + + // Revision 2 + checkBTreeRevisionBrowseFrom( btree, rev2, 3L, 3L ); + + // Revision 3 + checkBTreeRevisionBrowseFrom( btree, rev3, 3L, 3L, 5L ); + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + + // Check the stored element + assertTrue( btree1.hasKey( 1L ) ); + assertTrue( btree1.hasKey( 3L ) ); + assertTrue( btree1.hasKey( 5L ) ); + assertEquals( "V1", btree1.get( 1L ) ); + assertEquals( "V3", btree1.get( 3L ) ); + assertEquals( "V5", btree1.get( 5L ) ); + + // Check that we can read the revision again + // revision 1 + checkBTreeRevisionBrowseFrom( btree, rev1, 3L ); + + // Revision 2 + checkBTreeRevisionBrowseFrom( btree, rev2, 3L ); + + // Revision 3 + checkBTreeRevisionBrowseFrom( btree, rev3, 3L, 3L, 5L ); + } + + + /** + * Test a get() from a given revision + */ + @Test + public void testGetWithRevision() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + recordManager.setKeepRevisions( true ); + + // Now, add some elements in the BTree + btree.insert( 3L, "V3" ); + long rev1 = btree.getRevision(); + + btree.insert( 1L, "V1" ); + long rev2 = btree.getRevision(); + + btree.insert( 5L, "V5" ); + long rev3 = btree.getRevision(); + + // Delete one element + btree.delete( 3L ); + long rev4 = btree.getRevision(); + + // Check that we can get a value from each revision + // revision 1 + assertEquals( "V3", btree.get( rev1, 3L ) ); + + // revision 2 + assertEquals( "V1", btree.get( rev2, 1L ) ); + assertEquals( "V3", btree.get( rev2, 3L ) ); + + // revision 3 + assertEquals( "V1", btree.get( rev3, 1L ) ); + assertEquals( "V3", btree.get( rev3, 3L ) ); + assertEquals( "V5", btree.get( rev3, 5L ) ); + + // revision 4 + assertEquals( "V1", btree.get( rev4, 1L ) ); + assertEquals( "V5", btree.get( rev4, 5L ) ); + + try + { + btree.get( rev4, 3L ); + fail(); + } + catch ( KeyNotFoundException knfe ) + { + // expected + } + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + + // Check the stored element + assertTrue( btree1.hasKey( 1L ) ); + assertFalse( btree1.hasKey( 3L ) ); + assertTrue( btree1.hasKey( 5L ) ); + assertEquals( "V1", btree1.get( 1L ) ); + assertEquals( "V5", btree1.get( 5L ) ); + + // Check that we can get a value from each revision + // revision 1 + checkBTreeRevisionBrowse( btree, rev1 ); + + // revision 2 + checkBTreeRevisionBrowse( btree, rev2 ); + + // revision 3 + checkBTreeRevisionBrowse( btree, rev3 ); + + // revision 4 + checkBTreeRevisionBrowse( btree, rev4, 1L, 5L ); + + try + { + btree.get( rev4, 3L ); + fail(); + } + catch ( KeyNotFoundException knfe ) + { + // expected + } + } + + + /** + * Test a contain() from a given revision + */ + @Test + public void testContainWithRevision() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + recordManager.setKeepRevisions( true ); + + // Now, add some elements in the BTree + btree.insert( 3L, "V3" ); + long rev1 = btree.getRevision(); + + btree.insert( 1L, "V1" ); + long rev2 = btree.getRevision(); + + btree.insert( 5L, "V5" ); + long rev3 = btree.getRevision(); + + // Delete one element + btree.delete( 3L ); + long rev4 = btree.getRevision(); + + // Check that we can get a value from each revision + // revision 1 + assertFalse( btree.contains( rev1, 1L, "V1" ) ); + assertTrue( btree.contains( rev1, 3L, "V3" ) ); + assertFalse( btree.contains( rev1, 5L, "V5" ) ); + + // revision 2 + assertTrue( btree.contains( rev2, 1L, "V1" ) ); + assertTrue( btree.contains( rev2, 3L, "V3" ) ); + assertFalse( btree.contains( rev2, 5L, "V5" ) ); + + // revision 3 + assertTrue( btree.contains( rev3, 1L, "V1" ) ); + assertTrue( btree.contains( rev3, 3L, "V3" ) ); + assertTrue( btree.contains( rev3, 5L, "V5" ) ); + + // revision 4 + assertTrue( btree.contains( rev4, 1L, "V1" ) ); + assertFalse( btree.contains( rev4, 3L, "V3" ) ); + assertTrue( btree.contains( rev4, 5L, "V5" ) ); + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + + // Check the stored element + assertTrue( btree1.hasKey( 1L ) ); + assertFalse( btree1.hasKey( 3L ) ); + assertTrue( btree1.hasKey( 5L ) ); + assertEquals( "V1", btree1.get( 1L ) ); + assertEquals( "V5", btree1.get( 5L ) ); + + // Check that we can get a value from each revision + // revision 1 + assertFalse( btree.contains( rev1, 1L, "V1" ) ); + assertFalse( btree.contains( rev1, 3L, "V3" ) ); + assertFalse( btree.contains( rev1, 5L, "V5" ) ); + + // revision 2 + assertFalse( btree.contains( rev2, 1L, "V1" ) ); + assertFalse( btree.contains( rev2, 3L, "V3" ) ); + assertFalse( btree.contains( rev2, 5L, "V5" ) ); + + // revision 3 + assertFalse( btree.contains( rev3, 1L, "V1" ) ); + assertFalse( btree.contains( rev3, 3L, "V3" ) ); + assertFalse( btree.contains( rev3, 5L, "V5" ) ); + + // revision 4 + assertTrue( btree.contains( rev4, 1L, "V1" ) ); + assertFalse( btree.contains( rev4, 3L, "V3" ) ); + assertTrue( btree.contains( rev4, 5L, "V5" ) ); + } + + + /** + * Test a hasKey() from a given revision + */ + @Test + public void testHasKeyWithRevision() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + recordManager.setKeepRevisions( true ); + + // Now, add some elements in the BTree + btree.insert( 3L, "V3" ); + long rev1 = btree.getRevision(); + + btree.insert( 1L, "V1" ); + long rev2 = btree.getRevision(); + + btree.insert( 5L, "V5" ); + long rev3 = btree.getRevision(); + + // Delete one element + btree.delete( 3L ); + long rev4 = btree.getRevision(); + + // Check that we can get a value from each revision + // revision 1 + assertFalse( btree.hasKey( rev1, 1L ) ); + assertTrue( btree.hasKey( rev1, 3L ) ); + assertFalse( btree.hasKey( rev1, 5L ) ); + + // revision 2 + assertTrue( btree.hasKey( rev2, 1L ) ); + assertTrue( btree.hasKey( rev2, 3L ) ); + assertFalse( btree.hasKey( rev2, 5L ) ); + + // revision 3 + assertTrue( btree.hasKey( rev3, 1L ) ); + assertTrue( btree.hasKey( rev3, 3L ) ); + assertTrue( btree.hasKey( rev3, 5L ) ); + + // revision 4 + assertTrue( btree.hasKey( rev4, 1L ) ); + assertFalse( btree.hasKey( rev4, 3L ) ); + assertTrue( btree.hasKey( rev4, 5L ) ); + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + + // Check the stored element + assertTrue( btree1.hasKey( 1L ) ); + assertFalse( btree1.hasKey( 3L ) ); + assertTrue( btree1.hasKey( 5L ) ); + assertEquals( "V1", btree1.get( 1L ) ); + assertEquals( "V5", btree1.get( 5L ) ); + + // Check that we can get a value from each revision + // revision 1 + assertFalse( btree.hasKey( rev1, 1L ) ); + assertFalse( btree.hasKey( rev1, 3L ) ); + assertFalse( btree.hasKey( rev1, 5L ) ); + + // revision 2 + assertFalse( btree.hasKey( rev2, 1L ) ); + assertFalse( btree.hasKey( rev2, 3L ) ); + assertFalse( btree.hasKey( rev2, 5L ) ); + + // revision 3 + assertFalse( btree.hasKey( rev3, 1L ) ); + assertFalse( btree.hasKey( rev3, 3L ) ); + assertFalse( btree.hasKey( rev3, 5L ) ); + + // revision 4 + assertTrue( btree.hasKey( rev4, 1L ) ); + assertFalse( btree.hasKey( rev4, 3L ) ); + assertTrue( btree.hasKey( rev4, 5L ) ); + } + + + /** + * Test with BTrees containing duplicate keys + */ + @Test + public void testBTreesDuplicateKeys() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + int pageSize = 16; + int numKeys = 1; + String name = "duplicateTree"; + String[] testValues = new String[] + { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "10" }; + + BTree dupsTree = BTreeFactory.createPersistedBTree( name, LongSerializer.INSTANCE, + StringSerializer.INSTANCE, pageSize, true ); + + recordManager.manage( dupsTree ); + + for ( long i = 0; i < numKeys; i++ ) + { + for ( int k = 0; k < pageSize + 1; k++ ) + { + dupsTree.insert( i, testValues[k] ); + } + } + + // Now, try to reload the file back + openRecordManagerAndBtree(); + + dupsTree = recordManager.getManagedTree( name ); + + for ( long i = 0; i < numKeys; i++ ) + { + ValueCursor values = dupsTree.getValues( i ); + + for ( int k = 0; k < pageSize + 1; k++ ) + { + assertTrue( values.next().equals( testValues[k] ) ); + } + } + } + + + @Test + public void testAdds() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + btree.insert( 1L, "V1" ); + btree.insert( 2L, "V2" ); + } + + + @Ignore + @Test + public void testAddInTxns() throws IOException, BTreeAlreadyManagedException, KeyNotFoundException + { + /* + for ( Long key : recordManager.writeCounter.keySet() ) + { + System.out.println( "Page " + Long.toHexString( key ) + " written " + recordManager.writeCounter.get( key ) + + " times" ); + } + + System.out.println( "Test start" ); + */ + recordManager.beginTransaction(); + /* + System.out.println( "Before V1" ); + for ( Long key : recordManager.writeCounter.keySet() ) + { + System.out.println( "Page " + Long.toHexString( key ) + " written " + recordManager.writeCounter.get( key ) + + " times" ); + } + */ + btree.insert( 1L, "V1" ); + /* + for ( Long key : recordManager.writeCounter.keySet() ) + { + System.out.println( "Page " + Long.toHexString( key ) + " written " + recordManager.writeCounter.get( key ) + + " times" ); + } + + System.out.println( "After V1" ); + */ + + //System.out.println( "Before V2" ); + btree.insert( 2L, "V2" ); + //System.out.println( "After V2" ); + + //System.out.println( "Before V3" ); + btree.insert( 3L, "V3" ); + /* + for ( Long key : recordManager.writeCounter.keySet() ) + { + System.out.println( "Page " + Long.toHexString( key ) + " written " + recordManager.writeCounter.get( key ) + + " times" ); + } + */ + + recordManager.commit(); + + /* + for ( Long key : recordManager.writeCounter.keySet() ) + { + System.out.println( "Page " + Long.toHexString( key ) + " written " + recordManager.writeCounter.get( key ) + + " times" ); + } + */ + } + + + @Test + public void testInspector() throws Exception + { + MavibotInspector inspector = new MavibotInspector( new File( "/Users/elecharny/Downloads/mavibot.db" ) ); + inspector.start(); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerWithDuplicatesTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerWithDuplicatesTest.java new file mode 100644 index 000000000..cd8369677 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerWithDuplicatesTest.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.UUID; + +import org.apache.commons.io.FileUtils; +import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; +import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + + +/** + * test the RecordManager whith duplicate values + * + * @author Apache Directory Project + */ +public class RecordManagerWithDuplicatesTest +{ + private BTree btree = null; + + private RecordManager recordManager = null; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File dataDir = null; + + + @Before + public void createBTree() throws IOException + { + dataDir = tempFolder.newFolder( UUID.randomUUID().toString() ); + + openRecordManagerAndBtree(); + + try + { + // Create a new BTree which allows duplicate values + btree = recordManager.addBTree( "test", LongSerializer.INSTANCE, StringSerializer.INSTANCE, true ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + @After + public void cleanup() throws IOException + { + btree.close(); + + recordManager.close(); + assertTrue( recordManager.isContextOk() ); + + if ( dataDir.exists() ) + { + FileUtils.deleteDirectory( dataDir ); + } + } + + + private void openRecordManagerAndBtree() + { + try + { + if ( recordManager != null ) + { + recordManager.close(); + } + + // Now, try to reload the file back + recordManager = new RecordManager( dataDir.getAbsolutePath() ); + + // load the last created btree + btree = recordManager.getManagedTree( "test" ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + + /** + * Test the creation of a RecordManager, and that we can read it back. + */ + @Test + public void testRecordManager() throws IOException, BTreeAlreadyManagedException + { + assertEquals( 1, recordManager.getNbManagedTrees() ); + + Set managedBTrees = recordManager.getManagedTrees(); + + assertEquals( 1, managedBTrees.size() ); + assertTrue( managedBTrees.contains( "test" ) ); + + BTree btree1 = recordManager.getManagedTree( "test" ); + + assertNotNull( btree1 ); + assertEquals( btree.getKeyComparator().getClass().getName(), btree1.getKeyComparator().getClass().getName() ); + assertEquals( btree.getKeySerializer().getClass().getName(), btree1.getKeySerializer().getClass().getName() ); + assertEquals( btree.getName(), btree1.getName() ); + assertEquals( btree.getNbElems(), btree1.getNbElems() ); + assertEquals( btree.getPageSize(), btree1.getPageSize() ); + assertEquals( btree.getRevision(), btree1.getRevision() ); + assertEquals( btree.getValueSerializer().getClass().getName(), btree1.getValueSerializer().getClass().getName() ); + assertTrue( btree.isAllowDuplicates() ); + } + + + /** + * Test the creation of a RecordManager with a BTree containing data. + */ + @Test + public void testRecordManagerWithBTreeSameValue() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + // Now, add some elements in the BTree + btree.insert( 3L, "V3" ); + btree.insert( 3L, "V5" ); + + assertTrue( btree.contains( 3L, "V3" ) ); + assertTrue( btree.contains( 3L, "V5" ) ); + + // Now, try to reload the file back + openRecordManagerAndBtree(); + assertNotNull( btree ); + + assertTrue( btree.contains( 3L, "V3" ) ); + assertTrue( btree.contains( 3L, "V5" ) ); + } + + + /** + * Test the creation of a RecordManager with a BTree containing data. + */ + @Test + public void testRecordManagerWithBTreeVariousValues() throws IOException, BTreeAlreadyManagedException, + KeyNotFoundException + { + // Now, add some elements in the BTree + for ( long i = 1; i < 128; i++ ) + { + String v1 = "V" + i; + btree.insert( i, v1 ); + + String v2 = "V" + i + 1; + btree.insert( i, v2 ); + } + + // Check that the elements are present + for ( long i = 1; i < 128; i++ ) + { + String v1 = "V" + i; + String v2 = "V" + i + 1; + assertTrue( btree.contains( i, v1 ) ); + assertTrue( btree.contains( i, v2 ) ); + + } + + // Now, try to reload the file back + openRecordManagerAndBtree(); + assertNotNull( btree ); + + for ( long i = 1; i < 128; i++ ) + { + String v1 = "V" + i; + String v2 = "V" + i + 1; + assertTrue( btree.contains( i, v1 ) ); + assertTrue( btree.contains( i, v2 ) ); + + } + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RevisionNameComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RevisionNameComparatorTest.java new file mode 100644 index 000000000..8491a61fa --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RevisionNameComparatorTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the RevisionNameComparator class + * + * @author Apache Directory Project + */ +public class RevisionNameComparatorTest +{ + @Test + public void testRevisionNameComparator() + { + RevisionNameComparator comparator = RevisionNameComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( new RevisionName( 0L, "test" ), new RevisionName( 0L, "test" ) ) ); + assertEquals( 1, comparator.compare( new RevisionName( 3L, "test" ), new RevisionName( 0L, "test" ) ) ); + assertEquals( -1, comparator.compare( new RevisionName( 3L, "test" ), new RevisionName( 5L, "test" ) ) ); + assertEquals( 1, comparator.compare( new RevisionName( 3L, "test2" ), new RevisionName( 3L, "test1" ) ) ); + assertEquals( -1, comparator.compare( new RevisionName( 3L, "test" ), new RevisionName( 3L, "test2" ) ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RevisionNameSerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RevisionNameSerializerTest.java new file mode 100644 index 000000000..30ce23b26 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RevisionNameSerializerTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.apache.directory.mavibot.btree.serializer.BufferHandler; +import org.apache.directory.mavibot.btree.serializer.LongSerializer; +import org.apache.directory.mavibot.btree.serializer.StringSerializer; +import org.junit.Test; + + +/** + * Test the RevisionNameSerializer class + * + * @author Apache Directory Project + */ +public class RevisionNameSerializerTest +{ + private static RevisionNameSerializer serializer = RevisionNameSerializer.INSTANCE; + + + @Test + public void testRevisionNameSerializer() throws IOException + { + RevisionName value = null; + + try + { + serializer.serialize( value ); + fail(); + } + catch ( Exception e ) + { + //exptected + } + + // ------------------------------------------------------------------ + value = new RevisionName( 1L, null ); + byte[] result = serializer.serialize( value ); + + assertEquals( 12, result.length ); + + assertEquals( 1L, ( long ) LongSerializer.deserialize( result ) ); + assertNull( StringSerializer.deserialize( result, 8 ) ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ) ); + + // ------------------------------------------------------------------ + value = new RevisionName( 0L, "" ); + result = serializer.serialize( value ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ) ); + + // ------------------------------------------------------------------ + value = new RevisionName( 0L, "L\u00E9charny" ); + result = serializer.serialize( value ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/BooleanArrayComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/BooleanArrayComparatorTest.java new file mode 100644 index 000000000..709ae15c3 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/BooleanArrayComparatorTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the BooleanArrayComparator class + * + * @author Apache Directory Project + */ +public class BooleanArrayComparatorTest +{ + @Test + public void testBooleanArrayComparator() + { + BooleanArrayComparator comparator = BooleanArrayComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + + boolean[] b1 = new boolean[] + { true, true, true }; + boolean[] b2 = new boolean[] + { true, true, false }; + boolean[] b3 = new boolean[] + { true, false, true }; + boolean[] b4 = new boolean[] + { false, true, true }; + boolean[] b5 = new boolean[] + { true, true }; + + // 0 + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( new boolean[] + {}, new boolean[] + {} ) ); + assertEquals( 0, comparator.compare( b1, b1 ) ); + + // -1 + assertEquals( -1, comparator.compare( null, new boolean[] + {} ) ); + assertEquals( -1, comparator.compare( null, b1 ) ); + assertEquals( -1, comparator.compare( new boolean[] + {}, b1 ) ); + assertEquals( -1, comparator.compare( new boolean[] + {}, b4 ) ); + assertEquals( -1, comparator.compare( b5, b1 ) ); + assertEquals( -1, comparator.compare( b5, b3 ) ); + + // 1 + assertEquals( 1, comparator.compare( new boolean[] + {}, null ) ); + assertEquals( 1, comparator.compare( b1, null ) ); + assertEquals( 1, comparator.compare( b1, new boolean[] + {} ) ); + assertEquals( 1, comparator.compare( b1, b2 ) ); + assertEquals( 1, comparator.compare( b1, b3 ) ); + assertEquals( 1, comparator.compare( b1, b4 ) ); + assertEquals( 1, comparator.compare( b1, b5 ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/BooleanComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/BooleanComparatorTest.java new file mode 100644 index 000000000..e2daf9cdf --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/BooleanComparatorTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the BooleanComparator class + * + * @author Apache Directory Project + */ +public class BooleanComparatorTest +{ + @Test + public void testBooleanComparator() + { + BooleanComparator comparator = BooleanComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( true, true ) ); + assertEquals( 0, comparator.compare( false, false ) ); + assertEquals( 1, comparator.compare( false, null ) ); + assertEquals( 1, comparator.compare( true, null ) ); + assertEquals( 1, comparator.compare( true, false ) ); + assertEquals( -1, comparator.compare( null, false ) ); + assertEquals( -1, comparator.compare( null, true ) ); + assertEquals( -1, comparator.compare( false, true ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ByteArrayComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ByteArrayComparatorTest.java new file mode 100644 index 000000000..9a256d0d1 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ByteArrayComparatorTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the ByteArrayComparator class + * + * @author Apache Directory Project + */ +public class ByteArrayComparatorTest +{ + @Test + public void testByteArrayComparator() + { + ByteArrayComparator comparator = ByteArrayComparator.INSTANCE; + + // Check equality + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( new byte[] + {}, new byte[] + {} ) ); + assertEquals( 0, comparator.compare( new byte[] + { 0x01, 0x02 }, new byte[] + { 0x01, 0x02 } ) ); + + // The first byte[] is > the second + assertEquals( 1, comparator.compare( new byte[] + {}, null ) ); + assertEquals( 1, comparator.compare( new byte[] + { 0x01 }, null ) ); + assertEquals( 1, comparator.compare( new byte[] + { 0x01, 0x02 }, new byte[] + { 0x01, 0x01 } ) ); + assertEquals( 1, comparator.compare( new byte[] + { 0x01, 0x02, 0x01 }, new byte[] + { 0x01, 0x02 } ) ); + assertEquals( 1, comparator.compare( new byte[] + { 0x01, 0x02 }, new byte[] + { 0x01, 0x01, 0x02 } ) ); + + // The first byte[] is < the second + assertEquals( -1, comparator.compare( null, new byte[] + {} ) ); + assertEquals( -1, comparator.compare( null, new byte[] + { 0x01, 0x02 } ) ); + assertEquals( -1, comparator.compare( null, new byte[] + { ( byte ) 0xFF, 0x02 } ) ); + assertEquals( -1, comparator.compare( new byte[] + {}, new byte[] + { 0x01, 0x02 } ) ); + assertEquals( -1, comparator.compare( new byte[] + {}, new byte[] + { ( byte ) 0xFF, 0x02 } ) ); + assertEquals( -1, comparator.compare( new byte[] + { ( byte ) 0xFF, 0x01 }, new byte[] + { 0x01, 0x01, 0x02 } ) ); + byte[] array = new byte[3]; + array[0] = 0x01; + array[1] = 0x02; + assertEquals( -1, comparator.compare( new byte[] + { 0x01, 0x02 }, array ) ); + + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ByteComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ByteComparatorTest.java new file mode 100644 index 000000000..b72c5e9c2 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ByteComparatorTest.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.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the ByteComparator class + * + * @author Apache Directory Project + */ +public class ByteComparatorTest +{ + @Test + public void testByteComparator() + { + ByteComparator comparator = ByteComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( ( byte ) 0x00, ( byte ) 0x00 ) ); + assertEquals( 0, comparator.compare( ( byte ) 0xFE, ( byte ) 0xFE ) ); + assertEquals( 1, comparator.compare( ( byte ) 0x01, null ) ); + assertEquals( 1, comparator.compare( ( byte ) 0x01, ( byte ) 0x00 ) ); + assertEquals( 1, comparator.compare( ( byte ) 0x00, ( byte ) 0xFF ) ); + assertEquals( 1, comparator.compare( ( byte ) 0x7F, ( byte ) 0x01 ) ); + assertEquals( -1, comparator.compare( null, ( byte ) 0x00 ) ); + assertEquals( -1, comparator.compare( null, ( byte ) 0xFF ) ); + assertEquals( -1, comparator.compare( ( byte ) 0x00, ( byte ) 0x01 ) ); + assertEquals( -1, comparator.compare( ( byte ) 0xF0, ( byte ) 0xFF ) ); + assertEquals( -1, comparator.compare( ( byte ) 0xFF, ( byte ) 0x01 ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/CharArrayComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/CharArrayComparatorTest.java new file mode 100644 index 000000000..288bb588a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/CharArrayComparatorTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the CharArrayComparator class + * + * @author Apache Directory Project + */ +public class CharArrayComparatorTest +{ + @Test + public void testCharArrayComparator() + { + CharArrayComparator comparator = CharArrayComparator.INSTANCE; + + // Check equality + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( new char[] + {}, new char[] + {} ) ); + assertEquals( 0, comparator.compare( new char[] + { 'a', 'b' }, new char[] + { 'a', 'b' } ) ); + + // The first char[] is > the second + assertEquals( 1, comparator.compare( new char[] + {}, null ) ); + assertEquals( 1, comparator.compare( new char[] + { 'a' }, null ) ); + assertEquals( 1, comparator.compare( new char[] + { 'a', 'b' }, new char[] + { 'a', 'a' } ) ); + assertEquals( 1, comparator.compare( new char[] + { 'a', 'b', 'a' }, new char[] + { 'a', 'b' } ) ); + assertEquals( 1, comparator.compare( new char[] + { 'a', 'b' }, new char[] + { 'a', 'a', 'b' } ) ); + + // The first char[] is < the second + assertEquals( -1, comparator.compare( null, new char[] + {} ) ); + assertEquals( -1, comparator.compare( null, new char[] + { 'a', 'b' } ) ); + assertEquals( -1, comparator.compare( null, new char[] + { '\uffff', 'b' } ) ); + assertEquals( -1, comparator.compare( new char[] + {}, new char[] + { 'a', 'b' } ) ); + assertEquals( -1, comparator.compare( new char[] + {}, new char[] + { '\uffff', 'b' } ) ); + assertEquals( -1, comparator.compare( new char[] + { '0', 'a' }, new char[] + { 'a', 'a', 'b' } ) ); + char[] array = new char[3]; + array[0] = 'a'; + array[1] = 'b'; + assertEquals( -1, comparator.compare( new char[] + { 'a', 'b' }, array ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/CharComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/CharComparatorTest.java new file mode 100644 index 000000000..ec848611b --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/CharComparatorTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the CharComparator class + * + * @author Apache Directory Project + */ +public class CharComparatorTest +{ + @Test + public void testCharComparator() + { + CharComparator comparator = CharComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( 'a', 'a' ) ); + assertEquals( 0, comparator.compare( '\u00e9', '\u00e9' ) ); + assertEquals( 1, comparator.compare( 'a', null ) ); + assertEquals( -1, comparator.compare( 'A', 'a' ) ); + assertEquals( 1, comparator.compare( 'a', 'A' ) ); + assertEquals( -1, comparator.compare( null, 'a' ) ); + assertEquals( -1, comparator.compare( 'a', 'b' ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/IntArrayComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/IntArrayComparatorTest.java new file mode 100644 index 000000000..1e88ee0d5 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/IntArrayComparatorTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the IntArrayComparator class + * + * @author Apache Directory Project + */ +public class IntArrayComparatorTest +{ + @Test + public void testIntArrayComparator() + { + IntArrayComparator comparator = IntArrayComparator.INSTANCE; + + // Check equality + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( new int[] + {}, new int[] + {} ) ); + assertEquals( 0, comparator.compare( new int[] + { 1, 2 }, new int[] + { 1, 2 } ) ); + + // The first int[] is > the second + assertEquals( 1, comparator.compare( new int[] + {}, null ) ); + assertEquals( 1, comparator.compare( new int[] + { 1 }, null ) ); + assertEquals( 1, comparator.compare( new int[] + { 1, 2 }, new int[] + { 1, 1 } ) ); + assertEquals( 1, comparator.compare( new int[] + { 1, 2, 1 }, new int[] + { 1, 2 } ) ); + assertEquals( 1, comparator.compare( new int[] + { 1, 2 }, new int[] + { 1, 1, 2 } ) ); + + // The first int[] is < the second + assertEquals( -1, comparator.compare( null, new int[] + {} ) ); + assertEquals( -1, comparator.compare( null, new int[] + { 1, 2 } ) ); + assertEquals( -1, comparator.compare( null, new int[] + { -1, 2 } ) ); + assertEquals( -1, comparator.compare( new int[] + {}, new int[] + { 1, 2 } ) ); + assertEquals( -1, comparator.compare( new int[] + {}, new int[] + { -1, 2 } ) ); + assertEquals( -1, comparator.compare( new int[] + { -1, 1 }, new int[] + { 1, 1, 2 } ) ); + int[] array = new int[3]; + array[0] = 1; + array[1] = 2; + assertEquals( -1, comparator.compare( new int[] + { 1, 2 }, array ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/IntComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/IntComparatorTest.java new file mode 100644 index 000000000..27c9cf06c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/IntComparatorTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the IntComparator class + * + * @author Apache Directory Project + */ +public class IntComparatorTest +{ + @Test + public void testIntComparator() + { + IntComparator comparator = IntComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( 1, 1 ) ); + assertEquals( 0, comparator.compare( -1, -1 ) ); + assertEquals( 1, comparator.compare( 1, null ) ); + assertEquals( 1, comparator.compare( 2, 1 ) ); + assertEquals( 1, comparator.compare( 3, 1 ) ); + assertEquals( 1, comparator.compare( 1, -1 ) ); + assertEquals( -1, comparator.compare( null, 1 ) ); + assertEquals( -1, comparator.compare( 1, 2 ) ); + assertEquals( -1, comparator.compare( -1, 1 ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/LongArrayComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/LongArrayComparatorTest.java new file mode 100644 index 000000000..b2de9acd9 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/LongArrayComparatorTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the LongArrayComparator class + * + * @author Apache Directory Project + */ +public class LongArrayComparatorTest +{ + @Test + public void testLongArrayComparator() + { + LongArrayComparator comparator = LongArrayComparator.INSTANCE; + + // Check equality + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( new long[] + {}, new long[] + {} ) ); + assertEquals( 0, comparator.compare( new long[] + { 1L, 2L }, new long[] + { 1L, 2L } ) ); + + // The first long[] is > the second + assertEquals( 1, comparator.compare( new long[] + {}, null ) ); + assertEquals( 1, comparator.compare( new long[] + { 1L }, null ) ); + assertEquals( 1, comparator.compare( new long[] + { 1L, 2L }, new long[] + { 1L, 1L } ) ); + assertEquals( 1, comparator.compare( new long[] + { 1L, 2L, 1L }, new long[] + { 1L, 2L } ) ); + assertEquals( 1, comparator.compare( new long[] + { 1L, 2L }, new long[] + { 1L, 1L, 2L } ) ); + + // The first long[] is < the second + assertEquals( -1, comparator.compare( null, new long[] + {} ) ); + assertEquals( -1, comparator.compare( null, new long[] + { 1L, 2L } ) ); + assertEquals( -1, comparator.compare( null, new long[] + { -1L, 2L } ) ); + assertEquals( -1, comparator.compare( new long[] + {}, new long[] + { 1L, 2L } ) ); + assertEquals( -1, comparator.compare( new long[] + {}, new long[] + { -1L, 2L } ) ); + assertEquals( -1, comparator.compare( new long[] + { -1L, 1L }, new long[] + { 1L, 1L, 2L } ) ); + long[] array = new long[3]; + array[0] = 1L; + array[1] = 2L; + assertEquals( -1, comparator.compare( new long[] + { 1L, 2L }, array ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/LongComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/LongComparatorTest.java new file mode 100644 index 000000000..ad3a59b8f --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/LongComparatorTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the LongComparator class + * + * @author Apache Directory Project + */ +public class LongComparatorTest +{ + @Test + public void testLongComparator() + { + LongComparator comparator = LongComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( 1L, 1L ) ); + assertEquals( 0, comparator.compare( -1L, -1L ) ); + assertEquals( 1, comparator.compare( 1L, null ) ); + assertEquals( 1, comparator.compare( 2L, 1L ) ); + assertEquals( 1, comparator.compare( 3L, 1L ) ); + assertEquals( 1, comparator.compare( 1L, -1L ) ); + assertEquals( -1, comparator.compare( null, 1L ) ); + assertEquals( -1, comparator.compare( 1L, 2L ) ); + assertEquals( -1, comparator.compare( -1L, 1L ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ShortArrayComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ShortArrayComparatorTest.java new file mode 100644 index 000000000..bc536706a --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ShortArrayComparatorTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the ShortArrayComparator class + * + * @author Apache Directory Project + */ +public class ShortArrayComparatorTest +{ + @Test + public void testShortArrayComparator() + { + ShortArrayComparator comparator = ShortArrayComparator.INSTANCE; + + // Check equality + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( new short[] + {}, new short[] + {} ) ); + assertEquals( 0, comparator.compare( new short[] + { ( short ) 1, ( short ) 2 }, new short[] + { ( short ) 1, ( short ) 2 } ) ); + + // The first short[] is > the second + assertEquals( 1, comparator.compare( new short[] + {}, null ) ); + assertEquals( 1, comparator.compare( new short[] + { ( short ) 1 }, null ) ); + assertEquals( 1, comparator.compare( new short[] + { ( short ) 1, ( short ) 2 }, new short[] + { ( short ) 1, ( short ) 1 } ) ); + assertEquals( 1, comparator.compare( new short[] + { ( short ) 1, ( short ) 2, ( short ) 1 }, new short[] + { ( short ) 1, ( short ) 2 } ) ); + assertEquals( 1, comparator.compare( new short[] + { ( short ) 1, ( short ) 2 }, new short[] + { ( short ) 1, ( short ) 1, ( short ) 2 } ) ); + + // The first short[] is < the second + assertEquals( -1, comparator.compare( null, new short[] + {} ) ); + assertEquals( -1, comparator.compare( null, new short[] + { ( short ) 1, ( short ) 2 } ) ); + assertEquals( -1, comparator.compare( null, new short[] + { ( short ) -1, ( short ) 2 } ) ); + assertEquals( -1, comparator.compare( new short[] + {}, new short[] + { ( short ) 1, ( short ) 2 } ) ); + assertEquals( -1, comparator.compare( new short[] + {}, new short[] + { ( short ) -1, ( short ) 2 } ) ); + assertEquals( -1, comparator.compare( new short[] + { ( short ) -1, ( short ) 1 }, new short[] + { ( short ) 1, ( short ) 1, ( short ) 2 } ) ); + short[] array = new short[3]; + array[0] = ( short ) 1; + array[1] = ( short ) 2; + assertEquals( -1, comparator.compare( new short[] + { ( short ) 1, ( short ) 2 }, array ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ShortComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ShortComparatorTest.java new file mode 100644 index 000000000..4844f4a23 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ShortComparatorTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the ShortComparator class + * + * @author Apache Directory Project + */ +public class ShortComparatorTest +{ + @Test + public void testShortComparator() + { + ShortComparator comparator = ShortComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( ( short ) 1, ( short ) 1 ) ); + assertEquals( 0, comparator.compare( ( short ) -1, ( short ) -1 ) ); + assertEquals( 1, comparator.compare( ( short ) 1, null ) ); + assertEquals( 1, comparator.compare( ( short ) 2, ( short ) 1 ) ); + assertEquals( 1, comparator.compare( ( short ) 3, ( short ) 1 ) ); + assertEquals( 1, comparator.compare( ( short ) 1, ( short ) -1 ) ); + assertEquals( -1, comparator.compare( null, ( short ) 1 ) ); + assertEquals( -1, comparator.compare( ( short ) 1, ( short ) 2 ) ); + assertEquals( -1, comparator.compare( ( short ) -1, ( short ) 1 ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/StringComparatorTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/StringComparatorTest.java new file mode 100644 index 000000000..21fe3dc3e --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/StringComparatorTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.comparator; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Test the StringComparator class + * + * @author Apache Directory Project + */ +public class StringComparatorTest +{ + @Test + public void testStringComparator() + { + StringComparator comparator = StringComparator.INSTANCE; + + assertEquals( 0, comparator.compare( null, null ) ); + assertEquals( 0, comparator.compare( "", "" ) ); + assertEquals( 0, comparator.compare( "abc", "abc" ) ); + assertEquals( 1, comparator.compare( "", null ) ); + assertEquals( 1, comparator.compare( "abc", "" ) ); + assertEquals( 1, comparator.compare( "ac", "ab" ) ); + assertEquals( 1, comparator.compare( "abc", "ab" ) ); + assertEquals( -1, comparator.compare( null, "" ) ); + assertEquals( -1, comparator.compare( "ab", "abc" ) ); + assertEquals( -1, comparator.compare( "ab", "abc" ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/BooleanSerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/BooleanSerializerTest.java new file mode 100644 index 000000000..37417c95c --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/BooleanSerializerTest.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.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; + + +/** + * Test the BooleanSerializer class + * + * @author Apache Directory Project + */ +public class BooleanSerializerTest +{ + private static BooleanSerializer serializer = BooleanSerializer.INSTANCE; + + + @Test + public void testBooleanSerializer() throws IOException + { + boolean value = true; + byte[] result = BooleanSerializer.serialize( value ); + + assertEquals( ( byte ) 0x01, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).booleanValue() ); + + // ------------------------------------------------------------------ + value = false; + result = BooleanSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).booleanValue() ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ByteArraySerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ByteArraySerializerTest.java new file mode 100644 index 000000000..9c8ccf017 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ByteArraySerializerTest.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Test; + + +/** + * Test the BytesSerializer class + * + * @author Apache Directory Project + */ +public class ByteArraySerializerTest +{ + private static ByteArraySerializer serializer = ByteArraySerializer.INSTANCE; + + + @Test + public void testBytesSerializer() throws IOException + { + byte[] value = null; + byte[] result = serializer.serialize( value ); + + assertEquals( 4, result.length ); + assertEquals( ( byte ) 0xFF, result[0] ); + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0xFF, result[3] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ) ); + + // ------------------------------------------------------------------ + value = new byte[] + {}; + result = serializer.serialize( value ); + + assertEquals( 4, result.length ); + assertEquals( ( byte ) 0x00, result[0] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[3] ); + + assertTrue( Arrays.equals( value, serializer.deserialize( new BufferHandler( result ) ) ) ); + + // ------------------------------------------------------------------ + value = "test".getBytes(); + result = serializer.serialize( value ); + + assertEquals( 8, result.length ); + assertEquals( ( byte ) 0x00, result[0] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x04, result[3] ); + assertEquals( 't', result[4] ); + assertEquals( 'e', result[5] ); + assertEquals( 's', result[6] ); + assertEquals( 't', result[7] ); + + assertTrue( Arrays.equals( value, serializer.deserialize( new BufferHandler( result ) ) ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ByteSerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ByteSerializerTest.java new file mode 100644 index 000000000..5c9adff3d --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ByteSerializerTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; + + +/** + * Test the ByteSerializer class + * + * @author Apache Directory Project + */ +public class ByteSerializerTest +{ + private static ByteSerializer serializer = ByteSerializer.INSTANCE; + + + @Test + public void testByteSerializer() throws IOException + { + byte value = 0x00; + byte[] result = ByteSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).byteValue() ); + + // ------------------------------------------------------------------ + value = 0x01; + result = ByteSerializer.serialize( value ); + + assertEquals( ( byte ) 0x01, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).byteValue() ); + + // ------------------------------------------------------------------ + value = 0x7F; + result = ByteSerializer.serialize( value ); + + assertEquals( ( byte ) 0x7F, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).byteValue() ); + + // ------------------------------------------------------------------ + value = ( byte ) 0x80; + result = ByteSerializer.serialize( value ); + + assertEquals( ( byte ) 0x80, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).byteValue() ); + + // ------------------------------------------------------------------ + value = ( byte ) 0xFF; + result = ByteSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).byteValue() ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/CharSerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/CharSerializerTest.java new file mode 100644 index 000000000..abd588ae7 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/CharSerializerTest.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; + + +/** + * Test the CharSerializer class + * + * @author Apache Directory Project + */ +public class CharSerializerTest +{ + private static CharSerializer serializer = CharSerializer.INSTANCE; + + + @Test + public void testCharSerializer() throws IOException + { + char value = 0x0000; + byte[] result = CharSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).charValue() ); + + // ------------------------------------------------------------------ + value = 0x0001; + result = CharSerializer.serialize( value ); + + assertEquals( ( byte ) 0x01, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).charValue() ); + + // ------------------------------------------------------------------ + value = 0x00FF; + result = CharSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).charValue() ); + + // ------------------------------------------------------------------ + value = 0x0100; + result = CharSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x01, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).charValue() ); + + // ------------------------------------------------------------------ + value = 0x7FFF; + result = CharSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0x7F, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).charValue() ); + + // ------------------------------------------------------------------ + value = 0x8000; + result = CharSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x80, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).charValue() ); + + // ------------------------------------------------------------------ + value = 0xFFFF; + result = CharSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0xFF, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).charValue() ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/IntSerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/IntSerializerTest.java new file mode 100644 index 000000000..0b11aaff7 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/IntSerializerTest.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; + + +/** + * Test the IntSerializer class + * + * @author Apache Directory Project + */ +public class IntSerializerTest +{ + private static IntSerializer serializer = IntSerializer.INSTANCE; + + + @Test + public void testIntSerializer() throws IOException + { + int value = 0x00000000; + byte[] result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x00000001; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0x01, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x000000FF; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x00000100; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x01, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x0000FFFF; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x00010000; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x01, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x00FFFFFF; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x01000000; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x01, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x7FFFFFFF; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00FF, result[3] ); + assertEquals( ( byte ) 0x00FF, result[2] ); + assertEquals( ( byte ) 0x00FF, result[1] ); + assertEquals( ( byte ) 0x7F, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0x80000000; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x80, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + + // ------------------------------------------------------------------ + value = 0xFFFFFFFF; + result = IntSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0xFF, result[0] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ).intValue() ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/LongArraySerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/LongArraySerializerTest.java new file mode 100644 index 000000000..fe45b39df --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/LongArraySerializerTest.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.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Ignore; +import org.junit.Test; + + +/** + * Test the LongArraySerializer class + * + * @author Apache Directory Project + */ +public class LongArraySerializerTest +{ + LongArraySerializer longArraySerializer = LongArraySerializer.INSTANCE; + + @Test + @Ignore + public void testLongArraySerializer() throws IOException + { + long[] value = null; + byte[] result = longArraySerializer.serialize( value ); + int pos = 0; + + assertEquals( 4, result.length ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + + assertEquals( value, longArraySerializer.deserialize( new BufferHandler( result ) ) ); + + // ------------------------------------------------------------------ + value = new long[]{}; + result = longArraySerializer.serialize( value ); + pos = 0; + + assertEquals( 4, result.length ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + + assertTrue( Arrays.equals( value, longArraySerializer.deserialize( new BufferHandler( result ) ) ) ); + + // ------------------------------------------------------------------ + value = new long[]{ 1L }; + result = longArraySerializer.serialize( value ); + pos = 0; + + assertEquals( 12, result.length ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x01, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x01, result[pos++] ); + + assertTrue( Arrays.equals( value, longArraySerializer.deserialize( new BufferHandler( result ) ) ) ); + + // ------------------------------------------------------------------ + value = new long[]{ 1L, 0x00000000FFFFFFFFL, 0xFFFFFFFFFFFFFFFFL }; + result = longArraySerializer.serialize( value ); + pos = 0; + + assertEquals( 28, result.length ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x03, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x01, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0x00, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + assertEquals( ( byte ) 0xFF, result[pos++] ); + + assertTrue( Arrays.equals( value, longArraySerializer.deserialize( new BufferHandler( result ) ) ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/LongSerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/LongSerializerTest.java new file mode 100644 index 000000000..856920db5 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/LongSerializerTest.java @@ -0,0 +1,354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; + + +/** + * Test the LongSerializer class + * + * @author Apache Directory Project + */ +public class LongSerializerTest +{ + @Test + public void testLongSerializer() throws IOException + { + long value = 0x0000000000000000L; + byte[] result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000000000000001L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x01, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x00000000000000FFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000000000000100L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x01, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x000000000000FFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000000000010000L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x01, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000000000FFFFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0xFF, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000000001000000L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x01, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x000000007FFFFFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0xFF, result[5] ); + assertEquals( ( byte ) 0x7F, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000000080000000L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x80, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x00000000FFFFFFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0xFF, result[5] ); + assertEquals( ( byte ) 0xFF, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000000100000000L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x01, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x000000FFFFFFFFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0xFF, result[5] ); + assertEquals( ( byte ) 0xFF, result[4] ); + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000010000000000L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x01, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0000FFFFFFFFFFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0xFF, result[5] ); + assertEquals( ( byte ) 0xFF, result[4] ); + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0001000000000000L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x01, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x00FFFFFFFFFFFFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0xFF, result[5] ); + assertEquals( ( byte ) 0xFF, result[4] ); + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x0100000000000000L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x01, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x7FFFFFFFFFFFFFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0xFF, result[5] ); + assertEquals( ( byte ) 0xFF, result[4] ); + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0x7F, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0x8000000000000000L; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[7] ); + assertEquals( ( byte ) 0x00, result[6] ); + assertEquals( ( byte ) 0x00, result[5] ); + assertEquals( ( byte ) 0x00, result[4] ); + assertEquals( ( byte ) 0x00, result[3] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x80, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + + // ------------------------------------------------------------------ + value = 0xFFFFFFFFFFFFFFFFL; + result = LongSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[7] ); + assertEquals( ( byte ) 0xFF, result[6] ); + assertEquals( ( byte ) 0xFF, result[5] ); + assertEquals( ( byte ) 0xFF, result[4] ); + assertEquals( ( byte ) 0xFF, result[3] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0xFF, result[0] ); + + assertEquals( value, LongSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).longValue() ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ShortSerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ShortSerializerTest.java new file mode 100644 index 000000000..0ad605029 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ShortSerializerTest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; + + +/** + * Test the ShortSerializer class + * + * @author Apache Directory Project + */ +public class ShortSerializerTest +{ + @Test + public void testShortSerializer() throws IOException + { + short value = 0x0000; + byte[] result = ShortSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, ShortSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).shortValue() ); + + // ------------------------------------------------------------------ + value = 0x0001; + result = ShortSerializer.serialize( value ); + + assertEquals( ( byte ) 0x01, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, ShortSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).shortValue() ); + + // ------------------------------------------------------------------ + value = 0x00FF; + result = ShortSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0x00, result[0] ); + + assertEquals( value, ShortSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).shortValue() ); + + // ------------------------------------------------------------------ + value = 0x0100; + result = ShortSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x01, result[0] ); + + assertEquals( value, ShortSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).shortValue() ); + + // ------------------------------------------------------------------ + value = 0x7FFF; + result = ShortSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0x7F, result[0] ); + + assertEquals( value, ShortSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).shortValue() ); + + // ------------------------------------------------------------------ + value = ( short ) 0x8000; + result = ShortSerializer.serialize( value ); + + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x80, result[0] ); + + assertEquals( value, ShortSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).shortValue() ); + + // ------------------------------------------------------------------ + value = ( short ) 0xFFFF; + result = ShortSerializer.serialize( value ); + + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0xFF, result[0] ); + + assertEquals( value, ShortSerializer.INSTANCE.deserialize( new BufferHandler( result ) ).shortValue() ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/StringSerializerTest.java b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/StringSerializerTest.java new file mode 100644 index 000000000..a76bd5206 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/StringSerializerTest.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.directory.mavibot.btree.serializer; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; + + +/** + * Test the StringSerializer class + * + * @author Apache Directory Project + */ +public class StringSerializerTest +{ + private static StringSerializer serializer = StringSerializer.INSTANCE; + + + @Test + public void testStringSerializer() throws IOException + { + String value = null; + byte[] result = serializer.serialize( value ); + + assertEquals( 4, result.length ); + assertEquals( ( byte ) 0xFF, result[0] ); + assertEquals( ( byte ) 0xFF, result[1] ); + assertEquals( ( byte ) 0xFF, result[2] ); + assertEquals( ( byte ) 0xFF, result[3] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ) ); + + // ------------------------------------------------------------------ + value = ""; + result = serializer.serialize( value ); + + assertEquals( 4, result.length ); + assertEquals( ( byte ) 0x00, result[0] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x00, result[3] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ) ); + + // ------------------------------------------------------------------ + value = "test"; + result = serializer.serialize( value ); + + assertEquals( 8, result.length ); + assertEquals( ( byte ) 0x00, result[0] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x04, result[3] ); + assertEquals( 't', result[4] ); + assertEquals( 'e', result[5] ); + assertEquals( 's', result[6] ); + assertEquals( 't', result[7] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ) ); + + // ------------------------------------------------------------------ + value = "L\u00E9charny"; + result = serializer.serialize( value ); + + assertEquals( 13, result.length ); + assertEquals( ( byte ) 0x00, result[0] ); + assertEquals( ( byte ) 0x00, result[1] ); + assertEquals( ( byte ) 0x00, result[2] ); + assertEquals( ( byte ) 0x09, result[3] ); + assertEquals( 'L', result[4] ); + assertEquals( ( byte ) 0xC3, result[5] ); + assertEquals( ( byte ) 0xA9, result[6] ); + assertEquals( 'c', result[7] ); + assertEquals( 'h', result[8] ); + assertEquals( 'a', result[9] ); + assertEquals( 'r', result[10] ); + assertEquals( 'n', result[11] ); + assertEquals( 'y', result[12] ); + + assertEquals( value, serializer.deserialize( new BufferHandler( result ) ) ); + } +} diff --git a/Java-base/directory-mavibot/src/mavibot/src/test/resources/log4j.properties b/Java-base/directory-mavibot/src/mavibot/src/test/resources/log4j.properties new file mode 100644 index 000000000..540197201 --- /dev/null +++ b/Java-base/directory-mavibot/src/mavibot/src/test/resources/log4j.properties @@ -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. +############################################################################# +log4j.rootCategory=DEBUG, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %p [%c] - %m%n +#log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %p [%c-%X{Replica}] %C{1}.%M@%L - %m%n + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=[%d{HH:mm:ss}] %p [%c] - %m%n +log4j.appender.file.File=/tmp/server-integ.log +log4j.appender.file.MaxFileSize=7168KB +log4j.appender.file.MaxBackupIndex=100 + +#log4j.logger.org=FATAL +log4j.logger.org.apache.directory.mavibot.btree=ERROR +log4j.logger.org.apache.directory.mavibot.LOG_PAGES=ERROR +log4j.logger.org.apache.directory.mavibot.LOG_CHECK=ERROR +log4j.logger.net.sf.ehcache.Cache=ERROR +log4j.logger.TXN_LOG=INFO diff --git a/Java-base/directory-mavibot/src/pom.xml b/Java-base/directory-mavibot/src/pom.xml new file mode 100644 index 000000000..41a18a72c --- /dev/null +++ b/Java-base/directory-mavibot/src/pom.xml @@ -0,0 +1,451 @@ + + + + 4.0.0 + + + org.apache.directory.project + project + 42 + + + + + Apache Mavibot Project Parent + https://directory.apache.org/mavibot/ + + + org.apache.directory.mavibot + 1.0.0-M9-SNAPSHOT + mavibot-parent + ApacheDS Mavibot Parent + pom + + + 3.0.0 + + + https://directory.apache.org/mavibot + 2012 + + + jira + https://issues.apache.org/jira/browse/MAVIBOT + + + + scm:git:https://gitbox.apache.org/repos/asf/directory-mavibot.git + scm:git:https://gitbox.apache.org/repos/asf/directory-mavibot + https://github.com/apache/directory-mavibot/tree/${project.scm.tag} + master + + + + + + mail +
          dev@directory.apache.org
          +
          +
          +
          + + A MVCC BTree Implementation + + + + Apache 2.0 License + https://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + + 2.6.0 + 3.2.2 + 2.6 + 4.12 + 1.7.25 + 1.7.25 + + + + mavibot + distribution + + + + + + + com.github.ben-manes.caffeine + caffeine + ${com.github.ben-manes.caffeine.version} + + + + + junit + junit + ${junit.version} + test + + + + + org.slf4j + slf4j-api + ${slf4j.api.version} + + + + + + + + + + + org.slf4j + slf4j-api + + + + + + junit + junit + test + + + + + + + + org.apache.rat + apache-rat-plugin + + false + + + **/target/**/* + + **/.classpath + **/.project + **/.settings/**/* + + **/*.iml + **/*.ipr + **/*.iws + + **/MANIFEST.MF + + **/LICENSE.* + **/NOTICE*.txt + + **/img/**/* + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + true + true + true + UTF-8 + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-source + verify + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-release-plugin + + + @{project.version} + + + https://svn.apache.org/repos/asf/directory/mavibot/tags + + + + + + org.codehaus.mojo + findbugs-maven-plugin + + true + true + true + + + + + maven-enforcer-plugin + + + enforce-java-16 + + enforce + + + + + 1.6.0 + + + + + + + + + org.apache.geronimo.genesis.plugins + tools-maven-plugin + + + verify-legal-files + verify + + verify-legal-files + + + + false + + + + + + + + org.apache.maven.plugins + maven-site-plugin + + + + org.apache.maven.wagon + wagon-ssh + 2.1 + + + + + org.apache.maven.wagon + wagon-ssh-external + 2.1 + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + + true + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + + true + -Xmx1024m -XX:+UseConcMarkSweepGC + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + + + + org.codehaus.mojo + taglist-maven-plugin + + + TODO + @todo + @deprecated + FIXME + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 512m + 1g + true + + + todo + + a + To do: + + + 1.6 + + + + + aggregate + test-aggregate + + + + + + + org.codehaus.mojo + versions-maven-plugin + + + + dependency-updates-report + plugin-updates-report + property-updates-report + + + + + + + org.apache.rat + apache-rat-plugin + + + false + + + **/target/**/* + **/cobertura.ser + + **/.classpath + **/.project + **/.settings/**/* + + **/*.iml + **/*.ipr + **/*.iws + + **/MANIFEST.MF + + distribution/src/main/release/licenses/* + src/main/release/licenses/* + + **/dependency-reduced-pom.xml + + **/img/*.png + **/img/*.graphml + + + + + + org.codehaus.mojo + javancss-maven-plugin + + + + org.codehaus.mojo + jdepend-maven-plugin + + + + + + + + + + + apache-release + + + + + maven-javadoc-plugin + + + install + + + javadoc + + + true + + + + + + + maven-jxr-plugin + + true + + + + + install + + + jxr + test-jxr + + + + + + + + +
          + diff --git a/Java-base/geronimo-jcache-simple/Dockerfile b/Java-base/geronimo-jcache-simple/Dockerfile new file mode 100644 index 000000000..e208c4890 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:22.04 + +RUN export DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get install -y software-properties-common \ + && add-apt-repository ppa:deadsnakes/ppa \ + && apt-get update \ + && apt-get install -y \ + build-essential \ + git \ + vim \ + jq \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/list/* + +RUN apt-get -y install sudo \ + openjdk-8-jdk \ + maven + +RUN bash -c "echo 2 | update-alternatives --config java" + +COPY src /workspace +WORKDIR /workspace + +RUN mvn install -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false + +RUN mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 + +ENV TZ=Asia/Seoul diff --git a/Java-base/geronimo-jcache-simple/build.sh b/Java-base/geronimo-jcache-simple/build.sh new file mode 100755 index 000000000..186f33bc9 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/build.sh @@ -0,0 +1 @@ +docker build -t ghcr.io/kupl/starlab-benchmarks/java-base:geronimo-jcache-simple . diff --git a/Java-base/geronimo-jcache-simple/src/README.adoc b/Java-base/geronimo-jcache-simple/src/README.adoc new file mode 100644 index 000000000..9ec0965bf --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/README.adoc @@ -0,0 +1,12 @@ += Simple JCache Implementation + +A light implementation simply backed by a ConcurrentHashMap. + +It is intended for reference data cache usages. + +There are three modules: + +- default ones embeds next two +- cdi is only the CDI integration +- standalone is only the API implementation without CDI support + diff --git a/Java-base/geronimo-jcache-simple/src/pom.xml b/Java-base/geronimo-jcache-simple/src/pom.xml new file mode 100644 index 000000000..9834d80bb --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/pom.xml @@ -0,0 +1,325 @@ + + + + 4.0.0 + + + org.apache + apache + 18 + + + org.apache.geronimo + geronimo-jcache-simple + 1.0.5-SNAPSHOT + Geronimo :: Simple JCache Implementation + + + + ${project.groupId} + ${project.artifactId} + ${project.version} + + org.apache.geronimo.jcache.simple.SimpleManager + org.apache.geronimo.jcache.simple.SimpleCache + org.apache.geronimo.jcache.simple.SimpleEntry + org.apache.geronimo.jcache.simple.cdi.CacheKeyInvocationContextImpl + + + org.apache.geronimo.jcache.simple.ConfigurableMBeanServerIdBuilder + + MBeanServerGeronimo + + ${project.build.directory}/domainlib + domain.jar + + 1.0.0 + 1.1.0 + + + + + org.osgi + osgi.core + 7.0.0 + provided + true + + + org.osgi + osgi.annotation + 7.0.0 + provided + true + + + + org.apache.geronimo.specs + geronimo-jcache_1.0_spec + 1.0-alpha-1 + provided + + + org.apache.geronimo.specs + geronimo-jcdi_2.0_spec + 1.0.1 + provided + + + org.apache.geronimo.specs + geronimo-atinject_1.0_spec + 1.0 + provided + + + org.apache.geronimo.specs + geronimo-interceptor_1.2_spec + 1.0 + provided + + + org.apache.geronimo.specs + geronimo-annotation_1.3_spec + 1.0 + provided + + + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-library + 1.3 + test + + + javax.cache + test-domain + ${jcache.version} + test + + + javax.cache + app-domain + ${jcache.version} + test + + + javax.cache + cache-tests + ${tck.version} + test + + + javax.cache + cache-tests + ${tck.version} + tests + test + + + org.apache.openwebbeans + openwebbeans-impl + 2.0.5 + test + + + + + + + src/test/resources + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + default-jar + + jar + + + + ${project.build.directory}/manifest/global/MANIFEST.MF + + + + + cdi-jar + + jar + + + cdi + + org/apache/geronimo/jcache/simple/cdi/* + META-INF/services/javax.enterprise.inject.spi.Extension + + + + + no-cdi-jar + + jar + + + standalone + + org/apache/geronimo/jcache/simple/cdi/ + META-INF/services/javax.enterprise.inject.spi.Extension + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-cache-tests + generate-test-resources + + unpack-dependencies + + + ${project.build.testOutputDirectory} + cache-tests + test + **/unwrap.properties + + + + copy-domain + generate-test-resources + + copy + + + + + javax.cache + app-domain + ${jcache.version} + ${domain-lib-dir} + ${domain-jar} + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.1 + + + true + ${domain-lib-dir}/${domain-jar} + ${javax.management.builder.initial} + ${org.jsr107.tck.management.agentId} + ${CacheManagerImpl} + ${CacheImpl} + ${CacheEntryImpl} + ${CacheInvocationContextImpl} + + + + + + + org.apache.felix + maven-bundle-plugin + 4.2.1 + + + global-manifest + + manifest + + + ${project.build.directory}/manifest/global + + org.apache.geronimo.jcache.simple.osgi.JCacheActivator + + + + + + + + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + scm:git:https://gitbox.apache.org/repos/asf/geronimo-jcache-simple.git + scm:git:https://gitbox.apache.org/repos/asf/geronimo-jcache-simple.git + https://gitbox.apache.org/repos/asf/geronimo-jcache-simple.git + HEAD + + + + The Apache Software Foundation + http://www.apache.org/ + + + 2017 + + + + Apache Geronimo Community + https://geronimo.apache.org + Apache + + + + + ASF JIRA + https://issues.apache.org/jira/browse/GERONIMO + + diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Asserts.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Asserts.java new file mode 100644 index 000000000..a45de9eb6 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Asserts.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +public final class Asserts { + + private Asserts() { + // no-op + } + + static void assertNotNull(final Object value, final String msg) { + if (value == null) { + throw new NullPointerException(msg); + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ClassLoaderAwareCache.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ClassLoaderAwareCache.java new file mode 100644 index 000000000..0c398c832 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ClassLoaderAwareCache.java @@ -0,0 +1,372 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.cache.Cache; +import javax.cache.CacheManager; +import javax.cache.configuration.CacheEntryListenerConfiguration; +import javax.cache.configuration.Configuration; +import javax.cache.integration.CompletionListener; +import javax.cache.processor.EntryProcessor; +import javax.cache.processor.EntryProcessorException; +import javax.cache.processor.EntryProcessorResult; + +class ClassLoaderAwareCache implements Cache { + + private final ClassLoader loader; + + private final SimpleCache delegate; + + ClassLoaderAwareCache(final ClassLoader loader, final SimpleCache delegate) { + this.loader = loader; + this.delegate = delegate; + } + + private ClassLoader before(final Thread thread) { + final ClassLoader tccl = thread.getContextClassLoader(); + thread.setContextClassLoader(loader); + return tccl; + } + + public V get(final K key) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.get(key); + } finally { + thread.setContextClassLoader(loader); + } + } + + public Map getAll(final Set keys) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.getAll(keys); + } finally { + thread.setContextClassLoader(loader); + } + } + + public boolean containsKey(final K key) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.containsKey(key); + } finally { + thread.setContextClassLoader(loader); + } + } + + public void loadAll(final Set keys, boolean replaceExistingValues, final CompletionListener completionListener) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.loadAll(keys, replaceExistingValues, completionListener); + } finally { + thread.setContextClassLoader(loader); + } + } + + public void put(final K key, final V value) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.put(key, value); + } finally { + thread.setContextClassLoader(loader); + } + } + + public V getAndPut(final K key, final V value) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.getAndPut(key, value); + } finally { + thread.setContextClassLoader(loader); + } + } + + public void putAll(final Map map) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.putAll(map); + } finally { + thread.setContextClassLoader(loader); + } + } + + public boolean putIfAbsent(final K key, final V value) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.putIfAbsent(key, value); + } finally { + thread.setContextClassLoader(loader); + } + } + + public boolean remove(final K key) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.remove(key); + } finally { + thread.setContextClassLoader(loader); + } + } + + public boolean remove(final K key, final V oldValue) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.remove(key, oldValue); + } finally { + thread.setContextClassLoader(loader); + } + } + + public V getAndRemove(final K key) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.getAndRemove(key); + } finally { + thread.setContextClassLoader(loader); + } + } + + public boolean replace(final K key, final V oldValue, final V newValue) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.replace(key, oldValue, newValue); + } finally { + thread.setContextClassLoader(loader); + } + } + + public boolean replace(final K key, final V value) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.replace(key, value); + } finally { + thread.setContextClassLoader(loader); + } + } + + public V getAndReplace(final K key, final V value) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.getAndReplace(key, value); + } finally { + thread.setContextClassLoader(loader); + } + } + + public void removeAll(final Set keys) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.removeAll(keys); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public void removeAll() { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.removeAll(); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public void clear() { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.clear(); + } finally { + thread.setContextClassLoader(loader); + } + } + + public > C getConfiguration(final Class clazz) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.getConfiguration(clazz); + } finally { + thread.setContextClassLoader(loader); + } + } + + public T invoke(final K key, final EntryProcessor entryProcessor, final Object... arguments) + throws EntryProcessorException { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.invoke(key, entryProcessor, arguments); + } finally { + thread.setContextClassLoader(loader); + } + } + + public Map> invokeAll(final Set keys, + final EntryProcessor entryProcessor, final Object... arguments) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.invokeAll(keys, entryProcessor, arguments); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public String getName() { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.getName(); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public CacheManager getCacheManager() { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.getCacheManager(); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public void close() { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.close(); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public boolean isClosed() { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.isClosed(); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public T unwrap(final Class clazz) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.unwrap(clazz); + } finally { + thread.setContextClassLoader(loader); + } + } + + public void registerCacheEntryListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.registerCacheEntryListener(cacheEntryListenerConfiguration); + } finally { + thread.setContextClassLoader(loader); + } + } + + public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + delegate.deregisterCacheEntryListener(cacheEntryListenerConfiguration); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public Iterator> iterator() { + final Thread thread = Thread.currentThread(); + final ClassLoader loader = before(thread); + try { + return delegate.iterator(); + } finally { + thread.setContextClassLoader(loader); + } + } + + @Override + public boolean equals(final Object obj) { + if (ClassLoaderAwareCache.class.isInstance(obj)) { + return delegate.equals(ClassLoaderAwareCache.class.cast(obj).delegate); + } + return super.equals(obj); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + public static Cache wrap(final ClassLoader loader, final SimpleCache delegate) { + ClassLoader dontWrapLoader = ClassLoaderAwareCache.class.getClassLoader(); + while (dontWrapLoader != null) { + if (loader == dontWrapLoader) { + return delegate; + } + dontWrapLoader = dontWrapLoader.getParent(); + } + return new ClassLoaderAwareCache<>(loader, delegate); + } + + public static SimpleCache getDelegate(final Cache cache) { + if (SimpleCache.class.isInstance(cache)) { + return (SimpleCache) cache; + } + return ((ClassLoaderAwareCache) cache).delegate; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ConfigurableMBeanServerIdBuilder.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ConfigurableMBeanServerIdBuilder.java new file mode 100644 index 000000000..71307d4e2 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ConfigurableMBeanServerIdBuilder.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.geronimo.jcache.simple; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.management.ListenerNotFoundException; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanServer; +import javax.management.MBeanServerBuilder; +import javax.management.MBeanServerDelegate; +import javax.management.Notification; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; + +public class ConfigurableMBeanServerIdBuilder extends MBeanServerBuilder { + + private static ConcurrentMap JVM_SINGLETONS = new ConcurrentHashMap<>(); + + @Override + public MBeanServer newMBeanServer(final String defaultDomain, final MBeanServer outer, final MBeanServerDelegate delegate) { + final Key key = new Key(defaultDomain, outer); + MBeanServer server = JVM_SINGLETONS.get(key); + if (server == null) { + server = super.newMBeanServer(defaultDomain, outer, new ForceIdMBeanServerDelegate(delegate)); + final MBeanServer existing = JVM_SINGLETONS.putIfAbsent(key, server); + if (existing != null) { + server = existing; + } + } + return server; + } + + private static class Key { + + private final String domain; + + private final MBeanServer outer; + + private Key(final String domain, final MBeanServer outer) { + this.domain = domain; + this.outer = outer; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final Key key = Key.class.cast(o); + return !(domain != null ? !domain.equals(key.domain) : key.domain != null) + && !(outer != null ? !outer.equals(key.outer) : key.outer != null); + + } + + @Override + public int hashCode() { + int result = domain != null ? domain.hashCode() : 0; + result = 31 * result + (outer != null ? outer.hashCode() : 0); + return result; + } + } + + private class ForceIdMBeanServerDelegate extends MBeanServerDelegate { + + private final MBeanServerDelegate delegate; + + public ForceIdMBeanServerDelegate(final MBeanServerDelegate delegate) { + this.delegate = delegate; + } + + @Override + public String getMBeanServerId() { + return System.getProperty("org.jsr107.tck.management.agentId", delegate.getMBeanServerId()); + } + + @Override + public String getSpecificationName() { + return delegate.getSpecificationName(); + } + + @Override + public String getSpecificationVersion() { + return delegate.getSpecificationVersion(); + } + + @Override + public String getSpecificationVendor() { + return delegate.getSpecificationVendor(); + } + + @Override + public String getImplementationName() { + return delegate.getImplementationName(); + } + + @Override + public String getImplementationVersion() { + return delegate.getImplementationVersion(); + } + + @Override + public String getImplementationVendor() { + return delegate.getImplementationVendor(); + } + + @Override + public MBeanNotificationInfo[] getNotificationInfo() { + return delegate.getNotificationInfo(); + } + + @Override + public void addNotificationListener(final NotificationListener listener, final NotificationFilter filter, + final Object handback) throws IllegalArgumentException { + delegate.addNotificationListener(listener, filter, handback); + } + + @Override + public void removeNotificationListener(final NotificationListener listener, final NotificationFilter filter, + final Object handback) throws ListenerNotFoundException { + delegate.removeNotificationListener(listener, filter, handback); + } + + @Override + public void removeNotificationListener(final NotificationListener listener) throws ListenerNotFoundException { + delegate.removeNotificationListener(listener); + } + + @Override + public void sendNotification(final Notification notification) { + delegate.sendNotification(notification); + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ExceptionWrapperHandler.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ExceptionWrapperHandler.java new file mode 100644 index 000000000..f98bdd5c1 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ExceptionWrapperHandler.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.geronimo.jcache.simple; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public class ExceptionWrapperHandler implements InvocationHandler { + + private final T delegate; + + private final Constructor wrapper; + + public ExceptionWrapperHandler(final T delegate, final Class exceptionType) { + this.delegate = delegate; + try { + this.wrapper = exceptionType.getConstructor(Throwable.class); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException(e); + } + } + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { + if (AutoCloseable.class == method.getDeclaringClass() && !AutoCloseable.class.isInstance(delegate)) { + return null; + } + try { + return method.invoke(delegate, args); + } catch (final InvocationTargetException ite) { + final Throwable e = ite.getCause(); + if (RuntimeException.class.isInstance(e)) { + final RuntimeException re; + try { + re = wrapper.newInstance(e); + } catch (final Exception e1) { + throw new IllegalArgumentException(e1); + } + throw re; + } + throw e; + } + } + + public static T newProxy(final ClassLoader loader, final T delegate, + final Class exceptionType, final Class apis) { + return (T) Proxy.newProxyInstance(loader, new Class[] { apis, AutoCloseable.class }, + new ExceptionWrapperHandler<>(delegate, exceptionType)); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/JMXs.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/JMXs.java new file mode 100644 index 000000000..6356240ad --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/JMXs.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import java.lang.management.ManagementFactory; + +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; + +public class JMXs { + + private static final MBeanServer SERVER = findMBeanServer(); + + private JMXs() { + // no-op + } + + public static MBeanServer server() { + return SERVER; + } + + public static void register(final ObjectName on, final Object bean) { + if (!SERVER.isRegistered(on)) { + try { + SERVER.registerMBean(bean, on); + } catch (final Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + } + + public static void unregister(final ObjectName on) { + if (SERVER.isRegistered(on)) { + try { + SERVER.unregisterMBean(on); + } catch (final Exception e) { + // no-op + } + } + } + + private static MBeanServer findMBeanServer() { + if (System.getProperty("javax.management.builder.initial") != null) { + return MBeanServerFactory.createMBeanServer(); + } + return ManagementFactory.getPlatformMBeanServer(); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/NoLoader.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/NoLoader.java new file mode 100644 index 000000000..3f87589ce --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/NoLoader.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import java.util.HashMap; +import java.util.Map; + +import javax.cache.integration.CacheLoader; +import javax.cache.integration.CacheLoaderException; + +public class NoLoader implements CacheLoader { + + public static final NoLoader INSTANCE = new NoLoader(); + + private NoLoader() { + // no-op + } + + @Override + public V load(K key) throws CacheLoaderException { + return null; + } + + @Override + public Map loadAll(final Iterable keys) throws CacheLoaderException { + final Map entries = new HashMap(); + for (final K k : keys) { + entries.put(k, null); + } + return entries; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/NoWriter.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/NoWriter.java new file mode 100644 index 000000000..3fb405284 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/NoWriter.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.geronimo.jcache.simple; + +import java.util.Collection; + +import javax.cache.Cache; +import javax.cache.integration.CacheWriter; +import javax.cache.integration.CacheWriterException; + +public class NoWriter implements CacheWriter { + + public static final NoWriter INSTANCE = new NoWriter(); + + @Override + public void write(final Cache.Entry entry) throws CacheWriterException { + // no-op + } + + @Override + public void delete(final Object key) throws CacheWriterException { + // no-op + } + + @Override + public void writeAll(final Collection> entries) throws CacheWriterException { + for (final Cache.Entry entry : entries) { + write(entry); + } + } + + @Override + public void deleteAll(final Collection keys) throws CacheWriterException { + for (final Object k : keys) { + delete(k); + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Serializations.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Serializations.java new file mode 100644 index 000000000..fecd387d5 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Serializations.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.geronimo.jcache.simple; + +import static java.util.Arrays.asList; + +import java.io.BufferedInputStream; +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.lang.reflect.Proxy; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.regex.Pattern; + +public class Serializations { + + private final Collection acceptedClasses; + + public Serializations(final String acceptedClasses) { + this.acceptedClasses = acceptedClasses == null ? Collections. emptySet() + : new HashSet<>(asList(acceptedClasses.split(","))); + } + + public K copy(final ClassLoader loader, final K key) { + try { + return deSerialize(serialize(key), loader); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + } + + private byte[] serialize(final T obj) throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(baos); + try { + oos.writeObject(obj); + } finally { + oos.close(); + } + return baos.toByteArray(); + } + + private T deSerialize(final byte[] data, final ClassLoader loader) throws IOException, ClassNotFoundException { + final ByteArrayInputStream bais = new ByteArrayInputStream(data); + final BufferedInputStream bis = new BufferedInputStream(bais); + final ObjectInputStream ois = new ObjectInputStreamClassLoaderAware(bis, loader, acceptedClasses); + try { + return (T) ois.readObject(); + } finally { + ois.close(); + } + } + + private static class ObjectInputStreamClassLoaderAware extends ObjectInputStream { + + private static final Pattern PRIMITIVE_ARRAY = Pattern.compile("^\\[+[BCDFIJSVZ]$"); + + private final ClassLoader classLoader; + + private final Collection accepted; + + public ObjectInputStreamClassLoaderAware(final InputStream in, final ClassLoader classLoader, + final Collection accepted) throws IOException { + super(in); + this.classLoader = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader(); + this.accepted = accepted; + } + + @Override + protected Class resolveClass(final ObjectStreamClass desc) throws ClassNotFoundException { + if (isAccepted(desc.getName())) { + return Class.forName(desc.getName(), false, classLoader); + } + throw new SecurityException(desc.getName() + " not whitelisted"); + } + + @Override + protected Class resolveProxyClass(final String[] interfaces) throws ClassNotFoundException { + final Class[] cinterfaces = new Class[interfaces.length]; + for (int i = 0; i < cinterfaces.length; i++) { + if (isAccepted(interfaces[i])) { + cinterfaces[i] = Class.forName(interfaces[i], false, classLoader); + } else { + throw new SecurityException(interfaces[i] + " not whitelisted"); + } + } + + try { + return Proxy.getProxyClass(classLoader, cinterfaces); + } catch (IllegalArgumentException e) { + throw new ClassNotFoundException(null, e); + } + } + + private boolean isAccepted(final String name) { + if (PRIMITIVE_ARRAY.matcher(name).matches()) { + return false; + } + if (name.startsWith("[L") && name.endsWith(";")) { + return isAccepted(name.substring(2, name.length() - 1)); + } + return !accepted.contains(name); + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCache.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCache.java new file mode 100644 index 000000000..feb52d73b --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCache.java @@ -0,0 +1,876 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import static org.apache.geronimo.jcache.simple.Asserts.assertNotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; + +import javax.cache.Cache; +import javax.cache.CacheException; +import javax.cache.CacheManager; +import javax.cache.configuration.CacheEntryListenerConfiguration; +import javax.cache.configuration.Configuration; +import javax.cache.configuration.Factory; +import javax.cache.event.CacheEntryEvent; +import javax.cache.event.EventType; +import javax.cache.expiry.Duration; +import javax.cache.expiry.EternalExpiryPolicy; +import javax.cache.expiry.ExpiryPolicy; +import javax.cache.integration.CacheLoader; +import javax.cache.integration.CacheLoaderException; +import javax.cache.integration.CacheWriter; +import javax.cache.integration.CacheWriterException; +import javax.cache.integration.CompletionListener; +import javax.cache.processor.EntryProcessor; +import javax.cache.processor.EntryProcessorException; +import javax.cache.processor.EntryProcessorResult; +import javax.management.ObjectName; + +public class SimpleCache implements Cache { + + private final SimpleManager manager; + + private final SimpleConfiguration config; + + private final CacheLoader loader; + + private final CacheWriter writer; + + private final ExpiryPolicy expiryPolicy; + + private final ObjectName cacheConfigObjectName; + + private final ObjectName cacheStatsObjectName; + + private final String name; + + private final ConcurrentHashMap, SimpleElement> delegate; + + private final Map, SimpleListener> listeners = new ConcurrentHashMap<>(); + + private final Statistics statistics = new Statistics(); + + private final ExecutorService pool; + + private final Serializations serializations; + + private final Collection> poolTasks = new CopyOnWriteArraySet<>(); + + private volatile boolean closed = false; + + public SimpleCache(final ClassLoader classLoader, final SimpleManager mgr, final String cacheName, + final SimpleConfiguration configuration, final Properties properties, + final ExecutorService executorService) { + manager = mgr; + + name = cacheName; + + final int capacity = Integer.parseInt(property(properties, cacheName, "capacity", "1000")); + final float loadFactor = Float.parseFloat(property(properties, cacheName, "loadFactor", "0.75")); + final int concurrencyLevel = Integer.parseInt(property(properties, cacheName, "concurrencyLevel", "16")); + delegate = new ConcurrentHashMap<>(capacity, loadFactor, concurrencyLevel); + config = configuration; + pool = executorService; + + final long evictionPause = Long.parseLong( + properties.getProperty(cacheName + ".evictionPause", properties.getProperty("evictionPause", "30000"))); + if (evictionPause > 0) { + final long maxDeleteByEvictionRun = Long.parseLong(property(properties, cacheName, "maxDeleteByEvictionRun", "100")); + addPoolTask(new EvictionThread(evictionPause, maxDeleteByEvictionRun)); + } + + serializations = new Serializations(property(properties, cacheName, "serialization.whitelist", null)); + + final Factory> cacheLoaderFactory = configuration.getCacheLoaderFactory(); + if (cacheLoaderFactory == null) { + loader = NoLoader.INSTANCE; + } else { + loader = ExceptionWrapperHandler.newProxy(classLoader, cacheLoaderFactory.create(), CacheLoaderException.class, + CacheLoader.class); + } + + final Factory> cacheWriterFactory = configuration.getCacheWriterFactory(); + if (cacheWriterFactory == null) { + writer = NoWriter.INSTANCE; + } else { + writer = ExceptionWrapperHandler.newProxy(classLoader, cacheWriterFactory.create(), CacheWriterException.class, + CacheWriter.class); + } + + final Factory expiryPolicyFactory = configuration.getExpiryPolicyFactory(); + if (expiryPolicyFactory == null) { + expiryPolicy = new EternalExpiryPolicy(); + } else { + expiryPolicy = expiryPolicyFactory.create(); + } + + for (final CacheEntryListenerConfiguration listener : config.getCacheEntryListenerConfigurations()) { + listeners.put(listener, new SimpleListener<>(listener)); + } + + statistics.setActive(config.isStatisticsEnabled()); + + final String mgrStr = manager.getURI().toString().replaceAll(",|:|=|\n", "."); + final String cacheStr = name.replaceAll(",|:|=|\n", "."); + try { + cacheConfigObjectName = new ObjectName( + "javax.cache:type=CacheConfiguration," + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr); + cacheStatsObjectName = new ObjectName( + "javax.cache:type=CacheStatistics," + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr); + } catch (final Exception e) { + throw new IllegalArgumentException(e); + } + if (config.isManagementEnabled()) { + JMXs.register(cacheConfigObjectName, new SimpleCacheMXBean(this)); + } + if (config.isStatisticsEnabled()) { + JMXs.register(cacheStatsObjectName, new SimpleCacheStatisticsMXBean(statistics)); + } + } + + private void assertNotClosed() { + if (isClosed()) { + throw new IllegalStateException("cache closed"); + } + } + + @Override + public V get(final K key) { + assertNotClosed(); + assertNotNull(key, "key"); + final long getStart = Times.now(false); + return doGetControllingExpiry(getStart, key, true, false, false, true, loader); + } + + private V doLoad(final K key, final boolean update, final boolean propagateLoadException, final CacheLoader loader) { + V v = null; + try { + v = loader.load(key); + } catch (final CacheLoaderException e) { + if (propagateLoadException) { + throw e; + } + } + if (v != null) { + final Duration duration = update ? expiryPolicy.getExpiryForUpdate() : expiryPolicy.getExpiryForCreation(); + if (isNotZero(duration)) { + delegate.put(new SimpleKey<>(key), new SimpleElement<>(v, duration)); + } + } + return v; + } + + private void touch(final SimpleKey key, final SimpleElement element) { + if (config.isStoreByValue()) { + delegate.put(new SimpleKey<>(serializations.copy(manager.getClassLoader(), key.getKey())), element); + } + } + + @Override + public Map getAll(final Set keys) { + assertNotClosed(); + for (final K k : keys) { + assertNotNull(k, "key"); + } + + final Map result = new HashMap<>(); + for (final K key : keys) { + assertNotNull(key, "key"); + + final SimpleKey simpleKey = new SimpleKey<>(key); + final SimpleElement elt = delegate.get(simpleKey); + V val = elt != null ? elt.getElement() : null; + if (val == null && config.isReadThrough()) { + val = doLoad(key, false, false, loader); + if (val != null) { + result.put(key, val); + } + } else if (elt != null) { + final Duration expiryForAccess = expiryPolicy.getExpiryForAccess(); + if (isNotZero(expiryForAccess)) { + touch(simpleKey, elt); + result.put(key, val); + } else { + expires(simpleKey); + } + } + } + return result; + } + + @Override + public boolean containsKey(final K key) { + assertNotClosed(); + assertNotNull(key, "key"); + return delegate.get(new SimpleKey<>(key)) != null; + } + + @Override + public void put(final K key, final V rawValue) { + assertNotClosed(); + assertNotNull(key, "key"); + assertNotNull(rawValue, "value"); + + final boolean storeByValue = config.isStoreByValue(); + final SimpleKey simpleKey = new SimpleKey<>(storeByValue ? serializations.copy(manager.getClassLoader(), key) : key); + final SimpleElement oldElt = delegate.get(simpleKey); + final V old = oldElt != null ? oldElt.getElement() : null; + final V value = storeByValue ? serializations.copy(manager.getClassLoader(), rawValue) : rawValue; + + final boolean created = old == null; + final Duration duration = created ? expiryPolicy.getExpiryForCreation() : expiryPolicy.getExpiryForUpdate(); + if (isNotZero(duration)) { + final boolean statisticsEnabled = config.isStatisticsEnabled(); + final long start = Times.now(false); + + writer.write(new SimpleEntry<>(key, value)); + delegate.put(simpleKey, new SimpleElement<>(value, duration)); + if (!listeners.isEmpty()) { + for (final SimpleListener listener : listeners.values()) { + if (created) { + listener.onCreated(Collections.> singletonList( + new SimpleEvent<>(this, EventType.CREATED, null, key, value))); + } else + listener.onUpdated(Collections.> singletonList( + new SimpleEvent<>(this, EventType.UPDATED, old, key, value))); + } + } + + if (statisticsEnabled) { + statistics.increasePuts(1); + statistics.addPutTime(Times.now(false) - start); + } + } else { + if (!created) { + expires(simpleKey); + } + } + } + + private void expires(final SimpleKey cacheKey) { + final SimpleElement elt = delegate.get(cacheKey); + delegate.remove(cacheKey); + onExpired(cacheKey, elt); + } + + private void onExpired(final SimpleKey cacheKey, final SimpleElement elt) { + for (final SimpleListener listener : listeners.values()) { + listener.onExpired(Collections.> singletonList( + new SimpleEvent<>(this, EventType.REMOVED, null, cacheKey.getKey(), elt.getElement()))); + } + } + + @Override + public V getAndPut(final K key, final V value) { + assertNotClosed(); + assertNotNull(key, "key"); + assertNotNull(value, "value"); + final long getStart = Times.now(false); + final V v = doGetControllingExpiry(getStart, key, false, false, true, false, loader); + put(key, value); + return v; + } + + @Override + public void putAll(final Map map) { + assertNotClosed(); + final TempStateCacheView view = new TempStateCacheView(this); + for (final Map.Entry e : map.entrySet()) { + view.put(e.getKey(), e.getValue()); + } + view.merge(); + } + + @Override + public boolean putIfAbsent(final K key, final V value) { + final boolean statisticsEnabled = config.isStatisticsEnabled(); + if (!containsKey(key)) { + if (statisticsEnabled) { + statistics.increaseMisses(1); + } + put(key, value); + return true; + } else { + if (statisticsEnabled) { + statistics.increaseHits(1); + } + } + return false; + } + + @Override + public boolean remove(final K key) { + assertNotClosed(); + assertNotNull(key, "key"); + + final boolean statisticsEnabled = config.isStatisticsEnabled(); + final long start = Times.now(!statisticsEnabled); + + writer.delete(key); + final SimpleKey cacheKey = new SimpleKey<>(key); + + final SimpleElement v = delegate.remove(cacheKey); + if (v == null || v.isExpired()) { + return false; + } + + final V value = v.getElement(); + for (final SimpleListener listener : listeners.values()) { + listener.onRemoved(Collections.> singletonList( + new SimpleEvent<>(this, EventType.REMOVED, value, key, value))); + } + if (statisticsEnabled) { + statistics.increaseRemovals(1); + statistics.addRemoveTime(Times.now(false) - start); + } + + return true; + } + + @Override + public boolean remove(final K key, final V oldValue) { + assertNotClosed(); + assertNotNull(key, "key"); + assertNotNull(oldValue, "oldValue"); + final long getStart = Times.now(false); + final V v = doGetControllingExpiry(getStart, key, false, false, false, false, loader); + if (oldValue.equals(v)) { + remove(key); + return true; + } else if (v != null) { + // weird but just for stats to be right + // (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods()) + expiryPolicy.getExpiryForAccess(); + } + return false; + } + + @Override + public V getAndRemove(final K key) { + assertNotClosed(); + assertNotNull(key, "key"); + final long getStart = Times.now(false); + final V v = doGetControllingExpiry(getStart, key, false, false, true, false, loader); + remove(key); + return v; + } + + private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad, + final boolean skipLoad, final boolean propagateLoadException, final CacheLoader loader) { + final boolean statisticsEnabled = config.isStatisticsEnabled(); + final SimpleKey simpleKey = new SimpleKey<>(key); + final SimpleElement elt = delegate.get(simpleKey); + V v = elt != null ? elt.getElement() : null; + if (v == null && (config.isReadThrough() || forceDoLoad)) { + if (!skipLoad) { + v = doLoad(key, false, propagateLoadException, loader); + } + } else if (statisticsEnabled) { + if (v != null) { + statistics.increaseHits(1); + } else { + statistics.increaseMisses(1); + } + } + + if (updateAcess && elt != null) { + final Duration expiryForAccess = expiryPolicy.getExpiryForAccess(); + if (!isNotZero(expiryForAccess)) { + expires(simpleKey); + } + } + if (statisticsEnabled && v != null) { + statistics.addGetTime(Times.now(false) - getStart); + } + return v; + } + + @Override + public boolean replace(final K key, final V oldValue, final V newValue) { + assertNotClosed(); + assertNotNull(key, "key"); + assertNotNull(oldValue, "oldValue"); + assertNotNull(newValue, "newValue"); + final V value = doGetControllingExpiry(Times.now(config.isStatisticsEnabled()), key, false, config.isReadThrough(), false, + true, loader); + if (value != null && value.equals(oldValue)) { + put(key, newValue); + return true; + } else if (value != null) { + expiryPolicy.getExpiryForAccess(); + } + return false; + } + + @Override + public boolean replace(final K key, final V value) { + assertNotClosed(); + assertNotNull(key, "key"); + assertNotNull(value, "value"); + boolean statisticsEnabled = config.isStatisticsEnabled(); + if (containsKey(key)) { + if (statisticsEnabled) { + statistics.increaseHits(1); + } + put(key, value); + return true; + } else if (statisticsEnabled) { + statistics.increaseMisses(1); + } + return false; + } + + @Override + public V getAndReplace(final K key, final V value) { + assertNotClosed(); + assertNotNull(key, "key"); + assertNotNull(value, "value"); + + final boolean statisticsEnabled = config.isStatisticsEnabled(); + + final SimpleElement elt = delegate.get(new SimpleKey<>(key)); + if (elt != null) { + V oldValue = elt.getElement(); + if (oldValue == null && config.isReadThrough()) { + oldValue = doLoad(key, false, false, loader); + } else if (statisticsEnabled) { + statistics.increaseHits(1); + } + put(key, value); + return oldValue; + } else if (statisticsEnabled) { + statistics.increaseMisses(1); + } + return null; + } + + @Override + public void removeAll(final Set keys) { + assertNotClosed(); + assertNotNull(keys, "keys"); + for (final K k : keys) { + remove(k); + } + } + + @Override + public void removeAll() { + assertNotClosed(); + for (final SimpleKey k : delegate.keySet()) { + remove(k.getKey()); + } + } + + @Override + public void clear() { + assertNotClosed(); + delegate.clear(); + } + + @Override + public > C2 getConfiguration(final Class clazz) { + assertNotClosed(); + return clazz.cast(config); + } + + @Override + public void loadAll(final Set keys, final boolean replaceExistingValues, + final CompletionListener completionListener) { + assertNotClosed(); + assertNotNull(keys, "keys"); + if (loader == null) { // quick exit path + if (completionListener != null) { + completionListener.onCompletion(); + } + return; + } + for (final K k : keys) { + assertNotNull(k, "a key"); + } + addPoolTask(new Runnable() { + + @Override + public void run() { + doLoadAll(keys, replaceExistingValues, completionListener); + } + }); + } + + private void addPoolTask(final Runnable runnable) { + final AtomicReference> ref = new AtomicReference<>(); + final CountDownLatch refIsSet = new CountDownLatch(1); + ref.set(pool.submit(new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } finally { + try { + refIsSet.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + poolTasks.remove(ref.get()); + } + } + })); + refIsSet.countDown(); + poolTasks.add(ref.get()); + } + + private void doLoadAll(final Set keys, final boolean replaceExistingValues, + final CompletionListener completionListener) { + try { + final long now = Times.now(false); + final Map kvMap = loader.loadAll(keys); + if (kvMap == null) { + return; + } + final CacheLoader preloaded = new MapLoader<>(kvMap); + for (final K k : keys) { + if (replaceExistingValues) { + doLoad(k, containsKey(k), completionListener != null, preloaded); + } else if (!containsKey(k)) { + doGetControllingExpiry(now, k, true, true, false, completionListener != null, preloaded); + } + } + } catch (final RuntimeException e) { + if (completionListener != null) { + completionListener.onException(e); + return; + } + } + if (completionListener != null) { + completionListener.onCompletion(); + } + } + + @Override + public T invoke(final K key, final EntryProcessor entryProcessor, final Object... arguments) + throws EntryProcessorException { + final TempStateCacheView view = new TempStateCacheView(this); + final T t = doInvoke(view, key, entryProcessor, arguments); + view.merge(); + return t; + } + + private T doInvoke(final TempStateCacheView view, final K key, final EntryProcessor entryProcessor, + final Object... arguments) { + assertNotClosed(); + assertNotNull(entryProcessor, "entryProcessor"); + assertNotNull(key, "key"); + try { + if (config.isStatisticsEnabled()) { + if (containsKey(key)) { + statistics.increaseHits(1); + } else { + statistics.increaseMisses(1); + } + } + return entryProcessor.process(new SimpleMutableEntry<>(view, key), arguments); + } catch (final Exception ex) { + return throwEntryProcessorException(ex); + } + } + + @Override + public Map> invokeAll(final Set keys, + final EntryProcessor entryProcessor, final Object... arguments) { + assertNotClosed(); + assertNotNull(entryProcessor, "entryProcessor"); + final Map> results = new HashMap<>(); + for (final K k : keys) { + try { + final T invoke = invoke(k, entryProcessor, arguments); + if (invoke != null) { + results.put(k, new EntryProcessorResult() { + + @Override + public T get() throws EntryProcessorException { + return invoke; + } + }); + } + } catch (final Exception e) { + results.put(k, new EntryProcessorResult() { + + @Override + public T get() throws EntryProcessorException { + return throwEntryProcessorException(e); + } + }); + } + } + return results; + } + + @Override + public void registerCacheEntryListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + assertNotClosed(); + if (listeners.containsKey(cacheEntryListenerConfiguration)) { + throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered"); + } + listeners.put(cacheEntryListenerConfiguration, new SimpleListener<>(cacheEntryListenerConfiguration)); + config.addListener(cacheEntryListenerConfiguration); + } + + @Override + public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + assertNotClosed(); + listeners.remove(cacheEntryListenerConfiguration); + config.removeListener(cacheEntryListenerConfiguration); + } + + @Override + public Iterator> iterator() { + assertNotClosed(); + final Iterator> keys = new HashSet<>(delegate.keySet()).iterator(); + return new Iterator>() { + + private K lastKey = null; + + @Override + public boolean hasNext() { + return keys.hasNext(); + } + + @Override + public Entry next() { + lastKey = keys.next().getKey(); + return new SimpleEntry<>(lastKey, get(lastKey)); + } + + @Override + public void remove() { + if (isClosed() || lastKey == null) { + throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()"); + } + SimpleCache.this.remove(lastKey); + } + }; + } + + @Override + public String getName() { + assertNotClosed(); + return name; + } + + @Override + public CacheManager getCacheManager() { + assertNotClosed(); + return manager; + } + + @Override + public synchronized void close() { + if (isClosed()) { + return; + } + + for (final Future task : poolTasks) { + task.cancel(true); + } + + final CacheException ce = new CacheException(); + manager.release(getName()); + closed = true; + close(loader, ce); + close(writer, ce); + close(expiryPolicy, ce); + for (final SimpleListener listener : listeners.values()) { + try { + listener.close(); + } catch (final Exception e) { + ce.addSuppressed(e); + } + } + listeners.clear(); + JMXs.unregister(cacheConfigObjectName); + JMXs.unregister(cacheStatsObjectName); + delegate.clear(); + if (ce.getSuppressed().length > 0) { + throw ce; + } + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public T unwrap(final Class clazz) { + assertNotClosed(); + if (clazz.isInstance(this)) { + return clazz.cast(this); + } + if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class)) { + return clazz.cast(delegate); + } + throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap"); + } + + public Statistics getStatistics() { + return statistics; + } + + public void enableManagement() { + config.managementEnabled(); + JMXs.register(cacheConfigObjectName, new SimpleCacheMXBean(this)); + } + + public void disableManagement() { + config.managementDisabled(); + JMXs.unregister(cacheConfigObjectName); + } + + public void enableStatistics() { + config.statisticsEnabled(); + statistics.setActive(true); + JMXs.register(cacheStatsObjectName, new SimpleCacheStatisticsMXBean(statistics)); + } + + public void disableStatistics() { + config.statisticsDisabled(); + statistics.setActive(false); + JMXs.unregister(cacheStatsObjectName); + } + + private static String property(final Properties properties, final String cacheName, final String name, + final String defaultValue) { + return properties.getProperty(cacheName + "." + name, properties.getProperty(name, defaultValue)); + } + + private static boolean isNotZero(final Duration duration) { + return duration == null || !duration.isZero(); + } + + private static T throwEntryProcessorException(final Exception ex) { + if (EntryProcessorException.class.isInstance(ex)) { + throw EntryProcessorException.class.cast(ex); + } + throw new EntryProcessorException(ex); + } + + private static void close(final Object potentiallyCloseable, final CacheException wrapper) { + if (AutoCloseable.class.isInstance(potentiallyCloseable)) { + try { + AutoCloseable.class.cast(potentiallyCloseable).close(); + } catch (final Exception re) { + wrapper.addSuppressed(re); + } + } + } + + private class EvictionThread implements Runnable { + + private final long pause; + + private final long maxDelete; + + private EvictionThread(final long evictionPause, final long maxDelete) { + this.pause = evictionPause; + this.maxDelete = maxDelete; + } + + @Override + public void run() { + while (!isClosed()) { + try { + Thread.sleep(pause); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + if (delegate.isEmpty()) { + continue; + } + + try { + final List> keys = new ArrayList<>(delegate.keySet()); + Collections.sort(keys, new Comparator>() { + + @Override + public int compare(final SimpleKey o1, final SimpleKey o2) { + final long l = o1.lastAccess() - o2.lastAccess(); + if (l == 0) { + return keys.indexOf(o1) - keys.indexOf(o2); + } + return (int) l; + } + }); + + int delete = 0; + for (final SimpleKey key : keys) { + final SimpleElement elt = delegate.get(key); + if (elt != null && elt.isExpired()) { + delegate.remove(key); + statistics.increaseEvictions(1); + onExpired(key, elt); + delete++; + if (delete >= maxDelete) { + break; + } + } + } + } catch (final Exception e) { + // no-op + } + } + } + } + + private static class MapLoader implements CacheLoader { + + private final Map loaded; + + private MapLoader(final Map loaded) { + this.loaded = loaded; + } + + @Override + public V load(final K key) throws CacheLoaderException { + return loaded.get(key); + } + + @Override + public Map loadAll(final Iterable keys) throws CacheLoaderException { + throw new UnsupportedOperationException(); + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheMXBean.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheMXBean.java new file mode 100644 index 000000000..e40bebdc3 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheMXBean.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import javax.cache.Cache; +import javax.cache.configuration.CompleteConfiguration; +import javax.cache.configuration.Configuration; +import javax.cache.management.CacheMXBean; + +public class SimpleCacheMXBean implements CacheMXBean { + + private final Cache delegate; + + public SimpleCacheMXBean(final Cache delegate) { + this.delegate = delegate; + } + + private Configuration config() { + return delegate.getConfiguration(Configuration.class); + } + + private CompleteConfiguration completeConfig() { + return delegate.getConfiguration(CompleteConfiguration.class); + } + + @Override + public String getKeyType() { + return config().getKeyType().getName(); + } + + @Override + public String getValueType() { + return config().getValueType().getName(); + } + + @Override + public boolean isReadThrough() { + try { + return completeConfig().isReadThrough(); + } catch (final Exception e) { + return false; + } + } + + @Override + public boolean isWriteThrough() { + try { + return completeConfig().isWriteThrough(); + } catch (final Exception e) { + return false; + } + } + + @Override + public boolean isStoreByValue() { + return config().isStoreByValue(); + } + + @Override + public boolean isStatisticsEnabled() { + try { + return completeConfig().isStatisticsEnabled(); + } catch (final Exception e) { + return false; + } + } + + @Override + public boolean isManagementEnabled() { + try { + return completeConfig().isManagementEnabled(); + } catch (final Exception e) { + return false; + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheStatisticsMXBean.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheStatisticsMXBean.java new file mode 100644 index 000000000..436092016 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheStatisticsMXBean.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.geronimo.jcache.simple; + +import javax.cache.management.CacheStatisticsMXBean; + +public class SimpleCacheStatisticsMXBean implements CacheStatisticsMXBean { + + private final Statistics statistics; + + public SimpleCacheStatisticsMXBean(final Statistics stats) { + this.statistics = stats; + } + + @Override + public void clear() { + statistics.reset(); + } + + @Override + public long getCacheHits() { + return statistics.getHits(); + } + + @Override + public float getCacheHitPercentage() { + final long hits = getCacheHits(); + if (hits == 0) { + return 0; + } + return (float) hits / getCacheGets() * 100.0f; + } + + @Override + public long getCacheMisses() { + return statistics.getMisses(); + } + + @Override + public float getCacheMissPercentage() { + final long misses = getCacheMisses(); + if (misses == 0) { + return 0; + } + return (float) misses / getCacheGets() * 100.0f; + } + + @Override + public long getCacheGets() { + return getCacheHits() + getCacheMisses(); + } + + @Override + public long getCachePuts() { + return statistics.getPuts(); + } + + @Override + public long getCacheRemovals() { + return statistics.getRemovals(); + } + + @Override + public long getCacheEvictions() { + return statistics.getEvictions(); + } + + @Override + public float getAverageGetTime() { + return averageTime(statistics.getTimeTakenForGets()); + } + + @Override + public float getAveragePutTime() { + return averageTime(statistics.getTimeTakenForPuts()); + } + + @Override + public float getAverageRemoveTime() { + return averageTime(statistics.getTimeTakenForRemovals()); + } + + private float averageTime(final long timeTaken) { + final long gets = getCacheGets(); + if (timeTaken == 0 || gets == 0) { + return 0; + } + return timeTaken / gets; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleConfiguration.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleConfiguration.java new file mode 100644 index 000000000..1f90ba22b --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleConfiguration.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import static java.util.Collections.unmodifiableSet; + +import java.util.HashSet; +import java.util.Set; + +import javax.cache.configuration.CacheEntryListenerConfiguration; +import javax.cache.configuration.CompleteConfiguration; +import javax.cache.configuration.Configuration; +import javax.cache.configuration.Factory; +import javax.cache.expiry.EternalExpiryPolicy; +import javax.cache.expiry.ExpiryPolicy; +import javax.cache.integration.CacheLoader; +import javax.cache.integration.CacheWriter; + +public class SimpleConfiguration implements CompleteConfiguration { + + private final Class keyType; + + private final Class valueType; + + private final boolean storeByValue; + + private final boolean readThrough; + + private final boolean writeThrough; + + private final Factory> cacheLoaderFactory; + + private final Factory> cacheWristerFactory; + + private final Factory expiryPolicyFactory; + + private final Set> cacheEntryListenerConfigurations; + + private volatile boolean statisticsEnabled; + + private volatile boolean managementEnabled; + + public SimpleConfiguration(final Configuration configuration, final Class keyType, final Class valueType) { + this.keyType = keyType; + this.valueType = valueType; + if (configuration instanceof CompleteConfiguration) { + final CompleteConfiguration cConfiguration = (CompleteConfiguration) configuration; + storeByValue = configuration.isStoreByValue(); + readThrough = cConfiguration.isReadThrough(); + writeThrough = cConfiguration.isWriteThrough(); + statisticsEnabled = cConfiguration.isStatisticsEnabled(); + managementEnabled = cConfiguration.isManagementEnabled(); + cacheLoaderFactory = cConfiguration.getCacheLoaderFactory(); + cacheWristerFactory = cConfiguration.getCacheWriterFactory(); + this.expiryPolicyFactory = cConfiguration.getExpiryPolicyFactory(); + cacheEntryListenerConfigurations = new HashSet<>(); + + final Iterable> entryListenerConfigurations = cConfiguration + .getCacheEntryListenerConfigurations(); + if (entryListenerConfigurations != null) { + for (final CacheEntryListenerConfiguration kvCacheEntryListenerConfiguration : entryListenerConfigurations) { + cacheEntryListenerConfigurations.add(kvCacheEntryListenerConfiguration); + } + } + } else { + expiryPolicyFactory = EternalExpiryPolicy.factoryOf(); + storeByValue = true; + readThrough = false; + writeThrough = false; + statisticsEnabled = false; + managementEnabled = false; + cacheLoaderFactory = null; + cacheWristerFactory = null; + cacheEntryListenerConfigurations = new HashSet<>(); + } + } + + @Override + public Class getKeyType() { + return keyType == null ? (Class) Object.class : keyType; + } + + @Override + public Class getValueType() { + return valueType == null ? (Class) Object.class : valueType; + } + + @Override + public boolean isStoreByValue() { + return storeByValue; + } + + @Override + public boolean isReadThrough() { + return readThrough; + } + + @Override + public boolean isWriteThrough() { + return writeThrough; + } + + @Override + public boolean isStatisticsEnabled() { + return statisticsEnabled; + } + + @Override + public boolean isManagementEnabled() { + return managementEnabled; + } + + @Override + public Iterable> getCacheEntryListenerConfigurations() { + return unmodifiableSet(cacheEntryListenerConfigurations); + } + + @Override + public Factory> getCacheLoaderFactory() { + return cacheLoaderFactory; + } + + @Override + public Factory> getCacheWriterFactory() { + return cacheWristerFactory; + } + + @Override + public Factory getExpiryPolicyFactory() { + return expiryPolicyFactory; + } + + public synchronized void addListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + cacheEntryListenerConfigurations.add(cacheEntryListenerConfiguration); + } + + public synchronized void removeListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + cacheEntryListenerConfigurations.remove(cacheEntryListenerConfiguration); + } + + public void statisticsEnabled() { + statisticsEnabled = true; + } + + public void managementEnabled() { + managementEnabled = true; + } + + public void statisticsDisabled() { + statisticsEnabled = false; + } + + public void managementDisabled() { + managementEnabled = false; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleElement.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleElement.java new file mode 100644 index 000000000..e1314dc52 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleElement.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import javax.cache.expiry.Duration; + +public class SimpleElement { + + private final V element; + + private final long end; + + public SimpleElement(final V element, final Duration duration) { + this.element = element; + this.end = duration == null || duration.isEternal() ? Long.MAX_VALUE + : ((System.nanoTime() + duration.getTimeUnit().toNanos(duration.getDurationAmount())) / 1000); + } + + public V getElement() { + return element; + } + + public boolean isExpired() { + return end != -1 && (end == 0 || Times.now(false) > end); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleEntry.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleEntry.java new file mode 100644 index 000000000..9dd4cc629 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleEntry.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import javax.cache.Cache; + +public class SimpleEntry implements Cache.Entry { + + private final K key; + + private final V value; + + public SimpleEntry(final K key, final V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public T unwrap(final Class clazz) { + if (clazz.isInstance(this)) { + return clazz.cast(this); + } + throw new UnsupportedOperationException(); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleEvent.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleEvent.java new file mode 100644 index 000000000..7a7ec343e --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleEvent.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.geronimo.jcache.simple; + +import javax.cache.Cache; +import javax.cache.event.CacheEntryEvent; +import javax.cache.event.EventType; + +public class SimpleEvent extends CacheEntryEvent { + + private static final long serialVersionUID = 4761272981003897488L; + + private final V old; + + private final K key; + + private final V value; + + public SimpleEvent(final Cache source, final EventType eventType, final V old, final K key, final V value) { + super(source, eventType); + this.old = old; + this.key = key; + this.value = value; + } + + @Override + public V getOldValue() { + return old; + } + + @Override + public boolean isOldValueAvailable() { + return old != null; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public T unwrap(final Class clazz) { + if (clazz.isInstance(this)) { + return clazz.cast(this); + } + throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap"); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleKey.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleKey.java new file mode 100644 index 000000000..148090885 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleKey.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.geronimo.jcache.simple; + +import java.io.Serializable; + +public class SimpleKey implements Serializable { + + private final K key; + + private volatile long lastAccess = 0; + + public SimpleKey(final K key) { + this.key = key; + } + + public void access(final long time) { + lastAccess = time; + } + + public long lastAccess() { + return lastAccess; + } + + public K getKey() { + return key; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + // if (o == null || getClass() != o.getClass()) return false; // not needed normally + final SimpleKey k = SimpleKey.class.cast(o); + return key.equals(k.key); + + } + + @Override + public int hashCode() { + return key.hashCode(); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleListener.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleListener.java new file mode 100644 index 000000000..82e6dc324 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleListener.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import java.util.ArrayList; +import java.util.List; + +import javax.cache.configuration.CacheEntryListenerConfiguration; +import javax.cache.configuration.Factory; +import javax.cache.event.CacheEntryCreatedListener; +import javax.cache.event.CacheEntryEvent; +import javax.cache.event.CacheEntryEventFilter; +import javax.cache.event.CacheEntryExpiredListener; +import javax.cache.event.CacheEntryListener; +import javax.cache.event.CacheEntryListenerException; +import javax.cache.event.CacheEntryRemovedListener; +import javax.cache.event.CacheEntryUpdatedListener; + +public class SimpleListener implements AutoCloseable { + + private final CacheEntryEventFilter filter; + + private final CacheEntryListener delegate; + + private final boolean remove; + + private final boolean expire; + + private final boolean update; + + private final boolean create; + + public SimpleListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + final Factory> filterFactory = cacheEntryListenerConfiguration + .getCacheEntryEventFilterFactory(); + if (filterFactory == null) { + this.filter = NoFilter.INSTANCE; + } else { + final CacheEntryEventFilter filter = filterFactory.create(); + this.filter = (CacheEntryEventFilter) (filter == null ? NoFilter.INSTANCE : filter); + } + + delegate = cacheEntryListenerConfiguration.getCacheEntryListenerFactory().create(); + remove = CacheEntryRemovedListener.class.isInstance(delegate); + expire = CacheEntryExpiredListener.class.isInstance(delegate); + update = CacheEntryUpdatedListener.class.isInstance(delegate); + create = CacheEntryCreatedListener.class.isInstance(delegate); + } + + public void onRemoved(final List> events) throws CacheEntryListenerException { + if (remove) { + CacheEntryRemovedListener.class.cast(delegate).onRemoved(filter(events)); + } + } + + public void onExpired(final List> events) throws CacheEntryListenerException { + if (expire) { + CacheEntryExpiredListener.class.cast(delegate).onExpired(filter(events)); + } + } + + public void onUpdated(final List> events) throws CacheEntryListenerException { + if (update) { + CacheEntryUpdatedListener.class.cast(delegate).onUpdated(filter(events)); + } + } + + public void onCreated(final List> events) throws CacheEntryListenerException { + if (create) { + CacheEntryCreatedListener.class.cast(delegate).onCreated(filter(events)); + } + } + + private Iterable> filter( + final List> events) { + if (filter == NoFilter.INSTANCE) { + return events; + } + + final List> filtered = new ArrayList>( + events.size()); + for (final CacheEntryEvent event : events) { + if (filter.evaluate(event)) { + filtered.add(event); + } + } + return filtered; + } + + @Override + public void close() throws Exception { + if (AutoCloseable.class.isInstance(delegate)) { + AutoCloseable.class.cast(delegate).close(); + } + } + + public static class NoFilter implements CacheEntryEventFilter { + + public static final CacheEntryEventFilter INSTANCE = new NoFilter(); + + private NoFilter() { + // no-op + } + + @Override + public boolean evaluate(final CacheEntryEvent event) throws CacheEntryListenerException { + return true; + } + } +} \ No newline at end of file diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleManager.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleManager.java new file mode 100644 index 000000000..13a94ed93 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleManager.java @@ -0,0 +1,285 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import static java.util.Collections.unmodifiableSet; +import static org.apache.geronimo.jcache.simple.Asserts.assertNotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.cache.Cache; +import javax.cache.CacheException; +import javax.cache.CacheManager; +import javax.cache.configuration.Configuration; +import javax.cache.spi.CachingProvider; + +public class SimpleManager implements CacheManager { + + private final CachingProvider provider; + + private final URI uri; + + private final ClassLoader loader; + + private final Properties properties; + + private final ConcurrentMap> caches = new ConcurrentHashMap<>(); + + private final Properties configProperties; + + private final ExecutorService executorService; + + private volatile boolean closed = false; + + SimpleManager(final CachingProvider provider, final URI uri, final ClassLoader loader, final Properties properties) { + this.provider = provider; + this.uri = uri; + this.loader = loader; + this.properties = readConfig(uri, loader, properties); + this.configProperties = properties; + + ExecutorService executorService = rawProperty("geronimo.pool"); + if (executorService == null) { + final int poolSize = Integer.parseInt(this.properties.getProperty("pool.size", "16")); + final SimpleThreadFactory threadFactory = new SimpleThreadFactory("geronimo-simple-jcache-[" + uri.toASCIIString() + "]-"); + executorService = poolSize > 0 ? Executors.newFixedThreadPool(poolSize, threadFactory) + : Executors.newCachedThreadPool(threadFactory); + } + this.executorService = executorService; + } + + private T rawProperty(final String name) { + final Object value = this.properties.get(name); + if (value == null) { + return (T) this.properties.get(name); + } + return (T) value; + } + + private Properties readConfig(final URI uri, final ClassLoader loader, final Properties properties) { + final Properties props = new Properties(); + try { + if (SimpleProvider.DEFAULT_URI.toString().equals(uri.toString()) || uri.getScheme().equals("geronimo") + || uri.getScheme().equals("classpath")) { + + final Enumeration resources = loader.getResources(uri.toASCIIString().substring((uri.getScheme() + "://").length())); + while (resources.hasMoreElements()) { + do { + addProperties(resources.nextElement(), props); + } while (resources.hasMoreElements()); + } + } else { + props.load(uri.toURL().openStream()); + } + } catch (final IOException e) { + throw new IllegalStateException(e); + } + if (properties != null) { + props.putAll(properties); + } + return props; + } + + private void addProperties(final URL url, final Properties aggregator) { + try (final InputStream inputStream = url.openStream()) { + aggregator.load(inputStream); + } catch (final IOException e) { + throw new IllegalArgumentException(e); + } + } + + private void assertNotClosed() { + if (isClosed()) { + throw new IllegalStateException("cache manager closed"); + } + } + + @Override + public > Cache createCache(final String cacheName, final C configuration) + throws IllegalArgumentException { + assertNotClosed(); + assertNotNull(cacheName, "cacheName"); + assertNotNull(configuration, "configuration"); + final Class keyType = configuration.getKeyType(); + final Class valueType = configuration.getValueType(); + if (!caches.containsKey(cacheName)) { + final Cache cache = ClassLoaderAwareCache.wrap(loader, new SimpleCache(loader, this, cacheName, + new SimpleConfiguration<>(configuration, keyType, valueType), properties, executorService)); + caches.putIfAbsent(cacheName, cache); + } else { + throw new CacheException("cache " + cacheName + " already exists"); + } + return (Cache) getCache(cacheName, keyType, valueType); + } + + @Override + public void destroyCache(final String cacheName) { + assertNotClosed(); + assertNotNull(cacheName, "cacheName"); + final Cache cache = caches.remove(cacheName); + if (cache != null && !cache.isClosed()) { + cache.clear(); + cache.close(); + } + } + + @Override + public void enableManagement(final String cacheName, final boolean enabled) { + assertNotClosed(); + assertNotNull(cacheName, "cacheName"); + final SimpleCache cache = of(cacheName); + if (cache != null) { + if (enabled) { + cache.enableManagement(); + } else { + cache.disableManagement(); + } + } + } + + private SimpleCache of(final String cacheName) { + return SimpleCache.class.cast(ClassLoaderAwareCache.getDelegate(caches.get(cacheName))); + } + + @Override + public void enableStatistics(final String cacheName, final boolean enabled) { + assertNotClosed(); + assertNotNull(cacheName, "cacheName"); + final SimpleCache cache = of(cacheName); + if (cache != null) { + if (enabled) { + cache.enableStatistics(); + } else { + cache.disableStatistics(); + } + } + } + + @Override + public synchronized void close() { + if (isClosed()) { + return; + } + + assertNotClosed(); + for (final Cache c : caches.values()) { + c.close(); + } + caches.clear(); + for (final Runnable task : executorService.shutdownNow()) { + task.run(); + } + closed = true; + if (SimpleProvider.class.isInstance(provider)) { + SimpleProvider.class.cast(provider).remove(this); + } // else throw? + } + + @Override + public T unwrap(final Class clazz) { + if (clazz.isInstance(this)) { + return clazz.cast(this); + } + throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap"); + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public Cache getCache(final String cacheName) { + assertNotClosed(); + assertNotNull(cacheName, "cacheName"); + return (Cache) doGetCache(cacheName, null, null); + } + + @Override + public Iterable getCacheNames() { + assertNotClosed(); + return unmodifiableSet(new HashSet<>(caches.keySet())); + } + + @Override + public Cache getCache(final String cacheName, final Class keyType, final Class valueType) { + assertNotClosed(); + assertNotNull(cacheName, "cacheName"); + assertNotNull(keyType, "keyType"); + assertNotNull(valueType, "valueType"); + try { + return doGetCache(cacheName, keyType, valueType); + } catch (final IllegalArgumentException iae) { + throw new ClassCastException(iae.getMessage()); + } + } + + private Cache doGetCache(final String cacheName, final Class keyType, final Class valueType) { + final Cache cache = (Cache) caches.get(cacheName); + if (keyType == null && valueType == null) { + return cache; + } + if (cache == null) { + return null; + } + + final Configuration config = cache.getConfiguration(Configuration.class); + if ((keyType != null && !config.getKeyType().isAssignableFrom(keyType)) + || (valueType != null && !config.getValueType().isAssignableFrom(valueType))) { + throw new IllegalArgumentException( + "this cache is <" + config.getKeyType().getName() + ", " + config.getValueType().getName() + "> " + + " and not <" + keyType.getName() + ", " + valueType.getName() + ">"); + } + return cache; + } + + @Override + public CachingProvider getCachingProvider() { + return provider; + } + + @Override + public URI getURI() { + return uri; + } + + @Override + public ClassLoader getClassLoader() { + return loader; + } + + @Override + public Properties getProperties() { + return configProperties; + } + + public void release(final String name) { + caches.remove(name); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleMutableEntry.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleMutableEntry.java new file mode 100644 index 000000000..3eab1868b --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleMutableEntry.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import javax.cache.Cache; +import javax.cache.processor.MutableEntry; + +public class SimpleMutableEntry implements MutableEntry { + + private final Cache cache; + + private final K key; + + public SimpleMutableEntry(final Cache cache, final K key) { + this.cache = cache; + this.key = key; + } + + @Override + public boolean exists() { + return cache.containsKey(key); + } + + @Override + public void remove() { + cache.remove(key); + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return cache.get(key); + } + + @Override + public void setValue(final V value) { + cache.put(key, value); + } + + @Override + public T unwrap(final Class clazz) { + if (clazz.isInstance(this)) { + return clazz.cast(this); + } + throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap"); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleProvider.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleProvider.java new file mode 100644 index 000000000..92700f127 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleProvider.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.geronimo.jcache.simple; + +import java.net.URI; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.cache.CacheManager; +import javax.cache.configuration.OptionalFeature; +import javax.cache.spi.CachingProvider; + +public class SimpleProvider implements CachingProvider { + + static final URI DEFAULT_URI = URI.create("geronimo://simple-jcache.properties"); + + private final ConcurrentMap> cacheManagersByLoader = new ConcurrentHashMap<>(); + + @Override + public CacheManager getCacheManager(final URI inUri, final ClassLoader inClassLoader, final Properties properties) { + final URI uri = inUri != null ? inUri : getDefaultURI(); + final ClassLoader classLoader = inClassLoader != null ? inClassLoader : getDefaultClassLoader(); + + ConcurrentMap managers = cacheManagersByLoader.get(classLoader); + if (managers == null) { + managers = new ConcurrentHashMap<>(); + final ConcurrentMap existingManagers = cacheManagersByLoader.putIfAbsent(classLoader, managers); + if (existingManagers != null) { + managers = existingManagers; + } + } + + CacheManager mgr = managers.get(uri); + if (mgr == null) { + mgr = new SimpleManager(this, uri, classLoader, properties); + final CacheManager existing = managers.putIfAbsent(uri, mgr); + if (existing != null) { + mgr = existing; + } + } + + return mgr; + } + + @Override + public URI getDefaultURI() { + return DEFAULT_URI; + } + + @Override + public void close() { + for (final Map v : cacheManagersByLoader.values()) { + for (final CacheManager m : v.values()) { + m.close(); + } + v.clear(); + } + cacheManagersByLoader.clear(); + } + + @Override + public void close(final ClassLoader classLoader) { + final Map cacheManagers = cacheManagersByLoader.remove(classLoader); + if (cacheManagers != null) { + for (final CacheManager mgr : cacheManagers.values()) { + mgr.close(); + } + cacheManagers.clear(); + } + } + + @Override + public void close(final URI uri, final ClassLoader classLoader) { + final Map cacheManagers = cacheManagersByLoader.remove(classLoader); + if (cacheManagers != null) { + final CacheManager mgr = cacheManagers.remove(uri); + if (mgr != null) { + mgr.close(); + } + } + } + + @Override + public CacheManager getCacheManager(final URI uri, final ClassLoader classLoader) { + return getCacheManager(uri, classLoader, getDefaultProperties()); + } + + @Override + public CacheManager getCacheManager() { + return getCacheManager(getDefaultURI(), getDefaultClassLoader()); + } + + @Override + public boolean isSupported(final OptionalFeature optionalFeature) { + return optionalFeature == OptionalFeature.STORE_BY_REFERENCE; + } + + @Override + public ClassLoader getDefaultClassLoader() { + return SimpleProvider.class.getClassLoader(); + } + + @Override + public Properties getDefaultProperties() { + return new Properties(); + } + + void remove(final CacheManager mgr) { + final ClassLoader classLoader = mgr.getClassLoader(); + final Map mgrs = cacheManagersByLoader.get(classLoader); + if (mgrs != null) { + mgrs.remove(mgr.getURI()); + if (mgrs.isEmpty()) { + cacheManagersByLoader.remove(classLoader); + } + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleThreadFactory.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleThreadFactory.java new file mode 100644 index 000000000..a3c71b402 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleThreadFactory.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class SimpleThreadFactory implements ThreadFactory { + + private static final AtomicInteger POOL_IDX = new AtomicInteger(); + + private final AtomicInteger threadIdx = new AtomicInteger(); + + private final int poolIdx; + + private final String format; + + public SimpleThreadFactory(final String format) { + this.format = format; + this.poolIdx = POOL_IDX.incrementAndGet(); + } + + @Override + public Thread newThread(final Runnable r) { + final Thread thread = new Thread(r); + thread.setName(String.format(format, poolIdx, threadIdx.incrementAndGet())); + thread.setPriority(Thread.NORM_PRIORITY); + thread.setDaemon(false); // ensure to call close, that's it + return thread; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Statistics.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Statistics.java new file mode 100644 index 000000000..b2c99cf0f --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Statistics.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.geronimo.jcache.simple; + +import java.util.concurrent.atomic.AtomicLong; + +public class Statistics { + + private final AtomicLong removals = new AtomicLong(); + private final AtomicLong expires = new AtomicLong(); + private final AtomicLong puts = new AtomicLong(); + private final AtomicLong hits = new AtomicLong(); + private final AtomicLong misses = new AtomicLong(); + private final AtomicLong evictions = new AtomicLong(); + private final AtomicLong putTimeTaken = new AtomicLong(); + private final AtomicLong getTimeTaken = new AtomicLong(); + private final AtomicLong removeTimeTaken = new AtomicLong(); + private volatile boolean active = true; + + public long getHits() { + return hits.get(); + } + + public long getMisses() { + return misses.get(); + } + + public long getPuts() { + return puts.get(); + } + + public long getRemovals() { + return removals.get(); + } + + public long getEvictions() { + return evictions.get(); + } + + public long getTimeTakenForGets() { + return getTimeTaken.get(); + } + + public long getTimeTakenForPuts() { + return putTimeTaken.get(); + } + + public long getTimeTakenForRemovals() { + return removeTimeTaken.get(); + } + + public void increaseRemovals(final long number) { + increment(removals, number); + } + + public void increaseExpiries(final long number) { + increment(expires, number); + } + + public void increasePuts(final long number) { + increment(puts, number); + } + + public void increaseHits(final long number) { + increment(hits, number); + } + + public void increaseMisses(final long number) { + increment(misses, number); + } + + public void increaseEvictions(final long number) { + increment(evictions, number); + } + + public void addGetTime(final long duration) { + increment(duration, getTimeTaken); + } + + public void addPutTime(final long duration) { + increment(duration, putTimeTaken); + } + + public void addRemoveTime(final long duration) { + increment(duration, removeTimeTaken); + } + + private void increment(final AtomicLong counter, final long number) { + if (!active) { + return; + } + counter.addAndGet(number); + } + + private void increment(final long duration, final AtomicLong counter) { + if (!active) { + return; + } + + if (counter.get() + duration < Long.MAX_VALUE) { + counter.addAndGet(duration); + } else { + reset(); + counter.set(duration); + } + } + + public void reset() { + puts.set(0); + misses.set(0); + removals.set(0); + expires.set(0); + hits.set(0); + evictions.set(0); + getTimeTaken.set(0); + putTimeTaken.set(0); + removeTimeTaken.set(0); + } + + public void setActive(final boolean active) { + this.active = active; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/TempStateCacheView.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/TempStateCacheView.java new file mode 100644 index 000000000..6e0684a35 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/TempStateCacheView.java @@ -0,0 +1,283 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +import static org.apache.geronimo.jcache.simple.Asserts.assertNotNull; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +import javax.cache.Cache; +import javax.cache.CacheManager; +import javax.cache.configuration.CacheEntryListenerConfiguration; +import javax.cache.configuration.CompleteConfiguration; +import javax.cache.configuration.Configuration; +import javax.cache.integration.CompletionListener; +import javax.cache.processor.EntryProcessor; +import javax.cache.processor.EntryProcessorException; +import javax.cache.processor.EntryProcessorResult; + +// kind of transactional view for a Cache, to use with EntryProcessor +public class TempStateCacheView implements Cache { + + private final SimpleCache cache; + + private final Map put = new HashMap(); + + private final Collection remove = new LinkedList(); + + private boolean removeAll = false; + + private boolean clear = false; + + public TempStateCacheView(final SimpleCache entries) { + this.cache = entries; + } + + public V get(final K key) { + if (ignoreKey(key)) { + return null; + } + + final V v = put.get(key); + if (v != null) { + return v; + } + + // for an EntryProcessor we already incremented stats - to enhance + // surely + if (cache.getConfiguration(CompleteConfiguration.class).isStatisticsEnabled()) { + final Statistics statistics = cache.getStatistics(); + if (cache.containsKey(key)) { + statistics.increaseHits(-1); + } else { + statistics.increaseMisses(-1); + } + } + return cache.get(key); + } + + private boolean ignoreKey(final K key) { + return removeAll || clear || remove.contains(key); + } + + public Map getAll(final Set keys) { + final Map v = new HashMap(keys.size()); + final Set missing = new HashSet(); + for (final K k : keys) { + final V value = put.get(k); + if (value != null) { + v.put(k, value); + } else if (!ignoreKey(k)) { + missing.add(k); + } + } + if (!missing.isEmpty()) { + v.putAll(cache.getAll(missing)); + } + return v; + } + + public boolean containsKey(final K key) { + return !ignoreKey(key) && (put.containsKey(key) || cache.containsKey(key)); + } + + public void loadAll(final Set keys, final boolean replaceExistingValues, + final CompletionListener completionListener) { + cache.loadAll(keys, replaceExistingValues, completionListener); + } + + public void put(final K key, final V value) { + assertNotNull(key, "key"); + assertNotNull(value, "value"); + put.put(key, value); + remove.remove(key); + } + + public V getAndPut(final K key, final V value) { + final V v = get(key); + put(key, value); + return v; + } + + public void putAll(final Map map) { + put.putAll(map); + for (final K k : map.keySet()) { + remove.remove(k); + } + } + + public boolean putIfAbsent(final K key, final V value) { + if (!put.containsKey(key)) { + put.put(key, value); + remove.remove(key); + return true; + } + return false; + } + + public boolean remove(final K key) { + final boolean noop = put.containsKey(key); + put.remove(key); + if (!ignoreKey(key)) { + if (!noop) { + remove.add(key); + } + return true; + } + return false; + } + + public boolean remove(final K key, final V oldValue) { + put.remove(key); + if (!ignoreKey(key) && oldValue.equals(cache.get(key))) { + remove.add(key); + return true; + } + return false; + } + + public V getAndRemove(final K key) { + final V v = get(key); + remove.add(key); + put.remove(key); + return v; + } + + public boolean replace(final K key, final V oldValue, final V newValue) { + if (oldValue.equals(get(key))) { + put(key, newValue); + return true; + } + return false; + } + + public boolean replace(final K key, final V value) { + if (containsKey(key)) { + remove(key); + return true; + } + return false; + } + + public V getAndReplace(final K key, final V value) { + if (containsKey(key)) { + final V oldValue = get(key); + put(key, value); + return oldValue; + } + return null; + } + + public void removeAll(final Set keys) { + remove.addAll(keys); + for (final K k : keys) { + put.remove(k); + } + } + + @Override + public void removeAll() { + removeAll = true; + put.clear(); + remove.clear(); + } + + @Override + public void clear() { + clear = true; + put.clear(); + remove.clear(); + } + + public > C getConfiguration(final Class clazz) { + return cache.getConfiguration(clazz); + } + + public T invoke(final K key, final EntryProcessor entryProcessor, final Object... arguments) + throws EntryProcessorException { + return cache.invoke(key, entryProcessor, arguments); + } + + public Map> invokeAll(Set keys, final EntryProcessor entryProcessor, + final Object... arguments) { + return cache.invokeAll(keys, entryProcessor, arguments); + } + + @Override + public String getName() { + return cache.getName(); + } + + @Override + public CacheManager getCacheManager() { + return cache.getCacheManager(); + } + + @Override + public void close() { + cache.close(); + } + + @Override + public boolean isClosed() { + return cache.isClosed(); + } + + @Override + public T unwrap(final Class clazz) { + return cache.unwrap(clazz); + } + + public void registerCacheEntryListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + cache.registerCacheEntryListener(cacheEntryListenerConfiguration); + } + + public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { + cache.deregisterCacheEntryListener(cacheEntryListenerConfiguration); + } + + @Override + public Iterator> iterator() { + return cache.iterator(); + } + + public void merge() { + if (removeAll) { + cache.removeAll(); + } + if (clear) { + cache.clear(); + } + + for (final Map.Entry entry : put.entrySet()) { + cache.put(entry.getKey(), entry.getValue()); + } + put.clear(); + for (final K entry : remove) { + cache.remove(entry); + } + remove.clear(); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Times.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Times.java new file mode 100644 index 000000000..d9e2de373 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Times.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple; + +public class Times { + + private Times() { + // no-op + } + + public static long now(final boolean ignore) { + if (ignore) { + return -1; + } + return System.nanoTime() / 1000; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CDIJCacheHelper.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CDIJCacheHelper.java new file mode 100644 index 000000000..b921accd4 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CDIJCacheHelper.java @@ -0,0 +1,557 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Logger; + +import javax.annotation.PreDestroy; +import javax.cache.annotation.CacheDefaults; +import javax.cache.annotation.CacheKey; +import javax.cache.annotation.CacheKeyGenerator; +import javax.cache.annotation.CachePut; +import javax.cache.annotation.CacheRemove; +import javax.cache.annotation.CacheRemoveAll; +import javax.cache.annotation.CacheResolverFactory; +import javax.cache.annotation.CacheResult; +import javax.cache.annotation.CacheValue; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; +import javax.interceptor.InvocationContext; + +@ApplicationScoped +public class CDIJCacheHelper { + + private static final Logger LOGGER = Logger.getLogger(CDIJCacheHelper.class.getName()); + + private static final boolean CLOSE_CACHE = !Boolean.getBoolean("org.apache.geronimo.jcache.simple.cdi.skip-close"); + + private final CacheKeyGeneratorImpl defaultCacheKeyGenerator = new CacheKeyGeneratorImpl(); + + private final Collection> toRelease = new ArrayList>(); + + private final ConcurrentMap methods = new ConcurrentHashMap(); + + private volatile CacheResolverFactoryImpl defaultCacheResolverFactory = null; // lazy to not create any cache if not needed + + @Inject + private BeanManager beanManager; + + @PreDestroy + private void release() { + if (CLOSE_CACHE && defaultCacheResolverFactory != null) { + defaultCacheResolverFactory.release(); + } + for (final CreationalContext cc : toRelease) { + try { + cc.release(); + } catch (final RuntimeException re) { + LOGGER.warning(re.getMessage()); + } + } + } + + public MethodMeta findMeta(final InvocationContext ic) { + final Method mtd = ic.getMethod(); + final Class refType = findKeyType(ic.getTarget()); + final MethodKey key = new MethodKey(refType, mtd); + MethodMeta methodMeta = methods.get(key); + if (methodMeta == null) { + synchronized (this) { + methodMeta = methods.get(key); + if (methodMeta == null) { + methodMeta = createMeta(ic); + methods.put(key, methodMeta); + } + } + } + return methodMeta; + } + + private Class findKeyType(final Object target) { + if (null == target) { + return null; + } + return target.getClass(); + } + + // it is unlikely we have all annotations but for now we have a single meta model + private MethodMeta createMeta(final InvocationContext ic) { + final CacheDefaults defaults = findDefaults(ic.getTarget() == null ? null : ic.getTarget().getClass(), ic.getMethod()); + + final Class[] parameterTypes = ic.getMethod().getParameterTypes(); + final Annotation[][] parameterAnnotations = ic.getMethod().getParameterAnnotations(); + final List> annotations = new ArrayList>(); + for (final Annotation[] parameterAnnotation : parameterAnnotations) { + final Set set = new HashSet(parameterAnnotation.length); + set.addAll(Arrays.asList(parameterAnnotation)); + annotations.add(set); + } + + final Set mtdAnnotations = new HashSet(); + mtdAnnotations.addAll(Arrays.asList(ic.getMethod().getAnnotations())); + + final CacheResult cacheResult = ic.getMethod().getAnnotation(CacheResult.class); + final String cacheResultCacheResultName = cacheResult == null ? null + : defaultName(ic.getMethod(), defaults, cacheResult.cacheName()); + final CacheResolverFactory cacheResultCacheResolverFactory = cacheResult == null ? null + : cacheResolverFactoryFor(defaults, cacheResult.cacheResolverFactory()); + final CacheKeyGenerator cacheResultCacheKeyGenerator = cacheResult == null ? null + : cacheKeyGeneratorFor(defaults, cacheResult.cacheKeyGenerator()); + + final CachePut cachePut = ic.getMethod().getAnnotation(CachePut.class); + final String cachePutCachePutName = cachePut == null ? null : defaultName(ic.getMethod(), defaults, cachePut.cacheName()); + final CacheResolverFactory cachePutCacheResolverFactory = cachePut == null ? null + : cacheResolverFactoryFor(defaults, cachePut.cacheResolverFactory()); + final CacheKeyGenerator cachePutCacheKeyGenerator = cachePut == null ? null + : cacheKeyGeneratorFor(defaults, cachePut.cacheKeyGenerator()); + + final CacheRemove cacheRemove = ic.getMethod().getAnnotation(CacheRemove.class); + final String cacheRemoveCacheRemoveName = cacheRemove == null ? null + : defaultName(ic.getMethod(), defaults, cacheRemove.cacheName()); + final CacheResolverFactory cacheRemoveCacheResolverFactory = cacheRemove == null ? null + : cacheResolverFactoryFor(defaults, cacheRemove.cacheResolverFactory()); + final CacheKeyGenerator cacheRemoveCacheKeyGenerator = cacheRemove == null ? null + : cacheKeyGeneratorFor(defaults, cacheRemove.cacheKeyGenerator()); + + final CacheRemoveAll cacheRemoveAll = ic.getMethod().getAnnotation(CacheRemoveAll.class); + final String cacheRemoveAllCacheRemoveAllName = cacheRemoveAll == null ? null + : defaultName(ic.getMethod(), defaults, cacheRemoveAll.cacheName()); + final CacheResolverFactory cacheRemoveAllCacheResolverFactory = cacheRemoveAll == null ? null + : cacheResolverFactoryFor(defaults, cacheRemoveAll.cacheResolverFactory()); + + return new MethodMeta(parameterTypes, annotations, mtdAnnotations, keyParameterIndexes(ic.getMethod()), + getValueParameter(annotations), cacheResultCacheResultName, + cacheResultCacheResolverFactory, cacheResultCacheKeyGenerator, cacheResult, cachePutCachePutName, + cachePutCacheResolverFactory, cachePutCacheKeyGenerator, cachePut != null && cachePut.afterInvocation(), cachePut, + cacheRemoveCacheRemoveName, cacheRemoveCacheResolverFactory, cacheRemoveCacheKeyGenerator, + cacheRemove != null && cacheRemove.afterInvocation(), cacheRemove, cacheRemoveAllCacheRemoveAllName, + cacheRemoveAllCacheResolverFactory, cacheRemoveAll, + CompletionStage.class.isAssignableFrom(ic.getMethod().getReturnType())); + } + + private Integer getValueParameter(final List> annotations) { + int idx = 0; + for (final Set set : annotations) { + for (final Annotation a : set) { + if (a.annotationType() == CacheValue.class) { + return idx; + } + } + } + return -1; + } + + private String defaultName(final Method method, final CacheDefaults defaults, final String cacheName) { + if (!cacheName.isEmpty()) { + return cacheName; + } + if (defaults != null) { + final String name = defaults.cacheName(); + if (!name.isEmpty()) { + return name; + } + } + + final StringBuilder name = new StringBuilder(method.getDeclaringClass().getName()); + name.append("."); + name.append(method.getName()); + name.append("("); + final Class[] parameterTypes = method.getParameterTypes(); + for (int pIdx = 0; pIdx < parameterTypes.length; pIdx++) { + name.append(parameterTypes[pIdx].getName()); + if ((pIdx + 1) < parameterTypes.length) { + name.append(","); + } + } + name.append(")"); + return name.toString(); + } + + private CacheDefaults findDefaults(final Class targetType, final Method method) { + if (Proxy.isProxyClass(targetType)) // target doesnt hold annotations + { + final Class api = method.getDeclaringClass(); + for (final Class type : targetType.getInterfaces()) { + if (!api.isAssignableFrom(type)) { + continue; + } + return extractDefaults(type); + } + } + return extractDefaults(targetType); + } + + private CacheDefaults extractDefaults(final Class type) { + CacheDefaults annotation = null; + Class clazz = type; + while (clazz != null && clazz != Object.class) { + annotation = clazz.getAnnotation(CacheDefaults.class); + if (annotation != null) { + break; + } + clazz = clazz.getSuperclass(); + } + return annotation; + } + + public boolean isIncluded(final Class aClass, final Class[] in, final Class[] out) { + if (in.length == 0 && out.length == 0) { + return false; + } + for (final Class potentialIn : in) { + if (potentialIn.isAssignableFrom(aClass)) { + for (final Class potentialOut : out) { + if (potentialOut.isAssignableFrom(aClass)) { + return false; + } + } + return true; + } + } + return false; + } + + private CacheKeyGenerator cacheKeyGeneratorFor(final CacheDefaults defaults, + final Class cacheKeyGenerator) { + if (!CacheKeyGenerator.class.equals(cacheKeyGenerator)) { + return instance(cacheKeyGenerator); + } + if (defaults != null) { + final Class defaultCacheKeyGenerator = defaults.cacheKeyGenerator(); + if (!CacheKeyGenerator.class.equals(defaultCacheKeyGenerator)) { + return instance(defaultCacheKeyGenerator); + } + } + return defaultCacheKeyGenerator; + } + + private CacheResolverFactory cacheResolverFactoryFor(final CacheDefaults defaults, + final Class cacheResolverFactory) { + if (!CacheResolverFactory.class.equals(cacheResolverFactory)) { + return instance(cacheResolverFactory); + } + if (defaults != null) { + final Class defaultCacheResolverFactory = defaults.cacheResolverFactory(); + if (!CacheResolverFactory.class.equals(defaultCacheResolverFactory)) { + return instance(defaultCacheResolverFactory); + } + } + return defaultCacheResolverFactory(); + } + + private T instance(final Class type) { + final Set> beans = beanManager.getBeans(type); + if (beans.isEmpty()) { + if (CacheKeyGenerator.class == type) { + return (T) defaultCacheKeyGenerator; + } + if (CacheResolverFactory.class == type) { + return (T) defaultCacheResolverFactory(); + } + return null; + } + final Bean bean = beanManager.resolve(beans); + final CreationalContext context = beanManager.createCreationalContext(bean); + final Class scope = bean.getScope(); + final boolean normalScope = beanManager.isNormalScope(scope); + try { + final Object reference = beanManager.getReference(bean, bean.getBeanClass(), context); + if (!normalScope) { + toRelease.add(context); + } + return (T) reference; + } finally { + if (normalScope) { // TODO: release at the right moment, @PreDestroy? question is: do we assume it is thread safe? + context.release(); + } + } + } + + private CacheResolverFactoryImpl defaultCacheResolverFactory() { + if (defaultCacheResolverFactory != null) { + return defaultCacheResolverFactory; + } + synchronized (this) { + if (defaultCacheResolverFactory != null) { + return defaultCacheResolverFactory; + } + defaultCacheResolverFactory = new CacheResolverFactoryImpl(); + } + return defaultCacheResolverFactory; + } + + private Integer[] keyParameterIndexes(final Method method) { + final List keys = new LinkedList(); + final Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + + // first check if keys are specified explicitely + for (int i = 0; i < method.getParameterTypes().length; i++) { + final Annotation[] annotations = parameterAnnotations[i]; + for (final Annotation a : annotations) { + if (a.annotationType().equals(CacheKey.class)) { + keys.add(i); + break; + } + } + } + + // if not then use all parameters but value ones + if (keys.isEmpty()) { + for (int i = 0; i < method.getParameterTypes().length; i++) { + final Annotation[] annotations = parameterAnnotations[i]; + boolean value = false; + for (final Annotation a : annotations) { + if (a.annotationType().equals(CacheValue.class)) { + value = true; + break; + } + } + if (!value) { + keys.add(i); + } + } + } + return keys.toArray(new Integer[keys.size()]); + } + + private static final class MethodKey { + + private final Class base; + + private final Method delegate; + + private final int hash; + + private MethodKey(final Class base, final Method delegate) { + this.base = base; // we need a class to ensure inheritance don't fall in the same key + this.delegate = delegate; + this.hash = 31 * delegate.hashCode() + (base == null ? 0 : base.hashCode()); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final MethodKey classKey = MethodKey.class.cast(o); + return delegate.equals(classKey.delegate) + && ((base == null && classKey.base == null) || (base != null && base.equals(classKey.base))); + } + + @Override + public int hashCode() { + return hash; + } + } + + // TODO: split it in 5? + public static class MethodMeta { + + private final Class[] parameterTypes; + + private final List> parameterAnnotations; + + private final Set annotations; + + private final Integer[] keysIndices; + + private final Integer valueIndex; + + private final String cacheResultCacheName; + + private final CacheResolverFactory cacheResultResolverFactory; + + private final CacheKeyGenerator cacheResultKeyGenerator; + + private final CacheResult cacheResult; + + private final String cachePutCacheName; + + private final CacheResolverFactory cachePutResolverFactory; + + private final CacheKeyGenerator cachePutKeyGenerator; + + private final boolean cachePutAfter; + + private final CachePut cachePut; + + private final String cacheRemoveCacheName; + + private final CacheResolverFactory cacheRemoveResolverFactory; + + private final CacheKeyGenerator cacheRemoveKeyGenerator; + + private final boolean cacheRemoveAfter; + + private final CacheRemove cacheRemove; + + private final String cacheRemoveAllCacheName; + + private final CacheResolverFactory cacheRemoveAllResolverFactory; + + private final CacheRemoveAll cacheRemoveAll; + + private final boolean completionStage; + + public MethodMeta(Class[] parameterTypes, List> parameterAnnotations, Set annotations, + Integer[] keysIndices, Integer valueIndex, String cacheResultCacheName, + CacheResolverFactory cacheResultResolverFactory, CacheKeyGenerator cacheResultKeyGenerator, + CacheResult cacheResult, String cachePutCacheName, CacheResolverFactory cachePutResolverFactory, + CacheKeyGenerator cachePutKeyGenerator, boolean cachePutAfter, CachePut cachePut, String cacheRemoveCacheName, + CacheResolverFactory cacheRemoveResolverFactory, CacheKeyGenerator cacheRemoveKeyGenerator, + boolean cacheRemoveAfter, CacheRemove cacheRemove, String cacheRemoveAllCacheName, + CacheResolverFactory cacheRemoveAllResolverFactory, CacheRemoveAll cacheRemoveAll, + boolean completionStage) { + this.parameterTypes = parameterTypes; + this.parameterAnnotations = parameterAnnotations; + this.annotations = annotations; + this.keysIndices = keysIndices; + this.valueIndex = valueIndex; + this.cacheResultCacheName = cacheResultCacheName; + this.cacheResultResolverFactory = cacheResultResolverFactory; + this.cacheResultKeyGenerator = cacheResultKeyGenerator; + this.cacheResult = cacheResult; + this.cachePutCacheName = cachePutCacheName; + this.cachePutResolverFactory = cachePutResolverFactory; + this.cachePutKeyGenerator = cachePutKeyGenerator; + this.cachePutAfter = cachePutAfter; + this.cachePut = cachePut; + this.cacheRemoveCacheName = cacheRemoveCacheName; + this.cacheRemoveResolverFactory = cacheRemoveResolverFactory; + this.cacheRemoveKeyGenerator = cacheRemoveKeyGenerator; + this.cacheRemoveAfter = cacheRemoveAfter; + this.cacheRemove = cacheRemove; + this.cacheRemoveAllCacheName = cacheRemoveAllCacheName; + this.cacheRemoveAllResolverFactory = cacheRemoveAllResolverFactory; + this.cacheRemoveAll = cacheRemoveAll; + this.completionStage = completionStage; + } + + public boolean isCompletionStage() { + return completionStage; + } + + public boolean isCacheRemoveAfter() { + return cacheRemoveAfter; + } + + public boolean isCachePutAfter() { + return cachePutAfter; + } + + public Class[] getParameterTypes() { + return parameterTypes; + } + + public List> getParameterAnnotations() { + return parameterAnnotations; + } + + public String getCacheResultCacheName() { + return cacheResultCacheName; + } + + public CacheResolverFactory getCacheResultResolverFactory() { + return cacheResultResolverFactory; + } + + public CacheKeyGenerator getCacheResultKeyGenerator() { + return cacheResultKeyGenerator; + } + + public CacheResult getCacheResult() { + return cacheResult; + } + + public Set getAnnotations() { + return annotations; + } + + public Integer[] getKeysIndices() { + return keysIndices; + } + + public Integer getValueIndex() { + return valueIndex; + } + + public String getCachePutCacheName() { + return cachePutCacheName; + } + + public CacheResolverFactory getCachePutResolverFactory() { + return cachePutResolverFactory; + } + + public CacheKeyGenerator getCachePutKeyGenerator() { + return cachePutKeyGenerator; + } + + public CachePut getCachePut() { + return cachePut; + } + + public String getCacheRemoveCacheName() { + return cacheRemoveCacheName; + } + + public CacheResolverFactory getCacheRemoveResolverFactory() { + return cacheRemoveResolverFactory; + } + + public CacheKeyGenerator getCacheRemoveKeyGenerator() { + return cacheRemoveKeyGenerator; + } + + public CacheRemove getCacheRemove() { + return cacheRemove; + } + + public String getCacheRemoveAllCacheName() { + return cacheRemoveAllCacheName; + } + + public CacheResolverFactory getCacheRemoveAllResolverFactory() { + return cacheRemoveAllResolverFactory; + } + + public CacheRemoveAll getCacheRemoveAll() { + return cacheRemoveAll; + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationContextImpl.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationContextImpl.java new file mode 100644 index 000000000..bb48c4b6a --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationContextImpl.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; + +import javax.cache.annotation.CacheInvocationContext; +import javax.cache.annotation.CacheInvocationParameter; +import javax.interceptor.InvocationContext; + +public class CacheInvocationContextImpl extends CacheMethodDetailsImpl + implements CacheInvocationContext { + + private static final Object[] EMPTY_ARGS = new Object[0]; + + private CacheInvocationParameter[] parameters = null; + + public CacheInvocationContextImpl(final InvocationContext delegate, final A cacheAnnotation, final String cacheName, + final CDIJCacheHelper.MethodMeta meta) { + super(delegate, cacheAnnotation, cacheName, meta); + } + + @Override + public Object getTarget() { + return delegate.getTarget(); + } + + @Override + public CacheInvocationParameter[] getAllParameters() { + if (parameters == null) { + parameters = doGetAllParameters(null); + } + return parameters; + } + + @Override + public T unwrap(final Class cls) { + if (cls.isAssignableFrom(getClass())) { + return cls.cast(this); + } + throw new IllegalArgumentException(cls.getName()); + } + + protected CacheInvocationParameter[] doGetAllParameters(final Integer[] indexes) { + final Object[] parameters = delegate.getParameters(); + final Object[] args = parameters == null ? EMPTY_ARGS : parameters; + final Class[] parameterTypes = meta.getParameterTypes(); + final List> parameterAnnotations = meta.getParameterAnnotations(); + + final CacheInvocationParameter[] parametersAsArray = new CacheInvocationParameter[indexes == null ? args.length + : indexes.length]; + if (indexes == null) { + for (int i = 0; i < args.length; i++) { + parametersAsArray[i] = newCacheInvocationParameterImpl(parameterTypes[i], args[i], parameterAnnotations.get(i), + i); + } + } else { + int outIdx = 0; + for (int idx = 0; idx < indexes.length; idx++) { + final int i = indexes[idx]; + parametersAsArray[outIdx] = newCacheInvocationParameterImpl(parameterTypes[i], args[i], + parameterAnnotations.get(i), i); + outIdx++; + } + } + return parametersAsArray; + } + + private CacheInvocationParameterImpl newCacheInvocationParameterImpl(final Class type, final Object arg, + final Set annotations, final int i) { + return new CacheInvocationParameterImpl(type, arg, annotations, i); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationParameterImpl.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationParameterImpl.java new file mode 100644 index 000000000..6311ab99f --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationParameterImpl.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.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.cache.annotation.CacheInvocationParameter; + +public class CacheInvocationParameterImpl implements CacheInvocationParameter { + + private final Class type; + + private final Object value; + + private final Set annotations; + + private final int position; + + public CacheInvocationParameterImpl(final Class type, final Object value, final Set annotations, + final int position) { + this.type = type; + this.value = value; + this.annotations = annotations; + this.position = position; + } + + @Override + public Class getRawType() { + return type; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public Set getAnnotations() { + return annotations; + } + + @Override + public int getParameterPosition() { + return position; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyGeneratorImpl.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyGeneratorImpl.java new file mode 100644 index 000000000..d39730d47 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyGeneratorImpl.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.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; + +import javax.cache.annotation.CacheInvocationParameter; +import javax.cache.annotation.CacheKeyGenerator; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.GeneratedCacheKey; + +public class CacheKeyGeneratorImpl implements CacheKeyGenerator { + + @Override + public GeneratedCacheKey generateCacheKey(final CacheKeyInvocationContext cacheKeyInvocationContext) { + final CacheInvocationParameter[] keyParameters = cacheKeyInvocationContext.getKeyParameters(); + final Object[] parameters = new Object[keyParameters.length]; + for (int index = 0; index < keyParameters.length; index++) { + parameters[index] = keyParameters[index].getValue(); + } + return new GeneratedCacheKeyImpl(parameters); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyInvocationContextImpl.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyInvocationContextImpl.java new file mode 100644 index 000000000..bedc65d19 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyInvocationContextImpl.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.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; + +import javax.cache.annotation.CacheInvocationParameter; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.interceptor.InvocationContext; + +public class CacheKeyInvocationContextImpl extends CacheInvocationContextImpl + implements CacheKeyInvocationContext { + + private CacheInvocationParameter[] keyParams = null; + + private CacheInvocationParameter valueParam = null; + + public CacheKeyInvocationContextImpl(final InvocationContext delegate, final A annotation, final String name, + final CDIJCacheHelper.MethodMeta methodMeta) { + super(delegate, annotation, name, methodMeta); + } + + @Override + public CacheInvocationParameter[] getKeyParameters() { + if (keyParams == null) { + keyParams = doGetAllParameters(meta.getKeysIndices()); + } + return keyParams; + } + + @Override + public CacheInvocationParameter getValueParameter() { + if (valueParam == null) { + valueParam = meta.getValueIndex() >= 0 ? doGetAllParameters(new Integer[] { meta.getValueIndex() })[0] : null; + } + return valueParam; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheMethodDetailsImpl.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheMethodDetailsImpl.java new file mode 100644 index 000000000..9d80f6a67 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheMethodDetailsImpl.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.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Set; + +import javax.cache.annotation.CacheMethodDetails; +import javax.interceptor.InvocationContext; + +public class CacheMethodDetailsImpl implements CacheMethodDetails { + + protected final InvocationContext delegate; + + protected final CDIJCacheHelper.MethodMeta meta; + + private final Set annotations; + + private final A cacheAnnotation; + + private final String cacheName; + + public CacheMethodDetailsImpl(final InvocationContext delegate, final A cacheAnnotation, final String cacheName, + final CDIJCacheHelper.MethodMeta meta) { + this.delegate = delegate; + this.annotations = meta.getAnnotations(); + this.cacheAnnotation = cacheAnnotation; + this.cacheName = cacheName; + this.meta = meta; + } + + @Override + public Method getMethod() { + return delegate.getMethod(); + } + + @Override + public Set getAnnotations() { + return annotations; + } + + @Override + public A getCacheAnnotation() { + return cacheAnnotation; + } + + @Override + public String getCacheName() { + return cacheName; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CachePutInterceptor.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CachePutInterceptor.java new file mode 100644 index 000000000..c00a1a47c --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CachePutInterceptor.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple.cdi; + +import java.io.Serializable; +import java.util.concurrent.CompletionStage; + +import javax.annotation.Priority; +import javax.cache.Cache; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CachePut; +import javax.cache.annotation.CacheResolver; +import javax.cache.annotation.CacheResolverFactory; +import javax.cache.annotation.GeneratedCacheKey; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +@CachePut +@Interceptor +@Priority(/* LIBRARY_BEFORE */1000) +public class CachePutInterceptor implements Serializable { + + @Inject + private CDIJCacheHelper helper; + + @AroundInvoke + public Object cache(final InvocationContext ic) throws Throwable { + final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic); + + final String cacheName = methodMeta.getCachePutCacheName(); + + final CacheResolverFactory cacheResolverFactory = methodMeta.getCachePutResolverFactory(); + final CacheKeyInvocationContext context = new CacheKeyInvocationContextImpl(ic, + methodMeta.getCachePut(), cacheName, methodMeta); + final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context); + final Cache cache = cacheResolver.resolveCache(context); + + final GeneratedCacheKey cacheKey = methodMeta.getCachePutKeyGenerator().generateCacheKey(context); + final CachePut cachePut = methodMeta.getCachePut(); + final boolean afterInvocation = methodMeta.isCachePutAfter(); + + if (!afterInvocation) { + cache.put(cacheKey, context.getValueParameter()); + } + + final Object result; + try { + result = ic.proceed(); + if (CompletionStage.class.isInstance(result)) { + final CompletionStage completionStage = CompletionStage.class.cast(result); + completionStage.exceptionally(t -> { + if (afterInvocation) { + if (helper.isIncluded(t.getClass(), cachePut.cacheFor(), cachePut.noCacheFor())) { + cache.put(cacheKey, context.getValueParameter()); + } + } + if (RuntimeException.class.isInstance(t)) { + throw RuntimeException.class.cast(t); + } + throw new IllegalStateException(t); + }); + } + } catch (final Throwable t) { + if (afterInvocation) { + if (helper.isIncluded(t.getClass(), cachePut.cacheFor(), cachePut.noCacheFor())) { + cache.put(cacheKey, context.getValueParameter()); + } + } + + throw t; + } + + if (afterInvocation) { + cache.put(cacheKey, context.getValueParameter()); + } + + return result; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveAllInterceptor.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveAllInterceptor.java new file mode 100644 index 000000000..9af7120d4 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveAllInterceptor.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.geronimo.jcache.simple.cdi; + +import java.io.Serializable; +import java.util.concurrent.CompletionStage; + +import javax.annotation.Priority; +import javax.cache.Cache; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemoveAll; +import javax.cache.annotation.CacheResolver; +import javax.cache.annotation.CacheResolverFactory; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +@CacheRemoveAll +@Interceptor +@Priority(/* LIBRARY_BEFORE */1000) +public class CacheRemoveAllInterceptor implements Serializable { + + @Inject + private CDIJCacheHelper helper; + + @AroundInvoke + public Object cache(final InvocationContext ic) throws Throwable { + final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic); + + final String cacheName = methodMeta.getCacheRemoveAllCacheName(); + + final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheRemoveAllResolverFactory(); + final CacheKeyInvocationContext context = new CacheKeyInvocationContextImpl(ic, + methodMeta.getCacheRemoveAll(), cacheName, methodMeta); + final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context); + final Cache cache = cacheResolver.resolveCache(context); + + final boolean afterInvocation = methodMeta.isCachePutAfter(); + if (!afterInvocation) { + cache.removeAll(); + } + + final Object result; + try { + result = ic.proceed(); + if (CompletionStage.class.isInstance(result)) { + final CompletionStage completionStage = CompletionStage.class.cast(result); + completionStage.exceptionally(t -> { + if (afterInvocation) { + if (helper.isIncluded(t.getClass(), methodMeta.getCacheRemoveAll().evictFor(), + methodMeta.getCacheRemoveAll().noEvictFor())) { + cache.removeAll(); + } + } + if (RuntimeException.class.isInstance(t)) { + throw RuntimeException.class.cast(t); + } + throw new IllegalStateException(t); + }); + } + } catch (final Throwable t) { + if (afterInvocation) { + if (helper.isIncluded(t.getClass(), methodMeta.getCacheRemoveAll().evictFor(), + methodMeta.getCacheRemoveAll().noEvictFor())) { + cache.removeAll(); + } + } + throw t; + } + + if (afterInvocation) { + cache.removeAll(); + } + + return result; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveInterceptor.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveInterceptor.java new file mode 100644 index 000000000..a5cb9e083 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveInterceptor.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple.cdi; + +import java.io.Serializable; +import java.util.concurrent.CompletionStage; + +import javax.annotation.Priority; +import javax.cache.Cache; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemove; +import javax.cache.annotation.CacheResolver; +import javax.cache.annotation.CacheResolverFactory; +import javax.cache.annotation.GeneratedCacheKey; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +@CacheRemove +@Interceptor +@Priority(/* LIBRARY_BEFORE */1000) +public class CacheRemoveInterceptor implements Serializable { + + @Inject + private CDIJCacheHelper helper; + + @AroundInvoke + public Object cache(final InvocationContext ic) throws Throwable { + final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic); + + final String cacheName = methodMeta.getCacheRemoveCacheName(); + + final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheRemoveResolverFactory(); + final CacheKeyInvocationContext context = new CacheKeyInvocationContextImpl(ic, + methodMeta.getCacheRemove(), cacheName, methodMeta); + final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context); + final Cache cache = cacheResolver.resolveCache(context); + + final GeneratedCacheKey cacheKey = methodMeta.getCacheRemoveKeyGenerator().generateCacheKey(context); + final CacheRemove cacheRemove = methodMeta.getCacheRemove(); + final boolean afterInvocation = methodMeta.isCacheRemoveAfter(); + + if (!afterInvocation) { + cache.remove(cacheKey); + } + + final Object result; + try { + result = ic.proceed(); + if (CompletionStage.class.isInstance(result)) { + final CompletionStage completionStage = CompletionStage.class.cast(result); + completionStage.exceptionally(t -> { + if (afterInvocation) { + if (helper.isIncluded(t.getClass(), cacheRemove.evictFor(), cacheRemove.noEvictFor())) { + cache.remove(cacheKey); + } + } + if (RuntimeException.class.isInstance(t)) { + throw RuntimeException.class.cast(t); + } + throw new IllegalStateException(t); + }); + } + } catch (final Throwable t) { + if (afterInvocation) { + if (helper.isIncluded(t.getClass(), cacheRemove.evictFor(), cacheRemove.noEvictFor())) { + cache.remove(cacheKey); + } + } + + throw t; + } + + if (afterInvocation) { + cache.remove(cacheKey); + } + + return result; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverFactoryImpl.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverFactoryImpl.java new file mode 100644 index 000000000..94be758a2 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverFactoryImpl.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; + +import javax.cache.Cache; +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.annotation.CacheMethodDetails; +import javax.cache.annotation.CacheResolver; +import javax.cache.annotation.CacheResolverFactory; +import javax.cache.annotation.CacheResult; +import javax.cache.configuration.MutableConfiguration; +import javax.cache.spi.CachingProvider; + +public class CacheResolverFactoryImpl implements CacheResolverFactory { + + private final CacheManager cacheManager; + + private final CachingProvider provider; + + public CacheResolverFactoryImpl() { + provider = Caching.getCachingProvider(); + cacheManager = provider.getCacheManager(provider.getDefaultURI(), provider.getDefaultClassLoader()); + } + + @Override + public CacheResolver getCacheResolver(CacheMethodDetails cacheMethodDetails) { + return findCacheResolver(cacheMethodDetails.getCacheName()); + } + + @Override + public CacheResolver getExceptionCacheResolver(final CacheMethodDetails cacheMethodDetails) { + final String exceptionCacheName = cacheMethodDetails.getCacheAnnotation().exceptionCacheName(); + if (exceptionCacheName == null || exceptionCacheName.isEmpty()) { + throw new IllegalArgumentException("CacheResult.exceptionCacheName() not specified"); + } + return findCacheResolver(exceptionCacheName); + } + + private CacheResolver findCacheResolver(String exceptionCacheName) { + Cache cache = cacheManager.getCache(exceptionCacheName); + if (cache == null) { + cache = createCache(exceptionCacheName); + } + return new CacheResolverImpl(cache); + } + + private Cache createCache(final String exceptionCacheName) { + cacheManager.createCache(exceptionCacheName, new MutableConfiguration().setStoreByValue(false)); + return cacheManager.getCache(exceptionCacheName); + } + + public void release() { + cacheManager.close(); + provider.close(); + } + + public CacheManager getCacheManager() { + return cacheManager; + } + + public CachingProvider getProvider() { + return provider; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverImpl.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverImpl.java new file mode 100644 index 000000000..62d9d14cf --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverImpl.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.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; + +import javax.cache.Cache; +import javax.cache.annotation.CacheInvocationContext; +import javax.cache.annotation.CacheResolver; + +public class CacheResolverImpl implements CacheResolver { + + private final Cache delegate; + + public CacheResolverImpl(final Cache cache) { + delegate = cache; + } + + @Override + public Cache resolveCache(final CacheInvocationContext cacheInvocationContext) { + return (Cache) delegate; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResultInterceptor.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResultInterceptor.java new file mode 100644 index 000000000..0f8fda7ce --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResultInterceptor.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple.cdi; + +import java.io.Serializable; +import java.util.concurrent.CompletionStage; + +import javax.annotation.Priority; +import javax.cache.Cache; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheResolver; +import javax.cache.annotation.CacheResolverFactory; +import javax.cache.annotation.CacheResult; +import javax.cache.annotation.GeneratedCacheKey; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +@CacheResult +@Interceptor +@Priority(/* LIBRARY_BEFORE */1000) +public class CacheResultInterceptor implements Serializable { + + @Inject + private CDIJCacheHelper helper; + + @AroundInvoke + public Object cache(final InvocationContext ic) throws Throwable { + final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic); + + final String cacheName = methodMeta.getCacheResultCacheName(); + + final CacheResult cacheResult = methodMeta.getCacheResult(); + final CacheKeyInvocationContext context = new CacheKeyInvocationContextImpl(ic, cacheResult, + cacheName, methodMeta); + + final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheResultResolverFactory(); + final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context); + final Cache cache = cacheResolver.resolveCache(context); + + final GeneratedCacheKey cacheKey = methodMeta.getCacheResultKeyGenerator().generateCacheKey(context); + + Cache exceptionCache = null; // lazily created + + Object result; + if (!cacheResult.skipGet()) { + result = cache.get(cacheKey); + if (result != null) { + return result; + } + + if (!cacheResult.exceptionCacheName().isEmpty()) { + exceptionCache = cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context); + final Object exception = exceptionCache.get(cacheKey); + if (exception != null) { + if (methodMeta.isCompletionStage()) { + return exception; + } + throw Throwable.class.cast(exception); + } + } + } + + try { + result = ic.proceed(); + if (result != null) { + cache.put(cacheKey, result); + if (CompletionStage.class.isInstance(result)) { + final CompletionStage completionStage = CompletionStage.class.cast(result); + completionStage.exceptionally(t -> { + if (helper.isIncluded(t.getClass(), cacheResult.cachedExceptions(), cacheResult.nonCachedExceptions())) { + cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context).put(cacheKey, completionStage); + } else { + cache.remove(cacheKey); + } + if (RuntimeException.class.isInstance(t)) { + throw RuntimeException.class.cast(t); + } + throw new IllegalStateException(t); + }); + } + } + + return result; + } catch (final Throwable t) { + if (helper.isIncluded(t.getClass(), cacheResult.cachedExceptions(), cacheResult.nonCachedExceptions())) { + if (exceptionCache == null) { + exceptionCache = cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context); + } + exceptionCache.put(cacheKey, t); + } + throw t; + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/GeneratedCacheKeyImpl.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/GeneratedCacheKeyImpl.java new file mode 100644 index 000000000..c8a6cc71a --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/GeneratedCacheKeyImpl.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.geronimo.jcache.simple.cdi; + +import java.util.Arrays; + +import javax.cache.annotation.GeneratedCacheKey; + +public class GeneratedCacheKeyImpl implements GeneratedCacheKey { + + private final Object[] params; + + private final int hash; + + public GeneratedCacheKeyImpl(final Object[] parameters) { + this.params = parameters; + this.hash = Arrays.deepHashCode(parameters); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final GeneratedCacheKeyImpl that = GeneratedCacheKeyImpl.class.cast(o); + return Arrays.deepEquals(params, that.params); + + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/MakeJCacheCDIInterceptorFriendly.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/MakeJCacheCDIInterceptorFriendly.java new file mode 100644 index 000000000..d56f6ec5c --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/MakeJCacheCDIInterceptorFriendly.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import javax.cache.annotation.CachePut; +import javax.cache.annotation.CacheRemove; +import javax.cache.annotation.CacheRemoveAll; +import javax.cache.annotation.CacheResult; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.BeforeBeanDiscovery; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.InjectionTarget; +import javax.enterprise.inject.spi.PassivationCapable; +import javax.enterprise.inject.spi.ProcessAnnotatedType; + +// TODO: observe annotated type (or maybe sthg else) to cache data and inject this extension (used as metadata cache) +// to get class model and this way allow to add cache annotation on the fly - == avoid java pure reflection to get metadata +public class MakeJCacheCDIInterceptorFriendly implements Extension { + + private static final AtomicInteger id = new AtomicInteger(); + + private static final boolean USE_ID = !Boolean.getBoolean("org.apache.geronimo.jcache.simple.skip-id"); + private static final boolean SKIP = Boolean.getBoolean("org.apache.geronimo.jcache.simple.skip-cdi"); + + private boolean needHelper = true; + + protected void discoverInterceptorBindings(final @Observes BeforeBeanDiscovery beforeBeanDiscovery, + final BeanManager bm) { + if (SKIP) { + return; + } + Stream.of(CachePut.class, CacheRemove.class, CacheRemoveAll.class, CacheResult.class) + .forEach(it -> beforeBeanDiscovery.addInterceptorBinding(bm.createAnnotatedType(it))); + Stream.of( + CDIJCacheHelper.class, + CachePutInterceptor.class, CacheResultInterceptor.class, + CacheRemoveAllInterceptor.class, CacheRemoveInterceptor.class) + .forEach(it -> beforeBeanDiscovery.addAnnotatedType(bm.createAnnotatedType(it))); + } + + protected void addHelper(final @Observes AfterBeanDiscovery afterBeanDiscovery, final BeanManager bm) { + if (SKIP) { + return; + } + if (!needHelper) { + return; + } + final AnnotatedType annotatedType = bm.createAnnotatedType(CDIJCacheHelper.class); + final InjectionTarget injectionTarget = bm.createInjectionTarget(annotatedType); + final HelperBean bean = new HelperBean(annotatedType, injectionTarget, findIdSuffix()); + afterBeanDiscovery.addBean(bean); + } + + protected void vetoScannedCDIJCacheHelperQualifiers(final @Observes ProcessAnnotatedType pat) { + if (SKIP) { + return; + } + if (!needHelper) { // already seen, shouldn't really happen,just a protection + pat.veto(); + } + needHelper = false; + } + + // TODO: make it better for ear+cluster case with CDI 1.0 + private String findIdSuffix() { + // big disadvantage is all deployments of a cluster needs to be in the exact same order but it works with ears + if (USE_ID) { + return "lib" + id.incrementAndGet(); + } + return "default"; + } + + public static class HelperBean implements Bean, PassivationCapable { + + private final AnnotatedType at; + + private final InjectionTarget it; + + private final HashSet qualifiers; + + private final String id; + + public HelperBean(final AnnotatedType annotatedType, + final InjectionTarget injectionTarget, final String id) { + this.at = annotatedType; + this.it = injectionTarget; + this.id = "JCache#CDIHelper#" + id; + + this.qualifiers = new HashSet<>(); + this.qualifiers.add(Default.Literal.INSTANCE); + this.qualifiers.add(Any.Literal.INSTANCE); + } + + @Override + public Set getInjectionPoints() { + return it.getInjectionPoints(); + } + + @Override + public Class getBeanClass() { + return at.getJavaClass(); + } + + @Override + public boolean isNullable() { + return false; + } + + @Override + public Set getTypes() { + return at.getTypeClosure(); + } + + @Override + public Set getQualifiers() { + return qualifiers; + } + + @Override + public Class getScope() { + return ApplicationScoped.class; + } + + @Override + public String getName() { + return null; + } + + @Override + public Set> getStereotypes() { + return Collections.emptySet(); + } + + @Override + public boolean isAlternative() { + return false; + } + + @Override + public CDIJCacheHelper create(final CreationalContext context) { + final CDIJCacheHelper produce = it.produce(context); + it.inject(produce, context); + it.postConstruct(produce); + return produce; + } + + @Override + public void destroy(final CDIJCacheHelper instance, final CreationalContext context) { + it.preDestroy(instance); + it.dispose(instance); + context.release(); + } + + @Override + public String getId() { + return id; + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/osgi/JCacheActivator.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/osgi/JCacheActivator.java new file mode 100644 index 000000000..41c6ed6d0 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/osgi/JCacheActivator.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.geronimo.jcache.simple.osgi; + +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Objects; +import java.util.stream.Stream; + +import javax.cache.spi.CachingProvider; +import javax.enterprise.inject.spi.Extension; + +import org.apache.geronimo.jcache.simple.SimpleProvider; +import org.apache.geronimo.jcache.simple.cdi.MakeJCacheCDIInterceptorFriendly; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.PrototypeServiceFactory; +import org.osgi.framework.ServiceRegistration; + +public class JCacheActivator implements BundleActivator { + private ServiceRegistration cacheProviderRegistration; + private ServiceRegistration jcacheExtensionRegistration; + + @Override + public void start(final BundleContext context) { + final Dictionary cachingProvider = new Hashtable<>(); + cachingProvider.put("javax.cache.provider", CachingProvider.class.getName()); + cacheProviderRegistration = context.registerService( + CachingProvider.class, new SimpleProvider(), cachingProvider); + + final Dictionary jcacheExtension = new Hashtable<>(); + jcacheExtension.put("osgi.cdi.extension", "geronimo-jcache-simple"); + jcacheExtension.put("aries.cdi.extension.mode", "implicit"); // always enable/-able since it just enable interceptors + jcacheExtensionRegistration = context.registerService( + Extension.class, new PrototypeServiceFactory() { + @Override + public Extension getService(final Bundle bundle, final ServiceRegistration registration) { + return new MakeJCacheCDIInterceptorFriendly(); + } + + @Override + public void ungetService(final Bundle bundle, final ServiceRegistration registration, + final Extension service) { + // no-op + } + }, jcacheExtension); + } + + @Override + public void stop(final BundleContext context) { + Stream.of(cacheProviderRegistration, jcacheExtensionRegistration) + .filter(Objects::nonNull) + .forEach(it -> { + try { + it.unregister(); + } catch (final IllegalStateException ise) { + // no-op + } + }); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/osgi/package-info.java b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/osgi/package-info.java new file mode 100644 index 000000000..62976a170 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/osgi/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. + */ +@Capability(namespace = "osgi.cdi.extension", name = "geronimo-jcache-simple") +package org.apache.geronimo.jcache.simple.osgi; + +import org.osgi.annotation.bundle.Capability; diff --git a/Java-base/geronimo-jcache-simple/src/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider b/Java-base/geronimo-jcache-simple/src/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider new file mode 100644 index 000000000..d7652c5a2 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider @@ -0,0 +1 @@ +org.apache.geronimo.jcache.simple.SimpleProvider diff --git a/Java-base/geronimo-jcache-simple/src/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/Java-base/geronimo-jcache-simple/src/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 000000000..82c046a34 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1 @@ +org.apache.geronimo.jcache.simple.cdi.MakeJCacheCDIInterceptorFriendly diff --git a/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/CacheResultCompletionStageTest.java b/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/CacheResultCompletionStageTest.java new file mode 100644 index 000000000..e13962558 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/CacheResultCompletionStageTest.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.geronimo.jcache.simple.tck; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; + +import javax.cache.annotation.BeanProvider; +import javax.cache.annotation.CacheResult; +import javax.enterprise.context.Dependent; + +import org.apache.geronimo.jcache.simple.cdi.CacheResolverFactoryImpl; +import org.junit.Test; + +public class CacheResultCompletionStageTest { + + @Test + public void run() throws ExecutionException, InterruptedException { + final BeanProvider ioc = new OWBBeanProvider(); + final MyService myService = ioc.getBeanByType(MyService.class); + final Resolver resolver = ioc.getBeanByType(Resolver.class); + + { // cache is active + final CompletionStage result = myService.get(); + assertSame(result, myService.get()); + + // success + result.toCompletableFuture().complete("ok"); + assertEquals("ok", result.toCompletableFuture().get()); + } + + { // evict then fail + resolver.evict(); + assertFalse(myService.get().toCompletableFuture().isDone()); + final CompletionStage beforeFailure = myService.get(); + beforeFailure.toCompletableFuture().completeExceptionally(new IllegalArgumentException("failed")); + + final CompletionStage newInstanceSinceNotCached = myService.get(); + assertNotSame(beforeFailure, newInstanceSinceNotCached); + assertFalse(newInstanceSinceNotCached.toCompletableFuture().isCompletedExceptionally()); + assertFalse(newInstanceSinceNotCached.toCompletableFuture().isDone()); + } + + { // evict then fail a not configured exception + resolver.evict(); + assertFalse(myService.get().toCompletableFuture().isDone()); + final CompletionStage beforeFailure = myService.get(); + beforeFailure.toCompletableFuture().completeExceptionally(new IllegalStateException("failed")); + + final CompletionStage newInstanceSinceNotCached = myService.get(); + assertNotSame(beforeFailure, newInstanceSinceNotCached); + assertFalse(newInstanceSinceNotCached.toCompletableFuture().isCompletedExceptionally()); + assertFalse(newInstanceSinceNotCached.toCompletableFuture().isDone()); + } + + { // evict then fail a cached exception - so cached + resolver.evict(); + assertFalse(myService.get().toCompletableFuture().isDone()); + final CompletionStage beforeFailure = myService.get(); + beforeFailure.toCompletableFuture().completeExceptionally(new NullPointerException("failed")); + + final CompletionStage newInstanceSinceNotCached = myService.get(); + assertSame(beforeFailure, newInstanceSinceNotCached); + assertTrue(newInstanceSinceNotCached.toCompletableFuture().isCompletedExceptionally()); + try { + newInstanceSinceNotCached.toCompletableFuture().get(); + fail("npe expected"); + } catch (final ExecutionException npe) { + assertTrue(NullPointerException.class.isInstance(npe.getCause())); + assertEquals("failed", NullPointerException.class.cast(npe.getCause()).getMessage()); + } + } + } + + @Dependent + public static class Resolver extends CacheResolverFactoryImpl { + public void evict() { + getCacheManager() + .getCache("org.apache.geronimo.jcache.simple.tck.CacheResultCompletionStageTest$MyService.get()") + .clear(); + } + } + + @Dependent + public static class MyService { + + @CacheResult(nonCachedExceptions = IllegalArgumentException.class, cachedExceptions = NullPointerException.class, + exceptionCacheName = "org.apache.geronimo.jcache.simple.tck.CacheResultCompletionStageTest$MyService.get()_exception", + cacheResolverFactory = Resolver.class) + public CompletionStage get() { + return new CompletableFuture<>(); + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/ExpiryListenerTest.java b/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/ExpiryListenerTest.java new file mode 100644 index 000000000..ded8f4050 --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/ExpiryListenerTest.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.geronimo.jcache.simple.tck; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.cache.Cache; +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.configuration.FactoryBuilder; +import javax.cache.configuration.MutableCacheEntryListenerConfiguration; +import javax.cache.configuration.MutableConfiguration; +import javax.cache.event.CacheEntryEvent; +import javax.cache.event.CacheEntryExpiredListener; +import javax.cache.event.CacheEntryListenerException; +import javax.cache.expiry.CreatedExpiryPolicy; +import javax.cache.expiry.Duration; +import javax.cache.expiry.ExpiryPolicy; +import javax.cache.spi.CachingProvider; + +import org.junit.Test; + +public class ExpiryListenerTest { + + @Test + public void listener() throws InterruptedException { + final CachingProvider cachingProvider = Caching.getCachingProvider(); + final CacheManager cacheManager = cachingProvider.getCacheManager(cachingProvider.getDefaultURI(), + cachingProvider.getDefaultClassLoader(), + new Properties() {{ + setProperty("evictionPause", "2"); + }}); + + final CountDownLatch latch = new CountDownLatch(1); + final CacheEntryExpiredListenerImpl listener = new CacheEntryExpiredListenerImpl(latch); + cacheManager.createCache("default", new MutableConfiguration() + .setExpiryPolicyFactory(new FactoryBuilder.SingletonFactory( + new CreatedExpiryPolicy(new Duration(TimeUnit.MILLISECONDS, 2)))) + .addCacheEntryListenerConfiguration(new MutableCacheEntryListenerConfiguration<>( + FactoryBuilder.factoryOf(listener), + null, false, false + ))); + final Cache cache = cacheManager.getCache("default"); + assertFalse(cache.containsKey("foo")); + cache.put("foo", "bar"); + latch.await(1, TimeUnit.MINUTES); + assertEquals(1, listener.events.size()); + cachingProvider.close(); + } + + private static class CacheEntryExpiredListenerImpl implements CacheEntryExpiredListener, Serializable { + + private final Collection> events = + new ArrayList>(); + + private CountDownLatch latch; + + public CacheEntryExpiredListenerImpl(final CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onExpired(final Iterable> cacheEntryEvents) + throws CacheEntryListenerException { + for (final CacheEntryEvent cacheEntryEvent : cacheEntryEvents) { + events.add(cacheEntryEvent); + } + latch.countDown(); + } + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/OWBBeanProvider.java b/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/OWBBeanProvider.java new file mode 100644 index 000000000..62efd50cd --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/OWBBeanProvider.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.geronimo.jcache.simple.tck; + +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.cache.annotation.BeanProvider; +import javax.enterprise.inject.spi.Bean; + +import org.apache.webbeans.config.WebBeansContext; +import org.apache.webbeans.container.BeanManagerImpl; +import org.apache.webbeans.spi.ContainerLifecycle; + +public class OWBBeanProvider implements BeanProvider { + + private final BeanManagerImpl bm; + private static final AtomicBoolean STARTED = new AtomicBoolean(); + + public OWBBeanProvider() { + final WebBeansContext webBeansContext = WebBeansContext.currentInstance(); + if (STARTED.compareAndSet(false, true)) { + final ContainerLifecycle lifecycle = webBeansContext.getService(ContainerLifecycle.class); + lifecycle.startApplication(null); + Runtime.getRuntime().addShutdownHook(new Thread(() -> lifecycle.stopApplication(null))); + } + bm = webBeansContext.getBeanManagerImpl(); + } + + @Override + public T getBeanByType(final Class tClass) { + if (tClass == null) { + throw new IllegalArgumentException("no bean class specified"); + } + + final Set> beans = bm.getBeans(tClass); + if (beans.isEmpty()) { + throw new IllegalStateException("no bean of type " + tClass.getName()); + } + final Bean bean = bm.resolve(beans); + return (T) bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean)); + } +} diff --git a/Java-base/geronimo-jcache-simple/src/src/test/resources/ExcludeList b/Java-base/geronimo-jcache-simple/src/src/test/resources/ExcludeList new file mode 100644 index 000000000..170a7fadd --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/test/resources/ExcludeList @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# for tck this test needs to be excluded +org.jsr107.tck.CachingTest#dummyTest diff --git a/Java-base/geronimo-jcache-simple/src/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider b/Java-base/geronimo-jcache-simple/src/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider new file mode 100644 index 000000000..8825dc7cf --- /dev/null +++ b/Java-base/geronimo-jcache-simple/src/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider @@ -0,0 +1 @@ +org.apache.geronimo.jcache.simple.tck.OWBBeanProvider