From a55e35c8c2c0d2edccca62741c026c7826ee0241 Mon Sep 17 00:00:00 2001 From: Henry Lee Date: Thu, 30 Nov 2023 13:57:19 +0900 Subject: [PATCH] refactor(java): base image for 3 java projects --- Java-base/commons-validator/Dockerfile | 28 + Java-base/commons-validator/src/BUILDING.txt | 29 + .../commons-validator/src/CONTRIBUTING.md | 97 + Java-base/commons-validator/src/LICENSE.txt | 202 + Java-base/commons-validator/src/NOTICE.txt | 5 + Java-base/commons-validator/src/PROPOSAL.html | 112 + Java-base/commons-validator/src/README.md | 104 + .../commons-validator/src/RELEASE-NOTES.txt | 63 + .../src/checkstyle-suppressions.xml | 28 + .../commons-validator/src/checkstyle.xml | 169 + Java-base/commons-validator/src/pom.xml | 411 ++ .../src/src/assembly/bin.xml | 46 + .../src/src/assembly/src.xml | 47 + .../src/src/changes/changes.xml | 1143 +++++ .../src/src/changes/release-notes.vm | 122 + .../validator/example/ValidateBean.java | 113 + .../validator/example/ValidateExample.java | 197 + .../example/applicationResources.properties | 28 + .../validator/example/validator-example.xml | 61 + .../org/apache/commons/validator/Arg.java | 193 + .../validator/CreditCardValidator.java | 260 ++ .../commons/validator/DateValidator.java | 133 + .../commons/validator/EmailValidator.java | 224 + .../org/apache/commons/validator/Field.java | 958 ++++ .../org/apache/commons/validator/Form.java | 352 ++ .../org/apache/commons/validator/FormSet.java | 378 ++ .../commons/validator/FormSetFactory.java | 115 + .../validator/GenericTypeValidator.java | 473 ++ .../commons/validator/GenericValidator.java | 435 ++ .../commons/validator/ISBNValidator.java | 56 + .../org/apache/commons/validator/Msg.java | 162 + .../commons/validator/UrlValidator.java | 468 ++ .../apache/commons/validator/Validator.java | 391 ++ .../commons/validator/ValidatorAction.java | 796 ++++ .../commons/validator/ValidatorException.java | 46 + .../commons/validator/ValidatorResources.java | 662 +++ .../commons/validator/ValidatorResult.java | 205 + .../commons/validator/ValidatorResults.java | 152 + .../org/apache/commons/validator/Var.java | 218 + .../org/apache/commons/validator/package.html | 244 + .../routines/AbstractCalendarValidator.java | 427 ++ .../routines/AbstractFormatValidator.java | 221 + .../routines/AbstractNumberValidator.java | 277 ++ .../routines/BigDecimalValidator.java | 240 + .../routines/BigIntegerValidator.java | 211 + .../validator/routines/ByteValidator.java | 258 + .../validator/routines/CalendarValidator.java | 338 ++ .../validator/routines/CodeValidator.java | 283 ++ .../routines/CreditCardValidator.java | 517 ++ .../validator/routines/CurrencyValidator.java | 118 + .../validator/routines/DateValidator.java | 350 ++ .../validator/routines/DomainValidator.java | 2117 +++++++++ .../validator/routines/DoubleValidator.java | 252 + .../validator/routines/EmailValidator.java | 223 + .../validator/routines/FloatValidator.java | 270 ++ .../validator/routines/IBANValidator.java | 299 ++ .../validator/routines/ISBNValidator.java | 271 ++ .../validator/routines/ISINValidator.java | 124 + .../validator/routines/ISSNValidator.java | 202 + .../routines/InetAddressValidator.java | 190 + .../validator/routines/IntegerValidator.java | 257 + .../validator/routines/LongValidator.java | 252 + .../validator/routines/PercentValidator.java | 127 + .../validator/routines/RegexValidator.java | 230 + .../validator/routines/ShortValidator.java | 255 + .../validator/routines/TimeValidator.java | 285 ++ .../validator/routines/UrlValidator.java | 576 +++ .../checkdigit/ABANumberCheckDigit.java | 85 + .../routines/checkdigit/CUSIPCheckDigit.java | 100 + .../routines/checkdigit/CheckDigit.java | 71 + .../checkdigit/CheckDigitException.java | 55 + .../routines/checkdigit/EAN13CheckDigit.java | 78 + .../routines/checkdigit/IBANCheckDigit.java | 137 + .../routines/checkdigit/ISBN10CheckDigit.java | 115 + .../routines/checkdigit/ISBNCheckDigit.java | 106 + .../routines/checkdigit/ISINCheckDigit.java | 111 + .../routines/checkdigit/ISSNCheckDigit.java | 85 + .../routines/checkdigit/LuhnCheckDigit.java | 76 + .../checkdigit/ModulusCheckDigit.java | 204 + .../checkdigit/ModulusTenCheckDigit.java | 243 + .../routines/checkdigit/SedolCheckDigit.java | 114 + .../checkdigit/VerhoeffCheckDigit.java | 129 + .../routines/checkdigit/package.html | 29 + .../commons/validator/routines/package.html | 835 ++++ .../apache/commons/validator/util/Flags.java | 206 + .../validator/util/ValidatorUtils.java | 200 + .../commons/validator/util/package.html | 26 + .../commons/validator/digester-rules.xml | 102 + .../validator/resources/validator_1_0.dtd | 262 ++ .../validator/resources/validator_1_0_1.dtd | 261 ++ .../validator/resources/validator_1_1.dtd | 308 ++ .../validator/resources/validator_1_1_3.dtd | 328 ++ .../validator/resources/validator_1_2_0.dtd | 249 + .../validator/resources/validator_1_3_0.dtd | 249 + .../validator/resources/validator_1_4_0.dtd | 253 + .../src/site/resources/download_validator.cgi | 4 + .../src/src/site/resources/images/logo.png | Bin 0 -> 13087 bytes .../commons-validator/src/src/site/site.xml | 51 + .../src/src/site/xdoc/building.xml | 75 + .../src/src/site/xdoc/community.xml | 50 + .../src/src/site/xdoc/download_validator.xml | 154 + .../src/src/site/xdoc/index.xml | 123 + .../src/src/site/xdoc/issue-tracking.xml | 102 + .../src/src/site/xdoc/mail-lists.xml | 202 + .../src/src/site/xdoc/tasks.xml | 63 + .../site/xdoc/validator_2_0_0_proposal.dtd | 269 ++ .../commons/validator/AbstractCommonTest.java | 59 + .../commons/validator/AbstractNumberTest.java | 112 + .../apache/commons/validator/ByteTest.java | 99 + .../validator/CreditCardValidatorTest.java | 89 + .../CustomValidatorResourcesTest.java | 72 + .../apache/commons/validator/DateTest.java | 112 + .../apache/commons/validator/DoubleTest.java | 77 + .../apache/commons/validator/EmailTest.java | 449 ++ .../commons/validator/EntityImportTest.java | 51 + .../commons/validator/ExceptionTest.java | 150 + .../commons/validator/ExtensionTest.java | 365 ++ .../apache/commons/validator/FieldTest.java | 286 ++ .../apache/commons/validator/FloatTest.java | 76 + .../validator/GenericTypeValidatorImpl.java | 248 + .../validator/GenericTypeValidatorTest.java | 195 + .../validator/GenericValidatorImpl.java | 275 ++ .../validator/GenericValidatorTest.java | 77 + .../commons/validator/ISBNValidatorTest.java | 60 + .../apache/commons/validator/IntegerTest.java | 101 + .../apache/commons/validator/LocaleTest.java | 203 + .../apache/commons/validator/LongTest.java | 98 + .../validator/MultipleConfigFilesTest.java | 268 ++ .../commons/validator/MultipleTest.java | 356 ++ .../apache/commons/validator/NameBean.java | 56 + .../commons/validator/ParameterTest.java | 117 + .../validator/ParameterValidatorImpl.java | 42 + .../commons/validator/RequiredIfTest.java | 238 + .../commons/validator/RequiredNameTest.java | 269 ++ .../apache/commons/validator/ResultPair.java | 32 + .../commons/validator/RetrieveFormTest.java | 233 + .../apache/commons/validator/ShortTest.java | 76 + .../apache/commons/validator/TypeBean.java | 100 + .../org/apache/commons/validator/UrlTest.java | 284 ++ .../validator/ValidatorResourcesTest.java | 52 + .../validator/ValidatorResultsTest.java | 163 + .../commons/validator/ValidatorTest.java | 286 ++ .../apache/commons/validator/ValueBean.java | 42 + .../org/apache/commons/validator/VarTest.java | 109 + .../custom/CustomValidatorResources.java | 44 + .../AbstractCalendarValidatorTest.java | 261 ++ .../routines/AbstractNumberValidatorTest.java | 246 + .../routines/BigDecimalValidatorTest.java | 144 + .../routines/BigIntegerValidatorTest.java | 139 + .../validator/routines/ByteValidatorTest.java | 148 + .../routines/CalendarValidatorTest.java | 279 ++ .../validator/routines/CodeValidatorTest.java | 262 ++ .../routines/CreditCardValidatorTest.java | 650 +++ .../routines/CurrencyValidatorTest.java | 197 + .../validator/routines/DateValidatorTest.java | 169 + .../routines/DomainValidatorTest.java | 721 +++ .../routines/DoubleValidatorTest.java | 138 + .../routines/EmailValidatorTest.java | 545 +++ .../routines/FloatValidatorTest.java | 168 + .../validator/routines/IBANValidatorTest.java | 362 ++ .../validator/routines/ISBNValidatorTest.java | 326 ++ .../validator/routines/ISINValidatorTest.java | 100 + .../validator/routines/ISSNValidatorTest.java | 212 + .../routines/InetAddressValidatorTest.java | 621 +++ .../routines/IntegerValidatorTest.java | 152 + .../validator/routines/LongValidatorTest.java | 149 + .../routines/PercentValidatorTest.java | 117 + .../routines/RegexValidatorTest.java | 296 ++ .../routines/ShortValidatorTest.java | 140 + .../validator/routines/TimeValidatorTest.java | 342 ++ .../validator/routines/UrlValidatorTest.java | 618 +++ .../checkdigit/ABANumberCheckDigitTest.java | 53 + .../checkdigit/AbstractCheckDigitTest.java | 334 ++ .../checkdigit/CUSIPCheckDigitTest.java | 75 + .../checkdigit/EAN13CheckDigitTest.java | 50 + .../checkdigit/IBANCheckDigitTest.java | 255 + .../checkdigit/ISBN10CheckDigitTest.java | 51 + .../checkdigit/ISBNCheckDigitTest.java | 94 + .../checkdigit/ISINCheckDigitTest.java | 74 + .../checkdigit/ISSNCheckDigitTest.java | 67 + .../checkdigit/LuhnCheckDigitTest.java | 60 + .../ModulusTenABACheckDigitTest.java | 52 + .../ModulusTenCUSIPCheckDigitTest.java | 74 + .../ModulusTenEAN13CheckDigitTest.java | 49 + .../ModulusTenLuhnCheckDigitTest.java | 59 + .../ModulusTenSedolCheckDigitTest.java | 68 + .../checkdigit/SedolCheckDigitTest.java | 69 + .../checkdigit/VerhoeffCheckDigitTest.java | 56 + .../commons/validator/util/FlagsTest.java | 132 + .../commons/validator/DateTest-config.xml | 47 + .../commons/validator/EmailTest-config.xml | 34 + .../validator/EntityImportTest-byteform.xml | 19 + .../validator/EntityImportTest-config.xml | 33 + .../validator/ExceptionTest-config.xml | 34 + .../validator/ExtensionTest-config.xml | 55 + .../GenericTypeValidatorTest-config.xml | 122 + .../commons/validator/LocaleTest-config.xml | 72 + .../MultipleConfigFilesTest-1-config.xml | 84 + .../MultipleConfigFilesTest-2-config.xml | 78 + .../validator/MultipleTests-config.xml | 55 + .../validator/ParameterTest-config.xml | 41 + .../validator/RequiredIfTest-config.xml | 55 + .../validator/RequiredNameTest-config.xml | 39 + .../validator/RetrieveFormTest-config.xml | 157 + .../commons/validator/TestNumber-config.xml | 80 + .../validator/ValidatorResultsTest-config.xml | 55 + .../commons/validator/VarTest-config.xml | 52 + .../routines/checkdigit/IBANtests.txt | 113 + Java-base/directory-mavibot/Dockerfile | 28 + Java-base/directory-mavibot/src/LICENSE | 202 + Java-base/directory-mavibot/src/NOTICE | 5 + .../src/distribution/pom.xml | 90 + .../distribution/src/main/assembly/bin.xml | 78 + .../distribution/src/main/assembly/src.xml | 80 + .../src/mavibot/img/BTree.graphml | 732 +++ .../src/mavibot/img/BTree.png | Bin 0 -> 8128 bytes .../src/mavibot/img/ElementHolder.graphml | 195 + .../src/mavibot/img/ElementHolder.png | Bin 0 -> 49955 bytes .../src/mavibot/img/Leaf.graphml | 100 + .../src/mavibot/img/Leaf.png | Bin 0 -> 7274 bytes .../src/mavibot/img/MavibotValue.graphml | 349 ++ .../src/mavibot/img/MavibotValue.png | Bin 0 -> 50979 bytes .../src/mavibot/img/Node.graphml | 66 + .../src/mavibot/img/Node.png | Bin 0 -> 6911 bytes .../src/mavibot/img/PageHierarchy.graphml | 122 + .../src/mavibot/img/PageHierarchy.png | Bin 0 -> 8349 bytes .../src/mavibot/img/PageIOLogical.graphml | 386 ++ .../src/mavibot/img/PageIOLogical.png | Bin 0 -> 4369 bytes .../src/mavibot/img/PageMergeDelete.graphml | 2353 ++++++++++ .../src/mavibot/img/PageMergeDelete.png | Bin 0 -> 59696 bytes .../src/mavibot/img/PageMergeMove.graphml | 2686 +++++++++++ .../src/mavibot/img/PageMergeMove.png | Bin 0 -> 57993 bytes .../src/mavibot/img/R1-node.graphml | 138 + .../src/mavibot/img/R1-node.png | Bin 0 -> 3596 bytes .../src/mavibot/img/R2-node.graphml | 295 ++ .../src/mavibot/img/R2-node.png | Bin 0 -> 5893 bytes .../src/mavibot/img/R3-node.graphml | 697 +++ .../src/mavibot/img/R3-node.png | Bin 0 -> 15746 bytes .../src/mavibot/img/R4-node-bis.png | Bin 0 -> 27210 bytes .../src/mavibot/img/R4-node.graphml | 1121 +++++ .../src/mavibot/img/R4-node.png | Bin 0 -> 26276 bytes .../src/mavibot/img/RMHeader.graphml | 854 ++++ .../src/mavibot/img/RMHeader.png | Bin 0 -> 29839 bytes .../src/mavibot/img/UC1.2.1.graphml | 1335 ++++++ .../src/mavibot/img/UC1.2.1.png | Bin 0 -> 41493 bytes .../src/mavibot/img/UC1.2.2.graphml | 1778 +++++++ .../src/mavibot/img/UC1.2.2.png | Bin 0 -> 46814 bytes .../src/mavibot/img/UC11.graphml | 1361 ++++++ .../src/mavibot/img/UC11.png | Bin 0 -> 35655 bytes .../src/mavibot/img/abstractPage.graphml | 134 + .../src/mavibot/img/abstractPage.png | Bin 0 -> 9152 bytes .../src/mavibot/img/btreeHeader.graphml | 986 ++++ .../src/mavibot/img/btreeHeader.png | Bin 0 -> 42588 bytes .../src/mavibot/img/datastructure.graphml | 2824 +++++++++++ .../src/mavibot/img/datastructure.png | Bin 0 -> 137629 bytes .../src/mavibot/img/leaves.graphml | 1173 +++++ .../src/mavibot/img/leaves.png | Bin 0 -> 25267 bytes .../src/mavibot/img/nodeLeaf.graphml | 2661 +++++++++++ .../src/mavibot/img/nodeLeaf.png | Bin 0 -> 51546 bytes .../src/mavibot/img/nodes.graphml | 468 ++ .../src/mavibot/img/nodes.png | Bin 0 -> 16785 bytes .../src/mavibot/img/trans1.graphml | 236 + .../src/mavibot/img/trans1.png | Bin 0 -> 21167 bytes .../src/mavibot/img/trans2.graphml | 407 ++ .../src/mavibot/img/trans2.png | Bin 0 -> 27611 bytes .../src/mavibot/img/transko.graphml | 332 ++ .../src/mavibot/img/transko.png | Bin 0 -> 26104 bytes .../src/mavibot/img/transok.graphml | 374 ++ .../src/mavibot/img/transok.png | Bin 0 -> 29608 bytes .../directory-mavibot/src/mavibot/pom.xml | 108 + .../mavibot/btree/AbstractBTree.java | 1119 +++++ .../AbstractBorrowedFromSiblingResult.java | 127 + .../mavibot/btree/AbstractDeleteResult.java | 99 + .../directory/mavibot/btree/AbstractPage.java | 699 +++ .../mavibot/btree/AbstractResult.java | 106 + .../btree/AbstractTransactionManager.java | 30 + .../mavibot/btree/AbstractValueHolder.java | 441 ++ .../directory/mavibot/btree/Addition.java | 43 + .../apache/directory/mavibot/btree/BTree.java | 387 ++ .../directory/mavibot/btree/BTreeFactory.java | 906 ++++ .../directory/mavibot/btree/BTreeHeader.java | 315 ++ .../mavibot/btree/BTreeTypeEnum.java | 55 + .../mavibot/btree/BorrowedFromLeftResult.java | 81 + .../btree/BorrowedFromRightResult.java | 79 + .../btree/BorrowedFromSiblingResult.java | 53 + .../directory/mavibot/btree/BulkLoader.java | 1446 ++++++ .../directory/mavibot/btree/Cursor.java | 87 + .../directory/mavibot/btree/DeleteResult.java | 43 + .../directory/mavibot/btree/Deletion.java | 42 + .../mavibot/btree/EmptyTupleCursor.java | 201 + .../mavibot/btree/EmptyValueCursor.java | 188 + .../directory/mavibot/btree/ExistsResult.java | 32 + .../mavibot/btree/InMemoryBTree.java | 923 ++++ .../mavibot/btree/InMemoryBTreeBuilder.java | 501 ++ .../btree/InMemoryBTreeConfiguration.java | 322 ++ .../directory/mavibot/btree/InMemoryLeaf.java | 1029 ++++ .../directory/mavibot/btree/InMemoryNode.java | 1084 +++++ .../btree/InMemoryTransactionManager.java | 143 + .../mavibot/btree/InMemoryValueHolder.java | 313 ++ .../directory/mavibot/btree/InsertResult.java | 35 + .../directory/mavibot/btree/KeyCursor.java | 777 +++ .../directory/mavibot/btree/KeyHolder.java | 72 + .../directory/mavibot/btree/LevelInfo.java | 261 ++ .../mavibot/btree/MavibotInspector.java | 1457 ++++++ .../btree/MergedWithSiblingResult.java | 76 + .../directory/mavibot/btree/Modification.java | 50 + .../directory/mavibot/btree/ModifyResult.java | 114 + .../directory/mavibot/btree/NameRevision.java | 128 + .../mavibot/btree/NameRevisionComparator.java | 87 + .../mavibot/btree/NameRevisionSerializer.java | 248 + .../mavibot/btree/NotPresentResult.java | 72 + .../apache/directory/mavibot/btree/Page.java | 285 ++ .../directory/mavibot/btree/PageHolder.java | 60 + .../directory/mavibot/btree/PageIO.java | 273 ++ .../mavibot/btree/PageReclaimer.java | 215 + .../directory/mavibot/btree/ParentPos.java | 66 + .../mavibot/btree/PersistedBTree.java | 817 ++++ .../mavibot/btree/PersistedBTreeBuilder.java | 211 + .../btree/PersistedBTreeConfiguration.java | 290 ++ .../mavibot/btree/PersistedKeyHolder.java | 147 + .../mavibot/btree/PersistedLeaf.java | 1447 ++++++ .../mavibot/btree/PersistedNode.java | 1188 +++++ .../mavibot/btree/PersistedPageHolder.java | 180 + .../mavibot/btree/PersistedValueHolder.java | 801 ++++ .../directory/mavibot/btree/PoisonPill.java | 44 + .../mavibot/btree/ReadTransaction.java | 167 + .../mavibot/btree/RecordManager.java | 4158 +++++++++++++++++ .../directory/mavibot/btree/RemoveResult.java | 75 + .../directory/mavibot/btree/Result.java | 47 + .../directory/mavibot/btree/RevisionName.java | 138 + .../mavibot/btree/RevisionNameComparator.java | 67 + .../mavibot/btree/RevisionNameSerializer.java | 248 + .../mavibot/btree/RevisionOffset.java | 117 + .../btree/RevisionOffsetComparator.java | 80 + .../btree/RevisionOffsetSerializer.java | 199 + .../directory/mavibot/btree/SplitResult.java | 120 + .../mavibot/btree/TransactionManager.java | 64 + .../apache/directory/mavibot/btree/Tuple.java | 194 + .../mavibot/btree/TupleComparator.java | 71 + .../directory/mavibot/btree/TupleCursor.java | 1004 ++++ .../mavibot/btree/ValueArrayCursor.java | 174 + .../mavibot/btree/ValueBTreeCursor.java | 233 + .../directory/mavibot/btree/ValueCursor.java | 64 + .../directory/mavibot/btree/ValueHolder.java | 92 + .../mavibot/btree/WriteTransaction.java | 92 + .../comparator/BooleanArrayComparator.java | 109 + .../btree/comparator/BooleanComparator.java | 70 + .../btree/comparator/ByteArrayComparator.java | 128 + .../btree/comparator/ByteComparator.java | 81 + .../btree/comparator/CharArrayComparator.java | 136 + .../btree/comparator/CharComparator.java | 92 + .../btree/comparator/IntArrayComparator.java | 135 + .../btree/comparator/IntComparator.java | 81 + .../btree/comparator/LongArrayComparator.java | 135 + .../btree/comparator/LongComparator.java | 90 + .../comparator/ShortArrayComparator.java | 135 + .../btree/comparator/ShortComparator.java | 92 + .../btree/comparator/StringComparator.java | 82 + .../BTreeAlreadyCreatedException.java | 72 + .../BTreeAlreadyManagedException.java | 73 + .../exception/BTreeCreationException.java | 72 + .../exception/BTreeOperationException.java | 72 + .../BadTransactionStateException.java | 72 + .../DuplicateValueNotAllowedException.java | 73 + .../exception/EndOfFileExceededException.java | 75 + .../btree/exception/FileException.java | 72 + .../btree/exception/FreePageException.java | 72 + .../exception/InitializationException.java | 72 + .../exception/InvalidBTreeException.java | 72 + .../exception/InvalidOffsetException.java | 72 + .../btree/exception/KeyNotFoundException.java | 76 + .../exception/MissingSerializerException.java | 72 + .../PageSizeAlreadySetException.java | 74 + .../exception/RecordManagerException.java | 72 + .../SerializerCreationException.java | 72 + .../mavibot/btree/memory/BulkDataSorter.java | 273 ++ .../btree/persisted/BulkDataSorter.java | 273 ++ .../serializer/AbstractElementSerializer.java | 109 + .../btree/serializer/BooleanSerializer.java | 169 + .../btree/serializer/BufferHandler.java | 144 + .../btree/serializer/ByteArraySerializer.java | 344 ++ .../btree/serializer/ByteSerializer.java | 164 + .../btree/serializer/CharArraySerializer.java | 279 ++ .../btree/serializer/CharSerializer.java | 167 + .../btree/serializer/ElementSerializer.java | 111 + .../btree/serializer/IntSerializer.java | 171 + .../btree/serializer/LongArraySerializer.java | 334 ++ .../btree/serializer/LongSerializer.java | 189 + .../mavibot/btree/serializer/Serializer.java | 67 + .../btree/serializer/ShortSerializer.java | 165 + .../btree/serializer/StringSerializer.java | 381 ++ .../btree/util/IntTupleReaderWriter.java | 75 + .../directory/mavibot/btree/util/Strings.java | 571 +++ .../mavibot/btree/util/TupleReaderWriter.java | 44 + .../mavibot/btree/BulkLoaderTest.java | 1189 +++++ .../btree/InMemoryBTreeBuilderTest.java | 82 + .../btree/InMemoryBTreeConfigurationTest.java | 244 + .../btree/InMemoryBTreeDuplicateKeyTest.java | 768 +++ .../mavibot/btree/InMemoryBTreeFlushTest.java | 298 ++ .../mavibot/btree/InMemoryBTreeTest.java | 2011 ++++++++ .../mavibot/btree/InMemoryBTreeTestOps.java | 115 + .../btree/InMemoryBulkDataSorterTest.java | 187 + .../mavibot/btree/InMemoryLeafTest.java | 446 ++ .../btree/MultiThreadedInMemoryBtreeTest.java | 343 ++ .../mavibot/btree/PageReclaimerTest.java | 335 ++ .../btree/PersistedBTreeBrowseTest.java | 1292 +++++ .../btree/PersistedBTreeBuilderTest.java | 100 + .../btree/PersistedBTreeDuplicateKeyTest.java | 850 ++++ .../btree/PersistedBTreeTransactionTest.java | 170 + .../mavibot/btree/PersistedReadTest.java | 360 ++ .../mavibot/btree/PersistedStoreTest.java | 462 ++ .../btree/PersistedSubBtreeKeyCursorTest.java | 132 + .../btree/RecordManagerFreePageTest.java | 204 + .../btree/RecordManagerPrivateMethodTest.java | 144 + .../mavibot/btree/RecordManagerTest.java | 962 ++++ .../RecordManagerWithDuplicatesTest.java | 207 + .../btree/RevisionNameComparatorTest.java | 47 + .../btree/RevisionNameSerializerTest.java | 83 + .../BooleanArrayComparatorTest.java | 82 + .../comparator/BooleanComparatorTest.java | 50 + .../comparator/ByteArrayComparatorTest.java | 87 + .../btree/comparator/ByteComparatorTest.java | 53 + .../comparator/CharArrayComparatorTest.java | 86 + .../btree/comparator/CharComparatorTest.java | 49 + .../comparator/IntArrayComparatorTest.java | 86 + .../btree/comparator/IntComparatorTest.java | 51 + .../comparator/LongArrayComparatorTest.java | 86 + .../btree/comparator/LongComparatorTest.java | 51 + .../comparator/ShortArrayComparatorTest.java | 86 + .../btree/comparator/ShortComparatorTest.java | 51 + .../comparator/StringComparatorTest.java | 51 + .../serializer/BooleanSerializerTest.java | 58 + .../serializer/ByteArraySerializerTest.java | 85 + .../btree/serializer/ByteSerializerTest.java | 82 + .../btree/serializer/CharSerializerTest.java | 105 + .../btree/serializer/IntSerializerTest.java | 163 + .../serializer/LongArraySerializerTest.java | 129 + .../btree/serializer/LongSerializerTest.java | 354 ++ .../btree/serializer/ShortSerializerTest.java | 102 + .../serializer/StringSerializerTest.java | 103 + .../src/test/resources/log4j.properties | 36 + Java-base/directory-mavibot/src/pom.xml | 451 ++ Java-base/geronimo-jcache-simple/Dockerfile | 28 + Java-base/geronimo-jcache-simple/build.sh | 1 + .../geronimo-jcache-simple/src/README.adoc | 12 + Java-base/geronimo-jcache-simple/src/pom.xml | 325 ++ .../geronimo/jcache/simple/Asserts.java | 32 + .../jcache/simple/ClassLoaderAwareCache.java | 372 ++ .../ConfigurableMBeanServerIdBuilder.java | 153 + .../simple/ExceptionWrapperHandler.java | 69 + .../apache/geronimo/jcache/simple/JMXs.java | 65 + .../geronimo/jcache/simple/NoLoader.java | 48 + .../geronimo/jcache/simple/NoWriter.java | 54 + .../jcache/simple/Serializations.java | 127 + .../geronimo/jcache/simple/SimpleCache.java | 876 ++++ .../jcache/simple/SimpleCacheMXBean.java | 92 + .../simple/SimpleCacheStatisticsMXBean.java | 106 + .../jcache/simple/SimpleConfiguration.java | 172 + .../geronimo/jcache/simple/SimpleElement.java | 42 + .../geronimo/jcache/simple/SimpleEntry.java | 51 + .../geronimo/jcache/simple/SimpleEvent.java | 69 + .../geronimo/jcache/simple/SimpleKey.java | 59 + .../jcache/simple/SimpleListener.java | 126 + .../geronimo/jcache/simple/SimpleManager.java | 285 ++ .../jcache/simple/SimpleMutableEntry.java | 67 + .../jcache/simple/SimpleProvider.java | 136 + .../jcache/simple/SimpleThreadFactory.java | 47 + .../geronimo/jcache/simple/Statistics.java | 139 + .../jcache/simple/TempStateCacheView.java | 283 ++ .../apache/geronimo/jcache/simple/Times.java | 33 + .../jcache/simple/cdi/CDIJCacheHelper.java | 557 +++ .../cdi/CacheInvocationContextImpl.java | 91 + .../cdi/CacheInvocationParameterImpl.java | 63 + .../simple/cdi/CacheKeyGeneratorImpl.java | 39 + .../cdi/CacheKeyInvocationContextImpl.java | 54 + .../simple/cdi/CacheMethodDetailsImpl.java | 68 + .../simple/cdi/CachePutInterceptor.java | 97 + .../simple/cdi/CacheRemoveAllInterceptor.java | 94 + .../simple/cdi/CacheRemoveInterceptor.java | 97 + .../simple/cdi/CacheResolverFactoryImpl.java | 83 + .../jcache/simple/cdi/CacheResolverImpl.java | 39 + .../simple/cdi/CacheResultInterceptor.java | 112 + .../simple/cdi/GeneratedCacheKeyImpl.java | 53 + .../cdi/MakeJCacheCDIInterceptorFriendly.java | 192 + .../jcache/simple/osgi/JCacheActivator.java | 78 + .../jcache/simple/osgi/package-info.java | 22 + .../services/javax.cache.spi.CachingProvider | 1 + .../javax.enterprise.inject.spi.Extension | 1 + .../tck/CacheResultCompletionStageTest.java | 118 + .../jcache/simple/tck/ExpiryListenerTest.java | 96 + .../jcache/simple/tck/OWBBeanProvider.java | 59 + .../src/src/test/resources/ExcludeList | 19 + .../javax.cache.annotation.BeanProvider | 1 + 493 files changed, 120181 insertions(+) create mode 100644 Java-base/commons-validator/Dockerfile create mode 100644 Java-base/commons-validator/src/BUILDING.txt create mode 100644 Java-base/commons-validator/src/CONTRIBUTING.md create mode 100644 Java-base/commons-validator/src/LICENSE.txt create mode 100644 Java-base/commons-validator/src/NOTICE.txt create mode 100644 Java-base/commons-validator/src/PROPOSAL.html create mode 100644 Java-base/commons-validator/src/README.md create mode 100644 Java-base/commons-validator/src/RELEASE-NOTES.txt create mode 100644 Java-base/commons-validator/src/checkstyle-suppressions.xml create mode 100644 Java-base/commons-validator/src/checkstyle.xml create mode 100644 Java-base/commons-validator/src/pom.xml create mode 100644 Java-base/commons-validator/src/src/assembly/bin.xml create mode 100644 Java-base/commons-validator/src/src/assembly/src.xml create mode 100644 Java-base/commons-validator/src/src/changes/changes.xml create mode 100644 Java-base/commons-validator/src/src/changes/release-notes.vm create mode 100644 Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/ValidateBean.java create mode 100644 Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/ValidateExample.java create mode 100644 Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/applicationResources.properties create mode 100644 Java-base/commons-validator/src/src/example/org/apache/commons/validator/example/validator-example.xml create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Arg.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/CreditCardValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/DateValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/EmailValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Field.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Form.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/FormSet.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/FormSetFactory.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/GenericTypeValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/GenericValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ISBNValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Msg.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/UrlValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Validator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorAction.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorException.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResources.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResult.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/ValidatorResults.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/Var.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/package.html create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractCalendarValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractFormatValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/AbstractNumberValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/BigDecimalValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/BigIntegerValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ByteValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CalendarValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CodeValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/CurrencyValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DateValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DomainValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/DoubleValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/EmailValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/FloatValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/IBANValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISBNValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISINValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ISSNValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/InetAddressValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/IntegerValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/LongValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/PercentValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/RegexValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/ShortValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/TimeValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/UrlValidator.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ABANumberCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CUSIPCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/CheckDigitException.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/EAN13CheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/IBANCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISBN10CheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISBNCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISINCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ISSNCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/LuhnCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ModulusCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/ModulusTenCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/SedolCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/VerhoeffCheckDigit.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/checkdigit/package.html create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/routines/package.html create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/Flags.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/ValidatorUtils.java create mode 100644 Java-base/commons-validator/src/src/main/java/org/apache/commons/validator/util/package.html create mode 100644 Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/digester-rules.xml create mode 100644 Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_0.dtd create mode 100644 Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_0_1.dtd create mode 100644 Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_1.dtd create mode 100644 Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_1_3.dtd create mode 100644 Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_2_0.dtd create mode 100644 Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_3_0.dtd create mode 100644 Java-base/commons-validator/src/src/main/resources/org/apache/commons/validator/resources/validator_1_4_0.dtd create mode 100755 Java-base/commons-validator/src/src/site/resources/download_validator.cgi create mode 100644 Java-base/commons-validator/src/src/site/resources/images/logo.png create mode 100644 Java-base/commons-validator/src/src/site/site.xml create mode 100644 Java-base/commons-validator/src/src/site/xdoc/building.xml create mode 100644 Java-base/commons-validator/src/src/site/xdoc/community.xml create mode 100644 Java-base/commons-validator/src/src/site/xdoc/download_validator.xml create mode 100644 Java-base/commons-validator/src/src/site/xdoc/index.xml create mode 100644 Java-base/commons-validator/src/src/site/xdoc/issue-tracking.xml create mode 100644 Java-base/commons-validator/src/src/site/xdoc/mail-lists.xml create mode 100644 Java-base/commons-validator/src/src/site/xdoc/tasks.xml create mode 100644 Java-base/commons-validator/src/src/site/xdoc/validator_2_0_0_proposal.dtd create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/AbstractCommonTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/AbstractNumberTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ByteTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/CreditCardValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/CustomValidatorResourcesTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/DateTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/DoubleTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/EmailTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/EntityImportTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ExceptionTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ExtensionTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/FieldTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/FloatTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericTypeValidatorImpl.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericTypeValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericValidatorImpl.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/GenericValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ISBNValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/IntegerTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/LocaleTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/LongTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/MultipleConfigFilesTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/MultipleTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/NameBean.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ParameterTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ParameterValidatorImpl.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RequiredIfTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RequiredNameTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ResultPair.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/RetrieveFormTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ShortTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/TypeBean.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/UrlTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorResourcesTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorResultsTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/ValueBean.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/VarTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/custom/CustomValidatorResources.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/AbstractCalendarValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/AbstractNumberValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/BigDecimalValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/BigIntegerValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ByteValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CalendarValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CodeValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/CurrencyValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DateValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DomainValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/DoubleValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/EmailValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/FloatValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/IBANValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISBNValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISINValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ISSNValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/InetAddressValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/IntegerValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/LongValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/PercentValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/RegexValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/ShortValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/TimeValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ABANumberCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/AbstractCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/CUSIPCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/EAN13CheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/IBANCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISBN10CheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISBNCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISINCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ISSNCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/LuhnCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenABACheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenCUSIPCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenEAN13CheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenLuhnCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/ModulusTenSedolCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/SedolCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/routines/checkdigit/VerhoeffCheckDigitTest.java create mode 100644 Java-base/commons-validator/src/src/test/java/org/apache/commons/validator/util/FlagsTest.java create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/DateTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EmailTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EntityImportTest-byteform.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/EntityImportTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ExceptionTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ExtensionTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/GenericTypeValidatorTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/LocaleTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleConfigFilesTest-1-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleConfigFilesTest-2-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/MultipleTests-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ParameterTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RequiredIfTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RequiredNameTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/RetrieveFormTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/TestNumber-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/ValidatorResultsTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/VarTest-config.xml create mode 100644 Java-base/commons-validator/src/src/test/resources/org/apache/commons/validator/routines/checkdigit/IBANtests.txt create mode 100644 Java-base/directory-mavibot/Dockerfile create mode 100644 Java-base/directory-mavibot/src/LICENSE create mode 100644 Java-base/directory-mavibot/src/NOTICE create mode 100644 Java-base/directory-mavibot/src/distribution/pom.xml create mode 100644 Java-base/directory-mavibot/src/distribution/src/main/assembly/bin.xml create mode 100644 Java-base/directory-mavibot/src/distribution/src/main/assembly/src.xml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/BTree.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/BTree.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/ElementHolder.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/ElementHolder.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/Leaf.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/Leaf.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/MavibotValue.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/MavibotValue.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/Node.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/Node.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/PageHierarchy.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/PageHierarchy.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/PageIOLogical.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/PageIOLogical.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/PageMergeDelete.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/PageMergeDelete.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/PageMergeMove.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/PageMergeMove.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R1-node.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R1-node.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R2-node.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R2-node.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R3-node.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R3-node.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R4-node-bis.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R4-node.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/R4-node.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/RMHeader.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/RMHeader.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/UC1.2.1.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/UC1.2.1.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/UC1.2.2.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/UC1.2.2.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/UC11.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/UC11.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/abstractPage.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/abstractPage.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/btreeHeader.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/btreeHeader.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/datastructure.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/datastructure.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/leaves.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/leaves.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/nodeLeaf.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/nodeLeaf.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/nodes.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/nodes.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/trans1.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/trans1.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/trans2.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/trans2.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/transko.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/transko.png create mode 100644 Java-base/directory-mavibot/src/mavibot/img/transok.graphml create mode 100644 Java-base/directory-mavibot/src/mavibot/img/transok.png create mode 100644 Java-base/directory-mavibot/src/mavibot/pom.xml create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractBTree.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractBorrowedFromSiblingResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractDeleteResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractPage.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractTransactionManager.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/AbstractValueHolder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Addition.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTree.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeFactory.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeHeader.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BTreeTypeEnum.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromLeftResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromRightResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BorrowedFromSiblingResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/BulkLoader.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Cursor.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/DeleteResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Deletion.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/EmptyTupleCursor.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/EmptyValueCursor.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ExistsResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTree.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTreeBuilder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryBTreeConfiguration.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryLeaf.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryNode.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryTransactionManager.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InMemoryValueHolder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/InsertResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/KeyCursor.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/KeyHolder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/LevelInfo.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/MavibotInspector.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/MergedWithSiblingResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Modification.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ModifyResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevision.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevisionComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NameRevisionSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/NotPresentResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Page.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageHolder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageIO.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PageReclaimer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ParentPos.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTree.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTreeBuilder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedBTreeConfiguration.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedKeyHolder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedLeaf.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedNode.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedPageHolder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PersistedValueHolder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/PoisonPill.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ReadTransaction.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RecordManager.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RemoveResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Result.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionName.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionNameComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionNameSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffset.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffsetComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/RevisionOffsetSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/SplitResult.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TransactionManager.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/Tuple.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TupleComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/TupleCursor.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueArrayCursor.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueBTreeCursor.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueCursor.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/ValueHolder.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/WriteTransaction.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/BooleanArrayComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/BooleanComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ByteArrayComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ByteComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/CharArrayComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/CharComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/IntArrayComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/IntComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/LongArrayComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/LongComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ShortArrayComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/ShortComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/comparator/StringComparator.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeAlreadyCreatedException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeAlreadyManagedException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeCreationException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BTreeOperationException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/BadTransactionStateException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/DuplicateValueNotAllowedException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/EndOfFileExceededException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/FileException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/FreePageException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InitializationException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InvalidBTreeException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/InvalidOffsetException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/KeyNotFoundException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/MissingSerializerException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/PageSizeAlreadySetException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/RecordManagerException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/exception/SerializerCreationException.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/memory/BulkDataSorter.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/persisted/BulkDataSorter.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/AbstractElementSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/BooleanSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/BufferHandler.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ByteArraySerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ByteSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/CharArraySerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/CharSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ElementSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/IntSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/LongArraySerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/LongSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/Serializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/ShortSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/serializer/StringSerializer.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/IntTupleReaderWriter.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/Strings.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/main/java/org/apache/directory/mavibot/btree/util/TupleReaderWriter.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/BulkLoaderTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeBuilderTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeConfigurationTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeDuplicateKeyTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeFlushTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBTreeTestOps.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryBulkDataSorterTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/InMemoryLeafTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/MultiThreadedInMemoryBtreeTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PageReclaimerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeBrowseTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeBuilderTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeDuplicateKeyTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedBTreeTransactionTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedReadTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedStoreTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/PersistedSubBtreeKeyCursorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerFreePageTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerPrivateMethodTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RecordManagerWithDuplicatesTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RevisionNameComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/RevisionNameSerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/BooleanArrayComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/BooleanComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ByteArrayComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ByteComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/CharArrayComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/CharComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/IntArrayComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/IntComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/LongArrayComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/LongComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ShortArrayComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/ShortComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/comparator/StringComparatorTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/BooleanSerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ByteArraySerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ByteSerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/CharSerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/IntSerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/LongArraySerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/LongSerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/ShortSerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/java/org/apache/directory/mavibot/btree/serializer/StringSerializerTest.java create mode 100644 Java-base/directory-mavibot/src/mavibot/src/test/resources/log4j.properties create mode 100644 Java-base/directory-mavibot/src/pom.xml create mode 100644 Java-base/geronimo-jcache-simple/Dockerfile create mode 100755 Java-base/geronimo-jcache-simple/build.sh create mode 100644 Java-base/geronimo-jcache-simple/src/README.adoc create mode 100644 Java-base/geronimo-jcache-simple/src/pom.xml create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Asserts.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ClassLoaderAwareCache.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ConfigurableMBeanServerIdBuilder.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/ExceptionWrapperHandler.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/JMXs.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/NoLoader.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/NoWriter.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Serializations.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCache.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheMXBean.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheStatisticsMXBean.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleConfiguration.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleElement.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleEntry.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleEvent.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleKey.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleListener.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleManager.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleMutableEntry.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleProvider.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/SimpleThreadFactory.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Statistics.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/TempStateCacheView.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/Times.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CDIJCacheHelper.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationContextImpl.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationParameterImpl.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyGeneratorImpl.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyInvocationContextImpl.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheMethodDetailsImpl.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CachePutInterceptor.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveAllInterceptor.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveInterceptor.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverFactoryImpl.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverImpl.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResultInterceptor.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/GeneratedCacheKeyImpl.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/cdi/MakeJCacheCDIInterceptorFriendly.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/osgi/JCacheActivator.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/java/org/apache/geronimo/jcache/simple/osgi/package-info.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider create mode 100644 Java-base/geronimo-jcache-simple/src/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension create mode 100644 Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/CacheResultCompletionStageTest.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/ExpiryListenerTest.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/test/java/org/apache/geronimo/jcache/simple/tck/OWBBeanProvider.java create mode 100644 Java-base/geronimo-jcache-simple/src/src/test/resources/ExcludeList create mode 100644 Java-base/geronimo-jcache-simple/src/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider 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 0000000000000000000000000000000000000000..9ed617e295da48be70d35aa22c05a66868e92ee5 GIT binary patch literal 13087 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 + + + + 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 0000000000000000000000000000000000000000..783abb52995d4a6b07de68f825b8a8bcff0a5bf9 GIT binary patch literal 8128 zcmaiZcUV)|w>C&uFaiTAB_NI>T|rQhPz)U;h&1Vn^eRmWO+di`snQW55PC;KfKWx6 zp&5Etsi6x=D52aF{ms44Jm0dJal zRHqJsc`f59U_9qs9!W*T7p|eKr2k@KJ%hzx+pejT;Sy3QmUzro6qilwP^_72Jk;T8 zUsNqr*s+=NIsH7_;=ipfMxDO`cM4&Ql*~VrUp#7N5udI{;1SJlo3Y*3@-_k zDL;fV{_mInWrfkR+bu!o1aVq$CgxOXWGAlp$C1LdEF{S}OdN zM%u6*DqNdz3mX+OnZR~Il13d=Or=E?Yk||U>SE@H)R@z8iZ|@E1dBGy4Hp?)o(T%F zwaNTG-_4Umg)26LM@D)oke$Wp-nQ(G^}T;&qMu-{7q4a;3pS)SavHA+^zwSz2xmPa zqi1Wx<`&aUJ3rryHEe{V=vm6Xx=L8ePf0vPn`2bdW$qKO4~Z+@(}S>l!*9Wsfpz>> za-BZfKG4v}Fg}%_Q+}zws-1-Ay<1~MCf%n$yEc#o9b7^*BM62bb!Y5tMcGoy%uUa7 z4OM!tF54k^+atmOA8QSR4-apg7cWVZ4|qA%^y26CS*3iPUIcsve6n$1{Fq~J17j0@ z)<(kky4RK!Qq32CBdRQF&~I(bf4$@Grgq|wqoZ9B^>WK3jN;Rd)4^YZT3NU|P!8<9 zzr>qa(9L(O3>Q|Bmu;A0kF@ISW}BW}R-L+ba!42ygp-_2(XORgP|W+gXfsRt zTl@LJh3_ofBYj=V#csR)Lvt~pB;JIMs8wYC?hvTw%QT!&TN8NXq#C_8Q)(*W!f`!7 z1!{CZM(#3%6btTOo{(?EMMP8;>lgmO3Yj4DpQJL`T4_sJb6pq=C$pva?u0RO4aISQ z@JxSBqVGycl|RuU_;Pf8;cBq|nsIC9!VYQjVM78-YFkzL)`Zg>?7i`yCp_0bwEbv* zo+00;>?*fwmgd7Z7`4$qkgd|#(BOCG3?CB+Gzfd)C%^LgJa}*IV_j7qc4Mu~T&fyF z+?);k@%i&dyJVMX**c3x#z!@pet^Ak;DwNk<#a-d>L>}HC}Z96a;DK*!;KEv+1W!Y zbv-Zj2FqJ~TO)%iI3*4K@j`rHN^B25Bv@(0^{>8Rr$_STr>bTLiVQ+0-Q1qu_6bnuimB zWml!73So%azKnFYaY-h;5-lU(Y{HNR$qTJUKO z9CfmpYY8q9HYj=z!DwS**2=ntbtgmwAVmxqGX(yI@@NPCXm74ZHELyTayMmILZPOC zkdT`Cfv)4GR9>*q`u8UF{!}R~n>YRq8@vD{YZ8$25{tbV={%f<#J!&?O$kvyeoU>F zyGi6OmD8*)zCEpVfk4p`TeeiOm#*d5Jfc=l`hfY@-x?` zmjgJq{1bY1OkEu=pCpjw4wgSGFLy!wz=@cJ#Tk*KgnbE5i&^5002PVimupwBj(QlM z-mR_cb(OZg0qQaJl!$TXk8b5xfWZDd_@7vhC3E;Gt_$N&(k>3Rq{vTcS7j%QT7C#J`BWGo_M#%ZEQIi~SrHy00kI+yOtj&`&>y)H)G+<6 z-rlPFwp94wb6q7xxSheZ@zv3CK556H2o8}K+)Dda#Tc{8*VI8MYU2uF4UEsajLXFQ zm9cUb?xfgQU1Q@~>&~+?s*3Onj*LwBESZ;L=Sv3jVJCZYF%}_^GE=m}VBSyqQ@A!= zD5C}-#JSFjh3EFxI(l}*)p@4D_wyT87H*lIuC9TQj5+rqCQ!L$bD*>HnvrFIH(~iJ zH4VM8ib{KX`@+)FP8mf^C?r>{oaO35YmQ7;z%|f^∓dpAxvmOsc!ESP@ZCW)Mih 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8e65d716bca5b9a39e4cb083eaced4894eaef637 GIT binary patch literal 49955 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 z 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 Gfd2u#3SbHV literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2a2da0265f626edf56718d9dac580ff7016ed409 GIT binary patch literal 7274 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..52f74ec8de384a4855ec11e212b81ebf74c360f5 GIT binary patch literal 50979 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
yM2jduKRX{g)La8XnaG; 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 pw7|XNf0*m;y6{ + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..55da7cfa7da416121b3476c6b696cbdb0212f314 GIT binary patch literal 6911 zcmZ{Jbx>SS@Fx&lf(3VXcUfE(hs8p$;9&{Q27)d@gD&n692O_I1`mq`2qX~P-5o-1 zzxTVks{7;KRDa%7SNC*v&#Rfwy!ouJt4@GRjf;YULZAUu0U_VRNb$o)Ln6BrH6aQL z^{|GDqLJU?aqdTp_yvZM0#IXW48lqYl*AXQmR!^Dmp6L zD^=-(!Mk!$p=v}jdHB)h<7570P=0NIJH#C_+YQ*gzTezm+073Kn0ySDTlJna#6OF7 zH(sQCCOL~&k8zg;1#Tv7(5(8E+zqn~Nzilkod18+K;!zLHFH<+g(CMTVUe%m3~}2w zVe1pRW|1_vhB~@ti8QZ94;q{PJ#dC8)D84eD>*~q+$cYJi%Fk0t&^4R6=(VZl(ikl zCk))ndQ7?}J!e%!Y3hYarpZ7;*vsQ~y4w5iwD%vvK})UV4B(ZrHb?4rPiaL?M7FJL zKtCO8;ddUr_wl7{2=I!RCf|XWOb*)rsG@h;H>+L5L3PI3{n~fkliEO$`&9|iHiz;v zT~XH`m~Slhs*ps(Yn-z^b^~46twCZyTtt*abYq?J2Ydq)QF~QlKy)}3vC8ehdNAd5 zw~NOqx4{nCcl?NPqVEcp?3%sk`+X|D0{zQ)D$cu1$Fv3qW)suwj#mzWJp@j|0W zhGT8gv*=H#hYWOYzF|WkCuGz&?vZ)g9j}PttND5bH#fKc{l)g-ue#NqpxC!*I%#ph zbqxQZ{;TAO%IJuF47}OP_vswuWlH2_<+g$VM;q?9HV7vh1ZmhlF`l6=?-(hbp*Wr) zIo|9G^0LQYuk$&zQ_F2Lgpv z?P(ZdVq#)yD&*mctMPhgg60YiSH`W&Kq(6(XWyUT3KQc9ue9*iw4K1Q;r^9dWab@{ zI+V-bu;XKZ>y2wWu^;|wXRx$OKNas{5zqF9lNQ9q#kIe`Z)RqOfq?-ei+oF^#umWG z=BB$Ls9~pz(U_ScsqOH8Vdj5&q^3_cLQ&hPMd#r$2?+_Yv0A#ilarJDyu2#Ka8QYy za5-&y`Fkesj;U-zN3NYz#KmBrk2*Fxv3HEq#~3nU4$n;Hg%Z4flKZuqpEL$@C%?*i z3g!|2*F`hSm&|;McuOKwuFSR*9?^?tVqnC?#I(M?etv#VMMag!ARl~vU`(~Z!2?ns z6XrMs9Jg@%QC+eP=a5-Sy9%%#(>S8X6odDJ!!zh$!_> zV>Yf7mNiZnmbH3zxV!gC<~M7@CrquuFW*t=cPrTFSAjfS?XRt^Ra9{D!Y?i^;M81H zh%+BE#F+|GDsa=*`x;Z%w{uc1;S1|4hw^V&h=e#uJ_NkVD+epYWT)hgozWrBXQT9)nwj1_k)+-i z(8GY#NMv+P&V5`SqsZM+P^1&m-w9z6$pTu{p>|t>I~(LOWE(=q>d-5Z9~=Kk%jq8- z^(vc@7QP^Hfg;(`e9y)c@ZxK8?*q<1^gFJWzk;=FGzlJB-6afNCot`>-T(*}YPDrY zskoj#q+`rlA~Os}mSP=Q3W_m4WTtx~n9^ntVKdo7Ezxq@5@{Aa2Df|P<9D(yOKVG2 zZawDYVio1Y-u5igvqXpdt1`bSB0KXI+bvoR1}t414ZRy%TYR1zycEyGALYp1T*@zj zqtQletp{7ik*4-lFOxbx_Ddb??Q|S~C7%8f{BB5QgdDyeH&BnXnVC8+(Qx`R8@Q5c zQ&$@>_p2i3<7AeJuK2P{2Q%p0YO4RnS-gV6_nl7TDuYWeom5bXSYqlGkS=||WSPKh z9=n8pp!5%%M-73)Q+`St`33?$Z4ELL0zE(LYBe`gd$^!q_R@>{znzMGG_iZ><$^v0 zocQE7_2n2(^Z>aM)~b~Qy^lf4A70`D{|HHWNUtiV}-bgKy(x2!W`Up|XpsK7KY zI(M31T5?PHFj#s}gJ)eQ!xcZH<`@|n;SStPi=px6TbNEkDSv$r8#}CkJLWBZv1c`= z0By5d5-h;A(!f=d^-|2u@%)l>cLDUaKYKGUvS$xppUzKYOY&#kF!k-upHIB@Em&%J zr2($3`|3Qs)%t7swmNUQzGt|W%4-^N zPeRV4uwXJvJMH&EYhm6QJPTD~S*Bt3ndzjspKv?@%H zNg=4S#n)0Q!s-frRm@VlX=WPy*7lv?(WR~|Xoh9moTcRezMdfU0rk@6Y|^)Z{?pvq zMvkg=WV~9~B~Kmx?nI>;RCk~yU|MVA3RZbp29zuu~C;2p(=5se3m$LYB$aw_4B#1kuA1!$%wzPg`pXGbDh<+uUiqXdKCG$dcrd*m^nAD(5yWCUrtE@!klt(3t_W{ zeu#Y>+4U<*dT(whn(jSpii_|&5lX8|xXx7m{`*cUhFLECw->s%>vH7* z9*KtR_Ff1mzV1G3a-V-`I~CJ87sQmtO_f|XaQ!aYgfRku+wYk{0or5eN5PpLc)|LT zh#LbW$)s1|7j4R0qVJ`Go1*3Kulb@v)V~Dv{{aLBHU#U$Lo-|@!kg=!Iii%%UEbjb z3)&CYHk~wd4T<$K=6+V+62Ut^=zpeoZcJ*Ko()7aFNAP4K@i3W5rM2dgZU?tvM4l_ zi1tXzWhjj%)If|W!q9gmShA3iO#!W$d5EA{FaGE2*+qO9wz8(9Y5``YvYmFxsDO>X z{R9%^jDtNBNHO6m@yxlfFJy&J+E_N~aJ2Hq0 zN=z^bT@pp#H0iaZrj~hCl5X*_daE}I6KE23(g8(92Rp1fN2gUD@UMEjnfwGb>CNd^ zxdY{&370a+h2$j%UG3BBhw?if8PLF+5cq{u3+IRF2URt8wQ}Unwr6(S<-ZqR_;OY1 zcYM?455Vouyd6=hie}0q!v<2O*Vb39yFd$kcHDB&rg|S}JyS4Rjw?%;AjU z6gueB%!(iLE=oPfNEbldrptz6EP2j~);wC=R{eK}@@(Au(Pm$qC&8SU*wfj^KJd!q zc$0^S3X6dkTDFIaY4#KM0I1HWyIxcKd;3R(e-aZ_o+%azwXkU47UalL;PGQF6O#Pz zg!q=ahzyEPnO6vL`&6sL3eZn+S0dg{>-`?-nL-=E_$@+T4KW`h;J65{xTaY+)qi4Q zQ$Z~Ed`#QQNd0WN*5R(#x`P-*OjMR%+O(rqMVjWF_*uf`jn>U$%bm5+=dPvmOgr<~ zmVq*Sye^swBOo2^Qj>prNX62p{#RI?p*q z#>REs9VVC{Lkc}r9wj-%-pGC&sW68{JQL{8r+ixDS zFBlicb!l%TYq~ z(G&K^$`b#6;JxLp_6vS^EPo(4v<~{1hISq`!mt7SXmwj{UdPWDA3$)RY9LAZk;oFk zx1@v4Rf|=dzQNkSpsm5X;%s88Y3sub`rQtun=>>kXHDRgi)D|>;<-4{4E?Fljk}d0 zieiAp?JAt;fTc!Bu9is`jUm@~{#{L*YTdhDA?s`a9d{&E)v(^L0~Ufhwcz3RLY2sc zQTCgSa!I|i?68vGa17N3&=t^57V9R;x{7WcH^%VmAJB8q8#95%PwrHfzF<<0ZzfC< zMDnOuSiTtFFwm;}Vt~}-*erssXj3;=BB@bVo<8rKAYZUe`Pa*Lb_lcl&IPgBlE2X? ztg|g@Kf=BUeH}UKR4bD&AbbowZyD;Jdj7B`!3z<3<*}x&O%pmtrhD>UeZect{s#gD z&x3ZcK;o~qb>s0*@!ZbB*=RsaS8q`sW0~4 z#lqj8>mrDCkz_JoC0}W^zg$I)jv@6o;jLE^so>W$M`mu6p60{=(((9L9(E(641{a7cUiXV$IH6!1I%F zt+8z0Z^6Exj+&)sXwW;$h{q9@F7C=@fN@~DZi?NR;wREqgNnhtTdUBnLm0)2b^{m< zPGbLOuUf@PV?RF(`bec4hwnY;W_>USDt&Gg*6!M~SzAckj2N2=6?UTM zbIZ8|Ky!{_nxFs?D#9g7{1jubSUZ(36x+N}PL4P_x+&a_L(MyxoGDn3!w2b9?kY)I z1}0oRymVtcCkJGDIBRqFQZ62N75Wd*Z#v13Q-t8CF&+G`lav_9Q_6(iHFGx zLxndg+JUGCy`Mt)w*XNKQ7s?TMSfuJ-s761h(}&w@0RF+E_^%`=DvRMzD*X7e*&tt zs&M9mosUV;8yRc3)^Fb?h%>1deres;j^p7*JH620;fSA0MWAT?E#JRB)_}45_xSf) z!ru`nxGYyhP^EPnY`h<;s0!ivB12cX++|o03v5Ptaz<<5r48!ED-@MIPo6R-Yasxq zDHAl;4BFB^5n=`v$3boC$;|JK*;o9_j{c*LS7D6NmJ9C(GZS=&Z1&Pl2vE_kgfd}| znvL0KXa;5fjOKGQX6vtCyms|(?FuL4oy2=Y!dQ zDsS}vqq05T{q&+R3Psck`^z*M6dN#rfs)t6fs#jy&w<&0j_R@WGT}Cg#qzDYGJR#@ zvo~IWHLn5-1`5tmIpr79FijNPrigFBL~mc977R?&uQ^ml3y#upkpv+hP{JxXIzklf}t7!phjeZWS!IK>DyS+i5C-ur@j&f1U3|~ zi5#hfx17tgYbRNFHQi%rhOuD?y!K)OU$ozj@Smx!xC z>;nV8uKcN_ifV+$qI_fO6E#l|vc#V!O+dQD#}et~Z@o)l+`q@W zJZoKn1TL0YPr{<__P)Xe7WJk_J+H+M{A%v-Q6F64X16!e6F!F%)ZnZ2)VI$NCtrCN zk-XtI2ZmTJl*{I;PR<!{0&I^gRXDmF2m^ia~dA zmMhx*i&_2dtQS6god7JDpIAIF$Z3r7P)PNUmMZuY8EJRXPrqc6$~eg2ln;#{=9^FU zD(|`+gH`=!mfo$ zcXeE}CxK2mI0J%Hw@r*yv5!r(92IFfw$VmM+{1`yGg*}c;NRAi{h64C<8MnA%7J^& zt7z}u916>Dh$M|s5r`>nv(g7Dk7u1eibR-a8D3&1b(W=OT9^ZN_AJ}lT@GGaxHV=5Y1b$rU;b&R9@)p-8=}1{}Ik?TEPsG z48Gz%${h5O;AP;V-KikcwiMeLu47<=+zKes;9`*-htv1|HX$y70pC+l#9*$Ld494R zGl?6)#{qVG*&bAhWF4}hGvwEMduiahfCW9PfaG;_quQ`Qofb67y`jo-S|Gi!dc5VNI(0IKrh$#Y#GGxy<*jo$sMlSUZSJOK7 zi$>z6sVO?wzf<5)WMHa^*1EZt3NF`AXU6t)lHiX&^ajKITZp68Y@FQ|chaI-cU{P)swh)Wmz#D4Fde@u3aCNU4}#)5Da`YGX6}(5U(Q zHaEm*)JUsqvJ}|u%`{EaU~zWWBxkQFzy`u*Ca3+LBKwKZ8!PAglQnh8*=_$Jv{GGf z0IY!|a3Rj7YQ!+=3XaW(!m|rKz1*fpUSa;M5W15za!GX$+ ziY@BJ<}Qxf5?-o3jkeh?A^~M2w&U3%mzaVAdFVvZj1m1#)9z<$)9h+`U$@TrNHbZy zS!un2g)ak@F|BB$5iAh}wDMbRC-ythBMUU8s`fE(LNiD?j=Ag{(5P>rbf6{Pcsnugi^ddCW~h?&Ts52J9FDVcR*A=Z5k8?Rn*mEG|;;M3}LhAzXxmhqId zF~ovwNcQ(X+z-kAX7>KRZ0Co(sUmmVs`EPD*gxRqOLTv*?(pG~mhL!6in(3s=**!0 zH>C|ugOz{fNQ{b%wCW88@bU4<%lC38`Tu(ce2B^`D=!}z8HqwZ;o<0)I+ZjJq!+J< zi9IiOCdS9dM-mx&{jcE&SbqZ2_wUvIY;|?@)|PzZ~ZsIBT4dslWupiVVHU4uSCr{0Sn z3&#fazqJXNYF(V(^T;4DEQS4luZt32t=^eb-h?nP$4KDThDISTwoo)wbyaGW-hTRD DC`&&M literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..efb18b826ac799961e1b338f3ac2923baef1e5b1 GIT binary patch literal 8349 zcmaJ{WmHsAy9NZ25+tNSkWK}pJER#>X{1|1a_AZaq)TcDfuT{lQKUgShX#oug&Df; z(fi%I?yq}(%sT6wefECy+3)-8iPF?iB*3M@MMFa)P*#%HMngl72JYJ)VF14fr9e3} zv}abz@-n(U@PiyYebUXlzCKQe;u5P8OgqyV5>~CZ^n6^J+HqQmK`Lqj9GraICz^%@ zR@sFovUCFS{!@IXJ~#@b-Fi=mo}|aLJQ^JIY#wk9(c=l-zZVi~A`Zpf?=tPk-458w z$N{eN#?H&(>yeR�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 UStSqn+X0%gf`)vR?7Pta0_%O6B>(^b literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..87f9054bcfd15c02d8e9738275bbc6587644ad85 GIT binary patch literal 4369 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 A{{R30 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d1e7a6c93542e14c0ddb331b5b840b25b55d96ca GIT binary patch literal 59696 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6f08ed73942731f817b709d89e4e1b810aeb9c3d GIT binary patch literal 57993 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 MAAo5Sm3Gh>+f8X)j$>eW0L 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 + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..a3c416f41a512ed6c70d936dd301814900eb8a63 GIT binary patch literal 3596 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d2f59e513f99c054d22eba60855b442f164d846f GIT binary patch literal 5893 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 Gul@(7zKBu) literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..12bba835fe7c1dab156396fd3de9b244e92cef15 GIT binary patch literal 15746 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 GUjGAL&equg literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..35a265430a5d551bd593c057a061d4514aed7b33 GIT binary patch literal 27210 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 u + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..5791b782076068b88156a63710b45d8a82fc9ebc GIT binary patch literal 26276 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ᔍVuMt>Y0z4xb#xy+4Fk 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..937969db3997923b9ed46951368deb183328cf7a GIT binary patch literal 29839 zcmbTdWmH>T)Gdr_aF^l~*Wj+jwODZt?ozBc6nA$oUfd~KG`JUccZ$oGKKFgcH}2od zAV1E@&bGC(&zx({M5rpuprH_>KtVyF$;nEoLqS2qL0%F_FpxXxwp`3mP;OdsQsNq( ze@=4{bu@;a-e(oLyzT2`rqhv#ahUVvI*H9>2hv?)tkYI~LjAcGX}T*12k@Dib?PXQ z=)<|bR7B1Au#L@})N7Q;CLlqV`@CIbEW3I%qeLbU3MIC4>&xw79;1bVuV{ogShZEQ^6 z?+>SoRD}O(`1<;a@&j2}Sxk+!VXd)n}F9bP_)u1nqkeE1@OxXMGbeRvGv{I`&C=U$)h>nhSDx+0Mjs76$vDWVH z?d@Gm^y_gyYf|JH@Sd5Nc)j(V{MYS_!V&-xgG|5GX{%T+iN|hKYo% z#y3w-^>uWR5{6<(zLN*m&Xy`Q)Yr?)%LhTz2nv!6srh{*jn_O`{P}%&Yilb$>Om<* zG}*;=1bFj$zhPt&J71~Y@PDu4XQs(gt%+*6WMTi;FmgL9D?$!axM*yugyFflxq$&V zGDT(OupgnJp>;n)=f2BCMnzry+)NtJ;@!ExQ^yy5*zAQ3pVTr!vA}*nStn3O%jPi~ z1PmK208Z;nf1{*(Z`b}%$s5EtK0X%U=V$Q6zstB34+9Xv6yZOXGKwZk*y2q9;9he1 z^K+H}$J_M0l6#gH4nRKOZ3U5?y*(@pOc?ry(Yq!;KfkHJtfuHfPCh<9qBmgRC49rr zP#NPYOaIUrcogiHuIoqN7m9SmNP2fy1or*}YMFcj40QCjA<;KzjCxSO6-p$1*=T-I zm*-WXgwD5b-xdI)lK=pKh9qo5WAD2<(n<=aoB`<`sO;r*%^txrOe7uXd%yPea?Q=n zt!Gm8yU_D#!`|NBE^ten5OMf-Z)+=`Cmagw%ZUv6w?^lkPoWoh0EEw29^gRTFTH>% z@jf5%-_e#cO8|C%C|8LqggD>JUPNMW{&j|`bx+o|H|!uO;ipX{O}{c3_TW)I#;k(t zhOaF|*+fr00M))&a?!VktDQ?%AD`!Je%JhcYX^rgBWEQHj6Q<9y)>hRxw)Q6JUl!W z78cb3$w$`h&6^^fqwrbGJ$L?CtyMvg-{Z)&om zSXfw$XL6q`)tMdtn~)Q;PVMH+r0DkpNk=r5fEKOC0uu2joME_S2we{na8(W6`54P4T&rwA|en;^<2`q({V#2W#gno zAuT`XyS6w2eH`FY(#`EQhJ^PrcnJ_Me9?J>-YH1)nk($Hd1mZaI0InJLynU)CoEt0 zxe#V!V-pq@&R$>?)%ZG|&l%hPUu0|`k$H3Jp$HIUzYe&D{pE9=FghxKc*!*oNWt!W zdfw4*9O_?IUHxs>QUVXrLe9pfVjp9knhx^Ko%?o|3;8(0x#w`5Nq^Wy_q*T4dRJ@X zjxv&ESg%h{=W2g{|I7?07gw?%$^XNt3it?=g&f6C+lQy8rwccb)9N^iVatN?j5y_F zd|07^R6X~2k5ZF|>FZyYB!(zFl`j2~QFC{LBK_h~ioGgBW%ZH<7Y#PVj2Rt+&ul*B z>R$}z=>^Nxb(Skqro!Z#$?T5H@LGx>f3VL#WX4r<6-n~TQ@vH$G;f3}+R)2W{W{AL z$}^vE+z84eZQwMiG*!{^U5W+gURR<=%S7dgQl?UqD#F{f={0lGE12Rdw4~UikWr40uwmP7*z$-i8YbsQ!1!JD=?{OXUVJe1CpiIplPXSRa?ZbWSupL z=>05D}eaN&SI6A;^WFV$;nMF>(csvU*cn&!u1ZO4zXU4k^nIijjfsiK-3KNx1;O6GOKVP%8vVu|wAouw~v z34}D4L^BENP#?)wmE$XTxyq)X%FYMrv_4P$_hxVayJR7xy8#^K@NYtNa9GNR=e1(V zrRK|WS}AtFWmCgjUxG(%;xh_Zfn~L|wN+K8=W8AJ7aNY&)`8w~!Q}0ZVY*m5A#b`A z?jy)uUnt=3tpa!d()YtCIwm5nH5TY^CM6~&=HwWttHVgbB`ankem~C;caagZEO>&E zoAK11(FxUHu!hp4NKrTsKBrhqeSv0_WK5Y0mXD95y%&>~4NVj%$DwhDPt=V^VlYxq z8lu-$&*`VvbK#9rD=lV}tQd8Zv&huIoAC-Nrjk}?O-`nxM&87cJEx(fm3Iinj^C!W z$mc88kz=4oV9-^EiIj*O=npF9lTasRWq=8#43$R_rjcPPZtQ9$`r{-O~tRap0!fSk?(q`*Z5qjnnb6-ZH>+COjV7H zV--WLV!JDQT>j%p%#o$$WVmZNVP`hO=Q9l1;T4Sa(ANACY}wF?#_kX4z6wn;$KC8T?a z$PT!9AbZ-d`Itf(IGV1JN(5En&N@0~*C?);!$_B0w990Y$QLKqtC1X33~hy>szDeUbAB9qFOZ09!)MAZ}-lLFp4$0g(WGnax4#}uN=z>Jq zJ<~zhDrgr~UCV;x(ST1y+T83|6-0E&1&gM1n=ip{hT%^;I`FU*;=11@3=8e)6p&>S zl!qy@F+&aAE5|_eX4~cksYvM*CRUzTC1oVId*W(~9j0MH`D3(O&0lx2i9UIkS@3Jj`uy zo-Y;?4V9w|t=CHDQXC%pClmA*=|`WO@lN+TzEgRFLRW<_L{3S9A1D~*+81LeZid7z}>^P}M& zDXoIjos;Wvwx`Alik)R5Q0p&(h}1LWQ1vW+7dSKEc0IG~R@NYdxDxL93XSwLPrlEr z>3rYkx}V4<5muux_yATu`~cidv@IV7;FVRV<({rnUh53ZNf$$_rwhpRCJDf>#bul}| zF3?@Ay%QR46jj`c?NHrx98qRinp;y~I!6X*7K8$u&lnn4;o;b5Z^h$+OO`gH3g?n} z@>|hWT$`JY)eHd|B}E*ZMNP&}k=-x05|_OM z{iI@?y%5OEcRy-NpoV9#b~q)271gOTBI-rWpne5Gs|vIiJyyE9xp8@Kxb2p}nP#5Q ziH^fB3v)RW0SE4l#mo^tmlGBC0BY2MLYX)-tuW;nV5c+=$%0Ot$aL&HgF89#@~S)a zfEjtKTt+&Y#^$ht1HtAd_jT9Fw4GGRt|2+W8=(k}GWb3DhaqVjWY&GY!mW zR&Oi^tTqK26mU2G>kcJw7q}Q^p*_&p_z^-8LE!X+&!y2D4F`hbX)x)b`C=eiuBh_Z zCEB@%E}xhKaKIx{G;3b`7FpNB=OF{UUqzF(BQGN0oW zpjEi?!2ppeBB3?~JJ?>pJpb-DNXE5YE9WctvctxG)u)mJAlG?9sG_S&jWs(|1Gc)kwm39Rjnb-#x42p%zm9DCbJ>f?X{;Nf~E}c52Ijb>869NxXQX`i!Ic)5!GH1sbvo`Z*pYrLF$3uFdWG zgH7?BP*h8$OfHZ9usE7TD7%1{7bb;NFp=P$V*QJ!KC?)@+mS7V zT^n_Hd@(bl=NPy630ph6I=03o$;TM){gJ{fY=a1>e*wN$ZC?W8$-YhlmIuhsje*5X z23QK8KzysqMBnzoDr}wX#jQ)z%U;?hTl2>@ix7e`IF#4RRDghRv+(o1_Sc)Y=fiZJ znT(F!J3TH68rlXA@VTUhb#CgC#pijg75(vU=gZ~TME1q7=r zDwIvC^&g)i@dQ46NZWLl>~8~sj2<5z2#Jr2_)fXf*hI&h&b+rdK}aU7%(r4W|17NWN@BW}6e7ZmW>6+_|I2p(aQH0*Ekm5b-#)#Dya%w-@(vVv)0YfJ ztg-j(%-Qg5I89Q)?;Uo!+V-amgn_+}BmPU(fVmq6zT9ZAoa~dWx0=qDj&j3inc?B| zPHSBo=jU*~FP5WjaWpEo3%k5DGV;B892nTR{ORYHW$3xpCLIL==L(bj_R4ER@1cYB zsLZ7VV~VZD-?u_rfHw0g!#jh(n>G6mLfSQA$Ce-~4R+X$$hd>#+ng+2T^!HOjU`(X z1_sOgVFvozkT3g)rC&1u7Aa#{!D+WAKo zQAH)u`Pr-*;3TyM1&cf)hBP266^CO8Cm^7K#RtB7CA?L zB2l&=aIFo-IzL_fj`wXCkTB4+h4~_5sH?l<)oHYBYhrJ|xZx$^b9=b;^aQ*={Q`7Y z-3aM}#kEMv@Y*bjTlc&WwrBfEp5Oh|c^RraHO1X|y))y|ymtI}9+hH}^n-uEa$htN zTi4y6kV%m&ZC&qZE=l9f)E~LtHmj{z#&lIdLH(DWS_^?*H>I(VvdTggL!~yGX=z&x z7>^@sh&>pC!_jXwppl66BF;k~2<+4*hXisunOXDH51bnUwPr~>yas_W!?H1Z73p79 zBWoA?^fTO?%PN6GX@xenlk_xjVDEwoIi^Z?{Is4*JV%yG2NRiR zQBVT;u>9ZsKHvegJJQLircE&7Vp3B#w#O_LE0%!`>OY2#oDt>4GKd@ki0tBtJTaVM z3@DbJj@B{2Vy7|R!NxJ_@5eho;FD|?=;u;~;&>=vutDt9;2u0#STNu#A@DXmU4ujl zI)Ug;TCg7EbPC>dhMy7R2)0X;od5`1hV>5v7L-Pe5w!opC*rgO&H01l#U04N83o_L zwQ20Z!E*q}wr-58ru@&{am)9LAx zSj3|K6U=%M<1CPU^o=-UOV{$rUDLK)yY=PV5hP2;Pi@alo9CzKD7s4bto_b?yYWsF zvUDEhDv~|UTN%P13YsNnP+Fy^2m2`>_Bqtp#Sxa>s=Q-ZmafExn-!`C&c>QYl-5^_qK+5 ziFTS4l40x-s%XUaaJ-2Q!YvTio-@Ore$K&J&e0EKZJ7JsBBxLMpDg?JCuH{koMvtU z;KyVQ<@_X_EoD0DY~@b|qIln4HKI?B=+lz6Vu$)k{bU3f=eeh_iu+CO@+H*`;6F%l zN((@tw1{G!Inghl>AY3D&QJ4R%_Q3dyG{t!4u$zpV=L=6coPsnhF?sFm+ zKSZN(q+u(U5F+wI3R)v-Lk`lpf(xw4-=)wtiMN$!S0Ow_kwOt zP}d%29_OaHiCUj@j6ck?yr^|(nYL|77eel9pX6wO|FFbf|LNbVH-ni=+M(;k+UYNO zEZxL6-6@x!gD%6oM$ufiVo)V6P)7_8;Bze@|Hao(8FG7mx|@FTjJ#8F#W=D<39-nY zL+Yz4J+-RXZgs5t@=;UbiCcXJc4T}Y3NgDJcj@Y{()VMWmDfq~K+}OfrvwTO8YFR) zPbk>5btt&BK``+z(#RB|G)8Jdhg#x6Kl!GA#-}=}B^sIZRR^_Z73zr0ps7{yQnRT6 zm{imnziX(S7t-^VQ+XB2E(X!(^8M*zAFXE3)W(HUgbN4R1Z?;qR?07tFgjntU)VS~ z2_6lM-G234_^Rc5eF?X4Pae;cyR$D>#>W0VX74#2Hy&RKWtz0g^snZmvB$eQ(W#xR z&V;{!OS2db$6ZKMevM;$jYIf*ad(LF_rh+VX?&rri@2Klp47s=)cl_G;(i|nR(|H` zW#(yZCbKHNx~$SidP0V>kFdDmxT;C$*D}|Ya%S$8JZmkiYd?8jwe0_l|C!6;W)P8m z$xJ3uNA9u>F;u7ZS)lQerFHs=)hd?)w@qpAQ)zo1S>HBEY|$3y=OPUsHUIoTFlhp* z&9>NL{Hd*}xOPb-^fKl0f>dzL+@OAWT+1u)NY^%!x+0+!##$`BfKw@BGU7#b%0v4$ zpXx^&RY$o{!x1_?EJN3k2XWC{X+7?zzS0@<40$+pJUZ?)8WDOW8wMo@1|=KTSVvZ6 zE5=Ai#%No{Xz566?BK$L!b}vg1X(O4DpfvpozEilxD3VoqPKzc#nQM7>XB*_ThKesC}5cZHmfT$0Ei=Uo(Aa!uN41%+fi0*K40*W?n3nFsH-x@)$yCAMW4G$@Sf*bwW#;j_libr5HnFOpJvEi+BQ{6GLEP@`BjM=Yx# z-mLvEV0PR_`?d)-gG`1IM%e{KCd)}2FLPI3GU)) zmg+|6e{!x9E|-1#0EL8s0X+Poolf=lGRR-iPi z=A6mr?o^8UL$T_ofCxHmKSiOq#lR{QH9q{=w$N!4X!HDA%<)ftR{fEp9675=+f zA?=rl&D>>^Edei&?p6&!@CpQ1H+!4~4GyAq1sMBKDe^*gYzDA5(kcc+$Nq293JM*i zKSh0>?;LwzecyWVnD%>-@{54(=c`BS>(4)Dx-(?=60J%cr(>t4FZ{~d51;BM*#ECaP$fDCbv zs`C8b+U7J+Mc#WN@!Hv80*D}}dI?JKtKZeUw!h_mZ@gk|d;9Fy^VJ9egQG;+Xc{do zW5UTWgbpm~u+ksocoFDXnSM5=^&xOA2^b>Qa4R?_?am;=bP#5(a{jRtak@iBhUzHd z%frsl(^Erdrw29g;qb5%d~h*{(CYoy-}OM*EW?4qz@QqU8RiQ`eJbDoo{MqUdVbIX zTel7}CL&^?>b2huRw3@alS+4hBuf<$N^u3Awei0N1Q|B`dV2~%F4C&<#DPN)8|gwN zFlO|bC$joK;T;1DjTxeN-mmdrblnR?L%JRu z90N_}3VGi~y*~{3W)D;m0*Aw`hXLKDro^DeUR2BR%v$fiBoDNVT zY1Z#aj%J8P4p0RK;8+10Qx1IF+cP1D1pe%q5eH?e-p;89s3SuQA$J?h9stIi6o6)^ z?4s(bqpuSZ=AzsGRM@Ssi`KzD$z+n7MvgXse-6Uhip8=^GwrM@6k;w$;;A|hUh2zH zk><;bZ1)p*dO9M^{gOOZ?lgpBf<6>a20wuB;7DxZ&px`P+Sp$}_(M_+y>v_a{`>Md zYQq}SJYLnBiB`9p^wz?y1Jp=t$4wWyCSsmLY}YVkQ!*&CxFuzzy+23f3(!@tPqP90 zJ`T#kO0aeC6@x zNbU)ruqchiTIiG}16zj|@+YFd20!c%UYG$3%8xI0@V#l$;%Qi+XKG@0@NfIuP&a)6 zsQHCrHxlot@V_jjTWTj{e~YqS3#>}xd6KhU^Xu;WkcNed7O^$b>il=~$MXlaXEBtI z9+t`4GAKA;4y}#-5VzxAPVc)Lrr_E519)!-_=gx5X;K%}3KNHqH)(BZrGYDzwkc7v zY?TTXl!C_00+t}?z_2{3sWPTb9aU7us3xMmJfc38ai;#`uA)--mqkqy?^2N$X1)UM z+!c3EiVVtFcFB*a#hhJ5LQQAQm(WTk3dLABmS(DsR6vV4KKA!ao#~?+q1q5i z!*T6k><4Bh7e|(gGud{yp!JX@sf%<7LWswntq$#Je`Ub9ZX2@GF_J5TDbJqH@wq>D z_t2^gGJO@7Y8^ZEHk!b6Lm|ROK^^YnLsHY!)Rd7Sn$1P{i;0Ynm!^VA!Fs^B=|HS5YShjzd) zXZe&z=&;FCRHBpCgkG|&2W4Vn!oe0NKWx5G<-T>UjcjO17>m{L^f#h2LBYhSn56AR zI%O(zJYh8630w+K&j!i}-+*PHCJ}bAnzg+>i}x0+=8?ioz-R=x-z!fAbDJGCNK0B8 z9yX%C;C$rA#Rt>LOBk5UVF?gwu2BH{8=sS;21RV<20;TFw$pk>$7!JV%f z{Nl}$lnsyJ0n66FxYlb*HR6?Q%Og-36?abyv@x$(B zm_Wv1@D+@~#_b6oyn=-`RomFuq%8|VDEv(yEY;lIx$7|_zEcNljIQLzQy#{9w%Uay zNSoE4ueJpnu2WznsPa?na+u9$Qgk#lLPQk0gbd3RMZSG`phCwE=0P*L zCm9lCRp2!MVMbTs;NBgBX_|K|dvV3#a*!O`B6vR*hZ;-&F0_p6_ z$`+-*_S8&{`4!@9?~=k&L90O0IOK>BTw9VlLdiPZY_XtbolGh0n?FvbKP`U$L9iDx z?31F-iKp)V;Lj{+0vnOo?YGCnq-?Ln|zubK)GmhO@QJe?GDMf&MPRpz??t zIHjClF+)_EB_R{bbZz5Bl8mVt(wMJZW6#LMq&8|(c8b@k^Tm$)XMMA{rV?%NuPz1l zz$HFDS%jMkn)-k3q^Ph){qPwKK2DZ=oaK!ryj|`}#k0+-s$#|stcu;eJ2r3Li8E&o z!3nka?8)IDV54%{w~;j0_C_ARziqIdx_haYdq7D@da3!c&I#)RV-|)|0xe!p&qj%P z_-HQvQ-6g5-K|N8tR|5Vb2yfCQNjhj%hz5qjkm0VfzF8$r9>@ONLrf$BS}kbWp{6{ zAT?f@;-HlQ;LDLpZw<3|Pm)$?ud4Z3EE*YwRK^468naa3+L-5u+Ut*IVdb2YcW7(Y z+`Y*<55f9X^xFHce}jljmrMdV`afuX@Cx;{FcLG5uKKr5NFU{X5nLVxX$Od2oBecs6t7vRVH0E(DGt(0xRwGcaj8~m{9V9d{;ZXK_BP!V2>7!)dsS;ex z?Wy}GONxv0kwWP;5E`Cvdv6oIsr*~+^C)0C8fT__lq>9Lkae0KfJ9YVY%=r>FDk{{ zE0}M};yBHU6UoXACgFB^*Oc}PWCowWA*OP!=1+sb&q3_&kaqS-2?>G3MbN(p2Okdb zpA5h3Q;n^ewxkwXOTPZMuDdp(8A2fLDBCnWreg!r+>hhm@{kN7Jv4pfcMz$Y>tB#Y zCdOwP`GcUz4H3Dnk!MJ2KJTRSZT)xiBMS@KH~JZ56jyya%FYIgb^WQ70=x16z+eC4 z)t=4mzc~1FOiz=s(W#=b$uicMH-~5vq&EMn!3>4S8IcYd&6}1){+mxZoRxxD55aQZU{nb6 z9Vd+gCc^1wAO!>v4r>|~1~$4n^M?tz9A($nu;w>$tM`ZMtd1WX9I)w?UYz>I<~RWh z;1p$I(^j}yv=W=p+DWr|!?v%KWoaTPDfd-GU)_{rg?I9x9-|K`E9}{|=79V}7|utE z&g2oqn^Cz7Y!uGH?8fAoluGM><||K9EX=S)|MDs7kFUtn2x_ zBPPc0l?a;`3HC09s89zIE{0(M(Y5x^!%?!C^Wg+?I2fP6O&29Jw??e+8!uc~QLNH4 zW6hNBQrOpjY}vkt<~Z1+(X)C4vGOo5$Z2b@%((b#;{B3kkScgXb%ui>!DUpHu{;K6 zBnb)MYiRZ|+;DU0TY;JjY9`?QYFplk(s&PhFOHA1)0n@QI{HIRIwugJfAt8<5|5>2J53=W3yJ)alvR zTT9th3c%8LkZei?Octf)+d8yu#Y?W!-c>s|*jU)Q}}_mwWi=sEi-`QVXen?tMZ zLF3+!2YI57HrFPOoU^l8JEy)uIXi`_pDEE%;3Wr?KIEif9mdq`lMs7`|Mf7WX@}@5 zDmY2^1;N%Xa@@X1;^IR`T zI*vb0AOtUzC_Qh3Y`<31GjpByrUyyk)t2^#9r!I|7!OLdc183wh+e-854HX(|kIBc%d-l%{(^fTRmXOAF| zT{)jIF@dNsy~C<7gCshb0usRH1f~=_`~DZPpDS~Vf{Hm$GvEw%rgYz@TLC@>n82Qm z_kj>(v)kLoE35nx?up+PI9_e?GL}^4j6PIc4eO=KIqlv}BDsa)^o?9J(zQ)ZUw++8 zD0;^Jdb<_&(UL5o!95RL1cAsN4vTo#eN`3<5|@YC8g(QT0J7)2hjQgzBz=MSCD2=BBgxtY+54!CbSnlR4e)AOPu!;)2Ohx zzgVsPQtj>YMcUa~2XRv2{}5|mK{NP(<~{I`EtKdj`NJ(SP4ti;!76H zpm8Bc)jy!5Rv0^+W%;ai>QyRo2>C%fJlqZ@v#%6oV88VT6H>GJkg&v%FoUvB$b?Ub zvd^aAp}?;g)Q1olM%OFvX}~aY{>04ELf`N+zqlp?ufk5(fSQonfP1kFG=%ON;(cEx zfN)(v0~^YB|1%Vcn(&|+3Y3+ifF;bJGgFhz-+fT7#J%`V1w9v?hYS2+eV>AsV%@QN zKkkz=?2@zWk~0MPJCX*M?G$^naZ*x67FTgUOKIs?kFIJ7oU# z8u72!b&g;#GS_xBv$8tztQzT>7Vec8?m{;COftc0GQr$8OcL@+{-`)iJCXc-MMvV^ z)Yv%#NL67Nd5+o5o5IaT&fP}N*+Kc&0<)79_7Zg+Wbo3Zs5OaN;c#xph?an@R-)HG z2H}u6$-$Yod3H&kLj#AiG5tM_$RMec?(iCLqy<}oRYrZK&c?YGDPazxkAmDLowdgV zu2}(xQD|b1207LcIA*KaLq6b_)$9EK*XhoX4f#Kpbh9x$kV4k^V+N-meFRDZG>OMK)*zK>3Q#*90*A8kc+Y>Ao968i z!A^lvpB0Khs$*^^hr_s0uL|-of`YS<&d1zrZ5!NNo+g$aKDgfaqcg+l>FH%B%|e^) zXzuT4Q5frGwIt1IneE(z$CVIh|If5Etdj*wSMvNz9oE_%wg=L*s&pEC+un-h*j}Gb zy19(PHHX~HKH)XrpYhBpDcD}OI&+QM?~QgNBIu3;FFgeu?H@e`Lr$^c;*G+hmZSh6 zOLfG~U<@&E-;qiC7#@M_{5ukl%A^^jwwaX3DlL;27?7LWar@UuNnU{X!#879&b5KS zXoz#R%((sA{n`PguXptvkAt17!Cc z8n)uz&DJ?>a65WYEBXZaok(XL2NFM}=RD&#^1R?rpDvWr*}cb7xt$pAFW&D$3BnX} zg|zgZ_wGE%o3m6@hUj^LNEY^8mglP(iq-=Yn8kv$7aQ-~k?f!JeB^yPNZiyawOBT< znjt94#JmyC+q>>zV5X!Y41@fa$65X0CnvRJozqsgBN8OKdk?dsB2I^*;@z(hD@>70 zH=hqrwQXTOM5T+`q8{fT*XM81{o_ap*CPlxMW zHOzvpS8Snrn|XaC2V?e?VJobcIX6K!|Shrv-y`NF9<*CQ;ud?Yo_RVcr?dOL}vhhczNemR|Ne) z{?AafqM?0YYt_26PWJW1Pe9=F4Ezhvi*BZdra_OwgMa{z9SJ$h<1K(wE#`3+p`$SmS&Doj1dXK#V9%1W(?wfkRd-j?@Y58F2q zTV%3PKf4z;8m-|6PEQ-Dpe-TJ8AYYV9Dydk>B27s1*OLiAJEVa+}(fZ>iR|Lh29_1 zt+ZK9Qvn;ur-<~-9WheU^9r_LfWV}70X<1c1D|Jd3W~&`xoPJw{KN{q!Q`iBO2es; zU$?x|raM$YP$UtMo^vfb2P2MHZ}^Km!vGnMrO{CvpidG}dhe)> zXzmUVSBQAot(#!NaPQn4Veg!S>FvnT?8a6xA_gD-D~#o5l?zSFgKCtf7*$zUG!wfF zk#94Rc&Jg4`DAF;_J8FR1k+MIKC+yd>tGAO0j!mO}vhNL&%6TFtzax30B3UIG7qq@NHd0$#KkK-c1DUt=;-y|Q7JSFX!}me%6j=m;-Dp}ZpvS_zP>^}*_W%32$JA4zHKnIEn-J`yQNo76c#JYR;5vLuWGqTSSG_6_Vp=qc(h z!5uC4aWrHOWB9?`w)>SFuxNaeXCVb~{@VlH1u)h&?I?SQD}6OBuEUsCDLevZQxkiB ze$Q%xM$(zq=8fs&o}OD{Y;ovFOI7iDC@k;SV{D66fhq(byO*sR1EV@5oKu? z5N6HB?)W8vaXTIyME#Pd(%@XPv2{fI@bt1b)=kf*6+}cu72AK{XxqWzylo{a`Z>`F zKB(f8j7-!dZ@=qmaj^nY>AnQ#8d4QW&ebp=Yg6O+sB3%o7PkG9?O3iV-$l3~{M9Rj z;7NuFo$NG7uIT`bQj%uBGA2d;Zm$!nFD|}0Oc*;xxNmLEnPLzt3G{!30B+;sszHDs z;nMFN@fD#0W687C(i+{V)d|boKrR0ImJV9l9I>-TRR>B%{#bG&LEi^dlx%{~)b76v zdL+DkFDy$-55bUto1YMwhs`toTjyTtyJosWBN5f!cz)2)-N{d*zBwMAJFeW{S{nK{ z_djmls#_W-^WMo4XOm<+L)m_5I|C!C+i`p{IPft$HF55}^ z+Fzq@42}E|CIIPWXX~#C*YZf5{kanpSu$~NCv6A0)m5Fv4s{ZgFd62|3up;G4)cpX z%`G<&%GmC8bG#2i+EX|3m$PnRjSNEAK{NJ8#$FoFj9qz=>+-w}b<^T`&#sg9`m&Xv-}ZW-s}Sz^MRWVa^}+xJ4(DpnZ=&D=U*( z_oIi?{c3^)688xy?k?*>EZ3wG9?Cfa2&HS6!$-45HV%*N?k7&D1*j`cC$9LtgMOGP z(;D@6PGe0?P45u$U;eLZ_IoQc!{O|V%*$*4{@j2%!$GyarP+`!!vTzT!dXf(YcgzQ z#F$_|YGd1=A=cndKwe5JF77MR-L@P9(D`gOziL;$MVpfHqf)`1h=_eF6cFn|J5g~zq{iD#JR5@w z0Z8pu2%-zf8qS3Om=wBcDT#fUQ+Z?`pAIsC6K21>4iGog8MFz5?f?w;bq)*xV~%D7 zqVf%oQ@F!GXxL^xF9f+_{?BK#vWX-_o?C+;Fcun->3`6qiEI}A?ra<*e<{fi@ewzi zwZzN<_K3FCP}qe83_N7O@}}nA@#00d!ExWZ@>&M~0ajsNY++c!vY>f)La zC$*tN`6Y3Z01^72@wdfLAsj^q*IbRzSebgscXC=C+7yW6MZO1Rsox`F+8#(`u8dSV zAf6Z$5bOJ%nyrUsy?&p#W((g*oY}X4hq=PdO~KRghp(f(wX&(sqv_HWoRVo3&xonF z1dr}C2!ASAs`sYkqLcqlqe~k8vbmV*(?Gj&AdcV7fAh&TtVQF)=B!O)#c@8u(HYI2pskk=(NW3k?`Kk?Osx!=-heM)_N%iYR?GM)yoElyYlG zphK4}0*8Ir>TN*xYjt%MGsan#k|_}*kv`to5rcVvG>X%QWslVlAHICONL^Sk)q!p+ zxc^{k`z(QQ5g=KDwbGm@*fOKX)6{}GHimk>KAt2iAMr6|bVu1GW3U+b!tK%kY-5@H zv$7+v;L6CzNt-B95_5f%5p#X@=6||=t0uqlHi8NHDXmFpsCA1YDs7@~q2}Yj!muYD zmSVN z%g6ojkwu=10!@e4qK0xzY((ZZsiUG*XZO z^)kvpPo|pka`B(`pc(DJz(U0<2Ge?@j6jL|*>FGgp%C=|S$fBu$ygqSV|IG0yvq&% zQ(f3ns8L{GpMzIk^bGH(9g(d)#YRRi)>Ig&AdCDE`ZFBXm2kK-9EgO~5xiadY#>v) zeDPF()XeNQ5tSia|70M>E@`n@?qgz^WePU&J!Aof?}6FAkaRiysT8tT%R!p-*>Kw-~eAO^ggm^3>QuEKnpK2xm zTyh5ym?C>c6gG(Hx(j{nO7Kc%fInUCesso$EDxgPU_!-A&6l)8*@0w&v0-e=3Y*0P z`USk~YDQzTOGx%BOnJs^AUe)!%7JMDdnL>xjBw0RUh+M19Q7S?H#NoqvqiQX{u9hc z+35Z1KCI2!bSId$BNqYagr1elO#_1D7L)^^Ai3c0fq{Gid=AoluE*CJ*c3=m5Bro# ztzU&G(kXv?{d^cq$i!?wbnbG6*9C<^RrHV(Uk#EZ501W}tIgozP~`chG@cz_5{NHaK2vU%+z0BwEpOD_&HW zBu0pY=x9s(R9)W?DR-Z=|6Yj5G_&lR$ zYB!q97T;d-yAk1lw@)mqOOQ&`5}?8I^MKU<*Vb7^wef}BzPJU~AjMq@1cw5}`o|rL z777F@?#12RU0U3WYj6+lE=3CzEtFz+`o8OaxNF@{Bw<$O%*n_(`}ysC^bnQ};pW_^ z8_>#K^05U0bjJ_05ywknphy8LxuENy;9g#m+5#w-SY5E1deZuuZoD#hX+SJ@+} z9{vt+DaK8LMhE*)_+D#2&dhll9eQvN@L424>tO3k87&aHxkdha zt)AnGfQkB5R0JG{aaE;gZO*QS=s72abd4L{mONna(ZmoBKHAMZD}PFDE`=@bchf6MJ}c2ei~TPHd_RRz zoYqMa|9MAm>>W$TRDwzMMI6CaMcd6y2`CgU9}pA^+vl;FxKy?OPf1QY_CIkg0jFx_ zKps^h1>c2#)xF?~ERK7(DI&xavYH;rM6{Rf!mY$*^)V3!2IRn5ael$A$*OvZ#oKuh zPpo0HzXrnEYG+PPGZ{tqXM6kE#e?m=#z2dO-_BS-txI&G@e=^OrA zJ13KE>kghR`4%joisHlHt22Jd(U}9Cf zvAyZX4BES}d5lJ|Pa!^lCMvkBi-o9*awZmqg?x~Bx|tIe7Q_CZeyW?GbB{C7O`l%P zV(XUq2C~IjP+Oz1bs>QmrNwcg+q$(DGrA7P`P*14ADs@U2@D41z3UuZ5RfXw1Jh)G zIE%C}DHK%SnmVEDB0FiA5?^aR%fDJzJZ2z(_x!N4w{B}T8)nwmH{&^ePsO4vzCfpq z${?Agvax=k=GGHSA+g?kMo6g^sJ)aLFrGHy;=Z{=&k58jBZ{EYvtLuvtrEa0k~Mt% zgE$noLDA8y=5U~bczqa}6Kp=rVH} z3bi!x`z}-z7Zxsh#u;erT3G#A1Pp1=sYR(y6p`btFP19KSF$;GfIW(yUUjE$Xn5H5 z@>A~2H`+h?DNTDes)%1pXZ{d;KB;y&oTMRPw^-<88(8ys?GO!v!4hY}@!oT|zdz#h zjAo3D6%#uojGp2$s*nzX&j-$a7+Aa5AxkOIR(C?mjM_K$`UH`LelV!b7WIf46H}Ax z<~x zkpVaKc#onbd8(ihsb<@dmoA}1mO`SS>weo$cGxjvj)t=mcdY2zT+^7akvPmr1wj(ohDWT~TF zu$pATZVN+Fc5Rn{FvqFY=gOA+eCjKLQNK6@NJbWUzR2&Vud=I!h20Gds_vDJ89n@b z%f~-e8XEa(Dn#OGo5^xfwUxDr#b`;IUc%c&1f}~!Z*Q|NbEc-%mf)ub1m-0;DMNId zfRy^(dhSqLTToQApNJ{Z1XXkHjY{s{!>_UsDe0C9IFiM{TBCJF;+?3-6m3|T7ZiGO z@{Qct)xa`)f?wcIQXf;{-504DNgn6$<=JVIKD6xd8<%L=rEL)|$!eeFDt9DhZQL z7+zWS>i8LBZB4&z^jCKp8>OwS(LQ)(DY%l8?*>zlBdR@A%X}R#DS6@J`GR}kvrR3d zx>oy&cP_@$pvh*T#^@y_UuL&st|l04l9MU^R7}k78Wk1yi32^-Q*%XyJ2-WLgtD%1 z2i4|KIPoZ`{5Tz5>~MYB@g2Ce1mO~D7OV=Ru%{Kd&)+8DWnia3`4s{kpzty0t9yTZ zY)!BK{gmEg{~-F=9C&{Cl>4lLl9@H(Pj>Z;@71k1GVGK6=G995{v7}3&9EXsku-O` z;W1pM`8#U7K7kcl>grn3`FQmKP>Z#WcfCkme%sT^;VjSo9*C?paST5P$+=tnPw(&| zHNFVV`DGnL&5;KKqVuKFqg>;MYCusq*rNc-P~JhAnMAU*>{Pu-wlDR+`TwJnyM9 zchG&%oSY8vEKbQMAL7OJTb=0P3u3(>e0&q`_-294fyi%=9Uw1nsh(^*awSdWs7$px(btFAv`*N9m-~q8B$;Mjti&)I;MsqOrw@44 zoL8A79NY_DTSvphd4fq#kl9|C_&z;)Dm)8>u*f)}Kpso#G$P@3xNmftTec+YnpN?u zVn2SwwG8w>&zl31W+Y7eGGaiaKExnwrs+`>AkaW5TVN6n|3K$7HSrd0?%l_a5$Csy z2DP~oFZp4p6lOoV>Hk|NK^Y*+D1>dsW~9#E`O!jbf1Wmqd$~@yOug0A`%-ao#CIe6 zx4Grv+pYiXI!M{Ay0$y$)Up`3VSq0ILuRTJmQxc@J|=-ruUIcsyVF5VhY{A+y6`;h zwOt@exw$7NQ;9wz&AD0l4hb4_C9#5#HI~Hnv56*0W`AaTTI*N0hv9UaKqV(s+;)1k zNzwiMIqGpFUXK5W9;m3g3TO*A zB|FWK-{a-``B(l2L$9sxd#C425bx|-rU6jfwWo>$tK=zJr#3HMS3)ZY4xEDtV3?K_ zt+7fVvxF4z)85u2G}Ob%F4kq5Ov)RSfR?5t&L z=x};#%e5~=-OCF?jrMz`1==c`WeO*YYQ?4vY4aM?1&A%Kfw4t98=F(WYN)rWt)W5i zbJh{Bv(u5vs8M75eXs&~JKa8E2)-GM$Nj*T(hlqD0#}{|e}a%+PZl}F{IT%@h<7f) z(;H60)}mIiJ7hhDEnc8M zftKi+l=+WyyYp-Zx4eJqKx(Cga?L=}wOKAb*Y-BXatrjL7b+Qnsyno6r0f-2I*Mu( z<>3>;C;;d&*Z?6@kSn==35GZpU~`#Jqn(NZ>=Y2RhKS<~@%-Oc&d`xt6JVo_e>HVV zIMmS+c;gIg;tYMY9|Z2Cr}y3jO-#5ON^+UB?EQMZ_3t}hwI~1V*zK~hC$$0u1N{EA z=Z-Y$&n@)h)h!c_NVr)48J}hT!bU6=-ARYsTkhug<2H%n`B+d_j~8_P=bhlLXwe|L z?4?_`zi2X^ug$hMs{?;T4Apxg`1+h_L`3SyjT~YXx#TzgTWC2tJK~?Va#7;kDZ;z) zLe#D$`*HNHD`D4(cXQt=W^RY<{WNV+Fb@umW_e8@&cO0n)c1`-4U=C&9l4jf9=&La zbz3Mw9~a)XvF{?{Bn99xOjvxRePP)&uGhWQpKwnWrd^62yF7(oc_#eydb6XiSC%?* zE=BU96n1wEI@i?rlAAJmjm+8753YGwz_1Y~JFeeQa#>g2*|Kdx%Ith1&wEh=G%k5b zbVis{(R#O_;Oj|eV6Kq;F6nG0t@i=4(#G}6MteS0)KsTM`ppJTQwkfhd7$g1`^Dhe zyF-Tus--H?MwOmYwmS*E*N%vOen*O}0fv(3Zh}XyzJFE@mZPcUyBZkZeg-fHhWJUP zyJpuVXyJAKXdHIr(l6#^dZR9C+4BP=xo*wn?5V&tcE?=R<&zr4LQN$?3XHA=sa8lobag{K zWg54`xnLn!BPbz4+)O56+O?U1Zb65+9M4h2H?uz<@8@?1c{%kNl3}o%n;UceR<#lo zEb}0%s)LE-%dg~JG+M@^2gzfp;>i+R>t112h}%*LjDzRzh&wOveem9=Ah zE+sj}Ut~+b5c4c?BLFwdEKyFLn=j`D;&7BSOGfy<)aNiMCt2^xWCD(UBE?F7iW&D$ zE|fxnDD6O)NCaYJ+tDqvY*YDgJ8OUsH@qS#uw(Rc1Mn3st>|%5m#Rk^NqyQF`|vYf z0af82yFF~D?J2ZheI1?gv`<~H>iPaX0_H-&fTVhxrgPSTZ{Of<(Cw|#0a8T~0&7pz zF8?`UUUKrrB9np)cN+EbE}j=^<{Up>Ql-xOmWwkJCU2PA1WeN&PCLp z{FZQK>ny%beUJ(${i`d<-QQK$5UWnAzw3n^mAB6Zk!}{{L#!azcI|(tI12NmWYG*D zPx5I2qWsKho9SPqecw+Ke7_%&L|gfDl} zaxEq)ct0SADEH{4SW7Y-CU0;Xaw4GsA`$#g1#&K7DxhQ0+;;p@_?1>p>v9_%U=jwH zFt)Houp!nNVmFhs73@Ed%fSV4x?d@t-z^vDW1KL(=hG^)!`r^dBUiEuC9g6_Lq)~> zny*#q93+COkTvAn_hTB6rWDMizKb0un->gt04>reF)bo=-UF0J61}~xi zIp6`lst^;2n3lo<@R1*kEpMY-Vp-+W*L6Mb7!9Tb-&(olyQPY_`&@Flf zF8K}+eDJE-G!WAhdJb6bvy4%YP)xJ2_As2P2$7MI7(+1j2T#5HmG|YOSr$T&-$|M3 zRJ3ZZNZ*~2<015d{>XKW^%!>uvR;dba;UA?Pe%y5u>1q5#Wv~!J`k&8+YbFxoOH*g zoQP3F3=x-euGCO5|AEvyIc(`x?PLEMKuQ;!SowC$#qczLJDgVyuUR;L=a9|uyQ3vo z=Jbtw^=E%h8WSBv4fF&k`gd(`$JXzKZn+*ciKz%8Gi+N*d)J z==w0dt&6aF9J{0V=$LrwsJAkR=pRHqJYRy>bI{7gtrn!FO|RjMC@LR8&-S2}3L-** zQLHkHMSoaOHdTvW{%>P5FRK@g4Xz+f-2CS`8s*dla#4Wa0979t&@z=gLahVeU81iU zmiR@MQ1n1KfG_7Q9Dom`C~egsW`9@H{?;?7;7wL{A2!<@^b0;ejn4xo%#=4reu3xZ z^~dzr)}|l-`CDznbd3vBYK$YFA0dF7&W(^6*7#}g>;Cv}W@WGArH?Pg_fj0c4)B}x zl;BR+uffYPp(I=`z{V~oI)w9igX8HZg||NAMGm@w6IMU=HUK3@d;1f9ig>#Nz|y!; zWS@V?V`k^9{{Cb5#SWf9TB-!@vYfJ^TlkuxI6ecv4Z%zv;o3$S zkKbcJ{EC$76g4-(%=-HJ+;s%(`9xmceP)K_?N#DK9|+9VD*0(5;8i%LB468<@7;Y8 z&cRNZPs9ZE#7@-8 znOf0mkjOnZtZ=10E-E}tUVZ&XkJCR2il2QfOH11WFe7B#5U3kMIS}v>?brXTG~2!W z)RDxF1;$O>{fMc@W852iX;x!nLEG7Jk>52@1-h>%*Z20uTkCkZr=w%U;>WYKE4h;$ zc7O<35Y>H!_W6~kB{pbNfcG!@tS-l`e!J^tnL7PqjnU>ZdL%`sO)4yY>K*+zVnU{v z&3X&$8oS%`7ZQZ)A)C+LF#%s(1@rUrjCDu?R$Y|+;uNRw6chjf;P?CZ*W%u{<6@fv z?)ewml_Z{?wjaB^Xdjo-Kf*SaJ&D6kwJSG4*c)+@2KW*75yvt94z?kaa03olt9j(@ z@^|{hYJ-weL8D;s#Uv07gzeSe;%K|0kryA>o|7X}_!^Jn-V|3ssq(vOPoNzs|jG5UF07Re^buVhZZH&ScL_hk`;D5QR z=hzG+>;@XJki4k)8C%4e0pn)~groHngr+2Woj_SFfsx_k=g%BJ6~B%K`91!bnVBJV zG=;bvm`ikP!s0l#lrnuLzFaNUNFjg5`kwCsEyGOApBE*?YTA0P?Wr9TX}vhr+w z-A{HMW|pyD^$lYEYn^p-QvwC$Nen0)FD|~M9YR7R74dRs#;4V$HvO>vSnexq00Frd z&%Or!FV$-3JLzS7(w3zQnUIgYx(0@iqONX0`tZU+p4!yFu)(&}c#p~E-`}Xef4lGO zc=r-4E}js*3M9)di&Ka&i1;J_|I$7T_wSJC@Lebii+a2J)}C$%eKZ_guRL~0$jQ05 zA&~OQ27bPI*=ZSr3Cj1LlmuCfG=Km8^?Uk#rJM~btGN1k8uF%(6oD1DxmkpiG=G>= z>rdwxM91ee}J4)rW7m3jF2exYo1SJPKudTS?|UATw3eiL4~4pZE4!7s{|l| znZ-Yk;C1z@!HVC)!c0PrI_L<5GqRqC2lkN2R11A1eE*6Xt_2|EKPZeh2KU2+yW7?u>OM9yQe64_g0b! zuM+UIH;9%eehRB-|tn%M1BXEGFHaObQn^ zsuU2>0`3S4#y0PW_j7f0)+-86-mwBH4f;LeFPy{bAX-40GA$T`;)W;Uk#3n-FIE zi2mUhkfHptm)v4v-s*YzvJFB;&62wBU4#gwC@>U?u+j2Lzv@>Fg5~_fdI0bbwOmKt zv=IJYO91I%+?a0&QoP?aebh5~TU%&Q?{hZnwJcIz+NTzZO4U2;-)dMQVz9;Ib!I z*?IaWwe5k+dr_xZdWh{1e?ZdVCd=qPa(BaZ`_zGVp&6bxp>(*AXmIGG_#cy|1Np>{~Nfx*Nq=1ve993wocLXF_5Q>d`8xh~COvM}}!;q9u)mWW0*InA!yW_{@4>`8L2sYukr(Tds6=9hYsntNM6Vd4X zfJ-o-$r6+Rf#9W=3Kp`V!QB$McMe^}!1MSH8(R$EZt>$CC5&54y}in8XwaoQi{A`1 zwmk5%$Yi;rr1ITliHIcMKjzQWcjdywQ{+#yzdmQcTYm%aP{A!_fAUBarlS9g-Oq?% zAzI+tmiFvyz6B8p{Q5;94{7|z`OK_#-ZvaTU)vBcgpBWvelv|*UW9%DybXWk_~GE? zk2_~3a%4nADXA91pBKl6xJmVW-JskOmaZ>D1 zBqaG8GmmKUXx4ub=@C3JV{t-sQTWI*3DIBa1hLVj=nK}1@b_^hWcS!w(1RqUJ5(hs zy5_x$zi0juC>L=P8nb>jI$!%;>s7J&^Kj{BY4cS7rr;uI&sVp`P?-BwznX$7uAxKD z4B1AN}U0mP=3Z?7lBaZn(}_UY6)>$`Sf*jGtR3$H>4L@|_Ycc^~oS&qM8; z&{EH);O%D8oiC7Gf1yxX`{6sfMD5Y+Y?q44lu`uHE&biJ7fX+_0%XcixTnuzRel)) z{;0u-grRr#PP<7M;azmRG`-*Bj|Vu(gzRZA21Ax0xfvTfuA4`AgHOxzGYc8UKv`un z;uiK4Q%=8-#-FIUU`fMl^0F|7fjoTG*LLKw%~`%E zc&A6`wEyYRp-&K!Af+u049L(euYkGQ%+UpC^nnD44pb5KNqlny7W^E%u|#5EYicqf zcHqsh(>jT}``-!#{9(JIT(}zLZ4bs0{V&oi3Lc9w-vcr)USs1Jk$Fc-7{z8u16()f zD~CU*+;5ASg9S`wx8C>rB;EIBrv7YAR}!LAt#6gB=r@mYW*XMl^ROf}k;Vd|gZ6~H zB4ybSm*bsPO*s#1VU6K(p{K?HS$3J|XJwNIuA_2R)ocUBl3)Uem2`AONN5ry?!fJ`+EqHOF%@eimYut((@ndq75L1_cGf?H_>gHfPB zgl&c)#2ji&PKCW2WauabWIK;PL(5qH0}}sQE7-g=xU3zy83gY>S;+31Z?JOxk;Z%c z)lui6%P;bVt(~%dB@fC?jQXW@!}FAf(DL-s28ao%TsZWZ;Havf%M*ZSGYBAQnfXb6 zO#A3k?{N>Fdi?Pbit1Y^dUY?Yi`X@e`HWc7IZm5hA0*i8&`Lu}sf$;HN#CqJ_^Z=* z1`7Ud4kcqne?>2aWkc9$k~I6?%Mg1Ds>~U4G5y|?NY?A2@dkJN0zaRYq?r4s#Anfv0DpyE>2Scrx2OGu-?b$@^R>gujR z%MFues*tt|%3P6Dmcj+*gwvZTzk3?e?F&a|?S+M~FvH|>P;pH(VQ}f3gv9%96};-) zDOX4CqB`#4s?*NVly524>(4<=O*5J$WUS)zeh?%$#t|}h`;G^1UMi8zNZ$sKYjg2C z_!D^o`Njo_GBX@eO$J0%l;5bCGe!l*F4JSxyux-$a+J@zK(%$DrXQ4IU?{(quLu!y z;!;-E@6F1xNiN4(K-QtdBB>x?UWQxNhq5zZbo*2P)hi#>inOC=sANm^Svs_aoe-{C zoE&H~=U~WS?hLHp-;u?*uW8V}GK!BjVITB1R&_BKPNTW$H<0(% z*L+?C&Z=%7^aC{S%tGm~^3*!}HE^DGGNsMP)kebcqBIrbF^r?5DA2SZC0hCo{wtJY zW-4A*e$+8+%q0Az5cUHM2h#&&;gV*@|d8JG*}Zk_jr>nNd%`&1MiwJg5~oJQ*a)9W2w7Wfl<#bdTcLa%)8i zSEAC#`)fM+ol{nQ4g0b~bk^tB{_+nDzN|uR!ew}k?}_yx1wFaKCyE#w5iOzoq!E~5 zWFTFv9e0JbI-&rU2stRlHdSm#&_cz;S~3chqQ!`+z&k-`pCJ4$VT_cS8lQ{>aadBp zy}w3>0m<69Q2ttev*tt7d?lOjQXxtq_0Kq;Jc{<>#~4e4irh)2LU0yvjI!dEoVl26 zMAj2hw!zB>0+)SS_)u*HBwE>5V;^jT)h9SHNo)w-D6tPDz6FmrfmuWb1ePLm)foHv z(R&U+jHpp8&Al;M+Pg<(T1uUaiAcwEJb$$RZeO2#hfy@6^`VyvmjbcCH+p;c4)# zQTE%2Wa6(7jlc5P4T+4jn+G$$0NRP+6%jrX3dANYvhBl?72>t)m{9hs?qGtsJsRbD zHf5T~9++y-G_EG>9*z%p)WleZ%iX5BeyQlx8yymeu!AugXDp64Tk!=q?_-=$nD>|U z-SfnKb)BzuY)|4?B5~lKM=B8v^|fFyt901v;EcKe%hKloU~l$nee?B5Z6Se40HJB3 zWftZV7c)-=I>yFspP3<8X?RWNtRB3`F(5#;kivU~-L_uxYi;`G^c;hiL2ru|Ec`>^h*X(ERwn=RcIw*Z#-H9Q$eVH@{)_gSCi)kb}TU?7b}dnnZM!N7ks&MZvfk|g#E z&C7PMM?ztm{A-igxP!)KzsmpHL?{Wy6=#;=BVp*on8mms3Gwe@hGteyPT8LIzR5{L z-j>(LPt8{7<+q))&|;S6I7*tb7y$#Sjg>P+FLmc8-|MO>z(hfEJ{`G+gdsF8VO#xON9L2x<(t>_ z!LB6xM@FGc=doT=mKQSEM)<~a5Qj0W0^ShXZ*GDTvM+Ym5B$~#?;+B&Q4bZo~ zx2L_$0)J#`+uFGwkaOv^azVJ!gD$<8q+ne>n(taczL-py^xA7hl%CF{23;=>G)NtkJ@qXyhHIDmFy(Be6q`njZ(nV0^@ zXXN&3=J*_@5!c}Fx0o3Snf51%$IZ!lsbQ4}8}pg=mo+urk|hRNl_zx6`M9Qeu5NBp z1=;_pB_0nUZfxT)Ff-R0pLLq{S2Q=bCMM3WG?bpW8grj4E=dG@@}{xw=gt$`Mmns zMf~YzY5JTv&>pCMBqi0!7lO>Vo;SM)Q;wnHLIn}uHIUn9VBYwd_W2uagm>SlpQNRw zxmPScHrv{(;bqWT?J5bj_HdZDcgbYhhlhvJMMb|vxrLKYE9V}QgekS`cXeHQ-lJ7i zK8d~6tv0x0AJxO)FUfvoS>O{Um~ad=76jxNV3D4ehHiX`(keNM`#&(CO%W zW21>NksweLQ9C!)T9r)6njgW$H?VuMsW|P<=bZ#FZxv2bsI^8C?r#49LCIPc|0F}! za8ni9@l7=8R9p<7G&bwT1xIu+Iu6dz&=EJ^Sb-%NUfI|x+PghHe;9L=D z0*{AI=~|#t3*lbf_bf1jq#$_CPFi8i9=ZMP2br(nMNHc!AUbaw$C73UlSDmobMss^ z_iubt6AXaPaK&m7T<6T&-)-M`Y0Rp0KCXluH$x{Xldcy4-$o|bufgr)d?Xm9Ycm~j z!{k6XF=emB^Lu;<81sm54ox&B<+xei=ccDqFH%VmT{bB!oG|hDmXsIH%;3XF$tw*x zWnOzhL3uZFDs83WGkLCBxOspO&7xuSbB#m1Z* zs;Cvc^w@g7iWF*k4bMdW#nxDiJ;&G~abJ)a5iH@5eV zB*j6O5jjuKuz?fD(XJp{WR5d4C!d|=VlWMPl%5N?j^QAnRM_CGX`W!h5j!n(P$?gGusN*10nyY(Q}a0Y)6;VGJx1xJ*Iz zgMxoISStn3g~wT51UL&=~*V-Soa~T-3{0sw1I3;4ue))qYvsYs%r`h zT@0G52p}8cfvO^OeX+d2WVB>W*EQoM33LrCW@Z)k_9k%;<%k9Jb|O-&CJS;XFFdE5 zc#m>Nh2U~NJ0#X*(%aC=hOPcBo)vLp8O7E5*p#_cZ@I`{4+!%F(<32nE&*+s=;7YT z1AZJ|M#hIt9~W4|b@$MaAKj}Brb*XeHEun)>x1N3YxW+j9I}z8|NRq>?CBGc!Bq(5 zQHi^5D!h(-b!Co_$81P~e)??G_d%!9s7hdN!U^seDIAtCMENFCkbFu<$8T|BC}&s722E!2{5Bp5Nsjh;vKs2^d&YARqTXLyM6^p|*)ub@`2cnN~584A7&iEMsq znEtRU1k2rBj=OYDYG1MixMm14Y9n(~KGyF`3XZu(OKu-#n7kjRJySIoE6FYk&CPvB zPfxolDH!Vx41Z>ictZI8%Dhc8>SOEO2&pp`%HR&Olf$i3rHeO6j<}{p<`1wJu-f_i zyVn0E@md;bgL3cn>W673c*1}2vI0+Zc)X`QuVw7lj9FYKeuTciCof?@HdD9GVyT?dd zp|$q?#S|Ix@vj0}tRjggLTmbAWe!xuUR4WxpwD^)B!;BwqLuqwLR^!G?I5@XAH+%vDeH42K2R~igQ&Li}Ixjx;@9ytgjE-w)YC?E< zudc3uB)_bzEFhK+G75bqjAv(WUsF{zfAX6f&8Xd#HJH@c_JfbFFXtCMD3nPFOIB8P zVPT=Vy1J{Y3s{CNnUpD0iN*t1rE9IlVQFb8Jv|**5u>`U?t<-2=W<<0c(}n8?@{L9 z;2^LD9yT^M6&2Of)05mcC*BEeUS8+(&9@npfX$HhLq%m}WqEn|K`1E&NrXSgm&{(1 zfM<+K6tWIe{Qz`ouXFP(1Ukk#(mx3E9UUE_qM{DhebIJUpj#3c!Cz($r-#3P3x;h} zu?Pi%x6vHQI9_jKqoShz{jxLI96BJDMs$?VO)F`O` + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..9e3d750865770d8114912de9a949765c709cdcac GIT binary patch literal 41493 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 Gf&T+-M?dBO literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..47378ecad00c69cefcbf0d271d929e9d6fca0f68 GIT binary patch literal 46814 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8133c0fc93861cff427ac67c35b876a73e027e9d GIT binary patch literal 35655 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

K9Ak-MQLi%V&g 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 DKD4K( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7c6668eb18b91bcdfc66d017ae2fc97107006d20 GIT binary patch literal 9152 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 + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..2bc53ae36ddcdc33cce2d21ed9aea91c8ba73742 GIT binary patch literal 42588 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 + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..52468296830d372e2359afbf5ba1bbe9652744a5 GIT binary patch literal 137629 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≻@t5i8@^P}wKI7^_7ygg>&VJmAJwby3*@-AW}aidrpdcj@Mbq?(RXSLNY$=G zrHbWGzfD|K2Gml26qMD?t1b)5;Dy=Z*3Y*0pai~a@uunBRuCj;;4XM})eF({ zyCpBIFm%w&{ywj5rD;=9(K=X}i<;bCq7m9-X@ZDyY*x!b6^3DuY~I>l&LOQ-=Nh?Iu<(*rk+~+JHs_duka>%? zIZYE0vsw?BzpwDs5PLF`o9V2Sd|crY#6iL2!(2Pk&B z@c9`7q&2e`PX>fiRuk{I58!kDANH3)6#TWNm0q#Mx&ElK{-|I}ve*9XitLY8VWnIx zStUXvuxaacyg;0Ll}9c-3P1*2p!@K!(cbGZj*YtP3i?v?bJ4eyI!NKev;=Lg4)xrm zRcLN0(BGhPNlqoMQ#*}%Zd`PBE!}x)@Hq~0sXX*u7i>S3~c7PW3 zeafuNd^N?Hw;Y{#Vd_b%RTMtMaamr0(*cYvzPyfQxD(TkFsTz|)ey&{t1*J6JA$DL z{QaK8R2t1xHjS5h=nk+f2aRO#Fi^M*;~R1$itQuD4Z=qNiq!Q7Rt&hueAJhy_~+wq z*6oeTHD$}!@g8%ouO;aUqnxO?|44CFUTxNo*YbLQ2F2urC}<@NknL#8V^a_$ph#ef zVWhvcR8o?1@Dw8vxY(t%Gy8w%&x+L=rI*Q-f+SWgjUo0iT-gs8kQn9T3h6M+AcjiT=5n%+O2yx z+HG2QX%!NmAWwHPMHuMqtr#3!HZnhuZ+@OrT!_Dgh5OY_+qH{pPDL+wL}NmmQ$T-f z%jD;Rq;Yd5k5x0kJ&UhcGcN&YxHK;sFfZZ9517yCGXu65G-qhhL+R8Ct0SF=jSb7f zm$a!`Hn;oq6C=+)r1H4xJqx}_o2FfJ#t;Xt0kbc@Vpg$|I3W&>K03O#lhbXJ!?qO9 z+E0vXF5PP~d#uY6#$uaXN-w3Z>UdgZrSj$Ftd6!>2v5JjimR$M=>l_~Kjni58Uigz^$8s&EX?ENb5Aov#ol1oK-;#^v6dq`>TQPxC zIGbIGh{OzfEvS8Y$rpN;&P^*Zv#>vU3xBHSFO6r!g5U zEiJ*bjtZ|i{)9inCo&6|!X{y^>eqs=*{`t$c&KkbpUi8xq_q#ZlVtYrjSFUIfabH9 zz>STqdU?e>QJ0$%qiu617yaFtYhre}MubL-I}EGpunmzG7B>Es7*?E@YEd}5BJqkx z2L~DnMUwCCJ}i^>o~==0S-{KEhbyQeNIn^fJ)P}c1gHJU3x|AOJ;EUkr@4&O$ewF! zZyzwR?o9Y|R%VUk)4gw6_ge#Hhfy^qdrCGZ-*Q%n*3;A`Hcz_eZ~Ba^eh&nnzy0}D z6J6E>xpx?LD^?+#OpAmfb=F{2ztK!`ci?G}id4YlaJLv)xl{V6W*5gJGMh876vwy| zhqOlZ=6I|^1y{Hvw&|D(lJ>drmn5Pa!4q@JZ2GP3&K|X1M7}KN1%#3;#*pUFc4UJ(z_&!jZeZl~&G4>QIrSQya-P zV&D+XMzcjU(?f?>Y*&qru8#GA6`Z&?D50s=BLelb)x~oU}$UD&)_zR84dyrD!eC^qgg>ve)fO_mptITa%7)M<4ioLb9q3IU*NPwAp8! zzIGL+RQ)BA4$ws>6b#1^cPSQ zpKGka$OHpv-b-~R3$wFtgt`;*1dCAU4-c3g@8v^qOFn)lkup(NhxstQ4&Ko<=6Jx( zU&b7D5I(-c=lpZ`y>W+=EF||^?fH>>fnoS931iO}u*Bz+gFj!ddt@fV9B)tf4RE5~ zg0k@gKO+HEKhFV`{TndjzDMt~m~PQ;`tw2g1E$W|`3eb;L9r_OWy3Tlo9bDcyrf?m z$-)7&>3NREyanAsZFri5GoP!r4MQ52j?2`|R_$0#2QpitB>a;MO6inh z61_J1v+Rw1!a9XWJ?_cP?n0GQr0;5vQfjj>X>p`Pgi6rdJ*zc7Y} zih9sI4TnM|BSe49#U75wb})p~JfRiMPgtASG9VcbogXhm0fZeP>!NHrm1Dc@HZxrJ zgQOGpoXQo8kNH4@h=FuX&0_f5RXi)fDgZCeyOT<7sI9&vKB?_$XKEQI1+0w?4N+8B zbFEob$+bQ%8q=XXD<$*`3>HNIce@xqUBpiZnN%|?!P+jyRs!}=ZNm6_)inSYLg6Da z=&sZ3`oY~p{QOa1p`*Xex~?1uRSIOCDr#sg$F_*dk<7wP53lIvM3Bk9fFD;vd~Q5t z`%NM&?W^UX(g3!EF1L}KIt>j?K;a;YnSmtjB{XJ%XeOxl%ApgfZ}FMiRuxpl`@idV z8d(5tv@WE6^v4z)FZh;JpqP51|1MR{i~!cJJa|UwR^2ik)Vv{RqQ{Ua_f;-?h`U!s zSL7q0sg##)0{w5P(Ney(FPu1cDmdec4;Q}o(z&ej2PyQ8-b?v)*t7@-H=i{kgLaA$3tSpj|<#sSSz@m;0IK5025vO*R8UmJhoy&|$; zIYIe6_vzrGY2Y1QIK{1_!&fzk{UfyY9eR{y$(wGe>e;a{R9Y<79G^d*#&@l#JwJsZ zU4<()^amNkN86$A^bD^0&x8V+Kl`_jxuPbe5Z9_DGuO65K$;Wm(@k1vTT~!ds>%lR zwi?-B zTT?}I&yh2kG92?Z=%*hNFvf_z21NYORpNCH-K~{z7&Kik`AcjF0pS@Gy7IiToV?$E z3kdR&dYXXs{ve+}qAZ5^{n4%?2Bdz@zkGY4xy;cXJ3!=h4v2uCY8=aZsm;~0g2rGYbuFF%Pk7+R_dEigj8^fgx}l)&e#NK8n4vW zR2?wC3IZO$Dih9ntci2M|0}kPC;NS%pvtD?zc7h?-0EokR5u)%wUI2EiS3uhDg#mu z=<66#tKc4{UjlcU49O||+i#t!b_)BmXvVc9c&>Ug>Pi6cCYNo4x!;2*yRQG*TqtY_FuClA!^a|UjN>eOJOsUqyE0KzVF@pI>Zyme zVicK3)L(g`aw$&wetVpD+1)E_9*UFVJzZuQy|A;o%aHS2{k3XPxKiBDfO$r3^OZc9 zEwbOvrBgwkl8i_)00nXK%`axQRu6apq z&d)$!BvE+uj8Mk2i=^XZuAb3q?4^GZ=aW55m$1_VS3=rxoB-}bd@~V(W~=qI-+nBe z_2t$J$H~m_XKZC5&grbl619d>XXft>w3_2VPIX2@ViJ$y`ISdV2YVcie|tv##RYuP z+@Uw0YapNNMB@{dea1HX9(Kb4*`3m_#9exoa-N=>-MY=urTr)8FAJ5*jnf4JFMwjt zF%)H#NknmS4R%0v-;rS7mWZhqo^p=i>>3a66%euFxA-oZ&-b^lmg)Va;*U-@eV>Pw zd_J}>O*wrYM?eE#`soT=E}u(O?~AODvy@c8Gd-++PLrUhgi=9G&MrxYrT9gx&*Mty=`f= zbZ^GHP*%Rx54d8`C6u4~dJYQo}dui$k(Az5ea-^rQz~Y#pGc9mVvP-W4TM$j!-YH0k%p zhwa}!xzVskm%-omU4s2@-UUBMD#USd!iM71TWAUjNiS4=EY?`d%q;Bac%AHUYTWxM zf>LmC@i+d1Dkvz}azFc6Tz7@=7T?|>we>5j0rye9%kt>Ri1<{czwjWqVn)wj813kn z4-vN-Xk0GP?{uju@9T$cY8{Zw^g;p7W)n_gw^y=I>lua)NXifUPG5}lN5jvQq=3WO z61J9>fYz2CRuie(TrJyF(J+$!2`D%c6@-K=FuOrgv!npXOvuL$H+<1Wi?1oKtEK8)T5|~GdpJ)9B%8OD7=miP)9p_zxjpGS>kbX z{W+bJQy3sAv-!jSqv#IW;!(-KfR>O@x!YxZ!~MaHw~mfqTbqx*o^V=G5U$Iu;kVZc zK*{F!WcFrydT`L~T(pt#7s?N-O*`<0Ki;jcpXt;a9Gv%B3?JlO`ZWIK2f}Yb2$bss zHmL4ts?xc*RA{t+{`x8m_QU=kKt~<~@UBtNH(Q-ICp)V~CZO`Ew(lBPe0&4%22ry* zIo9XrCCDH03eLl_@^XLmy1nW0RhlB(Z>`l4K^1Pbnq=g4jg$Ht{o&*FSX%-bZnE*$ zuaK5dE;<`Nzof`WX`_uDGv{5#5VVxjcF4|y8@C)-l~^Hg*U;g*bDNK6k?u%}?@Y?N z@*x1IWX)n z>a(3o{~6HQ^;HNZdvcE0uzA;4^e^1kRFbZrrP4R4r0kKEL1-EvR#sE<%(XQ_{=g^h zOsJ!NZhZS)meY@AD&4a)v=GxGRwj=pK*9TqlmEL9jIl~ArhPsa8?rW2K4Xwex||Dj zAL7|=FGW()xm`$W+1?b8^ffg-;2?*5TYCG4_a6%j?`@^4HC{AyKD*{$eDhY$a=4Wt z{BouD*9}47&R4b?Vo)l|1tzXO=DiW$zLjDPeste15a3c@H2QKuL&8kq`kD}aFjE-( zde-{ost*yVz_i3Jq;Kz2RPklvBjp^MtyQTOS?^KNPi|g8BD_5r^n#ezpd+9q>8vEW z^6qw6Y-`NcF#Kmbf&gc<#rPcl-X~YNz$kU7=5#w)8S74eRABCbKlP8ZLSUZr=ANM5 zh2tGKyGThBqsJ&|r}IfGe{ACGv-asTA95uh0UK&TpV^o+s&mKJ@X4 zn}lj-z~iDGtc^>fh4_HzZT|N5CtQ9uQw*N2*i#st#(PnPNaa z>T3Kyn~n?2p$pB8AWgufTz4NY(k@7!5KYV$? z8clKvm)4dG3VMyhQ}pSPJR_yud5!_eI}|strPn=r9;Gv6j+${CecwukiG5#jjrlxZ zWY(jhscB|hVQ8#xFoTa0{7F~0s|&G8`RkXSXY#yumv6newGbe^16)MDr&zLg7~MV3 z*8%0CnG>La?W?&ghrYeu*F7L80|hOJK`}xRYguWfGu;O`6~y~zC?KvAbiPjD>W2V7 zQ*Zdwpk+Tcm+QlG<6ncVg}sE<&8)2CE$^Q$fN)ix-9n|v!}g0pg^1hd&581+U&Ad{ zswIl}EO~&UUc3cz0e#;L=%WLWEgx@=eyl$42|Diz?vf;t$cFp@s(RUd( zJ*_Jb4-Y<}vf zz4b6B%QS~GQ~2kPD+1uj&i|hc2Wue0l6MR|!rshe0UO89_5sDk#l4$epZd$btW?xb zJ3kOohGio6qKbRNu8hQYjG;H$`L%Vd%TuTAplyk5lc|+m zK#n)ufI%*#hCYxCu)hrClL>IKO^ux}~+%!a{W>m&;)Ug^7M`bm+wDW7y>50L8h9j*f?PDcc%yblYBw)lDM4 z#~It4yVF8trjM7EogD>k&}2iy(^CN;wi;8^>VB~B)!U_|DW@nsT;|w7t?BYzun*8R zF;Y_U*PwaVE_8cg2W`-u#kFT8xIkPba4Qzdg~didVZU2uc=8Eo@j2x1fLzj9}+hLqI%14*_M2i@Bh?n_EO+<#uOX4&<9%ZxL%Tl>o+v0|E#ANrv{G8m}Sdzv@w#s&%H>_A;lZk!5r_anWUN}WXw>F^mW8`05r5uFHCJhZ7 zfnf`LzP|nqR1!h1a8$%Ex;o`KJ zu0kMV>eIfUC|&Opw;+F*2jv$x4G=Bh$%rhuv zT7AU=!rMAx&O^*m0pI<-c;W_BKN{aag8Q0nAhb-iqd$X#9}|g5eq*@b=j2pbI0K>$Qs732PB>!cu(&A0~`)k7F$3Q29$q5Vu#Ndnl+m-Ti z7rH|vfqz;4JybLMff~BtMG{KL*^UeP_+BtlLs|G>)m8B_qe`Ycuzbn2jjAy)*t{%njYQJ40osgYP!04}(9 zxrf7G2t%=G;=i*nHyPB!_OyskfIsXeY`j=B-EF#16Z8*QD!zC6(_qMz4(N|3l>0AZ0$qK$Bi5M<18vE^%EQT1bDFW)iH;ar4a;oxt5V zSal}0VKVmfcwkpJ6>aO2R@l?j{vw@CiY7?YFi?jhyJjn}3f*|XZBq%f^|!H}5ZU?b zd_X>*djB`mUT}YV$#X3g{r8-bC&w|V4UgoGVsf7VFL~8u;DP^Tv_?@6 zy}NOvnll%M)9Ud?%}FhA>UB;?xvQyL(-EdPS-(JkV2l`_h=gwQth8!~V%RJV$E-H? zSr*A|7Rgy2?tU7*C-xtqxEIopR&$t1!hSM-mc!Np?3*(^Y@g4u&zjhO7sg?#(qB|* zuAFduJIsHk_akq2t|GcACI40*#A^P%L(zj_hTVff9)(iNEI zqoC&KJItf|Sjahi5Z2e$Gnmh(Zjxsb)SZ_K_p9mSUs#a^T*Vz1(hvIqQ^B$XWkkR9zV^n-#b#5RM`9CNI$9M zMWb<2J$%XD+$ZlVA5Hn=q!o=9eX~Tt$dp<)N$I|&7vZPNQ-W%SIb7%kl5H4=;*)ID z#P8j=>@J}qtNa`j31UP85K*rM(k>Qd5)aJ!yadjV!==gHb(FU?PzqsI5Zj(C_3@*` z3JPgthTSQXjrfJogL#eA*oMR=UGD8yt}2s}OgX5?dYuy4g|lH>wTMJi14o^*STc|ILx5*-_LL?Q#jd3(A~ty6`Xq%dkGyVFB4;0oGwyk9>ni1D9V^ z&v?;wr6v{dlx<6ckVMK^T9H{^s#zVZkPT>O!n>e|9ny-PSa7*;I5H$ej* zb3O#rao&46)cnXq)18T>O?|3MYl>B^qzDH?jvKSQ@#%OhV^Oeu?95o@VQk1x#c0T$ zF*`!0t53_}epMLcSJ;Oq6vPE9cR(!|%;ff{0=*ZZ9Dn>eJjV*;Vc78T zhMzV*m&_PjKbb|x!4ShLQMQw%qfZX{supI3(x2-YE2z2p#PBN=V&6#BWVlM?C`@42 zC}7M{A?wJ8-BQC-E~fQ|?Qe(elP7Y^rITo-Fl*7lS7XEbVPgh_RAEy+zcJMo9+Xuj z86^HCQN7oLGQ0(UWkS^aMdo+ECaJdyUbrjbhSnUCZof3%$ZlPnCnbLkCF!jce>D{Z zUrGR9O3=O((*~6iQbRRUJ*ZkcsCt4dxeC(}g6^t;H{mXU7sii4q8UxH5<$xLPH6SL ze-G72AKzvwYJsL$AO@*nRF7R#duf&Yf=;ext%`JQC#)|Z=A{w6l__&ysWy@CiBTa# zMvcxOhcPGTg-eZClODmiFDw5lsOO?!*&D0sn+^i=f%gJYumezb1G$j}QHIB2g|(Z> z`rr4C=U&sw@?00&R)xvYDYv!Kt~ZmTbS)8PBALBY+ro8T&PO9<+>a2vji7zD^t}at zryj+K6grYhR|$kx*wCgGDFr@>DX%wHq#bTlYuJgYBvesOX_M_H;LC@Uk+9^1k>0{l z%)r{b@fQh=S?Fiz`jtU>tmQX*GBwLZIB{%;Pq|pOd;|Umk#-mn} z)Ef*O42Fm3dH86qw@KCyALNfdr2QeC-T7+TV8{p~3fE}to(z(a-sz&+bg-#}K;$mU z{m-Ny}vYJ$Ef1nQW#)gJ^-j~IK^EMM2rDjW0)R;bV{ zrGk`F77r-WLZrxHFjo#JcSAe7Nu-!dMfo~J={hL*I$(fnM=&I*VKNSu@-!m`i8Pmz z+(=et74xLT>x4p#9W$a@))J1d9FeCSahVBou)FPzi2Gl`F0teUQ@Xtq`MX-hJNN*o z2IiJuIa1>jE-AW3V-zmLk72Nkb**q)ht7?rbCIT*e}#s!z>}IE7DI5@1`GY_v+cR3 zz@wIG-cZPVE~<~FWS~wfRP%?;$x+!_BX4Nt{hKbNu^R;>l*R+wU zfr70MMr15jGDs^sQLMpoCO;#C&q`w}W57=n>ALtSAq8Py_BeE=3idSR!(2o8Z`xq4 zE$Xr!2JK7bJMh*WgD5(~R*YZ7Vk_%fxU2J63AJ;Xk7nssK2aJ;e#K1o!pxmzr@IRG z_@WJV;e!;^qlx!iA@Wv3TsW^%E(D+UsRR#uzz|&@@lAcY@a=FBG39q-(!EN$b#2mf z4a`%W_oq6zN0qT&3<+-H4pd&agIHn?RL0BVsu8R|->lY3{{x8^|DcL$j&bN%gPNr- zt{YG-kNANbu~t3g8Baav=7oeAphw!|&5Vom8xP@v38iHj&VT^rHy+aOq$`P;?P;!< zv+W-BueaPL*TD5vWvy!3!#=Ol#zRyrs87pSrQ^Z5sVgG}^}wv( zWzF#9!!3?9l2tbG_fql4qa+@)Z zS3aRvc4CvaV@vJGrpBQ@ACdQq!~PMEeQP^>p64SH=@zkiAC{98wyL7+7=0nmU206a z)R5$;OF{WY-CvZF1jzv+837$fw}YS~gsppEhK4jz(N%NtHBk|WV1BklY_oZM8}^w7 z)b9YfrF!VFdBE)X!PRqH?%z@6PcAqw@|9D#(bD%flQEDUGoYGmB3SF)ZGAP1riy9n zi_J$Klti3EP%eqk6u&ktg?9*HzXXvn0g%Fk1P2mH6wA)Ya+jBYdSnDX%#k1QjJ-fL z187SEic@ zzCYv~YU7xaehB0m;^t6#JG985gg%tPrG%_D#V(Cj27^La8Dh5h_HE?xC44d)`xH%6 z2w8zmj!YCdda|}(g25=hC=0@#iTZTbxkpT!|_VM5w>3B_jBI~*M$2q{zS$Y?l|?5VBP6+zp1SUmy|blb^h+D`~pr5v~4t~JAJ*0Se)5u4#6MZ~+a|*d39=qEWwoR*1 zr4;dkw(>eP$t>$;&TG=1Fe)4~$6d}2dlk2I8=HiD_*1vs#K=R0mPqn0*u&j!~>XGc=DGUc_Ky zaZ##`*#X93mbR3%c1MB=CN2X6Ye`=#b;sFOUJl3Fr4%)1OE16GIbDFv3^-7DHsm1k}rwqv}S)(Tm@k?1F zZ|2rDq;N68EaFn^-H4P<96(f9z>mb1Xv_N~k<4KW!o6&i~9`E%{_Mh))dkZXYn3psDM~G5I-cLA?!;CPW39%Okf-#ZIqc58qOd)Oc{|B z$joVRuCf!y3Hz(_<602wivZ2y(QLC7;%{Ep)LWU=Ta|@_TXJfV_Q$@B0#F8&D^($Y zG39onvl*anC6)87G7`_YZp($^;AvyJ#K~u(N9RgtI4^LptNE04RkpXI45Ar24^bfM zZn*Zr5p24i4G^McIZEJ{e*0L}T=F~^iCy23MWn&Pu(k7`tAr3fq(_@6D$g}lHum%f zuboF)SsC8Ii3U%W2`ot7k*c`Av_uW zr3_Bf?i0E!j}o^uZfPT1M;mu-C9QGoQh&czIT1bGY<4A&SL@hR1P5~ld{-( zd%R!E?sRzr6B8jP3x66JN&FT_-F9)Joq#FsOE4I~(vdk~ExQ zaOr64>LUK6mYHuY>NF%gAoqE-#GVr<=Z`I;DJG2Ug#E#2{tbELO2yG8A+m@JnFGyBb#=_ zd?JsbbaiE~?lwp(Ec884KyN$gclNx!KUP6UE1-iJ8JHybO3^zW?@E=>WqWdwwYVR6 zHOI@+8U)$$uWua|Xp2*4#fB@AIy!iL`s}VQI2pF)pdDnm#0?vn=})BjYIAO{&%Q_k zn?uwVdQ?l)H9JNzaVhWivD_UT9L)NSGr*|jxphgnaFb{_T!hunNhnpj^ntmxq%mwD#az_?hv8Aeh#g%Xr#qtH0)4oSH#VK2X)6^(8 za1zwEEO14bYrB+dzElvA)MWt*0ZfZF#2TQ3U$>~VOP|X!=nPre=*A`9j>was+jSP? z5p5Lv#`;9)pX9o9vUBMaut)AvaDT5^viCWm)wt!$EaLQWr&r>o#t$5I{npT9X5Bb! zd7tEr85o5EA?f6I9pdX^n$;-lG)xfHXvi$6dIH`t%iUNcA z#~!>IZ9z}^U|(Gwttp^)ZT+6N`IwWtUDDr*;Us7Vp6Z0${*ek*&9#)i zh1}8s=`lmGrGXk&dFq3HNGYUAXaPqWrXY43?wDE|d1s2{&pgYOT>Io@myKA(bj3g&+U-1{Y6y4D?y^PaW_w}Hh-vZlhcoy3Jlj#m zmPfHKeqUX8U?0XCqbA$qY2|Twb25qXFVPepZ5f9S*Wzit#Hf15mVctk-RR(h1Gn2h z(duJaYw73=9#qb!pGz~9`psoT@rWDo$l%@&dGY$k^m7%R>@7c<}3de&J zELhvSCre3`v%SI9)gdS1imHNR2kK-LZ!GvZa><(R%I1cyGLqc z+TwQHzil}F;AUm8BdAzW&EwcW7j)%QM=KQnh(;}sw z-SGf4hRXIO3eMIv3Mc!uz!Qp74%}lBOyMCZdD)1xZ`HhSFv1uK;~laWGPbSeb6guv zs-n$d$d)X54l|n@u-%d4(?3inK`>-a$8PdfijPr4Qmfc*o}A~K+sQ50mfd( zc=)u3^NqdZTa$@(T_l+)=OwQASqz+Bz6%Ag2>qt260K@_a-+KfjXk zqO7=tKtp=vLOSHn8W=kdPk=CeiW>#J>52U^i^|ijV4|G4r7%825$-Nf(B|$o+{e+O zS`niVu}^5N8O3<0M(@IT<_+dL@ix=U6o{%oQhE_SLP8s?hBK6MDCi!4Ek85Y`%FzD)y z*f*vv?qg0QTE1@l&v%j6eiJWHdSpY#)xzalVZ!p;|XY0b;#LMc6ay)ik`%9I_x4l$t;^xYboxDEKhKxH>N z{yX2=MH>M%1l2}v$12`C&W>g!4ha`RFEI^p$?YP`cR=B-q5>u`P=3)oLA$_ACJyJC zM)(&&&o_!hLCm5e$J?!9b-({1UjTctr~|?~3HHs7xJJ;uM&ooY+@GU86uEM!Qeu z(LKkVQ)43q{Ttty1GB=F^?Sf_8CoEj=)>iFk_vN!Iwo@hi^{lklwkh*B{V9L#8Pd> zah~sJ3&W*`n+N;*{ffG&wn;)&C%xh;yf^aX#eBr$F2m%&4V+Ot|S6~Nl zRnilYRz#F8$y7m|@qLbQfn6R)1{z+Xudn|r6c>k~lH4nrKfQZs)gg@V5QQ*(8Sow# z&=WHiv~{?;&BjhZm7G86b=aVuQ)KgI%?9-Ru=g>cnE$8Gp0sE%T?2N17rMQIpS1%w zOtJ{?-im4JJKFo-vfkd&k|AjJ4mW4#cw#ZT;sNd-f+i{wc-Nl4SL3=wiL<(@nB9$& zh;~iCE-|6p%EEit+qa7}+ z$Bko5V6Gg1rg_>!2Ede%E*zJwVw^-oh+|~jb+E@qKs@W!#iOgbFV34j_h+9in;KPz_X59oovydqeA(Q> z`bVa!foS-2N4e`6=fK_hModo>2;TkuN2h|?-|{K1e+xdnK8Qb@2?S!l9 z^>-sMbLX%3OJbOK#fI>w`xl4RQ8)aki)Mi$DSr*f@Q>UGbuA~T%$hN6_b zJd{RTKh;LtpbtW^Xr1#3xJ|!>xO6iY2&xU;6LZfqaT7DH@jE1{2UH8 zx{?F`zWl|E1(THgi-B5sXNP7t@n>Od|5%x5&I|yJTMf6ZDF>M6nc)5NB`~0exPkz0 z5uN(~(lR;MPDh>5uyGEE5XepuootFcNl=gIbFw=^2{e z;{m*iiYYhWpY6A456|J5nR6^GvA~a5SeP>vcx()Q`E9pwAr$xoGH{~ze+vuLn59eq z5|en^Ig7^n{@y%Vsc$8E?<(`xdnk&)(dnbDL#tuCedd?nNjvYbNF4s#Du9d#Y?bz% z{D(Y4Gf!M$J5%3+w#I;u{M97lZW?@)1Ip$j8MliEA@^sG5%#76XqtR8{|I2mHaH^; z5qE!QdEe&TT+dycc%d`=b>jne%TgEM$#J%Wf4Toh0e#;4kPpmX&0Jo_?OFQds^<0@ zaO?VC009E<)P~G8iTyyfbq50=V`;K`6U@t)U8afxRxO!*g=#MWaC|F0JTSn_xw7We z8mngo41Gckczt&`aUhB3aU3{aA?UJ-Fs=6rkmk_c-QD@!KL7W9XTBLnW}LZ?oE__4d+pzKEx?2< zYi(`W&2hfjosIwV3o8FYBrf#gepU4?b-`wIf3y1#DQU+yLM-R2J;T$M(+2Be%8bom&jme?tWnM!QYJr zSM+k;p&-%4i&@vr@9%$sg7{xI<6ccu>a9N_o#dzWpjhu0kG~OpM2U?1{_-++a)N}U zmt$|g0=N{4J0k`xTykf2vhS)%wG_>2qiTNY{$H^FTQiaP3rd;CQsM3%0RTb``X}bz zq2mYBY!;S~Zc*3g2V4dQ%LG*Wr8^>30{{?Stu_bC$PE0AaYPESoT)TyGe>o)35<;F z=dt~g%3=DjGh*A)jCDpR33$*&hVxdL4>j|-TKGL*L&lSqTW%fIw^<`ul1ry;lp!Ad zNu`HuY^_yePE_|}gpxkDesAzBSiU@o`wQv5hBz$Ul_=jmKU}lf&sQUsBQY_Kmg$)C zfIkxr=ouL7UDQURbfxjIji#KB?TnPRG~X4P2d1S_5r1rDcijn+`#Fq@@*0u1NfjsW z1CWn+Y<{i9`)@S<48dGCq4!#A%~pQ#>~d*r?p|p-{l}B-j--EMzPdfc5+MH69uQq= z*kLyGQ)3ST2TL?9cNWoxFfHjU5z?2&1BIrXukDOfy1RRs1Yu$tw%;tkan}O?Tw5{m zB-LsIdJNmZq2Iz(1VrW6qA6u-QPcdN<`U;oerk}CdbOQv#AO-yXyzbG+>8e91INJ~p5X>oZmCLa!< z$q##ZJg039gMT(K--bb}HuQg+P(WUJMqB8A$mTe`0=*=kSA0rYm@RmDeMd)QhKGZI zK9_greP0!(EnO~d(LtVu7CX?pe>`2zeZ9>zT+er!e*T%5A+tO<%O_rR z^m8VL*ic4xVS2*FCdrdZT0*lJ5;J?5>H!S4w}(XRi<$W~jyDOG$?_73zPh@Zd7aVvpCVDwqxAG~w_(6k zxUT$o*53X%Fy}kRZKyw`oHaEsA>KCiQA1j?+tyo-L@}8m=SGhs8bQpQQ7Me>>7i>>3SHgQrYJsrB98}wtkH3Zc%EZBE` z$!Al3Y>IgD#|h3%C~BIN-fQlXP>x_rF^|sBB*tTRIY8 zU$eJ~Ve!s=yRx#lmk<^bqM{*JQ-HU67_|fx&w<*$NW?!%CpC2O)a>U^*EfNJX72b+ zrhSGx(1u2D9^Oy04hywDLqntqlWos8LuhCalir9ax;Ws%jJLh0Xt&n-_DAw#cGLOI zVr76R=|Z@L{WF9~Q@}nSba)syk_u>b4_~WlM2ny9vwZv}=R7k!b&l8*M|O38Y>cq? zBaAW>gJRG|Sh$&D+P?b4ydi9wWcX$$bEpVJZQ<2u4HY-8wA?=+(*G z+)hpotG2Q+PxxuJqay6)2efuUTAMS+Y2VnK=T>(?OR}*D|20kZWlj7$5Lfxe0kBTm zyZV4@^-S1$IU0z+up8`dLXmH#iUbuu2R?|)(!drg;U`x*H<7e)3I$$acYBs={|*2em1YjoS2Z<0#Z3D)M)qyy zBs_r^>xJArB)jHF1_v|B%7vEdk6EZ3MRc*B1+{r@X7r*O9ACd)86D0{^M7^VW>_!C zwQ92%0Wvs2%jM?el9I8Dsg8!(PeQL&elIIePloJwhcF>ufch}^=vy-3JQo%wY;8Tq z<@#fTs~_C|xaYz8vZJKBYr}ebAM7}%@mxLtoNjM~ns}Thx0SRg`MQ`nq_bbZwlVH2}|BC-jp& z%F9PD1Hzpao9XWwrDYg;^<}*Zsbe&4#HGVP;<$MpZx}KTHnoFy_we|rUYigv%kTGe zDMjX0)xzIcyuYO9DEK;~C%5W7gc2e8=7OW9B=%aSKC;0KnP_-}MQ^6Bha~hji*DUP zlSXM4(6`n73{UCiqN<9`V%Rg_d3V;Z@^upOg%E)IWIgG1QA33CzJ7hWdj(ze!!Iv? zQBY7kLh1vJ`kYGS=|&52Uq$rx90fUpqJW;%U(0&L@j>@n!;Ou{k{~F~mHb6d zS-lQtzJ5JkQ3X3wLh-5tOMcgV&zZX!_#^kU0xVP><^N| z=zSSZMF${ce}HhM{k4pR18j1=pSEsL0V~6KDPKlbj&u6j%4`Y0 zTII&gyttJQ$4n5@USyFo;+p{KoU=9H4STub(*djsHscD!6{p9+_rPneC!z;XN4e$> zl!G?4ft0amVh=5`yj*3naC!ISC2v25CgCATR_R06_$>bU-6j`6#%}`4;tEyKY-e{j z=%Kc|&x!GegEIdm-H66Rxex#Yn?y9ZY}W{^%=WKlyEJ5yd>RRAI{X0~lS&jDv;k>C z^X-ZcC8ftZtrdxcxW30Q9gRNCbLn0dnPLI>j`QrnH;9P1k)(U4-?ZYx0ZaW}sF1%a z!F4eg(x$j6>fzBY*okV{oL;nbU`t6K;Y1E0}HJt0nf zwuQfao7iK~wCTIo3=$r}z>osqk}miFeMEz3#gs3GTS?9-%@vRa<$!6ZKi%Tg7V_ro zOD$-*wM+i{Yv=F~6)G~x`K`9{-Em~gw;SA=+pFfRq}N8`tqd#GQs@P*zU$lkGnf6) zhLEW`PrujaFxQ=7dAXWi(B#w>KCussLgni7=-|pr&FE-nx!&w50J#HmMM%R50OsXo z%o3Eub|oE#t!~BJH^;BjlR-P?{o6yJxxO_-I{q_C>+L~HPl4Kt%cWMY`wD|}`Q3(s zZMz(&+cl%9_3l@|LVv~erFF<@dwC??aJlXf-9m#@uQ!Mt4}9-pf% z^d@ob!u%^$kXGeq(Df8EAl#|dP2Sje=CTmLAbp-@Kj0!7uqD`y3knhmLx24gJ?YGQ zOYD^aoSCj}j{T4JBZ8fCQ&@lw;{(TwpKh)^DT0#O4v7YQd`MAI#==e`+co3A^mLFo zY@rKYDZAD35K%mo9xvVF*K=089}?fir0C{FW$oRTf&Yc{^d#N8Pul-#rYu?zn&Dm( zPj5K)J@0o-%Uq?r=cH_c72BYnIiarIhs`0&q{jc|OdDL?V3!?K?BEXvY^F7Wgz!%s)0d= z`0A9O-zpH(f`Bu1W3%6Hr}a$^j*fWo!k4PwKnYwK(l-|)5)z^~^b_dPU(= zsL~Uh%$?=Lq;-J(&24*B8cbOLv|8@~KVQ=*Dneq%lugXKei0*TodW{NSOKreLXCIW zI@(qpcJq^5mLMckpZTe5{g=do?L0s@deaGi`*Jblzh;=G25H73Bit3N>}a1G*e9o| zZH*RCbaZsQh{$nfm7>h9I(tL7o1u{YrN_nTUpISoZmt#3OufF`c5Gq>J}eEN zRLnJByFW<|>zYPo9Ys}B8MbW`7VF?E$GD?k$ecR}m#2yk{(lLFIr56Kks%hReh9N=Bwh9xEO>Njsm ztzHzh=#4EhQ5P+2{L#l?fFf8}ScuBLv+VsNO}KoxA)%LdP^B|BnArqJm^pn*)Pg`I zPN|}#gkgWSAu*ekl_2T<@+9NSJz+)D$`Ru<3t+16~aO9n!3X9)K|r*t>fEl8H$~2#|q%ODlb0)79_a)m`U<${$-UVO>zXt{&|d+`hWic=>b;|z`;KN+c2g?DM;^QPrMNk zeD7ZNp>29$p%sQZD(i|ndB0N;0TAl|b%mKMxVig*`Q6i-T~5KiYEELm&&^%TTg&~3 zs-tU3oPbz%F&`B5jZzWpa*(J7nau;mIL@z$sb(3Q>G(Y>FtbYtMg(8Ch!}sB`uB-0 zrBH&8=L708jla4HGnD^@`+vU+U0){fpu>CsT~ME=lFBWE39wCNDnZU0YJUJ^{@)Er z#Y0)&L5+k2F8nF{FXo zIshuZHP}ExLffV!zo^eOl0baoax;!;MaPiN<4N-592=jvDF10f+&Kg_Y?NXiuzOz2L|((gTq9w!2Dk=}S%IlM- zM!ZECvT5dayt1d%^!h-2*47Jdj)?pm_k43s8<<0J)V~ z(&KY79;?+GlNr0I0o{0~NcwNxf)CV!2ze`C>U8Jz=~*S8Q~)+P;VfN=|te46#I~%pYWlPG`ZFa8vI$Qn-Os-gn{MgfC%S z&vEqdv5fGsW=LoSm*|Y1Z;>8x{T^}KA91qy|Dt`*0+ad<{x7S#;|f!WFwX6sg5V&Aacj15{TIrz}xR zL8xz`rIl0WhKvfOP&s;~IL7ZGxP`%Zg~2$om|eok#x83ZSQHd6XR`^^Fn2@|+0q+^ z8ApcSndxV`W~m==Y47l8@Afc!&@oRDJmJW^H%4*)HXTMtG+J*gMQuQF`&yEFTJR5@ zrh6K3B>DFvL;()BQ)f-yIR6;~vk18eMZzO3TumaOD2hty$DZFp&f7Gn^grx9GHb!H z*+EvKv6u?k?lS$b)PgEgNAh*2LrwtrIHe4s(FV6aDl*+F_)&dexbP=u0`60bvq9Ji z4`z(m4?`QDr*lKO{Y0W|b=QiiJ4{VZyTv?7CpNv}Hv;BdJIK%vDMt58bR)=Su-I&i zp|Hi!7iuUtVi%BaoC1GlHbdtSo|rul^gXhXKan(l|9RPfm3*4-uuAnkqeIT^!6SG# z4#m>Rgh<}w8Px4#>3sHLaV`U#@))m+ji-z1b(Ux2XAKhFi`D&d*qda2Or=2PQj*6~ z=xBRB?exO_j%f-f_|VJ|MpME;jEOAH4GR#Zqn1%=)fnqBgH@8|R;IL+Z;%{@jgW2R zrQX2(kwl<`IdfD;Vpr(&LLeVc`fDqDX`2Mkxhk0&m!*FCSDj6eAuEP~%sYRVsNf_( z&$K#=w%As!deN)+@ZM~ewpcE)r+{Ms*=Z`lv!3JTj_2FPcCYxN$NoMON*HVb7ycQ5 zGEG7BXZ+i<^Ehi**%*ZQ#PRmiF}BF?*K)hb*X5leFTYMv3G>2OZr+7+;oO`*ahY@x zTA4UYV|}K+Ta5B5+I%{KS>!duOXpop6ubMNxOWCZBbi(;pjvrMncMZuU3Er2_52R0 z$dNb#Q;%L^I4Os{9s0%__v}5YgTCI*HVUpJYiK(^@#!T4Hu`N&c$^71FpPDkujwS# zbs6D_WcN?CTG-~Tvt>h^aBKIh%;;*N&oMnpwFNxvr&qd|hv?h_I+JYXli9%Vo#=px zH*q8hL6m1n0-bnOu!?YWTQAH22H(T6_A3MGB0kC=t&9|#|42UYjyG-HpV}b6&sF*4 zt8m<@UsGir8Tl}%X;-m!h7dMnH`c8(GZiF6aH~l{8%9{=&TH8Fu?%3X`MFPu2rAk!na1sopDh${2>SHIvi&-r{vJa_n1erP6>$o zRH~x-szCRBBxyAc9K1atD2+|o-4dtEI^zafIFIi zQ=(9Pw$Fb`RDA(tO-W(Ag?;GGIJ|HqGn+o)PJr*nhu&MGl2eM*)h_Op9PX8Mbwh(e znS(Z^xY-p70sjQ=lZ58y0BpSOgpC`qv|S~>*5z&Ew<;gj(u(MkADCnQ@O)(!#0)Y& z*Wyk{b`d==YgPR%WgH##c~FOR`wg(=52Sl^1Tn+80@`$gfsQz;VN^#L2+eM2>qzZ+9G1=wx#?y{9D>qgOH-4Rx;|=N?H z>ABJe#PE;QDB;}Hf8w2bIs>en*hNcY0*F~C|E!-lSA3;kUM3^=Rj%3341JN`1-8<6 zxCV((MWgG1m1>b9(A&|{7F$==pZ%rPo2Y8t7vBS2u;5&=g87hPy?b8xOul7#z9C=R z?xA>sSHnGto)cUHW{qOm*e3rY8Qr7KY$F1=tt#CB{xYVj}(qm z+Cn6SA`2jrAjHCixdw;1KHXC3@G&yJ8p?{IO-YcqWB|r7aEiQ&wAZPC_rULZfr?7% zhgmokJ&!T3kZh6B(&YQ;A>TuQX3zJWE%Mw?@J35GQi1r!u6=nCb|C@yw%9-6Xg+7!vdvVNvYdXjh|Va}_WqP&wg*TwuGld7id28xav#>+t3IuP6M&;0GGglee~;fT46 zTo?)76I4jKvIqc23@?hQ5nfxB<|c1dcLEXl;UaESAdmEBPx_h;iBSzn)vq8)>Z~;B zfwCmuPh+5+hoPOPc(2H?ZmNiJzEm~vtV~|i6K0nY8xx4t=5T*B;^sou^avH%J*bNB1Hm=HQE%OcgaGNs4hu$x=(gyLl3%l$os*stL^VrXuu9BkT6 z3``Y}%5tef%BfIBRu(C^7l)BYegZ$E!tm1=WvwV(2x-&f5kBaU`|g$~`BL`+uN%4V zYW*_5!&Xcb*qx^>1q=M=-T0fv$@yo2Y=}c8_VT`EPidad2-! zn?&xH_rP*EWDyTOU=R+5>1sRpAZk>JLh%O9`x5g7%=qe;%?4Rb4_mw5p#--03dx7b z_vQJ0NNM5Esh`J!ejD}@wr;*dqYs1oH=^wiQ!+|BqOdBEJX#)~Ve1h?h1wnM;GJz5 zMs7?(;m_Ul1d@o?U*q@H=LDIRc89dlKJqFOd8?b!hQu<6!5B*>iJ*F#q+N;3-!z6zm!W$3RNb_ zRV2}1VpS&5V)SUqZd~K^88F07tKq}DHL4Q$6~JI327ghZ z5)D{mVid=~@T7rIDtXed!JKuJhy?_K9=v#x{dtnV(!5>&N<+MkdZ#~d(e!;iaE8Sc zc7*j~??Li97G6IwP3yLq`X&5t%8oN|j zwhnrDEq*N&+r6)pJQh-0iT%lW7c;| zTBOcfCb`GuY!NFW2iT8E^*S*G0uZ6O;C^_Qx5Suy!Z$`@cp`k-5Sf00GPzl`Qi3uC zF{?uy-K!eSSc0;I_nJj$nnf@ZU5w(K9|+6LWQfXyso{`=wvjk6gtSA$Cx23z)R~8; zbg7D886i`mgp)8!io@h~QHt|oeEfw>6Z{Sj5jzkO6|OFXAgBI=k@EXrPj5Bg1n-}^ ze90b6$oZ{ooSR*-N|nGfTr?6Wm3K@GwFWfbiqd6G*R%tv`Xm_HtA2lbj~OG6D~OJ>~hCo*&=nT?|HyO+=W8fS| zSXxoxV3eKFGvslgz$0Q*8bY86>k&yoyG>lk?NlXWDW)hP;pOiS>lIYa&N!ov+Zu%x z?qq{xI`3r6`2qGjQiQOmN*(L%&u3=n5&+2!ALhZE2S8{bjAMZeN& z_vQ%6H31GiGDi0k}uM^X1Kj$Saj5~-UC`*(Y2^Tmo_Hl z>8c(`nhnZhZJq9N&AnqqT=R|DOnfo>4QJhT_`44VddU@&nV%=L zuZzonSlg;1Gc4Ey<4DhFQc!w(b@;=R$ZJpAJfv()~0pTJlR>2 z`b*)deu(eliivZe(DCN({VFd0X3HNDX}+p1D9)qFh9}}X7*;w_>?syTB5q@C&7kj> z+A1QzXjk%19#=GYC`9@vY_gnihH;Ou00mkEOJi$HYab(l46C@v)d9l?Ra1&4tiSBv z-ccE)xaKXRm%||Hz(YLF-*AUpywD6V;LOc6lL0}l`Jq*~6k6zvn}*-V%uW1nUUX}s z9}Wn-gvNMLM$}W*Ra3Uc+1tRhsY~1%5a8T3(dO1rzkj?yJpZMw79#$KQHETbF?^Kw z!%qzOY$LpK)#A189?^O{P@*q^**aV{Y*1bmkb;Hz-4^RW4nh@WB zIDq5F{Q)+e_0-u_s2&rybvYkijty~Be)p_xeddBWEBlb2{{YIEskJLR0fw&kqOTuy1knSHt zhX)61E>!z6hZ3JPBIuZiwT6?1m>3z?TRbR0HAWJ?eO5G| zuGBNQLF%FcL~)mE9Upkn8n0o|OS}vxanupa8+ZmFO~DzQ$L^vL zGrr&A;`_`Cl-ynpYC<$2ET0Um6f+fVsqoQSCo_>k>Vf02p?UMe${PXR4ZL4U(DNr?Qc}djmiGx#x(c>u&s3KLpQD(}OgcZi0ILLA!g65q3=w@i*`I``pyZ ztLG-_txkmB@7sG3fPI$mVE6Z+=0GxcDgwKOhiIS&2gA^5r(Wqkf~|f0l@E6SYJ zKHhu#8g}Zej)0tHD5m82eQ5EZs?C?3afQ^ve+=y>EH%mL8&$^bR5^_SbM+JsKp>Em z)G0luVys3(9hNLt!1x;>s8D{=rAfCu^pl1oz2Nj#_P*P9OnK@;QHnq3U6}%*;~soQ zeQr!~DeARiMpET4M~yKe%!+P7Ry##rFTsBn?>$dIrwyv$65yCo6tYtUd9gp7LEoo$ zf4Vz~6MC^~q&GLOQ>J$5n29d@G0inP`pC$%si$l9dgC|*bONsXF&)?)*0718p^oFR z)vskcThYpqlM1|MXag@pgSZgo&!=dMKz(B14KY`?6;O^>4S)HNqgPSgyIgzvGDNx{ z>}}*590Z+NNzz`+{mt|ed6Dbl#4K$vtHEH(qV$URHb2wAFcZgrY&z8snB+;~0R^=z zA{UtoW3|H6e;Dl{{l1QAbplBd;}3Dp>V&_(O|8FvA+2DxEVuin#E58@(-QoLa9>_t zcEQc`6qu)=yB2qx84p&3xP-i zU@wHlO1J`TNuySU`N0BDXICdDE+1uC^lp4b;rtt$iGL~0+LpIaloyX@7w0~#zJgEt z$QyF+rsf&^4~m;yn&{9}kdYOEp~=pjaz|3owB2Ae1DS2`l>@cc^c)b|(p)CKgGiD4 zanDJuV(6O~q_nyvx>W3Sy}9HNGCMq_9v}__#hmU4r7}=?89{fnMpNnWUSp9~8e)CvUsAHEn+jL2v$TDOH)^7w~h8nA|mdJ$sw)Lxl=Lj1j) zDwECgK8;hRDT5^~Y4PoRb)tqy3+pQF%+sY+hd+0ZiEVWN9Aa_mHC*rD2f@tQN`y?2E$b zQp15!w7)7g&kz1`GUtCON^&VJM$)t_hf~^tKDZUE2rtbr*Mv*UnI6Dpnno$SQ_=%q z3@miN6?Zp8L9u?cP&d?9MIP?q#gQ8Pjs3Zm_rGx~1jvzt8f!}Ak;OUdauU}f9v}5o zCzpnn?EQqJ2XN@^D6)@J4~xn_;&hvX{(w!l2Ut&|9vVO6b$*cnz_mA64Umi_zC48! z54iTz;$8yeDXuGku4AcLd_r8GsL#mYNHx;_r^^A@^%Nu%ZzXTqgepAxNs&K(k&O zjJwl$WseuGdx4ACK8i?aLvfkBowbU#_OPrHdgm&7Hi$I-(@2(}9PMS-&=kPD_4V+s zvBkV6{?btQTv5*A)g3y$vNCgb_y^!cVra8#xsl#CmNF5B(d#W%&XnuLc3@*+DV7^N z9-N6{^Yee%L~wde1AdR`gULIOy7#l5Z1Xe7HRrxZqL+yL<%tv+QPMPHnXb9Td7$oy ztEp1xZkFjUR_Nz$b#so?sDpJVJ12dz7NGNLb!19Elmw*N1LyMPQ%UIC;2~A5JdI%L2s2Jx^cfuxuL}+v1#5U9I~8D^pTZ80>9=ZqRr( zkdngHzsk?AR;c8!T(FV+13^#EzuA7#e+>h$n=Kwc`yu<4d#$Sr=P6`u>*^T5;?I+X zMgI)c*(R%qa%E+BQLoj~;`7pde_&=8;59i|lJj7G&pzH`*~Uw^F;%4K&1wGmy2**c z)WyyxwP3q-zJ@%L=P-dTE2YinR;bwe(-DjR84fcWTNt1_UJ#S)N%&lEAXA~hanw&z zuaCA%h^sQ8mMmCTn_+9r;W|?mwhI2L;rk?0ok4U12m?&eLN0tKYEqPrWs<+^dcBdR z7gEcy{t5pUtxUg3y!h(JfPUvQ_0cFX(A)quD$H`kdvE!xVQk#%jc!kF^b+ns=Z8Bd zm~oF?hb1HF{y4y}Q=Jig(CkrE^u=*|un2JcGp{CheEx@`4^0=n`)4m(qcBNP*>>|i zW+V;;;WgDV%PcQR+4CYV?qloiN_ziWQW6i796)|jD{U4Q9q{1Ry^5O+Qp%CR!Z!5* zJZ39&>u>;b&qZEw9UZ}fanIQql<@#ILpRaqg&hch zHl7EG?FRPO)L3b=v(}kN)<~`5TQgx95BY5cycs{TxF8u9B^f2E9)*v-H?iQgT& z@9jw?H}_u;w_tjO!KA{WWEP*rUh^>D<|TeX(p4iqGweLxYR$iKKiEzroWnX^t}77{ z4U2WXHiM25yT|?S#*A-IpxoS&?rz#8cEzh`Bd+2XEMMm>@t!Xmh$7$Z* zYb~t+**!m?6BBs?KE(?o$1rwJiELN}!;n`GxuH59+ZQZC>k#}xz}I;HfR&6x)P84} zfh3lqewaqiqM*p|Z_~vOrrqAZSzY@s{;xq%__i?w97m`Aul%jK9H;{Y#b4gXIFpQu zY?Wzhc8q2T;<_cO$fuf<2{byNt%+dMMo&$ojm_*tuXF>lm*nny=PtOVSL(f2{;}7B z_wP?U$%>Rczeq{x8G!pAu1|Tm^;H`K7#S7!4o1m1O!kIT_nw}J!F_X{-2gW%8RKmr zCzn?;n5=QE+I)eyNY~6#Ue=x|=#x7bb+Oh3eT(jA{^yN5OS!YXJ>DLF6h7OI6I!`` z6Gmt_Wr(N`X=cOpbXbMqtDjg9>6bw&z$<$^b@ZgD;}sf(uOtRutlnlDbkI^+uPA*4 zW%$15y`LZ>BKH5}dl#>gH#Nra+0$wHY`rpui07SqUdchUi>>H7$Id6jw|N~}S6x9u zorHd3a<(pd#9G$B6W-TBExSRErjPRKS!)_<*c?`4Ok!c}Ka=~qkmIwnFM*+PaEKim zas@;Hg9C{|8}tR`uW;9K%Bd~-yW*Z<8m_Vv*YyvLOeaR z$!*~5uu>>dy4qO0hrnRf#2J9VCL0`{^{Fqe&gS^t_ST}`ZOMz6{{7Y02LV>1!pOM* zS>$I}6OOMBSI8dj61yjGmH9iQV$Qn{YHIHf6i-0M5i2OG&y3eFB(CQZB57YTQshCC`+q9N8G?`7xe}90&5kv7u6U*~+m&*&*hc`1WW%C`&X#DlPp`ORI&E<>SG!TEzdN-)|`I z5A4>N@<~AI)R00yy+G7rHo(8fW3m1xH8gZJhO9g*!8nE-P5(p%CpRPG28|qq114`H z%pFSU<42K+&Tegua@|#wIOXE9h}LVlG1&RplN)goXg*}YZ16BMRqU9O=3v75&IZEm zgy#0x+FcKYy9u|jE89J+M7OmA3Sq7%OFHZ8qXz1{e?q93z4g|SP^7XgPXXiIXS>;o z(=ir(Lvnue!N%Y>#J>17C{j*AV@eEkL!joJolw10&6>Di->4* z2Rch8cGMh8qC~nAca{HJ4THmK47q7_Db!t>@x=@VMNWZg6brcsOAC{d-Tf zz20g4C?12$jW__DB{}<567lonYh~*aveMwwUf$QM>sbnO*hhhg$?deXP0!XKPaN03 z(a0Sy%@x1L-`8i#87?dP)uFrVSEnD&wLmrQFF&KpVm{eSpFKq+a?32?*_+j=v?8tStqB1d&jcUkD zGu_5kX~!kzEMK1Nl%APt?2p|-@DYpE_Mq3}%`p@GSq87OyibeU+M5e!=~+r&FgjHN zfR%!8ViAe|tUSEYm(sCKQ@L!pB7n!Z=-a?sn3YO8VX z;X;L|Ijy+L)3}2Ju@rv*i5z5WPmwyhWYVmYYrXjU{^;^OnkDf=>SWREZ7ZZ7v@Re= zymgKyCRD_xdxZ!0VBNiGuXRHA-PA20kv2wnU4Ok<$m570!xvyEw%Gmx^RHKL2`w7D zDIQDvDE2_PMv z&?vQNYO>e3_a7PlnDg4=x=Y9HHr~m%c8u*v@Uwtj!S(e8;^v;bestdsUc9>Q=*`{V za6xoDM=&4Kud)(d50CnVskt6*o9`5?&h74=oRadStA!7|e806l!!oBeI*F?!@F%f! ze|64?7=SWqwuYT5%_~6(;lx9F+vm2e@BQ6+;=ijC@W%d;3 z2dBI#+D(S-0V?csyCSW%8BihgAtaLd(jj}h7Xbf0JzgxbG&+0lfO6q16@NudV<6l| zi#5L~lM38;Mfzq9rd(Yhe6{7LdmN3o7|C&7r^73tGxLVC>Qx1i$qOS^+SJ&m|udZW6 zp9=x7aVC(TQs3Mch@v|O5fSB`U5$OY#y-0I;1Cb}^o>=gnrp%L<;#y}JKGvzOS`$M zuA_CD@C}PGSGFZF-uqIgAa8Fu!cDWqB2inE4oryHl$aT0bhKlD?W1>65kVak)(8T- z97+2{V@IkR_kG3`NMo+Zu4TauNLNEb1c2>EmJ;}BHKQk-Hu1CJZY%hTmXx29u8;6E4dKQ*Sblq5?36)mg`Hy@J8Bsf zMSf~J3=x6C*4gtFJQ9WfX?oUB^Z?!#r+)Lw&EbUN3SolO403zo+^$9|Cy;^vbPSmCk7&!BJDuDeFJ`u z``xzO@-=IDp*ze%tuUIKW1b#Q4-bb#Q;Qr6r#5qLS2BcbBb{W?((w`b#tY`t+{_N} zAT{`zM?pb4iPR}M>2UyL+K-Rx9ZTXcm^4mH`-&L_kAfnk!7DMFPlI-?9;l^AR^4cvPpwSdIKq9izx<95)O?`MS6%YEN zAUzjkXSY!MDfAto+ghjPozlkERrkNNnXKHx{5qy^$I1>O{gqaUEaHXF^zR&yw_-Mb zbM;+m{Fxv6msnck?R#&p{fUO*k&cstQ&N&LAYdJ$;>l}ND_IA5c_qud_Ej42<2xdy zCV#a$SsJMNXaZEhPU*Q*;;Z!TF*-q?=&vvwd~C0}M0 z_b+kMGAqS8_d~j3`bUrccrA1iZRnM90l35t$en<`De%5%K|$y-q^0df-)JX40BQUT z=zKLW)=Kpk_+|oBO86+R%39!ce{l-vAMWmV*=~;?VcGVFhTQ_I&FM`F%XI7dr`+i9 z6jFY9phzgu>v_mfM%dfO5PWi=jtgri9N0}4gNvksB(LRsV8Y0-wRI)obJ4ar0PPr$ zCPDLVWE^v0O(Ej{W`Tf$f`|9~`?0bnEL9Vcq}e7p8^Pfp @ok34+BoM;1KVxppO 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

SKOjLY3Y@8QRL8R=Ys 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 + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..6a61b20f1ff1d607742ec89a0aec5f25e62f1402 GIT binary patch literal 51546 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 UzT-P8fCT + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..19dbb7d98290fce9dc26998050fe4f04e9c37db9 GIT binary patch literal 16785 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�*I08* zyxpazn0R=&Cbb;3FB1PoGOdvs9s|WeUdkPG;UunA_N3|-;Gh0Pp4u<^W!b8 zzCTbEZttAn6wep1Soi-DtxHdz1+XLZso(rddsRP?-D13ZVx3hdD0w3Vg?)^t8xt|X zg-Twl3y!p%DfA1gpka4c@C015RWxwy!r_PL_?krzCX{;(paF`Z;fY@TMDAn{>S@$_ z8oCcqcTYWkq`tFW9=azllYsKgCrOQb>KFW8@1|Az+8orBmEMa8h!QsXlFP3%e(t(8 zTY?)fvTE6&hX<%6{Ey>{cf6tLZ62hUIX>R1!wXO12W8fW0Hir2H35D;ZK0?9FYV(K>Ok>*dERjjA>q~H z_Xk3P9j|MyHOT6wv6wN>N0p_+hXx$VKps`lFwSfV6B9EoHkO0Q7zYPu|LDlV+Pb`` zs0)IEntJJ59iVaAghLpHRaCG5%(hgs#x&{`!jFWUoZQgB0Kj_i@bG;v+$(y}v7(cU zi;J6^o1vkIBf^!Hm8_6mkedK5gWIkE7^^HoQc_ZGZtm8(MFtc05a2xkopN4+@UE_| zl9H0^>uU~heeg=Fl_nu%U$*M%YJkG^?Kx&@m%Y5atgh_P8iBhmSH#avmH6=zO%P?^a!H@pXgI-@7u zkP&eSaR|i*2Mfi?D?L5k3S8fSlud!e>mw^E$&zU0#0gG{aJ`=mls8MK5IORb9;xap z7fu{5te*;g&lZI?m$H)>2Xfh7kf-cLyi}*Au~*^b)-ODOOEz5%_n{Ti$} zj z?@#L2(%DVq9Ubcj5!()zPHtd`oYt#`nE*6Af7*1!qz}D(zwV1-aOwi+gnWE_a$BDN zs`okqbWG&O`3=B3=VxdC%%~`cOG=$o~_IsFv0O0HQpWEbBLj5{7>| zZUPkfo4NU7o#oQ~UPeY#)W(rVi$359^IfU|D7Dt^$B!F8?|cRX;UnX7HDagDAZkF7 zC6wCe@P+^c#DmM7VOwgkdtmT>^>6hhb6UtoT%Io@70~!(08({4AXh@5UJO|8d zdeYA@L60V*$+h1Ws%BEZ{$SV_3aCT6`3(O8XoH!~pRUHI zM_AyDd4S8vlS}b;1gGl}aN4?PM-C7rI3FO7Xt`Z=^*EJ8xW$aQyu1WvN9JTn34;-| zuEWG4D|+0tn5<8DzCuR$J-h)hDCGd#Mw-sfESZ>qA$IfnP`_-4VC4xYu7WRj*{gAK zzCF*IU|=5?D)fDIrsrW>(>b?cZRZ~T9I;mXW$?D7Pc1KpCek7LA6#t9sZ-bEdb~b6 z*35n%7tmBn7q}yW z6MH|M-O*u^=79D2uC#tNxX*J^9GjDii;NjqweGlmmGS$G*?h~Ki3}mATV_#ze7TQ z2SF=d!nZ+x1flO9O1lFjYoBFm_|nzhICs!0ht#X_Ij2au2@k0XChvoVJ~+ZW4@L`} za2W!SE=L`)do(UAH6;NujInsRQsjsb><9LqzP|M1ai9O@>kljrBkMqO4FcG@KX|x} zOq}ZB(rev(R}6i`~XpnIS<{zX=Pkua4_r}_RM&LLGb+i+$Cse?!RbXf8Cs-sZb0GNm&_W~){_I&)o3dxC(zEh|WZ2YM!PLwdM^SrM$%}gS1JV>T zuKzWD*pzjJ^jg8o=j;ayjz1dsv${rV?Nraw+1F{J(t^@49yv-6f9t;O8f-I`zMH7r ztff#%xJ}Yq0AE;ITnr}?fS2h&F|_jU`jw-b|Eaw%;97FxPR5PnMI`&-Dq#NZS;jLJ z&$E{Xzuogd<`4FnzPKy&cZM!~1uD_^+7{wLb~sL7K4ogWg6u=ZrWVi!~D? zBO^XO;Q7-*wBS*LuiGsA><#8q%jQ>0ciJa_KUj=Z63O6L>+@rfO#?xbPHWSgX-;$7 zXF2y;TMnwM&ux&O8^7c07=cEs-VMo76%RX%R?0?^9JcC|<7|!{(GN9W+UUVDI+jmt zlWK5OQbcyVolb*138J5f>kYqRW?Dw>Or|RN)(~%p5`eV)iW$Ccz0)vD%rt*0i*`?S z)$Y|R00kDqE|WJdsllE`FCWl>IW}C@HP~@AuxT`NXf;g5hruai`DC)2nHU%tNJvNk zm4LguK-bg-(72dEG_CLJd+otMn$D}_I|JRW9&HHRIyv30K(9I^1b9BC1b99d2NdKv z^%Fv`dNc>ruK?oi%jQOlGxVxUaX|g@=Ef+utJAa`(W+Cn_xC*)r*0%79naQ)d2N&R zR9>R7^plN@*W-=ac^4jwc<=eITN@)uL^|%Z0UFvy>!G+rzvqrU0hg3fr1h+X}dO^ z3?#z|%&e{ZI}_ifP~e4`Ms_s1(5eu=H?y?Fbg)skw6sK$to5fu0;(DVIVuTC>7a{D zc^Mgm!*$`wFfm%7#b4HGC58(#rUubnAV$kd5esOwqJ;b$#!tb)@HI_jK_WrmF?2KApt zy?$FR?}!ye$)j@ZU+VumM5`EQv z!&nU2TrtAds1WW3G0{rs$ransSvYg4A*BNHR4q2hb>25$!xDK#QDs&Y(kpD* zYl=HbaA-5IZf$P*%W|r;kc>n&Jj5K!>8hSSGW$_dW3)o^$d|rXxI% zpO|j|Y}UJC7(nk7wq1eCQ>Md%L+FHyguV8zUzo2#S5^r}|A8((Q}q5qlyk+;&yN(l zS*4w11hx7pyT$;G_RBYH2q>M4OXWF8!0e^uKUunc)+uAfrO)?yDNUhKNEn?eqTivW#$xP~Hj= zrEc7SIGOUVWx%E#0-|sTD?TicC<8ApAC-=?RMcX8@hfa8vlYMLgmMvBrOB)^Q-SKn zBAURCdUqoQ*6*qvm)`Y;P{w$s9eMZ0jFJJiErNS16on`PJ;@#qDD+8GG#`m8CCFQi zf~B@h-ms)o=^tgCdQ&kpi{#s6OGS0wPF3j=TQ$O%eRhfo?B0~H%m{9LZACc-53H#D zs7mj5y}qRY&*?<7c#t0dGtbV5R|wZRM(t;S8+_UDfRtsz5O-Ap3MSDZdHurS^8TXs zjUcwn4Z*o^C=cqqM_Y1kd&8f;RZSRv?6YFb-@M3d!0pS`y+v+y9K|DHRGF%n$izRx zf+Q&u->?+jx4bjEv7~Om3{OVAG$I(P3H+$V_K8gil{xaoEcsxXJ}P7#QuhrXo_`KO z-E;_}fAYn6{-&N>g=9|WS+qi3dxsIw3Fbr&aZjR7Bg#l7*L++giF{wE5{doqWZ6>G z;GIh$%B=S>o47LT{1mw3xGWn^?ZXweTA) ze`bA+(3~4Qk4`RBZD5b6N@t;^|E2FDchZXPW(H@-C+gq#eu^~IkZ~c(Ey3yR>TD8I z{@7By!fS_07QRBjU~r+77Dte)R$+&I)s$*MqUuFR>}oZ=xzlxbCZW1TE4jBfQtVWE zL(9L(Wr1Vz#tfROj-*BQa|f~khN>1utK>!WWVBK$43I`T^9|;o*3~XNssNAJy)(we&ayRF89*?;<6{!sgG@Ah*W(=?|DEIFodD#=D9@>xp*KGcE2m4Qqj#)n2+ou-TnF$ zlZ-UsEy0dm%sq9KFj%lo@pi4T$KPFr9z*`nm+5`x_%LDuQOOnfC(kKjA9tz0QG+d( z9a40jTcP)+dY`yWtlXZtEk|#$uQTLX zj=4GXLR_S3MBKm`l`D4N(e^ZUxwDb5r#SoLPqad4u8H<&-mifPqR*o?uvgeB2+H;mq zzRug{Hsq&srSlX_rr@I7qAE1#R_PuXLoJoFOpYD<)YIvm+vwWZuTH zd=-=EQ-EWa&F~;&w(i&D{RQyU-)*w2N>QrkGfc45E!VGf#I6HoUvBglo1kLM{H;HZ z@N6M!uzWpC;xU4w_Iqk#vFd#aMM9t#p+0WlYs1Ac&u-Ji$w(}>U z?W+pLjr(Nys~<6oT3GbK5jM!`j^AQykHzKT;pZFhRc17~!p?1OD~z~13u>Ax;$~m> zc16cFwDTe+__Y>)@&av$2UxLe#DRmz3@BP46fH5R!u3cfG}%lqT z(^gRtJW6D0>bL3zz)tPrMGoZzC=@_#YYZl{#9vTQeL{gPC + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..e1a581d1c60b6810d3b8d215a054372e151da03b GIT binary patch literal 21167 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 + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..3577130ada2e66deef64334fe87b7f3a60bd0a32 GIT binary patch literal 27611 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 + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..9bae17ac821444cf851ad00a6c47b8616d3f12b2 GIT binary patch literal 26104 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8ac423ff9cf0ed9c769d360933592e146731225f GIT binary patch literal 29608 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 + + + 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
  • 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 Le)3tu(EI-Y6W)Lk literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..86a72bd6ed5a6281f27abc95cba39e28c13e71d2 GIT binary patch literal 25267 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